import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react'; import { Language, TranslationKeys, loadLanguage, fetchAvailableLanguageCodes, I18nCodeInfo } from '../../locales'; import { getUserDataCache } from '../../utils/userCache'; export type { Language }; type TranslateParams = Record; interface LanguageContextType { currentLanguage: Language; setLanguage: (language: Language) => void; t: (key: string, paramsOrFallback?: TranslateParams | string) => string; isLoading: boolean; reloadLanguage: () => Promise; availableLanguages: I18nCodeInfo[]; refreshAvailableLanguages: () => Promise; } const LanguageContext = createContext(undefined); interface LanguageProviderProps { children: ReactNode; } export const LanguageProvider: React.FC = ({ children }) => { const [currentLanguage, setCurrentLanguage] = useState('de'); const [translations, setTranslations] = useState({}); const [deTranslations, setDeTranslations] = useState({}); const [isLoading, setIsLoading] = useState(true); const [availableLanguages, setAvailableLanguages] = useState([]); // Function to load and set a language const loadAndSetLanguage = async (language: Language) => { setIsLoading(true); try { const deKeys = await loadLanguage('de'); setDeTranslations(deKeys); const targetKeys = language === 'de' ? deKeys : await loadLanguage(language); setTranslations(targetKeys); setCurrentLanguage(language); } catch (error) { console.error('Failed to load language:', error); // Keep current language and translations if loading fails } finally { setIsLoading(false); } }; // Load language from user profile on mount useEffect(() => { const initializeLanguage = async () => { let initialLanguage: Language = 'de'; // Priority 1: Check if user data has language setting (ONLY source of truth!) const userData = getUserDataCache(); if (userData?.language && String(userData.language).trim()) { initialLanguage = String(userData.language).trim() as Language; console.log('🌍 Using language from user profile (sessionStorage cache):', initialLanguage); await loadAndSetLanguage(initialLanguage); return; } // Priority 2: Detect browser language (fallback only if no user data) const browserLang = navigator.language.split('-')[0] as Language; try { const codes = await fetchAvailableLanguageCodes(); const codeSet = new Set(codes.map((c) => c.code)); if (codeSet.has(browserLang)) { initialLanguage = browserLang; console.log('🌍 Using browser language as fallback:', initialLanguage); } else { console.log('🌍 Using default language:', initialLanguage); } } catch { console.log('🌍 Using default language:', initialLanguage); } await loadAndSetLanguage(initialLanguage); }; initializeLanguage(); // Listen for user data updates to sync language const handleUserUpdate = () => { const userData = getUserDataCache(); if (userData?.language && String(userData.language).trim()) { const userLanguage = String(userData.language).trim() as Language; if (userLanguage !== currentLanguage) { console.log('🔄 Syncing language with user data (sessionStorage cache):', userLanguage); loadAndSetLanguage(userLanguage); } } }; // Listen for user info update events window.addEventListener('userInfoUpdated', handleUserUpdate); return () => { window.removeEventListener('userInfoUpdated', handleUserUpdate); }; }, []); const setLanguage = async (language: Language) => { // Load the new language immediately for UI await loadAndSetLanguage(language); // IMPORTANT: This should ONLY be called after the backend profile is updated // The settings component should: // 1. Update backend user profile with new language // 2. Refetch user data (which includes the new language) // 3. Update sessionStorage cache with new data (via setUserDataCache) // 4. Call this function to sync the UI }; const reloadLanguage = async () => { await loadAndSetLanguage(currentLanguage); }; const refreshAvailableLanguages = useCallback(async () => { try { const list = await fetchAvailableLanguageCodes(); setAvailableLanguages(list); } catch (e) { console.error('Failed to load language codes:', e); } }, []); useEffect(() => { refreshAvailableLanguages(); }, [refreshAvailableLanguages]); const _applyParams = (template: string, params?: TranslateParams): string => { if (!params) return template; let out = template; for (const [paramKey, rawVal] of Object.entries(params)) { if (rawVal === undefined || rawVal === null) continue; out = out.split(`{${paramKey}}`).join(String(rawVal)); } return out; }; const t = (key: string, paramsOrFallback?: TranslateParams | string): string => { let params: TranslateParams | undefined; if (typeof paramsOrFallback === 'string') { params = undefined; } else { params = paramsOrFallback; } const resolved = translations[key] ?? deTranslations[key] ?? (typeof paramsOrFallback === 'string' ? paramsOrFallback : undefined) ?? `[${key}]`; return _applyParams(resolved, params); }; const contextValue: LanguageContextType = { currentLanguage, setLanguage, t, isLoading, reloadLanguage, availableLanguages, refreshAvailableLanguages, }; return ( {children} ); }; export const useLanguage = (): LanguageContextType => { const context = useContext(LanguageContext); if (!context) { throw new Error('useLanguage must be used within a LanguageProvider'); } return context; };