'use client' import { createContext, useContext, useEffect, useState, useCallback, useRef } from 'react' import type { ReactNode } from 'react' import { SupportedLanguage, loadTranslations, getTranslationValue, Translations } from './load-translations' // Static imports for SSR-safe initial translations (prevents hydration mismatch) import enTranslations from '@/locales/en.json' type LanguageContextType = { language: SupportedLanguage setLanguage: (lang: SupportedLanguage) => void t: (key: string, params?: Record) => string translations: Translations } const LanguageContext = createContext(undefined) const RTL_LANGUAGES: SupportedLanguage[] = ['ar', 'fa'] const SUPPORTED_LANGS: SupportedLanguage[] = ['en', 'fr', 'es', 'de', 'fa', 'it', 'pt', 'ru', 'zh', 'ja', 'ko', 'ar', 'hi', 'nl', 'pl'] function updateDocumentDirection(lang: SupportedLanguage) { document.documentElement.lang = lang document.documentElement.dir = RTL_LANGUAGES.includes(lang) ? 'rtl' : 'ltr' } /** * Resolve the actual language to use: * 1. If localStorage has a saved preference, use that (client only) * 2. Otherwise fall back to the server-detected initialLanguage */ function resolveLanguage(fallback: SupportedLanguage): SupportedLanguage { if (typeof window !== 'undefined') { try { const saved = localStorage.getItem('user-language') as SupportedLanguage if (saved && SUPPORTED_LANGS.includes(saved)) return saved } catch {} } return fallback } export function LanguageProvider({ children, initialLanguage = 'en', initialTranslations }: { children: ReactNode initialLanguage?: SupportedLanguage initialTranslations?: Translations }) { // Resolve language synchronously from localStorage BEFORE any effect runs. // This prevents the flash where initialLanguage ('en') overrides RTL. const [language, setLanguageState] = useState(() => resolveLanguage(initialLanguage)) // Start with server-provided translations or English fallback const [translations, setTranslations] = useState( (initialTranslations || enTranslations) as unknown as Translations ) const cacheRef = useRef>(new Map()) const isFirstRender = useRef(true) // Load translations when language changes (with caching) // On first render, skip updateDocumentDirection since the inline script already set it. useEffect(() => { const cached = cacheRef.current.get(language) if (cached) { setTranslations(cached) if (!isFirstRender.current) updateDocumentDirection(language) isFirstRender.current = false return } const loadLang = async () => { const loaded = await loadTranslations(language) cacheRef.current.set(language, loaded) setTranslations(loaded) if (!isFirstRender.current) updateDocumentDirection(language) isFirstRender.current = false } loadLang() }, [language]) const setLanguage = useCallback((lang: SupportedLanguage) => { setLanguageState(lang) localStorage.setItem('user-language', lang) updateDocumentDirection(lang) }, []) const t = useCallback((key: string, params?: Record) => { if (!translations) return key let value: any = getTranslationValue(translations, key) // Replace parameters like {count}, {percentage}, etc. if (params && typeof value === 'string') { Object.entries(params).forEach(([param, paramValue]) => { value = value.replace(`{${param}}`, String(paramValue)) }) } return typeof value === 'string' ? value : key }, [translations]) return ( {children} ) } export function useLanguage() { const context = useContext(LanguageContext) if (context === undefined) { throw new Error('useLanguage must be used within a LanguageProvider') } return context }