"use client"; import { createContext, useContext, useState, useEffect, useMemo, useCallback, ReactNode } from "react"; type Language = "zh" | "en"; type Theme = "light" | "dark"; const VALID_LANGUAGES: Language[] = ["zh", "en"]; const VALID_THEMES: Theme[] = ["light", "dark"]; function isValidLanguage(val: unknown): val is Language { return typeof val === 'string' && VALID_LANGUAGES.includes(val as Language); } function isValidTheme(val: unknown): val is Theme { return typeof val === 'string' && VALID_THEMES.includes(val as Theme); } interface AppContextType { language: Language; setLanguage: (lang: Language) => void; theme: Theme; setTheme: (theme: Theme) => void; t: (key: string) => string; } const AppContext = createContext(undefined); // 模块级翻译缓存 const translationCache: Record> = {}; function loadTranslations(lang: Language): Record { if (!translationCache[lang]) { translationCache[lang] = require(`../locales/${lang}.json`); } return translationCache[lang]; } export function AppProvider({ children }: { children: ReactNode }) { const [language, setLanguageState] = useState("en"); const [theme, setThemeState] = useState("light"); const [mounted, setMounted] = useState(false); // SSR 守卫:仅 mounted 后读取 localStorage/matchMedia useEffect(() => { setMounted(true); try { const savedLanguage = localStorage.getItem("language"); if (isValidLanguage(savedLanguage)) { setLanguageState(savedLanguage); } // 强制 light 主题 localStorage.setItem("theme", "light"); } catch { // 隐私模式或 localStorage 不可用 } }, []); // Apply theme useEffect(() => { if (!mounted) return; try { localStorage.setItem("theme", theme); } catch { // 隐私模式兼容 } if (theme === "dark") { document.documentElement.classList.add("dark"); } else { document.documentElement.classList.remove("dark"); } }, [theme, mounted]); // Save language preference const setLanguage = useCallback((lang: Language) => { setLanguageState(lang); try { localStorage.setItem("language", lang); } catch { // 隐私模式兼容 } }, []); const setTheme = useCallback((t: Theme) => { setThemeState(t); }, []); // Translation function — 使用模块级缓存 const t = useCallback((key: string): string => { const translations = loadTranslations(language); const keys = key.split("."); let value: unknown = translations; for (const k of keys) { if (typeof value === 'object' && value !== null) { value = (value as Record)[k]; } else { return key; } } return typeof value === 'string' ? value : key; }, [language]); const contextValue = useMemo( () => ({ language, setLanguage, theme, setTheme, t }), [language, setLanguage, theme, setTheme, t] ); return ( {children} ); } export function useApp() { const context = useContext(AppContext); if (!context) { throw new Error("useApp must be used within AppProvider"); } return context; }