feat: localize various UI texts and labels to Spanish across application pages and interactive components.
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user