import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; import en from "./en"; import zh from "./zh"; export type Locale = "zh" | "en"; const STORAGE_KEY = "netx-locale"; const MESSAGES: Record = { zh, en }; function resolveMessage(obj: unknown, path: string): string { const keys = path.split("."); let cur: unknown = obj; for (const k of keys) { if (cur == null || typeof cur !== "object") return path; cur = (cur as Record)[k]; } return typeof cur === "string" ? cur : path; } function interpolate(text: string, vars?: Record): string { if (!vars) return text; let out = text; for (const [k, v] of Object.entries(vars)) { out = out.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v)); } return out; } type I18nContextValue = { locale: Locale; setLocale: (locale: Locale) => void; t: (key: string, vars?: Record) => string; }; const I18nContext = createContext(null); export function I18nProvider({ children }: { children: ReactNode }) { const [locale, setLocaleState] = useState(() => { try { const saved = localStorage.getItem(STORAGE_KEY); return saved === "en" ? "en" : "zh"; } catch { return "zh"; } }); const setLocale = useCallback((next: Locale) => { setLocaleState(next); try { localStorage.setItem(STORAGE_KEY, next); } catch { /* ignore */ } }, []); const t = useCallback( (key: string, vars?: Record) => { const raw = resolveMessage(MESSAGES[locale], key); return interpolate(raw, vars); }, [locale], ); useEffect(() => { document.documentElement.lang = locale === "en" ? "en" : "zh-CN"; }, [locale]); const value = useMemo(() => ({ locale, setLocale, t }), [locale, setLocale, t]); return {children}; } export function useI18n() { const ctx = useContext(I18nContext); if (!ctx) throw new Error("useI18n must be used within I18nProvider"); return ctx; }