105 lines
2.3 KiB
TypeScript
105 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
import { isChinaUser } from "is-china-user";
|
|
import { dictionaries, type Dictionary, type Locale } from "~/lib/i18n";
|
|
|
|
const STORAGE_KEY = "nomadro-locale";
|
|
|
|
type LocaleContextValue = {
|
|
locale: Locale;
|
|
isChina: boolean;
|
|
ready: boolean;
|
|
t: Dictionary;
|
|
setLocale: (locale: Locale) => void;
|
|
};
|
|
|
|
const LocaleContext = createContext<LocaleContextValue | null>(null);
|
|
|
|
function isLocale(value: string | null): value is Locale {
|
|
return value === "zh" || value === "en";
|
|
}
|
|
|
|
export function LocaleProvider({ children }: { children: ReactNode }) {
|
|
const [locale, setLocaleState] = useState<Locale>("zh");
|
|
const [ready, setReady] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
try {
|
|
// Prefer auto detection; only keep a locale if user unlocked and switched.
|
|
const unlocked = window.sessionStorage.getItem("nomadro-lang-unlock") === "1";
|
|
const saved = window.localStorage.getItem(STORAGE_KEY);
|
|
|
|
if (unlocked && isLocale(saved)) {
|
|
if (!cancelled) {
|
|
setLocaleState(saved);
|
|
setReady(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const china = Boolean(isChinaUser());
|
|
if (!cancelled) {
|
|
setLocaleState(china ? "zh" : "en");
|
|
setReady(true);
|
|
}
|
|
} catch {
|
|
if (!cancelled) {
|
|
setLocaleState("zh");
|
|
setReady(true);
|
|
}
|
|
}
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!ready) return;
|
|
document.documentElement.lang = dictionaries[locale].htmlLang;
|
|
}, [locale, ready]);
|
|
|
|
const setLocale = useCallback((next: Locale) => {
|
|
setLocaleState(next);
|
|
try {
|
|
window.localStorage.setItem(STORAGE_KEY, next);
|
|
} catch {
|
|
// ignore storage failures
|
|
}
|
|
}, []);
|
|
|
|
const value = useMemo<LocaleContextValue>(
|
|
() => ({
|
|
locale,
|
|
isChina: locale === "zh",
|
|
ready,
|
|
t: dictionaries[locale],
|
|
setLocale,
|
|
}),
|
|
[locale, ready, setLocale],
|
|
);
|
|
|
|
return (
|
|
<LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useLocale() {
|
|
const ctx = useContext(LocaleContext);
|
|
if (!ctx) {
|
|
throw new Error("useLocale must be used within LocaleProvider");
|
|
}
|
|
return ctx;
|
|
}
|