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:
2026-03-23 12:24:22 -03:00
parent 0598fc4865
commit 2ff06ee7ae
26 changed files with 2993 additions and 124 deletions
@@ -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">
+51 -5
View File
@@ -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(),
};
}
+204 -5
View File
@@ -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"
}
}
}
+204 -5
View File
@@ -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"
}
}
}
+208 -9
View File
@@ -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"
}
}
}