From 5012699aafad40c22c2759aeeb8f7078647ebf35 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Mon, 27 Apr 2026 09:23:01 +0200 Subject: [PATCH] Add persistent language switcher across all learn/parent pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useNativeLanguage: Now has setNativeLang() that persists to localStorage. Language selection carries across all pages automatically. LanguageSwitcher: Compact dropdown component added to learn/layout.tsx and parent/layout.tsx — visible on every sub-page (top-right). Parent portal: Language dropdown syncs both UI language and native language. Parents can switch language mid-session (e.g. when both parents speak different languages). Co-Authored-By: Claude Opus 4.6 (1M context) --- studio-v2/app/learn/layout.tsx | 14 ++++- studio-v2/app/parent/layout.tsx | 13 ++++- studio-v2/app/parent/page.tsx | 28 ++++----- .../components/learn/LanguageSwitcher.tsx | 58 +++++++++++++++++++ studio-v2/lib/useNativeLanguage.ts | 18 ++++-- 5 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 studio-v2/components/learn/LanguageSwitcher.tsx diff --git a/studio-v2/app/learn/layout.tsx b/studio-v2/app/learn/layout.tsx index 3e8b14f..a47255b 100644 --- a/studio-v2/app/learn/layout.tsx +++ b/studio-v2/app/learn/layout.tsx @@ -2,14 +2,16 @@ import { Sidebar } from '@/components/Sidebar' import { useTheme } from '@/lib/ThemeContext' +import { useNativeLanguage } from '@/lib/useNativeLanguage' +import { LanguageSwitcher } from '@/components/learn/LanguageSwitcher' /** * Shared layout for ALL /learn/* pages. - * Provides: Sidebar + gradient background + flex container. - * Individual pages only need to render their content. + * Provides: Sidebar + gradient background + language switcher. */ export default function LearnLayout({ children }: { children: React.ReactNode }) { const { isDark } = useTheme() + const { nativeLang, setNativeLang, isThirdLanguage } = useNativeLanguage() return (
+ {/* Sticky language switcher at top-right */} +
+ +
{children}
diff --git a/studio-v2/app/parent/layout.tsx b/studio-v2/app/parent/layout.tsx index d4a9eb5..a91fd0c 100644 --- a/studio-v2/app/parent/layout.tsx +++ b/studio-v2/app/parent/layout.tsx @@ -2,13 +2,16 @@ import { Sidebar } from '@/components/Sidebar' import { useTheme } from '@/lib/ThemeContext' +import { useNativeLanguage } from '@/lib/useNativeLanguage' +import { LanguageSwitcher } from '@/components/learn/LanguageSwitcher' /** * Shared layout for ALL /parent/* pages. - * Same design as learn layout — Sidebar + gradient. + * Same design as learn layout — Sidebar + gradient + language switcher. */ export default function ParentLayout({ children }: { children: React.ReactNode }) { const { isDark } = useTheme() + const { nativeLang, setNativeLang } = useNativeLanguage() return (
+ {/* Sticky language switcher at top-right */} +
+ +
{children}
diff --git a/studio-v2/app/parent/page.tsx b/studio-v2/app/parent/page.tsx index a8b8a95..517bea0 100644 --- a/studio-v2/app/parent/page.tsx +++ b/studio-v2/app/parent/page.tsx @@ -5,6 +5,7 @@ import Link from 'next/link' import { useTheme } from '@/lib/ThemeContext' import { useLanguage } from '@/lib/LanguageContext' import type { Language } from '@/lib/i18n' +import { useNativeLanguage } from '@/lib/useNativeLanguage' interface LearningUnit { id: string @@ -28,10 +29,19 @@ const parentT: Record> = { export default function ParentPage() { const { isDark } = useTheme() const { language, setLanguage } = useLanguage() + const { nativeLang, setNativeLang } = useNativeLanguage() const [units, setUnits] = useState([]) const [isLoading, setIsLoading] = useState(true) - const t = (key: string) => parentT[key]?.[language] || parentT[key]?.['de'] || key + // Use nativeLang for translations (synced with localStorage) + const activeLang = nativeLang || language + const t = (key: string) => parentT[key]?.[activeLang] || parentT[key]?.['de'] || key + + /** Switch both UI language and native language together */ + const switchLang = (lang: string) => { + setLanguage(lang as Language) + setNativeLang(lang) + } const glassCard = isDark ? 'bg-white/10 backdrop-blur-xl border border-white/10' @@ -59,19 +69,9 @@ export default function ParentPage() { {t('greeting')}

- + + {activeLang.toUpperCase()} + diff --git a/studio-v2/components/learn/LanguageSwitcher.tsx b/studio-v2/components/learn/LanguageSwitcher.tsx new file mode 100644 index 0000000..2101952 --- /dev/null +++ b/studio-v2/components/learn/LanguageSwitcher.tsx @@ -0,0 +1,58 @@ +'use client' + +import React from 'react' + +interface LanguageSwitcherProps { + currentLang: string + onLangChange: (lang: string) => void + isDark: boolean + compact?: boolean +} + +const LANGS = [ + { code: 'de', label: 'DE' }, + { code: 'en', label: 'EN' }, + { code: 'tr', label: 'TR' }, + { code: 'ar', label: 'AR' }, + { code: 'uk', label: 'UK' }, + { code: 'ru', label: 'RU' }, + { code: 'pl', label: 'PL' }, +] + +/** + * Compact language switcher for exercise pages. + * Shows as dropdown or pill buttons depending on compact prop. + */ +export function LanguageSwitcher({ currentLang, onLangChange, isDark, compact = true }: LanguageSwitcherProps) { + if (compact) { + return ( + + ) + } + + return ( +
+ {LANGS.map(l => ( + + ))} +
+ ) +} diff --git a/studio-v2/lib/useNativeLanguage.ts b/studio-v2/lib/useNativeLanguage.ts index c2efe7c..233dcfb 100644 --- a/studio-v2/lib/useNativeLanguage.ts +++ b/studio-v2/lib/useNativeLanguage.ts @@ -1,20 +1,26 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { exerciseT, type ExerciseKey } from './exerciseTranslations' const STORAGE_KEY = 'bp_native_language' /** - * Hook to read the user's native language. - * Returns translation helper for exercise UI texts. + * Hook for native language state + translations. + * Persists to localStorage. Can be changed at any time (e.g. parent switches language). */ export function useNativeLanguage() { - const [nativeLang, setNativeLang] = useState('de') + const [nativeLang, setNativeLangState] = useState('de') useEffect(() => { const stored = localStorage.getItem(STORAGE_KEY) - if (stored) setNativeLang(stored) + if (stored) setNativeLangState(stored) + }, []) + + /** Change native language (persists to localStorage) */ + const setNativeLang = useCallback((lang: string) => { + setNativeLangState(lang) + localStorage.setItem(STORAGE_KEY, lang) }, []) const isThirdLanguage = nativeLang !== 'de' && nativeLang !== 'en' @@ -34,5 +40,5 @@ export function useNativeLanguage() { return typeof entry === 'string' ? entry : entry.text || '' } - return { nativeLang, isThirdLanguage, t, wordInNative } + return { nativeLang, setNativeLang, isThirdLanguage, t, wordInNative } }