Unify language system: one setting for all modules
Some checks failed
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 41s
CI / test-go-edu-search (push) Successful in 30s
CI / test-python-klausur (push) Failing after 2m29s
CI / test-python-agent-core (push) Successful in 19s
CI / test-nodejs-website (push) Successful in 26s
Some checks failed
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 41s
CI / test-go-edu-search (push) Successful in 30s
CI / test-python-klausur (push) Failing after 2m29s
CI / test-python-agent-core (push) Successful in 19s
CI / test-nodejs-website (push) Successful in 26s
- Merge two separate language systems (bp_language + bp_native_language) into one - NativeLanguageContext now reads from LanguageContext (same localStorage key) - Extend i18n.ts to 26 languages with flags (UI falls back to EN/DE) - Replace LanguageSwitcher with LanguageDropdown (flags) in learn + parent layouts - Migration: old bp_native_language value auto-migrates to bp_language - Onboarding page writes to bp_language (unified key) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,16 +2,15 @@
|
|||||||
|
|
||||||
import { Sidebar } from '@/components/Sidebar'
|
import { Sidebar } from '@/components/Sidebar'
|
||||||
import { useTheme } from '@/lib/ThemeContext'
|
import { useTheme } from '@/lib/ThemeContext'
|
||||||
import { useNativeLanguage } from '@/lib/useNativeLanguage'
|
import { LanguageDropdown } from '@/components/LanguageDropdown'
|
||||||
import { LanguageSwitcher } from '@/components/learn/LanguageSwitcher'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared layout for ALL /learn/* pages.
|
* Shared layout for ALL /learn/* pages.
|
||||||
* Provides: Sidebar + gradient background + language switcher.
|
* Provides: Sidebar + gradient background + language dropdown (flags).
|
||||||
|
* Uses the central LanguageContext (same as all other modules).
|
||||||
*/
|
*/
|
||||||
export default function LearnLayout({ children }: { children: React.ReactNode }) {
|
export default function LearnLayout({ children }: { children: React.ReactNode }) {
|
||||||
const { isDark } = useTheme()
|
const { isDark } = useTheme()
|
||||||
const { nativeLang, setNativeLang, isThirdLanguage } = useNativeLanguage()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen flex relative overflow-hidden ${
|
<div className={`min-h-screen flex relative overflow-hidden ${
|
||||||
@@ -23,13 +22,9 @@ export default function LearnLayout({ children }: { children: React.ReactNode })
|
|||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex flex-col relative z-10 overflow-y-auto scrollbar-hide" style={{ scrollbarWidth: 'none' }}>
|
<div className="flex-1 flex flex-col relative z-10 overflow-y-auto scrollbar-hide" style={{ scrollbarWidth: 'none' }}>
|
||||||
{/* Sticky language switcher at top-right */}
|
{/* Language dropdown at top-right (same as worksheet-editor etc.) */}
|
||||||
<div className="sticky top-0 z-20 flex justify-end px-4 py-2">
|
<div className="sticky top-0 z-20 flex justify-end px-4 py-2">
|
||||||
<LanguageSwitcher
|
<LanguageDropdown />
|
||||||
currentLang={nativeLang}
|
|
||||||
onLangChange={setNativeLang}
|
|
||||||
isDark={isDark}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
<div className="text-center py-4">
|
<div className="text-center py-4">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface LangOption {
|
|||||||
rtl: boolean
|
rtl: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const STORAGE_KEY = 'bp_native_language'
|
const STORAGE_KEY = 'bp_language'
|
||||||
|
|
||||||
export default function OnboardingPage() {
|
export default function OnboardingPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -2,16 +2,14 @@
|
|||||||
|
|
||||||
import { Sidebar } from '@/components/Sidebar'
|
import { Sidebar } from '@/components/Sidebar'
|
||||||
import { useTheme } from '@/lib/ThemeContext'
|
import { useTheme } from '@/lib/ThemeContext'
|
||||||
import { useNativeLanguage } from '@/lib/useNativeLanguage'
|
import { LanguageDropdown } from '@/components/LanguageDropdown'
|
||||||
import { LanguageSwitcher } from '@/components/learn/LanguageSwitcher'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared layout for ALL /parent/* pages.
|
* Shared layout for ALL /parent/* pages.
|
||||||
* Same design as learn layout — Sidebar + gradient + language switcher.
|
* Same design as learn layout — Sidebar + gradient + flag language dropdown.
|
||||||
*/
|
*/
|
||||||
export default function ParentLayout({ children }: { children: React.ReactNode }) {
|
export default function ParentLayout({ children }: { children: React.ReactNode }) {
|
||||||
const { isDark } = useTheme()
|
const { isDark } = useTheme()
|
||||||
const { nativeLang, setNativeLang } = useNativeLanguage()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen flex relative overflow-hidden ${
|
<div className={`min-h-screen flex relative overflow-hidden ${
|
||||||
@@ -23,13 +21,8 @@ export default function ParentLayout({ children }: { children: React.ReactNode }
|
|||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex flex-col relative z-10 overflow-y-auto scrollbar-hide" style={{ scrollbarWidth: 'none' }}>
|
<div className="flex-1 flex flex-col relative z-10 overflow-y-auto scrollbar-hide" style={{ scrollbarWidth: 'none' }}>
|
||||||
{/* Sticky language switcher at top-right */}
|
|
||||||
<div className="sticky top-0 z-20 flex justify-end px-4 py-2">
|
<div className="sticky top-0 z-20 flex justify-end px-4 py-2">
|
||||||
<LanguageSwitcher
|
<LanguageDropdown />
|
||||||
currentLang={nativeLang}
|
|
||||||
onLangChange={setNativeLang}
|
|
||||||
isDark={isDark}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ export function LanguageProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uebersetzungsfunktion
|
// Uebersetzungsfunktion (Fallback: en → de → key)
|
||||||
const t = (key: string): string => {
|
const t = (key: string): string => {
|
||||||
return translations[language][key] || translations[defaultLanguage][key] || key
|
return translations[language]?.[key] || translations['en']?.[key] || translations['de']?.[key] || key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Waehrend SSR: Default anzeigen
|
// Waehrend SSR: Default anzeigen
|
||||||
@@ -55,7 +55,7 @@ export function LanguageProvider({ children }: { children: ReactNode }) {
|
|||||||
value={{
|
value={{
|
||||||
language: defaultLanguage,
|
language: defaultLanguage,
|
||||||
setLanguage: () => {},
|
setLanguage: () => {},
|
||||||
t: (key) => translations[defaultLanguage][key] || key,
|
t: (key) => translations[defaultLanguage]?.[key] || key,
|
||||||
isRTL: false,
|
isRTL: false,
|
||||||
availableLanguages,
|
availableLanguages,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
|
import React, { createContext, useContext, useCallback } from 'react'
|
||||||
import { exerciseT, type ExerciseKey } from './exerciseTranslations'
|
import { exerciseT, type ExerciseKey } from './exerciseTranslations'
|
||||||
|
import { useLanguage } from './LanguageContext'
|
||||||
|
|
||||||
const STORAGE_KEY = 'bp_native_language'
|
/**
|
||||||
|
* NativeLanguageContext — unified with LanguageContext.
|
||||||
|
*
|
||||||
|
* Reads/writes the SAME language as the central LanguageContext (bp_language).
|
||||||
|
* No separate localStorage key — one language setting for the entire app.
|
||||||
|
*/
|
||||||
|
|
||||||
interface NativeLanguageState {
|
interface NativeLanguageState {
|
||||||
nativeLang: string
|
nativeLang: string
|
||||||
@@ -22,17 +28,14 @@ const NativeLanguageContext = createContext<NativeLanguageState>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function NativeLanguageProvider({ children }: { children: React.ReactNode }) {
|
export function NativeLanguageProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [nativeLang, setNativeLangState] = useState('de')
|
const { language, setLanguage } = useLanguage()
|
||||||
|
|
||||||
useEffect(() => {
|
// Sync: nativeLang = the central language setting
|
||||||
const stored = localStorage.getItem(STORAGE_KEY)
|
const nativeLang = language
|
||||||
if (stored) setNativeLangState(stored)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const setNativeLang = useCallback((lang: string) => {
|
const setNativeLang = useCallback((lang: string) => {
|
||||||
setNativeLangState(lang)
|
setLanguage(lang)
|
||||||
localStorage.setItem(STORAGE_KEY, lang)
|
}, [setLanguage])
|
||||||
}, [])
|
|
||||||
|
|
||||||
const isThirdLanguage = nativeLang !== 'de' && nativeLang !== 'en'
|
const isThirdLanguage = nativeLang !== 'de' && nativeLang !== 'en'
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
* RTL-Support fuer Arabisch
|
* RTL-Support fuer Arabisch
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type Language = 'de' | 'en' | 'tr' | 'ar' | 'ru' | 'uk' | 'pl'
|
export type Language = string
|
||||||
|
|
||||||
export const availableLanguages: Record<Language, { name: string; flag: string; rtl?: boolean }> = {
|
export const availableLanguages: Record<string, { name: string; flag: string; rtl?: boolean }> = {
|
||||||
de: { name: 'Deutsch', flag: '🇩🇪' },
|
de: { name: 'Deutsch', flag: '🇩🇪' },
|
||||||
en: { name: 'English', flag: '🇬🇧' },
|
en: { name: 'English', flag: '🇬🇧' },
|
||||||
tr: { name: 'Türkçe', flag: '🇹🇷' },
|
tr: { name: 'Türkçe', flag: '🇹🇷' },
|
||||||
@@ -16,12 +16,31 @@ export const availableLanguages: Record<Language, { name: string; flag: string;
|
|||||||
ru: { name: 'Русский', flag: '🇷🇺' },
|
ru: { name: 'Русский', flag: '🇷🇺' },
|
||||||
uk: { name: 'Українська', flag: '🇺🇦' },
|
uk: { name: 'Українська', flag: '🇺🇦' },
|
||||||
pl: { name: 'Polski', flag: '🇵🇱' },
|
pl: { name: 'Polski', flag: '🇵🇱' },
|
||||||
|
fr: { name: 'Français', flag: '🇫🇷' },
|
||||||
|
es: { name: 'Español', flag: '🇪🇸' },
|
||||||
|
it: { name: 'Italiano', flag: '🇮🇹' },
|
||||||
|
pt: { name: 'Português', flag: '🇵🇹' },
|
||||||
|
nl: { name: 'Nederlands', flag: '🇳🇱' },
|
||||||
|
ro: { name: 'Română', flag: '🇷🇴' },
|
||||||
|
el: { name: 'Ελληνικά', flag: '🇬🇷' },
|
||||||
|
bg: { name: 'Български', flag: '🇧🇬' },
|
||||||
|
hr: { name: 'Hrvatski', flag: '🇭🇷' },
|
||||||
|
cs: { name: 'Čeština', flag: '🇨🇿' },
|
||||||
|
hu: { name: 'Magyar', flag: '🇭🇺' },
|
||||||
|
sv: { name: 'Svenska', flag: '🇸🇪' },
|
||||||
|
da: { name: 'Dansk', flag: '🇩🇰' },
|
||||||
|
fi: { name: 'Suomi', flag: '🇫🇮' },
|
||||||
|
sk: { name: 'Slovenčina', flag: '🇸🇰' },
|
||||||
|
sl: { name: 'Slovenščina', flag: '🇸🇮' },
|
||||||
|
lt: { name: 'Lietuvių', flag: '🇱🇹' },
|
||||||
|
lv: { name: 'Latviešu', flag: '🇱🇻' },
|
||||||
|
et: { name: 'Eesti', flag: '🇪🇪' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultLanguage: Language = 'de'
|
export const defaultLanguage: Language = 'de'
|
||||||
|
|
||||||
// Uebersetzungen fuer Studio v2 UI
|
// Uebersetzungen fuer Studio v2 UI (7 Sprachen voll, Rest faellt auf EN/DE zurueck)
|
||||||
export const translations: Record<Language, Record<string, string>> = {
|
export const translations: Record<string, Record<string, string>> = {
|
||||||
de: {
|
de: {
|
||||||
// Kopfleiste
|
// Kopfleiste
|
||||||
dashboard: 'Dashboard',
|
dashboard: 'Dashboard',
|
||||||
@@ -377,7 +396,13 @@ export function getStoredLanguage(): Language {
|
|||||||
if (typeof window === 'undefined') return defaultLanguage
|
if (typeof window === 'undefined') return defaultLanguage
|
||||||
const stored = localStorage.getItem(STORAGE_KEY)
|
const stored = localStorage.getItem(STORAGE_KEY)
|
||||||
if (stored && stored in availableLanguages) {
|
if (stored && stored in availableLanguages) {
|
||||||
return stored as Language
|
return stored
|
||||||
|
}
|
||||||
|
// Migration: bp_native_language → bp_language
|
||||||
|
const native = localStorage.getItem('bp_native_language')
|
||||||
|
if (native && native in availableLanguages) {
|
||||||
|
localStorage.setItem(STORAGE_KEY, native)
|
||||||
|
return native
|
||||||
}
|
}
|
||||||
return defaultLanguage
|
return defaultLanguage
|
||||||
}
|
}
|
||||||
@@ -388,9 +413,9 @@ export function setStoredLanguage(lang: Language): void {
|
|||||||
localStorage.setItem(STORAGE_KEY, lang)
|
localStorage.setItem(STORAGE_KEY, lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uebersetzung abrufen
|
// Uebersetzung abrufen (Fallback: en → de → key)
|
||||||
export function t(key: string, lang: Language): string {
|
export function t(key: string, lang: Language): string {
|
||||||
return translations[lang][key] || translations[defaultLanguage][key] || key
|
return translations[lang]?.[key] || translations['en']?.[key] || translations['de']?.[key] || key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ist die Sprache RTL?
|
// Ist die Sprache RTL?
|
||||||
|
|||||||
Reference in New Issue
Block a user