Files
breakpilot-lehrer/studio-v2/components/learn/ExerciseLayout.tsx
Benjamin Admin ef821831a4 Fix: Fallback to English (not German) for unsupported languages
When a language like PT, FR, IT is selected but has no translation
in exerciseTranslations.ts, the system now falls back to English
instead of German. English is more universally understood.

Applies to: exercise explanations, button labels, instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 13:37:19 +02:00

148 lines
9.8 KiB
TypeScript

'use client'
import React from 'react'
import { useTheme } from '@/lib/ThemeContext'
import { useNativeLanguage } from '@/lib/useNativeLanguage'
interface ExerciseLayoutProps {
/** Main exercise content (2/3 left) */
children: React.ReactNode
/** Native language helper content for the right panel */
nativeHelper?: React.ReactNode
/** Explanation text for parents (shown above native words) */
exerciseExplanation?: string
/** Exercise type key for auto-explanation lookup (e.g. 'match', 'flashcards') */
exerciseType?: string
/** Title for the exercise in the header */
title: string
/** Progress: current / total */
progress?: { current: number; total: number }
/** Back button handler */
onBack?: () => void
/** Score display */
score?: React.ReactNode
}
const explanations: Record<string, Record<string, string>> = {
match: {
de: 'Ihr Kind soll die richtige Uebersetzung finden. Es klickt zuerst ein Wort in der linken Spalte und dann die passende Uebersetzung in der rechten Spalte. In diesem Bereich sehen Sie die Woerter in Ihrer Sprache.',
en: 'Your child should find the correct translation. They click a word on the left, then the matching translation on the right. Here you see the words in your language.',
tr: 'Cocugunuz dogru ceviriyi bulmali. Soldaki bir kelimeye, sonra sagdaki dogru ceviriye tiklar. Bu alanda kelimeleri kendi dilinizde gorursunuz.',
ar: '\u064a\u062c\u0628 \u0639\u0644\u0649 \u0637\u0641\u0644\u0643 \u0625\u064a\u062c\u0627\u062f \u0627\u0644\u062a\u0631\u062c\u0645\u0629 \u0627\u0644\u0635\u062d\u064a\u062d\u0629. \u064a\u0646\u0642\u0631 \u0639\u0644\u0649 \u0643\u0644\u0645\u0629 \u0639\u0644\u0649 \u0627\u0644\u064a\u0633\u0627\u0631 \u062b\u0645 \u0627\u0644\u062a\u0631\u062c\u0645\u0629 \u0627\u0644\u0645\u0646\u0627\u0633\u0628\u0629 \u0639\u0644\u0649 \u0627\u0644\u064a\u0645\u064a\u0646. \u0647\u0646\u0627 \u062a\u0631\u0649 \u0627\u0644\u0643\u0644\u0645\u0627\u062a \u0628\u0644\u063a\u062a\u0643.',
uk: '\u0412\u0430\u0448\u0430 \u0434\u0438\u0442\u0438\u043d\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0437\u043d\u0430\u0439\u0442\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043f\u0435\u0440\u0435\u043a\u043b\u0430\u0434. \u0412\u043e\u043d\u0430 \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u0454 \u043d\u0430 \u0441\u043b\u043e\u0432\u043e \u043b\u0456\u0432\u043e\u0440\u0443\u0447, \u043f\u043e\u0442\u0456\u043c \u043d\u0430 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u0438\u0439 \u043f\u0435\u0440\u0435\u043a\u043b\u0430\u0434 \u043f\u0440\u0430\u0432\u043e\u0440\u0443\u0447. \u0422\u0443\u0442 \u0432\u0438 \u0431\u0430\u0447\u0438\u0442\u0435 \u0441\u043b\u043e\u0432\u0430 \u0432\u0430\u0448\u043e\u044e \u043c\u043e\u0432\u043e\u044e.',
ru: '\u0412\u0430\u0448 \u0440\u0435\u0431\u0435\u043d\u043e\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u043d\u0430\u0439\u0442\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434. \u041e\u043d \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u0442 \u043d\u0430 \u0441\u043b\u043e\u0432\u043e \u0441\u043b\u0435\u0432\u0430, \u0437\u0430\u0442\u0435\u043c \u043d\u0430 \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0441\u043f\u0440\u0430\u0432\u0430. \u0417\u0434\u0435\u0441\u044c \u0432\u044b \u0432\u0438\u0434\u0438\u0442\u0435 \u0441\u043b\u043e\u0432\u0430 \u043d\u0430 \u0432\u0430\u0448\u0435\u043c \u044f\u0437\u044b\u043a\u0435.',
pl: 'Twoje dziecko powinno znalezc poprawne tlumaczenie. Klika slowo po lewej, potem odpowiednie tlumaczenie po prawej. Tutaj widzisz slowa w swoim jezyku.',
},
flashcards: {
de: 'Ihr Kind sieht ein englisches Wort und soll die deutsche Uebersetzung wissen. Klicken Sie auf die Karte um sie umzudrehen. Hier sehen Sie die Woerter in Ihrer Sprache.',
tr: 'Cocugunuz bir Ingilizce kelime gorur ve Almanca cevirisini bilmeli. Karti cevirmek icin tiklayin. Burada kelimeleri kendi dilinizde gorursunuz.',
ar: '\u064a\u0631\u0649 \u0637\u0641\u0644\u0643 \u0643\u0644\u0645\u0629 \u0625\u0646\u062c\u0644\u064a\u0632\u064a\u0629 \u0648\u064a\u062c\u0628 \u0623\u0646 \u064a\u0639\u0631\u0641 \u0627\u0644\u062a\u0631\u062c\u0645\u0629 \u0627\u0644\u0623\u0644\u0645\u0627\u0646\u064a\u0629. \u0627\u0646\u0642\u0631 \u0644\u0642\u0644\u0628 \u0627\u0644\u0628\u0637\u0627\u0642\u0629. \u0647\u0646\u0627 \u062a\u0631\u0649 \u0627\u0644\u0643\u0644\u0645\u0627\u062a \u0628\u0644\u063a\u062a\u0643.',
uk: '\u0414\u0438\u0442\u0438\u043d\u0430 \u0431\u0430\u0447\u0438\u0442\u044c \u0430\u043d\u0433\u043b\u0456\u0439\u0441\u044c\u043a\u0435 \u0441\u043b\u043e\u0432\u043e \u0456 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0437\u043d\u0430\u0442\u0438 \u043d\u0456\u043c\u0435\u0446\u044c\u043a\u0438\u0439 \u043f\u0435\u0440\u0435\u043a\u043b\u0430\u0434. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u0442\u0430\u043d\u043d\u044f. \u0422\u0443\u0442 \u0441\u043b\u043e\u0432\u0430 \u0432\u0430\u0448\u043e\u044e \u043c\u043e\u0432\u043e\u044e.',
ru: '\u0420\u0435\u0431\u0435\u043d\u043e\u043a \u0432\u0438\u0434\u0438\u0442 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u0438 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u043d\u0430\u0442\u044c \u043d\u0435\u043c\u0435\u0446\u043a\u0438\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u0442\u044c. \u0417\u0434\u0435\u0441\u044c \u0441\u043b\u043e\u0432\u0430 \u043d\u0430 \u0432\u0430\u0448\u0435\u043c \u044f\u0437\u044b\u043a\u0435.',
pl: 'Dziecko widzi angielskie slowo i powinno znac niemieckie tlumaczenie. Kliknij aby odwrocic kartke. Tutaj slowa w Twoim jezyku.',
en: 'Your child sees an English word and should know the German translation. Click to flip the card. Here you see the words in your language.',
},
}
/**
* Standard exercise layout: 2/3 work area (left) + 1/3 native helper (right).
* The right panel only appears for non-DE/EN speakers.
*/
export function ExerciseLayout({
children,
nativeHelper,
exerciseExplanation,
exerciseType,
title,
progress,
onBack,
score,
}: ExerciseLayoutProps) {
const { isDark } = useTheme()
const { nativeLang, isThirdLanguage, t } = useNativeLanguage()
const glassCard = isDark
? 'bg-white/10 backdrop-blur-xl border border-white/10'
: 'bg-white/80 backdrop-blur-xl border border-black/5'
const typeKey = exerciseType || title.toLowerCase()
const explanation = exerciseExplanation
|| explanations[typeKey]?.[nativeLang]
|| explanations[typeKey]?.['en']
|| explanations[typeKey]?.['de']
|| ''
return (
<>
{/* Header */}
<div className={`${glassCard} border-0 border-b`}>
<div className="max-w-5xl mx-auto px-6 py-3 flex items-center justify-between">
{onBack ? (
<button onClick={onBack} className={`flex items-center gap-2 text-sm ${isDark ? 'text-white/60 hover:text-white' : 'text-slate-500 hover:text-slate-900'}`}>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" /></svg>
{t('back')}
</button>
) : <span />}
<h1 className={`text-lg font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>{title}</h1>
{score || <span />}
</div>
</div>
{/* Progress bar */}
{progress && (
<div className="max-w-5xl mx-auto w-full px-6 pt-3">
<div className="flex items-center gap-3">
<div className={`flex-1 h-2 rounded-full ${isDark ? 'bg-white/10' : 'bg-slate-200'}`}>
<div className="h-full rounded-full bg-gradient-to-r from-indigo-500 to-violet-500 transition-all"
style={{ width: `${(progress.current / Math.max(progress.total, 1)) * 100}%` }} />
</div>
<span className={`text-xs font-medium tabular-nums ${isDark ? 'text-white/50' : 'text-slate-400'}`}>
{progress.current}/{progress.total}
</span>
</div>
</div>
)}
{/* Main content: 2/3 left + 1/3 right */}
<div className="max-w-5xl mx-auto w-full px-6 py-6">
<div className="flex gap-0">
{/* Left: Exercise area (2/3 or full) */}
<div className={isThirdLanguage ? 'w-2/3 pr-6' : 'w-full'}>
{children}
</div>
{/* Divider line */}
{isThirdLanguage && (
<div className={`w-px self-stretch ${isDark ? 'bg-white/10' : 'bg-slate-200'}`} />
)}
{/* Right: Native language helper (1/3) — only for migrants */}
{isThirdLanguage && (
<div className="w-1/3 pl-6">
<div className="sticky top-20 space-y-4">
{/* Explanation card */}
{explanation && (
<div className={`rounded-2xl p-4 ${isDark ? 'bg-cyan-500/5 border border-cyan-400/20' : 'bg-cyan-50 border border-cyan-200'}`}>
<h3 className={`text-xs font-semibold mb-2 flex items-center gap-2 ${isDark ? 'text-cyan-300' : 'text-cyan-700'}`}>
<span className="text-base">💡</span>
{nativeLang.toUpperCase()} · {t('english')} · {t('german')}
</h3>
<p className={`text-xs leading-relaxed ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
{explanation}
</p>
</div>
)}
{/* Native words */}
{nativeHelper}
</div>
</div>
)}
</div>
</div>
</>
)
}
export { explanations as exerciseExplanations }