feat: i18n full support, responsive UI, multi-model AI config, and bug fixes
Major Features:
- Internationalization (i18n) with auto-detection for ES/EN/PT
- Mobile-first responsive design for Studio and Experience
- Multi-model AI configuration (llama3.2:3b, qwen3.5:9b, gpt-oss:latest)
- Course language configuration (auto-detect or fixed per course)
Backend Changes:
- shared/common: ModelType enum for intelligent model selection
- LMS: log_ai_usage function migration (fix chat tutor 500 error)
- LMS/CMS: course language config fields (language_setting, fixed_language)
- LMS: /courses/{id}/language-config endpoint for language detection
Frontend Changes:
- Experience: Enhanced i18n with browser language detection
- Experience: Audio recording with HTTPS check and error handling
- Studio: Memory game with unique pair IDs and debug logging
- Studio: Expanded translations (250+ keys for ES, EN, PT)
- Both: Language selector in headers (mobile responsive)
Documentation:
- AI_MODELS_CONFIG.md: Multi-model configuration guide
- RESPONSIVIDAD_GUIA.md: Mobile-first design patterns
- I18N_RESPONSIVIDAD_IMPLEMENTACION.md: Implementation details
- DEBUG_AUDIO_RECORDING.md: Audio troubleshooting guide
- DEBUG_MEMORY_GAME.md: Memory game debugging steps
Bug Fixes:
- Fix chat tutor 500 error (missing log_ai_usage function)
- Fix audio recording (HTTPS check, browser compatibility)
- Fix memory game pair IDs (unique ID generation)
- Fix HotspotBlock TypeScript errors
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -66,32 +66,72 @@ export default function AudioResponsePlayer({
|
||||
}, []);
|
||||
|
||||
const startRecording = async () => {
|
||||
// Check if browser supports MediaRecorder
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
console.error('MediaRecorder API not supported');
|
||||
alert('Your browser does not support audio recording. Please use Chrome, Firefox, or Edge.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if page is served over HTTPS or localhost
|
||||
const isSecureContext = window.isSecureContext || window.location.protocol === 'https:' || window.location.hostname === 'localhost';
|
||||
if (!isSecureContext) {
|
||||
console.error('getUserMedia requires secure context (HTTPS or localhost)');
|
||||
alert('Audio recording requires HTTPS. Please access the site via HTTPS or localhost.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
const mediaRecorder = new MediaRecorder(stream);
|
||||
console.log('[AudioResponse] Requesting microphone access...');
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
sampleRate: 44100
|
||||
}
|
||||
});
|
||||
console.log('[AudioResponse] Microphone access granted');
|
||||
|
||||
const mediaRecorder = new MediaRecorder(stream, {
|
||||
mimeType: MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm'
|
||||
});
|
||||
mediaRecorderRef.current = mediaRecorder;
|
||||
audioChunksRef.current = [];
|
||||
|
||||
mediaRecorder.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) {
|
||||
audioChunksRef.current.push(event.data);
|
||||
console.log('[AudioResponse] Data available, chunk size:', event.data.size);
|
||||
}
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
console.log('[AudioResponse] Recording stopped, creating blob...');
|
||||
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
|
||||
setAudioBlob(audioBlob);
|
||||
console.log('[AudioResponse] Blob created, size:', audioBlob.size, 'bytes');
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
};
|
||||
|
||||
mediaRecorder.onerror = (event) => {
|
||||
console.error('[AudioResponse] MediaRecorder error:', event);
|
||||
alert('Recording error occurred. Please try again.');
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
setIsRecording(true);
|
||||
setRecordingTime(0);
|
||||
console.log('[AudioResponse] Recording started');
|
||||
|
||||
// Start speech recognition
|
||||
if (recognitionRef.current) {
|
||||
setTranscript("");
|
||||
recognitionRef.current.start();
|
||||
try {
|
||||
recognitionRef.current.start();
|
||||
console.log('[AudioResponse] Speech recognition started');
|
||||
} catch (err) {
|
||||
console.warn('[AudioResponse] Could not start speech recognition:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Start timer
|
||||
@@ -99,14 +139,29 @@ export default function AudioResponsePlayer({
|
||||
setRecordingTime(prev => {
|
||||
const newTime = prev + 1;
|
||||
if (timeLimit && newTime >= timeLimit) {
|
||||
console.log('[AudioResponse] Time limit reached, stopping...');
|
||||
stopRecording();
|
||||
}
|
||||
return newTime;
|
||||
});
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
console.error('Error accessing microphone:', error);
|
||||
alert('Could not access microphone. Please check permissions.');
|
||||
} catch (error: any) {
|
||||
console.error('[AudioResponse] Error accessing microphone:', error);
|
||||
let errorMessage = 'Could not access microphone. ';
|
||||
|
||||
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
|
||||
errorMessage += 'Please allow microphone access and try again.';
|
||||
} else if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
|
||||
errorMessage += 'No microphone found. Please connect a microphone and try again.';
|
||||
} else if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
|
||||
errorMessage += 'Microphone is already in use by another application.';
|
||||
} else if (error.name === 'OverconstrainedError') {
|
||||
errorMessage += 'Microphone does not meet the required constraints.';
|
||||
} else {
|
||||
errorMessage += 'Please check permissions and try again.';
|
||||
}
|
||||
|
||||
alert(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -177,6 +232,31 @@ export default function AudioResponsePlayer({
|
||||
|
||||
return (
|
||||
<div className="space-y-6" id={id}>
|
||||
{/* Browser Compatibility Check */}
|
||||
{typeof window !== 'undefined' && (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) && (
|
||||
<div className="p-6 bg-red-500/10 border-2 border-red-500/20 rounded-2xl">
|
||||
<div className="flex items-center gap-3 text-red-600 dark:text-red-400 mb-2">
|
||||
<X className="w-6 h-6" />
|
||||
<h3 className="text-lg font-bold">Browser Not Supported</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300">
|
||||
Your browser does not support audio recording. Please use Chrome, Firefox, Edge, or Safari.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!window.isSecureContext && window.location.protocol !== 'https:' && window.location.hostname !== 'localhost' && (
|
||||
<div className="p-6 bg-yellow-500/10 border-2 border-yellow-500/20 rounded-2xl">
|
||||
<div className="flex items-center gap-3 text-yellow-600 dark:text-yellow-400 mb-2">
|
||||
<BrainCircuit className="w-6 h-6" />
|
||||
<h3 className="text-lg font-bold">HTTPS Required</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300">
|
||||
Audio recording requires a secure connection (HTTPS). Please access this site via HTTPS or localhost.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-8 glass border-black/5 dark:border-white/5 rounded-3xl space-y-6 bg-black/[0.02] dark:bg-black/20">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-3 bg-purple-600/10 dark:bg-purple-500/20 rounded-xl">
|
||||
|
||||
@@ -7,27 +7,71 @@ import pt from '../lib/locales/pt.json';
|
||||
|
||||
const translations: Record<string, any> = { en, es, pt };
|
||||
|
||||
// Idiomas soportados
|
||||
export const SUPPORTED_LANGUAGES = ['es', 'en', 'pt'] as const;
|
||||
export type SupportedLanguage = typeof SUPPORTED_LANGUAGES[number];
|
||||
|
||||
interface I18nContextType {
|
||||
language: string;
|
||||
setLanguage: (lang: string) => void;
|
||||
t: (path: string) => string;
|
||||
detectBrowserLanguage: () => string;
|
||||
}
|
||||
|
||||
const I18nContext = createContext<I18nContextType | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Detecta el idioma del navegador del usuario
|
||||
*/
|
||||
function detectBrowserLanguage(): string {
|
||||
if (typeof navigator === 'undefined') return 'es';
|
||||
|
||||
// Obtener idiomas del navegador (puede ser un array)
|
||||
const browserLanguages = navigator.languages || [navigator.language || (navigator as any).userLanguage];
|
||||
|
||||
// Buscar el primer idioma soportado
|
||||
for (const browserLang of browserLanguages) {
|
||||
// Idioma completo (ej: 'en-US')
|
||||
if (SUPPORTED_LANGUAGES.includes(browserLang as SupportedLanguage)) {
|
||||
return browserLang;
|
||||
}
|
||||
|
||||
// Solo código de idioma (ej: 'en' de 'en-US')
|
||||
const langCode = browserLang.split('-')[0].toLowerCase();
|
||||
if (SUPPORTED_LANGUAGES.includes(langCode as SupportedLanguage)) {
|
||||
return langCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback a español
|
||||
return 'es';
|
||||
}
|
||||
|
||||
export function I18nProvider({ children }: { children: React.ReactNode }) {
|
||||
const [language, setLanguageState] = useState('es'); // Default to Spanish for Experience as it's more localized by default
|
||||
const [language, setLanguageState] = useState('es');
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// 1. Primero intentar cargar de localStorage
|
||||
const savedLang = localStorage.getItem('experience_language');
|
||||
if (savedLang) {
|
||||
|
||||
if (savedLang && SUPPORTED_LANGUAGES.includes(savedLang as SupportedLanguage)) {
|
||||
setLanguageState(savedLang);
|
||||
} else {
|
||||
// 2. Si no hay guardado, detectar del navegador
|
||||
const detectedLang = detectBrowserLanguage();
|
||||
setLanguageState(detectedLang);
|
||||
localStorage.setItem('experience_language', detectedLang);
|
||||
}
|
||||
|
||||
setIsInitialized(true);
|
||||
}, []);
|
||||
|
||||
const setLanguage = (lang: string) => {
|
||||
setLanguageState(lang);
|
||||
localStorage.setItem('experience_language', lang);
|
||||
if (SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage)) {
|
||||
setLanguageState(lang);
|
||||
localStorage.setItem('experience_language', lang);
|
||||
}
|
||||
};
|
||||
|
||||
const t = (path: string): string => {
|
||||
@@ -46,7 +90,7 @@ export function I18nProvider({ children }: { children: React.ReactNode }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<I18nContext.Provider value={{ language, setLanguage, t }}>
|
||||
<I18nContext.Provider value={{ language, setLanguage, t, detectBrowserLanguage }}>
|
||||
{children}
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
@@ -59,3 +103,5 @@ export function useTranslation() {
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export { detectBrowserLanguage };
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useTranslation } from '@/context/I18nContext';
|
||||
|
||||
interface CourseLanguageConfig {
|
||||
language_setting: 'auto' | 'fixed';
|
||||
fixed_language: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook para manejar el idioma específico de un curso
|
||||
*
|
||||
* - Si el curso está en modo 'auto', usa el idioma del usuario
|
||||
* - Si el curso está en modo 'fixed', usa el idioma fijo del curso
|
||||
*
|
||||
* @param courseId - ID del curso actual
|
||||
* @returns El idioma que debe usarse para este curso
|
||||
*/
|
||||
export function useCourseLanguage(courseId: string | null) {
|
||||
const { language: userLanguage } = useTranslation();
|
||||
const [courseLanguage, setCourseLanguage] = useState<string>(userLanguage);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [config, setConfig] = useState<CourseLanguageConfig | null>(null);
|
||||
|
||||
// Función para cargar la configuración de idioma del curso
|
||||
const loadCourseLanguageConfig = useCallback(async () => {
|
||||
if (!courseId) {
|
||||
setCourseLanguage(userLanguage);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Importar dinámicamente para evitar circular dependencies
|
||||
const { lmsApi } = await import('@/lib/api');
|
||||
|
||||
const response = await lmsApi.get(`/courses/${courseId}/language-config`);
|
||||
const data = response.data;
|
||||
|
||||
setConfig(data);
|
||||
|
||||
// Determinar qué idioma usar
|
||||
if (data.language_setting === 'fixed' && data.fixed_language) {
|
||||
setCourseLanguage(data.fixed_language);
|
||||
} else {
|
||||
// Modo 'auto' o sin configuración: usar idioma del usuario
|
||||
setCourseLanguage(userLanguage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading course language config:', error);
|
||||
// Fallback: usar idioma del usuario
|
||||
setCourseLanguage(userLanguage);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [courseId, userLanguage]);
|
||||
|
||||
// Cargar configuración cuando cambia el courseId
|
||||
useEffect(() => {
|
||||
loadCourseLanguageConfig();
|
||||
}, [loadCourseLanguageConfig]);
|
||||
|
||||
// Función para verificar si el curso usa idioma fijo
|
||||
const isFixedLanguage = useCallback(() => {
|
||||
return config?.language_setting === 'fixed';
|
||||
}, [config]);
|
||||
|
||||
// Función para obtener el idioma actual (curso o usuario)
|
||||
const getCurrentLanguage = useCallback(() => {
|
||||
return courseLanguage;
|
||||
}, [courseLanguage]);
|
||||
|
||||
return {
|
||||
courseLanguage,
|
||||
isFixedLanguage,
|
||||
getCurrentLanguage,
|
||||
isLoading,
|
||||
refreshConfig: loadCourseLanguageConfig,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook para cambiar el idioma del usuario (solo funciona si el curso está en modo 'auto')
|
||||
*
|
||||
* @param courseId - ID del curso actual
|
||||
*/
|
||||
export function useCourseLanguageSwitcher(courseId: string | null) {
|
||||
const { setLanguage, language } = useTranslation();
|
||||
const { isFixedLanguage } = useCourseLanguage(courseId);
|
||||
|
||||
const canChangeLanguage = !isFixedLanguage();
|
||||
|
||||
const changeLanguage = (newLanguage: string) => {
|
||||
if (canChangeLanguage) {
|
||||
setLanguage(newLanguage);
|
||||
} else {
|
||||
console.warn('Cannot change language: course has fixed language setting');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
currentLanguage: language,
|
||||
canChangeLanguage,
|
||||
changeLanguage,
|
||||
isFixed: isFixedLanguage(),
|
||||
};
|
||||
}
|
||||
@@ -5,14 +5,31 @@
|
||||
"next": "Next",
|
||||
"finish": "Finish",
|
||||
"error": "Error",
|
||||
"retry": "Retry"
|
||||
"retry": "Retry",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"close": "Close",
|
||||
"confirm": "Confirm",
|
||||
"success": "Success",
|
||||
"warning": "Warning",
|
||||
"info": "Info",
|
||||
"search": "Search",
|
||||
"noResults": "No results",
|
||||
"viewAll": "View all",
|
||||
"learnMore": "Learn more"
|
||||
},
|
||||
"nav": {
|
||||
"catalog": "Catalog",
|
||||
"myLearning": "My Learning",
|
||||
"bookmarks": "Bookmarks",
|
||||
"profile": "Profile",
|
||||
"signOut": "Sign Out"
|
||||
"signOut": "Sign Out",
|
||||
"selectLanguage": "Select Language",
|
||||
"menu": "Menu",
|
||||
"notifications": "Notifications",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"course": {
|
||||
"modules": "Modules",
|
||||
@@ -20,12 +37,194 @@
|
||||
"progress": "Progress",
|
||||
"calendar": "Timeline",
|
||||
"start": "Start",
|
||||
"continue": "Continue"
|
||||
"continue": "Continue",
|
||||
"enroll": "Enroll",
|
||||
"enrolled": "Enrolled",
|
||||
"price": "Price",
|
||||
"free": "Free",
|
||||
"duration": "Duration",
|
||||
"level": "Level",
|
||||
"description": "Description",
|
||||
"objectives": "Objectives",
|
||||
"requirements": "Requirements",
|
||||
"instructor": "Instructor",
|
||||
"certificate": "Certificate",
|
||||
"aboutCourse": "About Course",
|
||||
"content": "Content",
|
||||
"reviews": "Reviews",
|
||||
"faq": "FAQ"
|
||||
},
|
||||
"lesson": {
|
||||
"summary": "AI Summary",
|
||||
"transcription": "Transcription",
|
||||
"complete": "Mark as Completed",
|
||||
"nextLesson": "Next Lesson"
|
||||
"nextLesson": "Next Lesson",
|
||||
"previousLesson": "Previous Lesson",
|
||||
"locked": "Locked",
|
||||
"lockedMessage": "Complete previous lessons to unlock",
|
||||
"markComplete": "Mark as Completed",
|
||||
"inProgress": "In Progress",
|
||||
"completed": "Completed",
|
||||
"notStarted": "Not Started",
|
||||
"dueDate": "Due Date",
|
||||
"timeRemaining": "Time Remaining",
|
||||
"overdue": "Overdue"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"fullName": "Full Name",
|
||||
"forgotPassword": "Forgot Password?",
|
||||
"resetPassword": "Reset Password",
|
||||
"noAccount": "Don't have an account?",
|
||||
"hasAccount": "Already have an account?",
|
||||
"signIn": "Sign In",
|
||||
"signUp": "Sign Up",
|
||||
"orContinueWith": "Or continue with",
|
||||
"google": "Google",
|
||||
"microsoft": "Microsoft",
|
||||
"sso": "Single Sign-On"
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Welcome",
|
||||
"continueLearning": "Continue Learning",
|
||||
"recommendedCourses": "Recommended Courses",
|
||||
"recentActivity": "Recent Activity",
|
||||
"myProgress": "My Progress",
|
||||
"certificates": "Certificates",
|
||||
"badges": "Badges",
|
||||
"stats": {
|
||||
"coursesCompleted": "Courses Completed",
|
||||
"hoursLearned": "Hours Learned",
|
||||
"averageGrade": "Average Grade",
|
||||
"currentStreak": "Current Streak"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"myProfile": "My Profile",
|
||||
"editProfile": "Edit Profile",
|
||||
"avatar": "Avatar",
|
||||
"bio": "Bio",
|
||||
"language": "Language",
|
||||
"timezone": "Timezone",
|
||||
"notifications": "Notifications",
|
||||
"privacy": "Privacy",
|
||||
"account": "Account",
|
||||
"changePassword": "Change Password",
|
||||
"deleteAccount": "Delete Account",
|
||||
"portfolio": "Portfolio",
|
||||
"achievements": "Achievements",
|
||||
"publicProfile": "Public Profile"
|
||||
},
|
||||
"gamification": {
|
||||
"level": "Level",
|
||||
"xp": "Experience Points",
|
||||
"leaderboard": "Leaderboard",
|
||||
"rank": "Rank",
|
||||
"points": "Points",
|
||||
"badge": "Badge",
|
||||
"badges": "Badges",
|
||||
"earned": "Earned",
|
||||
"locked": "Locked",
|
||||
"nextLevel": "Next Level",
|
||||
"xpToNextLevel": "XP to next level"
|
||||
},
|
||||
"grading": {
|
||||
"grade": "Grade",
|
||||
"grades": "Grades",
|
||||
"score": "Score",
|
||||
"percentage": "Percentage",
|
||||
"passingGrade": "Passing Grade",
|
||||
"failed": "Failed",
|
||||
"passed": "Passed",
|
||||
"excellent": "Excellent",
|
||||
"good": "Good",
|
||||
"regular": "Regular",
|
||||
"poor": "Poor",
|
||||
"feedback": "Feedback",
|
||||
"submissionDate": "Submission Date",
|
||||
"gradedDate": "Graded Date"
|
||||
},
|
||||
"quiz": {
|
||||
"startQuiz": "Start Quiz",
|
||||
"submitAnswers": "Submit Answers",
|
||||
"questionNumber": "Question",
|
||||
"of": "of",
|
||||
"correctAnswer": "Correct Answer",
|
||||
"yourAnswer": "Your Answer",
|
||||
"explanation": "Explanation",
|
||||
"tryAgain": "Try Again",
|
||||
"attempts": "Attempts",
|
||||
"attemptsRemaining": "attempts remaining",
|
||||
"timeLimit": "Time Limit",
|
||||
"timeExpired": "Time Expired"
|
||||
},
|
||||
"forum": {
|
||||
"discussions": "Discussions",
|
||||
"newThread": "New Discussion",
|
||||
"title": "Title",
|
||||
"content": "Content",
|
||||
"post": "Post",
|
||||
"reply": "Reply",
|
||||
"replies": "Replies",
|
||||
"views": "Views",
|
||||
"lastActivity": "Last Activity",
|
||||
"subscribe": "Subscribe",
|
||||
"unsubscribe": "Unsubscribe",
|
||||
"markAsSolved": "Mark as Solved",
|
||||
"upvote": "Upvote",
|
||||
"downvote": "Downvote"
|
||||
},
|
||||
"payments": {
|
||||
"purchase": "Purchase",
|
||||
"buyNow": "Buy Now",
|
||||
"paymentMethod": "Payment Method",
|
||||
"cardNumber": "Card Number",
|
||||
"expiryDate": "Expiry Date",
|
||||
"cvv": "CVV",
|
||||
"cardholderName": "Cardholder Name",
|
||||
"paymentSuccess": "Payment Success",
|
||||
"paymentFailed": "Payment Failed",
|
||||
"refund": "Refund",
|
||||
"invoice": "Invoice",
|
||||
"receipt": "Receipt"
|
||||
},
|
||||
"accessibility": {
|
||||
"skipToContent": "Skip to content",
|
||||
"increaseContrast": "Increase Contrast",
|
||||
"decreaseContrast": "Decrease Contrast",
|
||||
"largerText": "Larger Text",
|
||||
"smallerText": "Smaller Text",
|
||||
"screenReader": "Screen Reader"
|
||||
},
|
||||
"language": {
|
||||
"selectLanguage": "Select Language",
|
||||
"auto": "Automatic",
|
||||
"spanish": "Spanish",
|
||||
"english": "English",
|
||||
"portuguese": "Portuguese",
|
||||
"courseLanguage": "Course Language",
|
||||
"interfaceLanguage": "Interface Language"
|
||||
},
|
||||
"errors": {
|
||||
"notFound": "Not found",
|
||||
"unauthorized": "Unauthorized",
|
||||
"serverError": "Server error",
|
||||
"networkError": "Network error",
|
||||
"sessionExpired": "Session expired",
|
||||
"invalidCredentials": "Invalid credentials",
|
||||
"emailInUse": "Email already in use",
|
||||
"weakPassword": "Weak password"
|
||||
},
|
||||
"dates": {
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"tomorrow": "Tomorrow",
|
||||
"daysAgo": "{{days}} days ago",
|
||||
"weeksAgo": "{{weeks}} weeks ago",
|
||||
"monthsAgo": "{{months}} months ago",
|
||||
"yearsAgo": "{{years}} years ago"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,31 @@
|
||||
"next": "Siguiente",
|
||||
"finish": "Finalizar",
|
||||
"error": "Error",
|
||||
"retry": "Reintentar"
|
||||
"retry": "Reintentar",
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar",
|
||||
"edit": "Editar",
|
||||
"close": "Cerrar",
|
||||
"confirm": "Confirmar",
|
||||
"success": "Éxito",
|
||||
"warning": "Advertencia",
|
||||
"info": "Información",
|
||||
"search": "Buscar",
|
||||
"noResults": "Sin resultados",
|
||||
"viewAll": "Ver todo",
|
||||
"learnMore": "Más información"
|
||||
},
|
||||
"nav": {
|
||||
"catalog": "Catálogo",
|
||||
"myLearning": "Mi Aprendizaje",
|
||||
"bookmarks": "Marcadores",
|
||||
"profile": "Perfil",
|
||||
"signOut": "Cerrar Sesión"
|
||||
"signOut": "Cerrar Sesión",
|
||||
"selectLanguage": "Seleccionar Idioma",
|
||||
"menu": "Menú",
|
||||
"notifications": "Notificaciones",
|
||||
"settings": "Configuración"
|
||||
},
|
||||
"course": {
|
||||
"modules": "Módulos",
|
||||
@@ -20,12 +37,194 @@
|
||||
"progress": "Progreso",
|
||||
"calendar": "Cronología",
|
||||
"start": "Comenzar",
|
||||
"continue": "Continuar"
|
||||
"continue": "Continuar",
|
||||
"enroll": "Inscribirse",
|
||||
"enrolled": "Inscrito",
|
||||
"price": "Precio",
|
||||
"free": "Gratis",
|
||||
"duration": "Duración",
|
||||
"level": "Nivel",
|
||||
"description": "Descripción",
|
||||
"objectives": "Objetivos",
|
||||
"requirements": "Requisitos",
|
||||
"instructor": "Instructor",
|
||||
"certificate": "Certificado",
|
||||
"aboutCourse": "Acerca del Curso",
|
||||
"content": "Contenido",
|
||||
"reviews": "Reseñas",
|
||||
"faq": "Preguntas Frecuentes"
|
||||
},
|
||||
"lesson": {
|
||||
"summary": "Resumen AI",
|
||||
"transcription": "Transcripción",
|
||||
"complete": "Marcar como Completado",
|
||||
"nextLesson": "Siguiente Lección"
|
||||
"nextLesson": "Siguiente Lección",
|
||||
"previousLesson": "Lección Anterior",
|
||||
"locked": "Bloqueado",
|
||||
"lockedMessage": "Completa las lecciones anteriores para desbloquear",
|
||||
"markComplete": "Marcar como Completada",
|
||||
"inProgress": "En Progreso",
|
||||
"completed": "Completada",
|
||||
"notStarted": "No Iniciada",
|
||||
"dueDate": "Fecha Límite",
|
||||
"timeRemaining": "Tiempo Restante",
|
||||
"overdue": "Vencido"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Iniciar Sesión",
|
||||
"register": "Registrarse",
|
||||
"email": "Correo Electrónico",
|
||||
"password": "Contraseña",
|
||||
"fullName": "Nombre Completo",
|
||||
"forgotPassword": "¿Olvidaste tu contraseña?",
|
||||
"resetPassword": "Restablecer Contraseña",
|
||||
"noAccount": "¿No tienes cuenta?",
|
||||
"hasAccount": "¿Ya tienes cuenta?",
|
||||
"signIn": "Ingresar",
|
||||
"signUp": "Crear Cuenta",
|
||||
"orContinueWith": "O continúa con",
|
||||
"google": "Google",
|
||||
"microsoft": "Microsoft",
|
||||
"sso": "Inicio de Sesión Único"
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Bienvenido",
|
||||
"continueLearning": "Continuar Aprendiendo",
|
||||
"recommendedCourses": "Cursos Recomendados",
|
||||
"recentActivity": "Actividad Reciente",
|
||||
"myProgress": "Mi Progreso",
|
||||
"certificates": "Certificados",
|
||||
"badges": "Insignias",
|
||||
"stats": {
|
||||
"coursesCompleted": "Cursos Completados",
|
||||
"hoursLearned": "Horas Aprendidas",
|
||||
"averageGrade": "Nota Promedio",
|
||||
"currentStreak": "Racha Actual"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"myProfile": "Mi Perfil",
|
||||
"editProfile": "Editar Perfil",
|
||||
"avatar": "Avatar",
|
||||
"bio": "Biografía",
|
||||
"language": "Idioma",
|
||||
"timezone": "Zona Horaria",
|
||||
"notifications": "Notificaciones",
|
||||
"privacy": "Privacidad",
|
||||
"account": "Cuenta",
|
||||
"changePassword": "Cambiar Contraseña",
|
||||
"deleteAccount": "Eliminar Cuenta",
|
||||
"portfolio": "Portafolio",
|
||||
"achievements": "Logros",
|
||||
"publicProfile": "Perfil Público"
|
||||
},
|
||||
"gamification": {
|
||||
"level": "Nivel",
|
||||
"xp": "Puntos de Experiencia",
|
||||
"leaderboard": "Tabla de Clasificación",
|
||||
"rank": "Rango",
|
||||
"points": "Puntos",
|
||||
"badge": "Insignia",
|
||||
"badges": "Insignias",
|
||||
"earned": "Obtenida",
|
||||
"locked": "Bloqueada",
|
||||
"nextLevel": "Siguiente Nivel",
|
||||
"xpToNextLevel": "XP para el siguiente nivel"
|
||||
},
|
||||
"grading": {
|
||||
"grade": "Nota",
|
||||
"grades": "Notas",
|
||||
"score": "Puntaje",
|
||||
"percentage": "Porcentaje",
|
||||
"passingGrade": "Nota de Aprobación",
|
||||
"failed": "Reprobado",
|
||||
"passed": "Aprobado",
|
||||
"excellent": "Excelente",
|
||||
"good": "Bueno",
|
||||
"regular": "Regular",
|
||||
"poor": "Deficiente",
|
||||
"feedback": "Retroalimentación",
|
||||
"submissionDate": "Fecha de Entrega",
|
||||
"gradedDate": "Fecha de Calificación"
|
||||
},
|
||||
"quiz": {
|
||||
"startQuiz": "Comenzar Cuestionario",
|
||||
"submitAnswers": "Enviar Respuestas",
|
||||
"questionNumber": "Pregunta",
|
||||
"of": "de",
|
||||
"correctAnswer": "Respuesta Correcta",
|
||||
"yourAnswer": "Tu Respuesta",
|
||||
"explanation": "Explicación",
|
||||
"tryAgain": "Intentar de Nuevo",
|
||||
"attempts": "Intentos",
|
||||
"attemptsRemaining": "intentos restantes",
|
||||
"timeLimit": "Límite de Tiempo",
|
||||
"timeExpired": "Tiempo Expirado"
|
||||
},
|
||||
"forum": {
|
||||
"discussions": "Discusiones",
|
||||
"newThread": "Nueva Discusión",
|
||||
"title": "Título",
|
||||
"content": "Contenido",
|
||||
"post": "Publicar",
|
||||
"reply": "Responder",
|
||||
"replies": "Respuestas",
|
||||
"views": "Vistas",
|
||||
"lastActivity": "Última Actividad",
|
||||
"subscribe": "Suscribirse",
|
||||
"unsubscribe": "Desuscribirse",
|
||||
"markAsSolved": "Marcar como Resuelto",
|
||||
"upvote": "Votar a Favor",
|
||||
"downvote": "Votar en Contra"
|
||||
},
|
||||
"payments": {
|
||||
"purchase": "Comprar",
|
||||
"buyNow": "Comprar Ahora",
|
||||
"paymentMethod": "Método de Pago",
|
||||
"cardNumber": "Número de Tarjeta",
|
||||
"expiryDate": "Fecha de Vencimiento",
|
||||
"cvv": "CVV",
|
||||
"cardholderName": "Nombre del Titular",
|
||||
"paymentSuccess": "Pago Exitoso",
|
||||
"paymentFailed": "Pago Fallido",
|
||||
"refund": "Reembolso",
|
||||
"invoice": "Factura",
|
||||
"receipt": "Recibo"
|
||||
},
|
||||
"accessibility": {
|
||||
"skipToContent": "Saltar al contenido",
|
||||
"increaseContrast": "Aumentar Contraste",
|
||||
"decreaseContrast": "Disminuir Contraste",
|
||||
"largerText": "Texto Más Grande",
|
||||
"smallerText": "Texto Más Pequeño",
|
||||
"screenReader": "Lector de Pantalla"
|
||||
},
|
||||
"language": {
|
||||
"selectLanguage": "Seleccionar Idioma",
|
||||
"auto": "Automático",
|
||||
"spanish": "Español",
|
||||
"english": "Inglés",
|
||||
"portuguese": "Portugués",
|
||||
"courseLanguage": "Idioma del Curso",
|
||||
"interfaceLanguage": "Idioma de la Interfaz"
|
||||
},
|
||||
"errors": {
|
||||
"notFound": "No encontrado",
|
||||
"unauthorized": "No autorizado",
|
||||
"serverError": "Error del servidor",
|
||||
"networkError": "Error de red",
|
||||
"sessionExpired": "Sesión expirada",
|
||||
"invalidCredentials": "Credenciales inválidas",
|
||||
"emailInUse": "Correo ya en uso",
|
||||
"weakPassword": "Contraseña débil"
|
||||
},
|
||||
"dates": {
|
||||
"today": "Hoy",
|
||||
"yesterday": "Ayer",
|
||||
"tomorrow": "Mañana",
|
||||
"daysAgo": "hace {{days}} días",
|
||||
"weeksAgo": "hace {{weeks}} semanas",
|
||||
"monthsAgo": "hace {{months}} meses",
|
||||
"yearsAgo": "hace {{years}} años"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,29 +3,228 @@
|
||||
"loading": "Carregando...",
|
||||
"back": "Voltar",
|
||||
"next": "Próximo",
|
||||
"finish": "Finalizar",
|
||||
"finish": "Concluir",
|
||||
"error": "Erro",
|
||||
"retry": "Repetir"
|
||||
"retry": "Tentar Novamente",
|
||||
"save": "Salvar",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Excluir",
|
||||
"edit": "Editar",
|
||||
"close": "Fechar",
|
||||
"confirm": "Confirmar",
|
||||
"success": "Sucesso",
|
||||
"warning": "Aviso",
|
||||
"info": "Informação",
|
||||
"search": "Buscar",
|
||||
"noResults": "Sem resultados",
|
||||
"viewAll": "Ver tudo",
|
||||
"learnMore": "Saiba mais"
|
||||
},
|
||||
"nav": {
|
||||
"catalog": "Catálogo",
|
||||
"myLearning": "Meu Aprendizado",
|
||||
"bookmarks": "Favoritos",
|
||||
"profile": "Perfil",
|
||||
"signOut": "Sair"
|
||||
"signOut": "Sair",
|
||||
"selectLanguage": "Selecionar Idioma",
|
||||
"menu": "Menu",
|
||||
"notifications": "Notificações",
|
||||
"settings": "Configurações"
|
||||
},
|
||||
"course": {
|
||||
"modules": "Módulos",
|
||||
"lessons": "Lições",
|
||||
"lessons": "Aulas",
|
||||
"progress": "Progresso",
|
||||
"calendar": "Cronologia",
|
||||
"calendar": "Cronograma",
|
||||
"start": "Começar",
|
||||
"continue": "Continuar"
|
||||
"continue": "Continuar",
|
||||
"enroll": "Inscrever-se",
|
||||
"enrolled": "Inscrito",
|
||||
"price": "Preço",
|
||||
"free": "Gratuito",
|
||||
"duration": "Duração",
|
||||
"level": "Nível",
|
||||
"description": "Descrição",
|
||||
"objectives": "Objetivos",
|
||||
"requirements": "Requisitos",
|
||||
"instructor": "Instrutor",
|
||||
"certificate": "Certificado",
|
||||
"aboutCourse": "Sobre o Curso",
|
||||
"content": "Conteúdo",
|
||||
"reviews": "Avaliações",
|
||||
"faq": "Perguntas Frequentes"
|
||||
},
|
||||
"lesson": {
|
||||
"summary": "Resumo IA",
|
||||
"transcription": "Transcrição",
|
||||
"complete": "Marcar como Concluído",
|
||||
"nextLesson": "Próxima Lição"
|
||||
"complete": "Marcar como Concluída",
|
||||
"nextLesson": "Próxima Aula",
|
||||
"previousLesson": "Aula Anterior",
|
||||
"locked": "Bloqueada",
|
||||
"lockedMessage": "Complete as aulas anteriores para desbloquear",
|
||||
"markComplete": "Marcar como Concluída",
|
||||
"inProgress": "Em Progresso",
|
||||
"completed": "Concluída",
|
||||
"notStarted": "Não Iniciada",
|
||||
"dueDate": "Data Limite",
|
||||
"timeRemaining": "Tempo Restante",
|
||||
"overdue": "Atrasada"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Entrar",
|
||||
"register": "Cadastrar",
|
||||
"email": "E-mail",
|
||||
"password": "Senha",
|
||||
"fullName": "Nome Completo",
|
||||
"forgotPassword": "Esqueceu a senha?",
|
||||
"resetPassword": "Redefinir Senha",
|
||||
"noAccount": "Não tem uma conta?",
|
||||
"hasAccount": "Já tem uma conta?",
|
||||
"signIn": "Entrar",
|
||||
"signUp": "Criar Conta",
|
||||
"orContinueWith": "Ou continue com",
|
||||
"google": "Google",
|
||||
"microsoft": "Microsoft",
|
||||
"sso": "Login Único"
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Bem-vindo",
|
||||
"continueLearning": "Continuar Aprendendo",
|
||||
"recommendedCourses": "Cursos Recomendados",
|
||||
"recentActivity": "Atividade Recente",
|
||||
"myProgress": "Meu Progresso",
|
||||
"certificates": "Certificados",
|
||||
"badges": "Distintivos",
|
||||
"stats": {
|
||||
"coursesCompleted": "Cursos Concluídos",
|
||||
"hoursLearned": "Horas Aprendidas",
|
||||
"averageGrade": "Nota Média",
|
||||
"currentStreak": "Sequência Atual"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"myProfile": "Meu Perfil",
|
||||
"editProfile": "Editar Perfil",
|
||||
"avatar": "Avatar",
|
||||
"bio": "Biografia",
|
||||
"language": "Idioma",
|
||||
"timezone": "Fuso Horário",
|
||||
"notifications": "Notificações",
|
||||
"privacy": "Privacidade",
|
||||
"account": "Conta",
|
||||
"changePassword": "Alterar Senha",
|
||||
"deleteAccount": "Excluir Conta",
|
||||
"portfolio": "Portfólio",
|
||||
"achievements": "Conquistas",
|
||||
"publicProfile": "Perfil Público"
|
||||
},
|
||||
"gamification": {
|
||||
"level": "Nível",
|
||||
"xp": "Pontos de Experiência",
|
||||
"leaderboard": "Classificação",
|
||||
"rank": "Posição",
|
||||
"points": "Pontos",
|
||||
"badge": "Distintivo",
|
||||
"badges": "Distintivos",
|
||||
"earned": "Conquistado",
|
||||
"locked": "Bloqueado",
|
||||
"nextLevel": "Próximo Nível",
|
||||
"xpToNextLevel": "XP para o próximo nível"
|
||||
},
|
||||
"grading": {
|
||||
"grade": "Nota",
|
||||
"grades": "Notas",
|
||||
"score": "Pontuação",
|
||||
"percentage": "Porcentagem",
|
||||
"passingGrade": "Nota Mínima",
|
||||
"failed": "Reprovado",
|
||||
"passed": "Aprovado",
|
||||
"excellent": "Excelente",
|
||||
"good": "Bom",
|
||||
"regular": "Regular",
|
||||
"poor": "Insuficiente",
|
||||
"feedback": "Feedback",
|
||||
"submissionDate": "Data de Entrega",
|
||||
"gradedDate": "Data de Avaliação"
|
||||
},
|
||||
"quiz": {
|
||||
"startQuiz": "Iniciar Questionário",
|
||||
"submitAnswers": "Enviar Respostas",
|
||||
"questionNumber": "Pergunta",
|
||||
"of": "de",
|
||||
"correctAnswer": "Resposta Correta",
|
||||
"yourAnswer": "Sua Resposta",
|
||||
"explanation": "Explicação",
|
||||
"tryAgain": "Tentar Novamente",
|
||||
"attempts": "Tentativas",
|
||||
"attemptsRemaining": "tentativas restantes",
|
||||
"timeLimit": "Limite de Tempo",
|
||||
"timeExpired": "Tempo Esgotado"
|
||||
},
|
||||
"forum": {
|
||||
"discussions": "Discussões",
|
||||
"newThread": "Nova Discussão",
|
||||
"title": "Título",
|
||||
"content": "Conteúdo",
|
||||
"post": "Publicar",
|
||||
"reply": "Responder",
|
||||
"replies": "Respostas",
|
||||
"views": "Visualizações",
|
||||
"lastActivity": "Última Atividade",
|
||||
"subscribe": "Inscrever-se",
|
||||
"unsubscribe": "Cancelar Inscrição",
|
||||
"markAsSolved": "Marcar como Resolvido",
|
||||
"upvote": "Votar a Favor",
|
||||
"downvote": "Votar Contra"
|
||||
},
|
||||
"payments": {
|
||||
"purchase": "Comprar",
|
||||
"buyNow": "Comprar Agora",
|
||||
"paymentMethod": "Método de Pagamento",
|
||||
"cardNumber": "Número do Cartão",
|
||||
"expiryDate": "Data de Validade",
|
||||
"cvv": "CVV",
|
||||
"cardholderName": "Nome do Titular",
|
||||
"paymentSuccess": "Pagamento Bem-Sucedido",
|
||||
"paymentFailed": "Pagamento Falhou",
|
||||
"refund": "Reembolso",
|
||||
"invoice": "Fatura",
|
||||
"receipt": "Recibo"
|
||||
},
|
||||
"accessibility": {
|
||||
"skipToContent": "Pular para o conteúdo",
|
||||
"increaseContrast": "Aumentar Contraste",
|
||||
"decreaseContrast": "Diminuir Contraste",
|
||||
"largerText": "Texto Maior",
|
||||
"smallerText": "Texto Menor",
|
||||
"screenReader": "Leitor de Tela"
|
||||
},
|
||||
"language": {
|
||||
"selectLanguage": "Selecionar Idioma",
|
||||
"auto": "Automático",
|
||||
"spanish": "Espanhol",
|
||||
"english": "Inglês",
|
||||
"portuguese": "Português",
|
||||
"courseLanguage": "Idioma do Curso",
|
||||
"interfaceLanguage": "Idioma da Interface"
|
||||
},
|
||||
"errors": {
|
||||
"notFound": "Não encontrado",
|
||||
"unauthorized": "Não autorizado",
|
||||
"serverError": "Erro do servidor",
|
||||
"networkError": "Erro de rede",
|
||||
"sessionExpired": "Sessão expirada",
|
||||
"invalidCredentials": "Credenciais inválidas",
|
||||
"emailInUse": "E-mail já em uso",
|
||||
"weakPassword": "Senha fraca"
|
||||
},
|
||||
"dates": {
|
||||
"today": "Hoje",
|
||||
"yesterday": "Ontem",
|
||||
"tomorrow": "Amanhã",
|
||||
"daysAgo": "há {{days}} dias",
|
||||
"weeksAgo": "há {{weeks}} semanas",
|
||||
"monthsAgo": "há {{months}} meses",
|
||||
"yearsAgo": "há {{years}} anos"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user