"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { I18nextProvider } from "react-i18next"; import { createI18nInstance } from "@/lib/i18n/createI18nInstance"; import { getDirection, type AppLanguage, } from "@/lib/i18n/registry"; import { persistLanguagePreference, readStoredLanguagePreference, } from "@/lib/i18n/clientLanguage"; import { syncDocumentLanguage } from "@/lib/i18n/syncDocumentLanguage"; type LanguageContextValue = { language: AppLanguage; dir: "rtl" | "ltr"; isRtl: boolean; ready: boolean; setLanguage: (lang: AppLanguage) => Promise; }; const LanguageContext = createContext(null); export function LanguageProvider({ children, initialLanguage, }: { children: React.ReactNode; initialLanguage: AppLanguage; }) { // One i18next instance per render of this component — since Next.js renders // "use client" components fresh on the server for each request (not from a // shared module-level singleton), this naturally gives every request its own // isolated instance instead of sharing one across the whole server process. // That's what actually fixes the hydration mismatch: the old shared singleton // was stuck on the server's default language for the life of the process, // so every visitor's page content server-rendered in that language and then // flipped to the real one after hydration. const [instance] = useState(() => createI18nInstance(initialLanguage)); // initialLanguage is resolved server-side (cookie → Accept-Language → default) // and passed down as a prop, so server and first client render agree from the // start — no more guessing a different value on the client than what the // server already committed to in the HTML. const [language, setLanguageState] = useState(initialLanguage); const [ready, setReady] = useState(true); useEffect(() => { // A stored preference (from an earlier explicit choice) can still disagree // with initialLanguage — e.g. localStorage survived a cleared cookie. Only // switch in that case; otherwise there's nothing to reconcile since the // server already rendered the right language. const stored = readStoredLanguagePreference(); if (stored && stored !== initialLanguage) { void instance.changeLanguage(stored).then(() => { setLanguageState(stored); syncDocumentLanguage(stored); }); } else if (!stored) { persistLanguagePreference(initialLanguage); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const setLanguage = useCallback( async (lang: AppLanguage) => { await instance.changeLanguage(lang); persistLanguagePreference(lang); setLanguageState(lang); syncDocumentLanguage(lang); }, [instance] ); const value = useMemo( () => ({ language, dir: getDirection(language), isRtl: getDirection(language) === "rtl", ready, setLanguage, }), [language, ready, setLanguage] ); return ( {children} ); } export function useAppLanguage() { const ctx = useContext(LanguageContext); if (!ctx) { throw new Error("useAppLanguage must be used within LanguageProvider"); } return ctx; }