import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'; import { vi, type Dict, type MsgKey } from './vi'; import { en } from './en'; // Two dictionaries, no i18n library: the whole set is a few KB of text and the // `Dict` type makes a missing English key a build error. Reach for a library // (or lazy-loading) when the dictionaries outgrow the bundle — not before. export type Lang = 'vi' | 'en'; const DICTS: Record = { vi, en }; const KEY = 'rc.lang'; function readLang(): Lang { const stored = localStorage.getItem(KEY); if (stored === 'vi' || stored === 'en') return stored; return (navigator.language || '').toLowerCase().startsWith('vi') ? 'vi' : 'en'; } interface I18nValue { lang: Lang; setLang: (lang: Lang) => void; t: (key: MsgKey, params?: Record) => string; } const Ctx = createContext(null); export function I18nProvider({ children }: { children: ReactNode }) { const [lang, setLangState] = useState(readLang); const setLang = useCallback((next: Lang) => { localStorage.setItem(KEY, next); setLangState(next); }, []); useEffect(() => { document.documentElement.lang = lang; }, [lang]); const value = useMemo(() => ({ lang, setLang, t: (key, params) => { let text: string = DICTS[lang][key] ?? vi[key] ?? key; if (params) { for (const [name, v] of Object.entries(params)) text = text.replace(`{${name}}`, String(v)); } return text; }, }), [lang, setLang]); return {children}; } export function useI18n(): I18nValue { const value = useContext(Ctx); if (!value) throw new Error('useI18n used outside I18nProvider'); return value; } export const LANGS: { id: Lang; label: string }[] = [ { id: 'vi', label: vi['lang.name'] }, { id: 'en', label: en['lang.name'] }, ];