94 lines
2.3 KiB
TypeScript
94 lines
2.3 KiB
TypeScript
/**
|
||
* 用户偏好:深色 / 浅色模式与界面语言。
|
||
* 两项都持久化到 localStorage,并同步到 <html data-theme> 供 CSS 变量取色。
|
||
*/
|
||
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||
import type { ReactNode } from "react";
|
||
import { makeT, setGlobalLang } from "./i18n";
|
||
import type { Lang, TFn } from "./i18n";
|
||
|
||
export type ThemeMode = "light" | "dark";
|
||
|
||
const MODE_KEY = "nex_math_theme";
|
||
const LANG_KEY = "nex_math_lang";
|
||
|
||
function readMode(): ThemeMode {
|
||
try {
|
||
return localStorage.getItem(MODE_KEY) === "dark" ? "dark" : "light";
|
||
} catch {
|
||
return "light";
|
||
}
|
||
}
|
||
|
||
function readLang(): Lang {
|
||
try {
|
||
return localStorage.getItem(LANG_KEY) === "en" ? "en" : "zh";
|
||
} catch {
|
||
return "zh";
|
||
}
|
||
}
|
||
|
||
interface Prefs {
|
||
mode: ThemeMode;
|
||
lang: Lang;
|
||
t: TFn;
|
||
toggleMode: () => void;
|
||
setLang: (lang: Lang) => void;
|
||
}
|
||
|
||
const PrefsContext = createContext<Prefs>({
|
||
mode: "light",
|
||
lang: "zh",
|
||
t: makeT("zh"),
|
||
toggleMode: () => {},
|
||
setLang: () => {},
|
||
});
|
||
|
||
export function PrefsProvider({ children }: { children: ReactNode }) {
|
||
const [mode, setMode] = useState<ThemeMode>(readMode);
|
||
const [lang, setLangState] = useState<Lang>(readLang);
|
||
|
||
useEffect(() => {
|
||
document.documentElement.dataset.theme = mode;
|
||
document.documentElement.style.colorScheme = mode;
|
||
try {
|
||
localStorage.setItem(MODE_KEY, mode);
|
||
} catch {
|
||
// 隐私模式下写入失败时忽略
|
||
}
|
||
}, [mode]);
|
||
|
||
useEffect(() => {
|
||
setGlobalLang(lang);
|
||
document.documentElement.lang = lang === "en" ? "en" : "zh-CN";
|
||
// 浏览器标签页标题跟随语言,文案同样只来自 i18n
|
||
document.title = makeT(lang)("app.name");
|
||
try {
|
||
localStorage.setItem(LANG_KEY, lang);
|
||
} catch {
|
||
// 同上
|
||
}
|
||
}, [lang]);
|
||
|
||
const value = useMemo<Prefs>(
|
||
() => ({
|
||
mode,
|
||
lang,
|
||
t: makeT(lang),
|
||
toggleMode: () => setMode((current) => (current === "dark" ? "light" : "dark")),
|
||
setLang: (next: Lang) => setLangState(next),
|
||
}),
|
||
[mode, lang],
|
||
);
|
||
|
||
return <PrefsContext.Provider value={value}>{children}</PrefsContext.Provider>;
|
||
}
|
||
|
||
export function usePrefs(): Prefs {
|
||
return useContext(PrefsContext);
|
||
}
|
||
|
||
export function useT(): TFn {
|
||
return useContext(PrefsContext).t;
|
||
}
|