Translate language dropdown tabs + add info box in 26 languages
Some checks failed
CI / test-go-edu-search (push) Waiting to run
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 40s
CI / test-python-klausur (push) Failing after 2m35s
CI / test-python-agent-core (push) Successful in 18s
CI / test-nodejs-website (push) Successful in 28s
Some checks failed
CI / test-go-edu-search (push) Waiting to run
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 40s
CI / test-python-klausur (push) Failing after 2m35s
CI / test-python-agent-core (push) Successful in 18s
CI / test-nodejs-website (push) Successful in 28s
- Tab labels (Muttersprache/Schulsprache) now translate with selected language e.g. Turkish: "Ana dilim" / "Okul dili" - Info box below tabs explains each setting in the user's native language e.g. "Evde konustunuz dil. Tum menuler bu dilde gorunecektir." - Wider dropdown (w-72) to fit translated text - All 26 European languages covered Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,41 @@ import { useLanguage } from '@/lib/LanguageContext'
|
||||
import { useTheme } from '@/lib/ThemeContext'
|
||||
import { Language } from '@/lib/i18n'
|
||||
|
||||
interface LanguageDropdownProps {
|
||||
className?: string
|
||||
/** Tab labels + info text in all 26 languages */
|
||||
const TAB_LABELS: Record<string, { native: string; school: string; info_native: string; info_school: string }> = {
|
||||
de: { native: 'Muttersprache', school: 'Schulsprache', info_native: 'Die Sprache, die Sie zu Hause sprechen. Alle Menus und Erklaerungen erscheinen in dieser Sprache.', info_school: 'Die Sprache, in der Ihr Kind in der Schule unterrichtet wird.' },
|
||||
en: { native: 'My language', school: 'School language', info_native: 'The language you speak at home. All menus and explanations will appear in this language.', info_school: 'The language your child is taught in at school.' },
|
||||
tr: { native: 'Ana dilim', school: 'Okul dili', info_native: 'Evde konustunuz dil. Tum menuler ve aciklamalar bu dilde gorunecektir.', info_school: 'Cocugunuzun okulda ogretildigi dil.' },
|
||||
ar: { native: '\u0644\u063a\u062a\u064a', school: '\u0644\u063a\u0629 \u0627\u0644\u0645\u062f\u0631\u0633\u0629', info_native: '\u0627\u0644\u0644\u063a\u0629 \u0627\u0644\u062a\u064a \u062a\u062a\u062d\u062f\u062b\u0647\u0627 \u0641\u064a \u0627\u0644\u0645\u0646\u0632\u0644. \u0633\u062a\u0638\u0647\u0631 \u062c\u0645\u064a\u0639 \u0627\u0644\u0642\u0648\u0627\u0626\u0645 \u0628\u0647\u0630\u0647 \u0627\u0644\u0644\u063a\u0629.', info_school: '\u0627\u0644\u0644\u063a\u0629 \u0627\u0644\u062a\u064a \u064a\u062a\u0639\u0644\u0645 \u0628\u0647\u0627 \u0637\u0641\u0644\u0643 \u0641\u064a \u0627\u0644\u0645\u062f\u0631\u0633\u0629.' },
|
||||
uk: { native: '\u041c\u043e\u044f \u043c\u043e\u0432\u0430', school: '\u041c\u043e\u0432\u0430 \u0448\u043a\u043e\u043b\u0438', info_native: '\u041c\u043e\u0432\u0430, \u044f\u043a\u043e\u044e \u0432\u0438 \u0440\u043e\u0437\u043c\u043e\u0432\u043b\u044f\u0454\u0442\u0435 \u0432\u0434\u043e\u043c\u0430. \u0412\u0441\u0456 \u043c\u0435\u043d\u044e \u0431\u0443\u0434\u0443\u0442\u044c \u0446\u0456\u0454\u044e \u043c\u043e\u0432\u043e\u044e.', info_school: '\u041c\u043e\u0432\u0430, \u044f\u043a\u043e\u044e \u0432\u0430\u0448\u0430 \u0434\u0438\u0442\u0438\u043d\u0430 \u043d\u0430\u0432\u0447\u0430\u0454\u0442\u044c\u0441\u044f \u0432 \u0448\u043a\u043e\u043b\u0456.' },
|
||||
ru: { native: '\u041c\u043e\u0439 \u044f\u0437\u044b\u043a', school: '\u042f\u0437\u044b\u043a \u0448\u043a\u043e\u043b\u044b', info_native: '\u042f\u0437\u044b\u043a, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0432\u044b \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0435 \u0434\u043e\u043c\u0430. \u0412\u0441\u0435 \u043c\u0435\u043d\u044e \u0431\u0443\u0434\u0443\u0442 \u043d\u0430 \u044d\u0442\u043e\u043c \u044f\u0437\u044b\u043a\u0435.', info_school: '\u042f\u0437\u044b\u043a, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0432\u0430\u0448 \u0440\u0435\u0431\u0435\u043d\u043e\u043a \u0443\u0447\u0438\u0442\u0441\u044f \u0432 \u0448\u043a\u043e\u043b\u0435.' },
|
||||
pl: { native: 'Moj jezyk', school: 'Jezyk szkolny', info_native: 'Jezyk, ktorym mowisz w domu. Wszystkie menu beda w tym jezyku.', info_school: 'Jezyk, w ktorym uczy sie Twoje dziecko w szkole.' },
|
||||
fr: { native: 'Ma langue', school: 'Langue scolaire', info_native: 'La langue que vous parlez a la maison. Tous les menus apparaitront dans cette langue.', info_school: 'La langue dans laquelle votre enfant est enseigne a l\'ecole.' },
|
||||
es: { native: 'Mi idioma', school: 'Idioma escolar', info_native: 'El idioma que habla en casa. Todos los menus apareceran en este idioma.', info_school: 'El idioma en el que su hijo es ensenado en la escuela.' },
|
||||
it: { native: 'La mia lingua', school: 'Lingua scolastica', info_native: 'La lingua che parli a casa. Tutti i menu appariranno in questa lingua.', info_school: 'La lingua in cui tuo figlio viene insegnato a scuola.' },
|
||||
pt: { native: 'Minha lingua', school: 'Lingua escolar', info_native: 'A lingua que voce fala em casa. Todos os menus aparecerao neste idioma.', info_school: 'A lingua em que seu filho e ensinado na escola.' },
|
||||
nl: { native: 'Mijn taal', school: 'Schooltaal', info_native: 'De taal die u thuis spreekt. Alle menu\'s verschijnen in deze taal.', info_school: 'De taal waarin uw kind op school les krijgt.' },
|
||||
ro: { native: 'Limba mea', school: 'Limba scolara', info_native: 'Limba pe care o vorbiti acasa. Toate meniurile vor aparea in aceasta limba.', info_school: 'Limba in care copilul dvs. invata la scoala.' },
|
||||
el: { native: '\u0397 \u03b3\u03bb\u03ce\u03c3\u03c3\u03b1 \u03bc\u03bf\u03c5', school: '\u03a3\u03c7\u03bf\u03bb\u03b9\u03ba\u03ae \u03b3\u03bb\u03ce\u03c3\u03c3\u03b1', info_native: '\u0397 \u03b3\u03bb\u03ce\u03c3\u03c3\u03b1 \u03c0\u03bf\u03c5 \u03bc\u03b9\u03bb\u03ac\u03c4\u03b5 \u03c3\u03c4\u03bf \u03c3\u03c0\u03af\u03c4\u03b9.', info_school: '\u0397 \u03b3\u03bb\u03ce\u03c3\u03c3\u03b1 \u03c3\u03c4\u03b7\u03bd \u03bf\u03c0\u03bf\u03af\u03b1 \u03b4\u03b9\u03b4\u03ac\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf \u03c0\u03b1\u03b9\u03b4\u03af \u03c3\u03b1\u03c2.' },
|
||||
bg: { native: '\u041c\u043e\u044f\u0442 \u0435\u0437\u0438\u043a', school: '\u0423\u0447\u0438\u043b\u0438\u0449\u0435\u043d \u0435\u0437\u0438\u043a', info_native: '\u0415\u0437\u0438\u043a\u044a\u0442, \u043a\u043e\u0439\u0442\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0435 \u0443 \u0434\u043e\u043c\u0430.', info_school: '\u0415\u0437\u0438\u043a\u044a\u0442, \u043d\u0430 \u043a\u043e\u0439\u0442\u043e \u0434\u0435\u0442\u0435\u0442\u043e \u0432\u0438 \u0443\u0447\u0438 \u0432 \u0443\u0447\u0438\u043b\u0438\u0449\u0435.' },
|
||||
hr: { native: 'Moj jezik', school: 'Skolski jezik', info_native: 'Jezik koji govorite kod kuce. Svi izbornici ce biti na ovom jeziku.', info_school: 'Jezik na kojem se vase dijete poducava u skoli.' },
|
||||
cs: { native: 'Muj jazyk', school: 'Skolni jazyk', info_native: 'Jazyk, kterym mluvite doma. Vsechny nabidky budou v tomto jazyce.', info_school: 'Jazyk, ve kterem se vase dite uci ve skole.' },
|
||||
hu: { native: 'Az en nyelvem', school: 'Iskolai nyelv', info_native: 'A nyelv, amelyet otthon beszel. Minden menu ezen a nyelven jelenik meg.', info_school: 'A nyelv, amelyen gyermeke az iskolaban tanul.' },
|
||||
sv: { native: 'Mitt sprak', school: 'Skolsprak', info_native: 'Spraket du talar hemma. Alla menyer visas pa detta sprak.', info_school: 'Spraket som ditt barn undervisas pa i skolan.' },
|
||||
da: { native: 'Mit sprog', school: 'Skolesprog', info_native: 'Det sprog, du taler derhjemme. Alle menuer vises pa dette sprog.', info_school: 'Det sprog, dit barn undervises pa i skolen.' },
|
||||
fi: { native: 'Oma kieleni', school: 'Koulun kieli', info_native: 'Kieli, jota puhut kotona. Kaikki valikot nakyvet talla kielella.', info_school: 'Kieli, jolla lastasi opetetaan koulussa.' },
|
||||
sk: { native: 'Moj jazyk', school: 'Skolsky jazyk', info_native: 'Jazyk, ktorym hovorite doma.', info_school: 'Jazyk, v ktorom sa vase dieta uci v skole.' },
|
||||
sl: { native: 'Moj jezik', school: 'Solski jezik', info_native: 'Jezik, ki ga govorite doma.', info_school: 'Jezik, v katerem se vas otrok uci v soli.' },
|
||||
lt: { native: 'Mano kalba', school: 'Mokyklos kalba', info_native: 'Kalba, kuria kalbate namuose.', info_school: 'Kalba, kuria jusu vaikas mokosi mokykloje.' },
|
||||
lv: { native: 'Mana valoda', school: 'Skolas valoda', info_native: 'Valoda, kuru runajat majas.', info_school: 'Valoda, kura jusu berns macas skola.' },
|
||||
et: { native: 'Minu keel', school: 'Kooli keel', info_native: 'Keel, mida raagite kodus.', info_school: 'Keel, milles teie last koolis opetatakse.' },
|
||||
}
|
||||
|
||||
export function LanguageDropdown({ className = '' }: LanguageDropdownProps) {
|
||||
function getLabels(lang: string) {
|
||||
return TAB_LABELS[lang] || TAB_LABELS['en']
|
||||
}
|
||||
|
||||
export function LanguageDropdown({ className = '' }: { className?: string }) {
|
||||
const { language, setLanguage, schoolLanguage, setSchoolLanguage, availableLanguages } = useLanguage()
|
||||
const { isDark } = useTheme()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
@@ -17,39 +47,30 @@ export function LanguageDropdown({ className = '' }: LanguageDropdownProps) {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
const h = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) setIsOpen(false) }
|
||||
document.addEventListener('mousedown', h)
|
||||
return () => document.removeEventListener('mousedown', h)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
function handleEscape(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') setIsOpen(false)
|
||||
}
|
||||
document.addEventListener('keydown', handleEscape)
|
||||
return () => document.removeEventListener('keydown', handleEscape)
|
||||
const h = (e: KeyboardEvent) => { if (e.key === 'Escape') setIsOpen(false) }
|
||||
document.addEventListener('keydown', h)
|
||||
return () => document.removeEventListener('keydown', h)
|
||||
}, [])
|
||||
|
||||
const nativeLang = availableLanguages[language]
|
||||
const schoolLang = availableLanguages[schoolLanguage]
|
||||
const activeLang = mode === 'native' ? language : schoolLanguage
|
||||
const labels = getLabels(language)
|
||||
|
||||
const handleSelect = (lang: Language) => {
|
||||
if (mode === 'native') {
|
||||
setLanguage(lang)
|
||||
} else {
|
||||
setSchoolLanguage(lang)
|
||||
}
|
||||
if (mode === 'native') setLanguage(lang)
|
||||
else setSchoolLanguage(lang)
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={dropdownRef} className={`relative ${className}`}>
|
||||
{/* Trigger: shows both native + school language */}
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={`flex items-center gap-2 px-4 py-2.5 backdrop-blur-xl border rounded-2xl transition-all ${
|
||||
@@ -57,8 +78,6 @@ export function LanguageDropdown({ className = '' }: LanguageDropdownProps) {
|
||||
? 'bg-white/10 border-white/20 text-white hover:bg-white/20'
|
||||
: 'bg-black/5 border-black/10 text-slate-700 hover:bg-black/10'
|
||||
}`}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<span className="text-lg">{nativeLang?.flag}</span>
|
||||
<span className="text-sm font-medium hidden sm:inline">{nativeLang?.name}</span>
|
||||
@@ -67,20 +86,17 @@ export function LanguageDropdown({ className = '' }: LanguageDropdownProps) {
|
||||
🏫 {schoolLang?.flag}
|
||||
</span>
|
||||
)}
|
||||
<svg
|
||||
className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''} ${isDark ? 'text-white/60' : 'text-slate-500'}`}
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
>
|
||||
<svg className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''} ${isDark ? 'text-white/60' : 'text-slate-500'}`}
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Dropdown */}
|
||||
{isOpen && (
|
||||
<div className={`absolute right-0 mt-2 w-56 backdrop-blur-2xl border rounded-2xl shadow-xl overflow-hidden z-50 ${
|
||||
isDark ? 'bg-slate-900/90 border-white/20' : 'bg-white/95 border-black/10'
|
||||
<div className={`absolute right-0 mt-2 w-72 backdrop-blur-2xl border rounded-2xl shadow-xl overflow-hidden z-50 ${
|
||||
isDark ? 'bg-slate-900/95 border-white/20' : 'bg-white/98 border-black/10'
|
||||
}`}>
|
||||
{/* Tab switcher: Muttersprache / Schulsprache */}
|
||||
{/* Tabs — translated labels */}
|
||||
<div className={`flex border-b ${isDark ? 'border-white/10' : 'border-slate-200'}`}>
|
||||
<button
|
||||
onClick={() => setMode('native')}
|
||||
@@ -90,7 +106,7 @@ export function LanguageDropdown({ className = '' }: LanguageDropdownProps) {
|
||||
: isDark ? 'text-white/50 hover:text-white/70' : 'text-slate-400 hover:text-slate-600'
|
||||
}`}
|
||||
>
|
||||
🌍 Muttersprache
|
||||
🌍 {labels.native}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMode('school')}
|
||||
@@ -100,12 +116,17 @@ export function LanguageDropdown({ className = '' }: LanguageDropdownProps) {
|
||||
: isDark ? 'text-white/50 hover:text-white/70' : 'text-slate-400 hover:text-slate-600'
|
||||
}`}
|
||||
>
|
||||
🏫 Schulsprache
|
||||
🏫 {labels.school}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Info box — explains what this setting does */}
|
||||
<div className={`px-4 py-2.5 text-xs leading-relaxed ${isDark ? 'bg-white/5 text-white/50' : 'bg-blue-50/50 text-slate-500'}`}>
|
||||
💡 {mode === 'native' ? labels.info_native : labels.info_school}
|
||||
</div>
|
||||
|
||||
{/* Language list */}
|
||||
<ul role="listbox" className="py-1 max-h-72 overflow-y-auto">
|
||||
<ul role="listbox" className="py-1 max-h-64 overflow-y-auto">
|
||||
{(Object.keys(availableLanguages) as Language[]).map((lang) => {
|
||||
const langInfo = availableLanguages[lang]
|
||||
const isSelected = lang === activeLang
|
||||
@@ -118,8 +139,6 @@ export function LanguageDropdown({ className = '' }: LanguageDropdownProps) {
|
||||
? isDark ? 'bg-white/20 text-white' : 'bg-indigo-100 text-slate-900'
|
||||
: isDark ? 'text-white/80 hover:bg-white/10' : 'text-slate-700 hover:bg-slate-100'
|
||||
}`}
|
||||
role="option"
|
||||
aria-selected={isSelected}
|
||||
>
|
||||
<span className="text-lg">{langInfo.flag}</span>
|
||||
<span className="text-sm font-medium">{langInfo.name}</span>
|
||||
|
||||
Reference in New Issue
Block a user