import React, { createContext, useContext, useEffect, useMemo, ReactNode } from 'react' import { useSettingsStore } from '../store/settingsStore' import de from './translations/de' import en from './translations/en' import es from './translations/es' import fr from './translations/fr' import hu from './translations/hu' import it from './translations/it' import ru from './translations/ru' import zh from './translations/zh' import zhTw from './translations/zhTw' import nl from './translations/nl' import id from './translations/id' import ar from './translations/ar' import br from './translations/br' import cs from './translations/cs' import pl from './translations/pl' import { SUPPORTED_LANGUAGES, SupportedLanguageCode } from './supportedLanguages' export { SUPPORTED_LANGUAGES } type TranslationStrings = Record // Keyed by SupportedLanguageCode so TypeScript enforces all languages have a translation. const translations: Record = { de, en, es, fr, hu, it, ru, zh, 'zh-TW': zhTw, nl, id, ar, br, cs, pl, } // Derived from SUPPORTED_LANGUAGES — add new languages there, not here. const LOCALES: Record = Object.fromEntries( SUPPORTED_LANGUAGES.map(l => [l.value, l.locale]) ) const RTL_LANGUAGES = new Set(['ar']) export function getLocaleForLanguage(language: string): string { return LOCALES[language] || LOCALES.en } export function getIntlLanguage(language: string): string { if (language === 'br') return 'pt-BR' return ['de', 'es', 'fr', 'hu', 'it', 'ru', 'zh', 'zh-TW', 'nl', 'ar', 'cs', 'pl', 'id'].includes(language) ? language : 'en' } export function isRtlLanguage(language: string): boolean { return RTL_LANGUAGES.has(language) } // Detects the user's preferred language from the browser/OS settings and maps // it to one of the supported language codes. Returns null if no match is found. export function detectBrowserLanguage(): string | null { if (typeof navigator === 'undefined') return null const browserLangs = navigator.languages?.length ? navigator.languages : navigator.language ? [navigator.language] : [] const supported = SUPPORTED_LANGUAGES.map(l => l.value) for (const lang of browserLangs) { // Exact match (e.g. 'de', 'zh-TW') — case-insensitive const exactMatch = supported.find(s => s.toLowerCase() === lang.toLowerCase()) if (exactMatch) return exactMatch // pt-BR has no exact match (our code is 'br', not 'pt-BR'), so map it explicitly. // pt-PT and bare 'pt' are NOT mapped — they fall through to null and let the // server default or 'en' fallback apply instead. if (lang.toLowerCase() === 'pt-br') return 'br' // Prefix match (e.g. 'de-AT' → 'de', 'zh-CN' → 'zh') — case-insensitive const prefix = lang.split('-')[0].toLowerCase() const prefixMatch = supported.find(s => s.toLowerCase() === prefix) if (prefixMatch) return prefixMatch } return null } interface TranslationContextValue { t: (key: string, params?: Record) => string language: string locale: string } const TranslationContext = createContext({ t: (k: string) => k, language: 'en', locale: 'en-US' }) interface TranslationProviderProps { children: ReactNode } export function TranslationProvider({ children }: TranslationProviderProps) { const language = useSettingsStore((s) => s.settings.language) || 'en' useEffect(() => { document.documentElement.lang = language document.documentElement.dir = isRtlLanguage(language) ? 'rtl' : 'ltr' }, [language]) const value = useMemo((): TranslationContextValue => { const strings = translations[language] || translations.en const fallback = translations.en function t(key: string, params?: Record): string { let val: string = (strings[key] ?? fallback[key] ?? key) as string if (params) { Object.entries(params).forEach(([k, v]) => { val = val.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v)) }) } return val } return { t, language, locale: getLocaleForLanguage(language) } }, [language]) return {children} } export function useTranslation(): TranslationContextValue { return useContext(TranslationContext) }