mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-06-02 22:20:01 +08:00
60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import { createContext, useCallback, useContext, useMemo, useState, useEffect } from 'react';
|
|
import type { PropsWithChildren } from 'react';
|
|
import en from './locales/en.json';
|
|
import zhOverrides from './locales/zh.json';
|
|
|
|
type Lang = 'zh' | 'en';
|
|
type Dict = Record<string, string>;
|
|
|
|
const dicts: Record<Lang, Dict> = {
|
|
en,
|
|
zh: { ...en, ...zhOverrides },
|
|
};
|
|
|
|
export interface I18nContextValue {
|
|
lang: Lang;
|
|
setLang: (lang: Lang) => void;
|
|
t: (key: string, params?: Record<string, string | number>) => string;
|
|
}
|
|
|
|
const I18nContext = createContext<I18nContextValue | null>(null);
|
|
|
|
function interpolate(template: string, params?: Record<string, string | number>): string {
|
|
if (!params) return template;
|
|
return template.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? `{${k}}`));
|
|
}
|
|
|
|
export function I18nProvider({ children }: PropsWithChildren) {
|
|
const [lang, setLangState] = useState<Lang>(() => (localStorage.getItem('lang') as Lang) || 'zh');
|
|
|
|
const setLang = useCallback((l: Lang) => {
|
|
setLangState(l);
|
|
localStorage.setItem('lang', l);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
document.documentElement.lang = lang;
|
|
window.dispatchEvent(new CustomEvent('foxel:langchange', { detail: { lang } }));
|
|
}, [lang]);
|
|
|
|
const t = useCallback((key: string, params?: Record<string, string | number>) => {
|
|
const dict = dicts[lang] || {};
|
|
const raw = dict[key] ?? key; // fallback to key (English)
|
|
return interpolate(raw, params);
|
|
}, [lang]);
|
|
|
|
const value = useMemo<I18nContextValue>(() => ({ lang, setLang, t }), [lang, setLang, t]);
|
|
|
|
return (
|
|
<I18nContext.Provider value={value}>
|
|
{children}
|
|
</I18nContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useI18n() {
|
|
const ctx = useContext(I18nContext);
|
|
if (!ctx) throw new Error('useI18n must be used within I18nProvider');
|
|
return ctx;
|
|
}
|