'use client'; // theme.tsx — 语言 + 明暗主题上下文(localStorage 持久化,SSR 安全) import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'; import type { Lang } from './i18n'; type Theme = 'light' | 'dark'; interface Ctx { lang: Lang; setLang: (l: Lang) => void; theme: Theme; setTheme: (t: Theme) => void; toggleTheme: () => void; } const ThemeCtx = createContext(null); const LANG_KEY = 'pg_uc_lang'; const THEME_KEY = 'pg_uc_theme'; export function UIProvider({ children }: { children: React.ReactNode }) { const [lang, setLangState] = useState('en'); // 默认英文(国际化默认语种) const [theme, setThemeState] = useState('light'); // 挂载后读取持久化(避免 hydration 不一致) useEffect(() => { try { const l = window.localStorage.getItem(LANG_KEY) as Lang | null; const t = window.localStorage.getItem(THEME_KEY) as Theme | null; if (l && (['zh', 'en', 'ja', 'ko', 'ru', 'es'] as Lang[]).includes(l)) setLangState(l); // 默认恒浅色:只有用户手动切换过(localStorage 有显式偏好)才用保存值, // 不跟随系统 prefers-color-scheme(避免系统暗色把登录页/用户中心染黑)。 const initial: Theme = t === 'dark' || t === 'light' ? t : 'light'; setThemeState(initial); } catch { /* ignore */ } }, []); useEffect(() => { document.documentElement.setAttribute('data-theme', theme); document.documentElement.setAttribute('lang', lang); }, [theme, lang]); const setLang = useCallback((l: Lang) => { setLangState(l); try { window.localStorage.setItem(LANG_KEY, l); } catch { /* ignore */ } }, []); const setTheme = useCallback((t: Theme) => { setThemeState(t); try { window.localStorage.setItem(THEME_KEY, t); } catch { /* ignore */ } }, []); const toggleTheme = useCallback(() => setTheme(theme === 'dark' ? 'light' : 'dark'), [theme, setTheme]); return {children}; } export function useUI(): Ctx { const c = useContext(ThemeCtx); if (!c) throw new Error('useUI must be used within UIProvider'); return c; }