feat: localize various UI texts and labels to Spanish across application pages and interactive components.

This commit is contained in:
2026-01-16 23:25:36 -03:00
parent 2cfd1f204b
commit ffbef17396
19 changed files with 178 additions and 181 deletions
+5 -5
View File
@@ -21,15 +21,15 @@ export default function AppHeader() {
</div>
<div className="flex flex-col -gap-1">
<span className="font-black text-lg tracking-tighter text-white leading-none">
{branding?.name?.toUpperCase() || 'LEARN'}
{branding?.name?.toUpperCase() || 'APRENDER'}
</span>
{!branding && <span className="text-[10px] font-black tracking-widest text-blue-500 uppercase">EXPERIENCE</span>}
{!branding && <span className="text-[10px] font-black tracking-widest text-blue-500 uppercase">EXPERIENCIA</span>}
</div>
</Link>
<nav className="hidden md:flex items-center gap-8">
<Link href="/" className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-400 hover:text-white transition-colors">Catalog</Link>
<Link href="/my-learning" className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-400 hover:text-white transition-colors">My Learning</Link>
<Link href="/" className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-400 hover:text-white transition-colors">Catálogo</Link>
<Link href="/my-learning" className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-400 hover:text-white transition-colors">Mi Aprendizaje</Link>
<div className="flex items-center gap-4 pl-4 border-l border-white/10">
<Link href="/profile" className="flex items-center gap-2 group/profile">
@@ -40,7 +40,7 @@ export default function AppHeader() {
<button
onClick={logout}
className="p-2 hover:bg-red-500/10 rounded-full text-gray-400 hover:text-red-400 transition-colors"
title="Logout"
title="Cerrar Sesión"
>
<LogOut size={16} />
</button>
@@ -43,7 +43,7 @@ export default function InteractiveTranscript({ cues, currentTime, onSeek }: Int
<div className="flex flex-col h-full glass-card overflow-hidden border-white/5 bg-black/20">
<div className="p-6 border-b border-white/5 flex items-center gap-3 bg-white/5">
<Clock className="w-4 h-4 text-blue-400" />
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-400">Interactive Transcript</h3>
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-400">Transcriptor Interactivo</h3>
</div>
<div
@@ -53,7 +53,7 @@ export default function InteractiveTranscript({ cues, currentTime, onSeek }: Int
{cues.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center p-8">
<span className="text-4xl mb-4">🤐</span>
<p className="text-xs text-gray-500 uppercase tracking-widest font-bold">No transcription available for this content</p>
<p className="text-xs text-gray-500 uppercase tracking-widest font-bold">No hay transcripción disponible para este contenido</p>
</div>
) : (
cues.map((cue, index) => {
@@ -83,7 +83,7 @@ export default function InteractiveTranscript({ cues, currentTime, onSeek }: Int
</div>
<div className="p-4 bg-white/5 border-t border-white/5 flex items-center justify-between">
<span className="text-[8px] font-bold text-gray-500 uppercase tracking-widest">Click any segment to jump</span>
<span className="text-[8px] font-bold text-gray-500 uppercase tracking-widest">Haz clic en cualquier segmento para saltar</span>
<div className="flex gap-1">
<div className="w-1 h-1 rounded-full bg-blue-500 animate-pulse"></div>
<div className="w-1 h-1 rounded-full bg-blue-500/50"></div>
@@ -14,7 +14,7 @@ export default function Leaderboard() {
const data = await lmsApi.getLeaderboard();
setTopUsers(data);
} catch (err) {
console.error("Failed to fetch leaderboard", err);
console.error("Error al obtener la tabla de clasificación", err);
} finally {
setLoading(false);
}
@@ -33,7 +33,7 @@ export default function Leaderboard() {
return (
<div className="glass-card p-8 border-white/5 bg-white/[0.01] rounded-3xl overflow-hidden relative">
<h3 className="text-xs font-black uppercase tracking-widest text-gray-500 mb-6 flex items-center gap-2">
<Trophy size={14} className="text-amber-500" /> Leaderboard
<Trophy size={14} className="text-amber-500" /> Tabla de Clasificación
</h3>
<div className="space-y-3">
@@ -53,7 +53,7 @@ export default function Leaderboard() {
<div className="flex-1">
<div className="text-sm font-bold text-gray-200 line-clamp-1">{user.full_name}</div>
<div className="text-[10px] font-bold text-gray-500 uppercase tracking-widest">Level {user.level || 1}</div>
<div className="text-[10px] font-bold text-gray-500 uppercase tracking-widest">Nivel {user.level || 1}</div>
</div>
<div className="text-right">
@@ -65,7 +65,7 @@ export default function Leaderboard() {
{topUsers.length === 0 && (
<div className="text-center py-8 text-gray-600 italic text-sm">
No ranking data available yet.
Aún no hay datos de clasificación disponibles.
</div>
)}
</div>
@@ -11,7 +11,7 @@ export default function DescriptionPlayer({ id, title, content }: DescriptionPla
<div className="space-y-8" id={id}>
<div className="space-y-2">
<h3 className="text-xl font-bold border-l-4 border-blue-500 pl-4 py-1 tracking-tight text-white uppercase tracking-widest text-[10px]">
{title || "Overview"}
{title || "Resumen"}
</h3>
</div>
@@ -46,7 +46,7 @@ export default function FillInTheBlanksPlayer({ id, title, content, allowRetry =
<div className="space-y-8" id={id}>
<div className="space-y-2">
<h3 className="text-xl font-bold border-l-4 border-blue-500 pl-4 py-1 tracking-tight text-white uppercase tracking-widest text-[10px]">
{title || "Fill in the Blanks"}
{title || "Rellena los Espacios en Blanco"}
</h3>
</div>
@@ -84,7 +84,7 @@ export default function FillInTheBlanksPlayer({ id, title, content, allowRetry =
onClick={() => setSubmitted(true)}
className="btn-premium w-full py-5 font-black text-xs uppercase tracking-[0.2em] shadow-xl shadow-blue-500/20"
>
Validate Answers
Validar Respuestas
</button>
)}
@@ -93,7 +93,7 @@ export default function FillInTheBlanksPlayer({ id, title, content, allowRetry =
onClick={handleReset}
className="w-full py-5 glass text-blue-400 font-black text-xs uppercase tracking-[0.2em] hover:bg-white/5 transition-all rounded-3xl border-white/5"
>
Try Again
Intentar de Nuevo
</button>
)}
</>
@@ -41,13 +41,13 @@ export default function MatchingPlayer({ id, title, pairs, allowRetry = true }:
<div className="space-y-8" id={id}>
<div className="space-y-2">
<h3 className="text-xl font-bold border-l-4 border-blue-500 pl-4 py-1 tracking-tight text-white uppercase tracking-widest text-[10px]">
{title || "Concept Matching"}
{title || "Emparejamiento de Conceptos"}
</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 p-8 glass border-white/5 rounded-3xl relative">
<div className="space-y-4">
<label className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4 block">Term</label>
<label className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4 block">Término</label>
{(pairs || []).map((pair, i) => (
<button
key={i}
@@ -63,7 +63,7 @@ export default function MatchingPlayer({ id, title, pairs, allowRetry = true }:
</div>
<div className="space-y-4">
<label className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4 block">Definition</label>
<label className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4 block">Definición</label>
{shuffledRight.map((item, i) => {
const matchedLeftIdx = Object.keys(matches).find(k => matches[parseInt(k)] === item.originalIdx);
const isCorrect = submitted && matchedLeftIdx !== undefined && parseInt(matchedLeftIdx) === item.originalIdx;
@@ -98,7 +98,7 @@ export default function MatchingPlayer({ id, title, pairs, allowRetry = true }:
onClick={() => setSubmitted(true)}
className="btn-premium w-full py-5 font-black text-xs uppercase tracking-[0.2em] shadow-xl shadow-blue-500/20"
>
Validate Matching
Validar Emparejamiento
</button>
)}
{submitted && (
@@ -106,7 +106,7 @@ export default function MatchingPlayer({ id, title, pairs, allowRetry = true }:
onClick={handleReset}
className="w-full py-5 glass text-blue-400 font-black text-xs uppercase tracking-[0.2em] hover:bg-white/5 transition-all rounded-2xl border-white/5"
>
Try Again
Intentar de Nuevo
</button>
)}
</div>
@@ -59,15 +59,15 @@ export default function MediaPlayer({ id, title, url, media_type, config, initia
if (locked) {
return (
<div className="space-y-4" id={id}>
<h3 className="text-xs font-black uppercase tracking-widest text-gray-400">{title || "Multimedia Content"}</h3>
<h3 className="text-xs font-black uppercase tracking-widest text-gray-400">{title || "Contenido Multimedia"}</h3>
<div className="glass-card aspect-video flex flex-col items-center justify-center gap-6 border-red-500/20 bg-red-500/5">
<div className="w-16 h-16 rounded-full bg-red-500/10 flex items-center justify-center text-red-500">
<Lock size={32} />
</div>
<div className="text-center">
<p className="text-xl font-bold text-white mb-2">Content Locked</p>
<p className="text-xl font-bold text-white mb-2">Contenido Bloqueado</p>
<p className="text-sm text-gray-500 max-w-xs uppercase tracking-widest font-black">
You have reached the limit of {maxPlays} plays for this content.
Has alcanzado el límite de {maxPlays} reproducciones para este contenido.
</p>
</div>
</div>
@@ -89,10 +89,10 @@ export default function MediaPlayer({ id, title, url, media_type, config, initia
return (
<div className="space-y-6" id={id}>
<div className="flex items-center justify-between">
<h3 className="text-xs font-black uppercase tracking-widest text-gray-400">{title || "Multimedia Content"}</h3>
<h3 className="text-xs font-black uppercase tracking-widest text-gray-400">{title || "Contenido Multimedia"}</h3>
{maxPlays > 0 && (
<span className="text-[10px] font-bold uppercase tracking-widest px-3 py-1 rounded-full bg-white/5 border border-white/5 text-gray-500">
{playCount} / {maxPlays} PLAYS
{playCount} / {maxPlays} REPRODUCCIONES
</span>
)}
</div>
@@ -134,7 +134,7 @@ export default function MediaPlayer({ id, title, url, media_type, config, initia
{maxPlays > 0 && playCount > 0 && (
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-orange-500/70 p-4 rounded-xl bg-orange-500/5 border border-orange-500/10">
<AlertCircle size={14} />
<span>Watch carefully. Content will lock after {maxPlays} plays.</span>
<span>Presta atención. El contenido se bloqueará después de {maxPlays} reproducciones.</span>
</div>
)}
</div>
@@ -37,14 +37,14 @@ export default function OrderingPlayer({ id, title, items, allowRetry = true }:
<div className="space-y-8" id={id}>
<div className="space-y-2">
<h3 className="text-xl font-bold border-l-4 border-blue-500 pl-4 py-1 tracking-tight text-white uppercase tracking-widest text-[10px]">
{title || "Sequence Ordering"}
{title || "Ordenamiento de Secuencia"}
</h3>
</div>
<div className="space-y-8 p-8 glass border-white/5 rounded-3xl">
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
<div className="space-y-4">
<label className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4 block">Available Items</label>
<label className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4 block">Elementos Disponibles</label>
<div className="flex flex-wrap gap-3">
{shuffledItems.map((item, i) => {
const isPicked = userOrder.includes(item.originalIdx);
@@ -65,9 +65,9 @@ export default function OrderingPlayer({ id, title, items, allowRetry = true }:
</div>
<div className="space-y-4">
<label className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4 block">Your Sequence</label>
<label className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4 block">Tu Secuencia</label>
<div className="space-y-3">
{userOrder.length === 0 && <p className="text-xs text-gray-600 italic py-4">Click items to build the sequence...</p>}
{userOrder.length === 0 && <p className="text-xs text-gray-600 italic py-4">Haz clic en los elementos para construir la secuencia...</p>}
{userOrder.map((idx, i) => {
const isItemCorrect = submitted && idx === i;
const isItemWrong = submitted && idx !== i;
@@ -100,7 +100,7 @@ export default function OrderingPlayer({ id, title, items, allowRetry = true }:
onClick={() => setSubmitted(true)}
className="btn-premium w-full py-5 font-black text-xs uppercase tracking-[0.2em] shadow-xl shadow-blue-500/20"
>
Validate Sequence
Validar Secuencia
</button>
)}
{submitted && (
@@ -108,7 +108,7 @@ export default function OrderingPlayer({ id, title, items, allowRetry = true }:
onClick={handleReset}
className="w-full py-5 glass text-blue-400 font-black text-xs uppercase tracking-[0.2em] hover:bg-white/5 transition-all rounded-2xl border-white/5"
>
Try Again
Intentar de Nuevo
</button>
)}
</div>
@@ -65,11 +65,11 @@ export default function QuizPlayer({ id, title, quizData, allowRetry = true, max
<div className="space-y-8 notranslate" id={id} translate="no">
<div className="space-y-2">
<h3 className="text-xl font-bold border-l-4 border-blue-500 pl-4 py-1 tracking-tight text-white uppercase tracking-widest text-[10px]">
{title || "Knowledge Check"}
{title || "Verificación de Conocimientos"}
</h3>
{maxAttempts > 0 && (
<span className="text-[10px] font-bold uppercase tracking-widest px-3 py-1 rounded-full bg-white/5 border border-white/5 text-gray-500">
Attempt {attempts} / {maxAttempts}
Intento {attempts} / {maxAttempts}
</span>
)}
</div>
@@ -106,7 +106,7 @@ export default function QuizPlayer({ id, title, quizData, allowRetry = true, max
<span>{opt}</span>
{submitted && isActuallyCorrect && <span></span>}
{submitted && isWrongSelection && <span></span>}
{submitted && missedCorrect && <span className="text-[10px] uppercase font-black tracking-tighter">Correct Answer</span>}
{submitted && missedCorrect && <span className="text-[10px] uppercase font-black tracking-tighter">Respuesta Correcta</span>}
</div>
</button>
);
@@ -123,7 +123,7 @@ export default function QuizPlayer({ id, title, quizData, allowRetry = true, max
disabled={maxAttempts > 0 && attempts >= maxAttempts}
className={`btn-premium w-full py-5 font-black text-xs uppercase tracking-[0.2em] shadow-xl shadow-blue-500/20 ${maxAttempts > 0 && attempts >= maxAttempts ? 'opacity-50 cursor-not-allowed' : ''}`}
>
{maxAttempts > 0 && attempts >= maxAttempts ? 'Max Attempts Reached' : 'Validate Answers'}
{maxAttempts > 0 && attempts >= maxAttempts ? 'Máximo de Intentos Alcanzado' : 'Validar Respuestas'}
</button>
)}
{submitted && (
@@ -136,7 +136,7 @@ export default function QuizPlayer({ id, title, quizData, allowRetry = true, max
disabled={maxAttempts > 0 && attempts >= maxAttempts}
className={`w-full py-5 glass text-blue-400 font-black text-xs uppercase tracking-[0.2em] hover:bg-white/5 transition-all rounded-3xl border-white/5 ${maxAttempts > 0 && attempts >= maxAttempts ? 'opacity-50 cursor-not-allowed' : ''}`}
>
{maxAttempts > 0 && attempts >= maxAttempts ? 'Max Attempts Reached' : 'Try Again'}
{maxAttempts > 0 && attempts >= maxAttempts ? 'Máximo de Intentos Alcanzado' : 'Intentar de Nuevo'}
</button>
)}
</>
@@ -25,12 +25,12 @@ export default function ShortAnswerPlayer({ id, title, prompt, correctAnswers, a
<div className="space-y-8" id={id}>
<div className="space-y-2">
<h3 className="text-xl font-bold border-l-4 border-blue-500 pl-4 py-1 tracking-tight text-white uppercase tracking-widest text-[10px]">
{title || "Short Answer"}
{title || "Respuesta Corta"}
</h3>
</div>
<div className="p-8 glass border-white/5 rounded-3xl space-y-8">
<p className="text-xl font-bold text-gray-100">{prompt || "Please enter your answer below:"}</p>
<p className="text-xl font-bold text-gray-100">{prompt || "Por favor, introduce tu respuesta a continuación:"}</p>
<div className="space-y-4">
<input
@@ -42,12 +42,12 @@ export default function ShortAnswerPlayer({ id, title, prompt, correctAnswers, a
? (isCorrect ? "border-green-500 bg-green-500/10 text-green-400" : "border-red-500 bg-red-500/10 text-red-100")
: "border-white/10 focus:border-blue-500 text-white"
}`}
placeholder="Type your answer..."
placeholder="Escribe tu respuesta..."
/>
{submitted && !isCorrect && (
<div className="p-4 bg-orange-500/10 border border-orange-500/20 rounded-xl animate-in fade-in duration-500">
<p className="text-[10px] text-orange-400 uppercase font-black tracking-widest">Suggested Answer(s):</p>
<p className="text-[10px] text-orange-400 uppercase font-black tracking-widest">Respuesta(s) Sugerida(s):</p>
<p className="text-sm text-gray-400 mt-1">{(correctAnswers || [])[0]}</p>
</div>
)}
@@ -61,7 +61,7 @@ export default function ShortAnswerPlayer({ id, title, prompt, correctAnswers, a
disabled={!userAnswer.trim()}
className="btn-premium w-full py-5 font-black text-xs uppercase tracking-[0.2em] shadow-xl shadow-blue-500/20 disabled:opacity-50 disabled:grayscale"
>
Submit Answer
Enviar Respuesta
</button>
)}
@@ -70,7 +70,7 @@ export default function ShortAnswerPlayer({ id, title, prompt, correctAnswers, a
onClick={handleReset}
className="w-full py-5 glass text-blue-400 font-black text-xs uppercase tracking-[0.2em] hover:bg-white/5 transition-all rounded-3xl border-white/5"
>
Try Again
Intentar de Nuevo
</button>
)}
</>