"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { getLocalePlugin, getSupportedLocales, resolveBrowserLocale } from "@/lib/i18n/registry"; import { translateMessage } from "@/lib/i18n/format"; import type { Locale, LocalePlugin, TranslationParams } from "@/lib/i18n/types"; const LOCALE_STORAGE_KEY = "pi-locale"; const defaultLocale: Locale = "en"; interface I18nContextValue { locale: Locale; setLocale: (locale: Locale) => void; t: (key: string, params?: TranslationParams) => string; supportedLocales: LocalePlugin[]; } const I18nContext = createContext(null); function getMessages(): Record> { return Object.fromEntries(getSupportedLocales().flatMap((id) => { const plugin = getLocalePlugin(id); return plugin ? [[id, plugin.messages]] : []; })); } function readInitialLocale(): Locale { try { const stored = window.localStorage.getItem(LOCALE_STORAGE_KEY); if (stored === "en" || stored === "zh-CN") return stored; } catch { // 隐私模式或存储不可用时继续使用浏览器语言。 } return resolveBrowserLocale(window.navigator.languages.length ? window.navigator.languages : [window.navigator.language]); } /** * 提供 Pi Web 的界面语言状态和翻译能力。 * @param props React 子节点 * @returns 包含语言上下文的 React 节点 */ export function I18nProvider({ children }: { children: React.ReactNode }) { const [locale, setLocaleState] = useState(defaultLocale); const [hydrated, setHydrated] = useState(false); const supportedLocales = useMemo( () => getSupportedLocales().map((id) => getLocalePlugin(id)).filter((plugin): plugin is LocalePlugin => Boolean(plugin)), [], ); const messages = useMemo(() => getMessages(), []); useEffect(() => { const next = readInitialLocale(); setLocaleState(next); document.documentElement.lang = next; setHydrated(true); }, []); const setLocale = useCallback((next: Locale) => { if (!getLocalePlugin(next)) return; setLocaleState(next); document.documentElement.lang = next; try { window.localStorage.setItem(LOCALE_STORAGE_KEY, next); } catch { // 存储失败不影响当前页面内的语言切换。 } }, []); const t = useCallback((key: string, params?: TranslationParams) => translateMessage(locale, key, messages, params), [locale, messages]); const value = useMemo(() => ({ locale: hydrated ? locale : defaultLocale, setLocale, t, supportedLocales }), [hydrated, locale, setLocale, t, supportedLocales]); return {children}; } /** * 获取当前组件树中的国际化能力。 * @returns 当前 locale、翻译函数、语言切换函数和支持的语言列表 * @throws 当组件不在 I18nProvider 内时抛出异常 */ export function useI18n(): I18nContextValue { const context = useContext(I18nContext); if (!context) throw new Error("useI18n must be used inside I18nProvider"); return context; }