nex_math/frontend/src/prefs.tsx

94 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 用户偏好:深色 / 浅色模式与界面语言。
* 两项都持久化到 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;
}