mirror of
https://github.com/hansjone/netx.git
synced 2026-10-10 03:30:49 +08:00
- Replace sidebar app with workbench home and module tabs (openOrFocusModule) - Remove legacy pages; add module registry, shared tab channel, WEB.md - Add zh/en i18n, global ToastProvider, restore oclaw bridge pill in header Co-authored-by: Cursor <cursoragent@cursor.com>
78 lines
2.1 KiB
TypeScript
78 lines
2.1 KiB
TypeScript
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
|
|
import en from "./en";
|
|
import zh from "./zh";
|
|
|
|
export type Locale = "zh" | "en";
|
|
|
|
const STORAGE_KEY = "netx-locale";
|
|
|
|
const MESSAGES: Record<Locale, typeof zh> = { zh, en };
|
|
|
|
function resolveMessage(obj: unknown, path: string): string {
|
|
const keys = path.split(".");
|
|
let cur: unknown = obj;
|
|
for (const k of keys) {
|
|
if (cur == null || typeof cur !== "object") return path;
|
|
cur = (cur as Record<string, unknown>)[k];
|
|
}
|
|
return typeof cur === "string" ? cur : path;
|
|
}
|
|
|
|
function interpolate(text: string, vars?: Record<string, string | number>): string {
|
|
if (!vars) return text;
|
|
let out = text;
|
|
for (const [k, v] of Object.entries(vars)) {
|
|
out = out.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
type I18nContextValue = {
|
|
locale: Locale;
|
|
setLocale: (locale: Locale) => void;
|
|
t: (key: string, vars?: Record<string, string | number>) => string;
|
|
};
|
|
|
|
const I18nContext = createContext<I18nContextValue | null>(null);
|
|
|
|
export function I18nProvider({ children }: { children: ReactNode }) {
|
|
const [locale, setLocaleState] = useState<Locale>(() => {
|
|
try {
|
|
const saved = localStorage.getItem(STORAGE_KEY);
|
|
return saved === "en" ? "en" : "zh";
|
|
} catch {
|
|
return "zh";
|
|
}
|
|
});
|
|
|
|
const setLocale = useCallback((next: Locale) => {
|
|
setLocaleState(next);
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, next);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}, []);
|
|
|
|
const t = useCallback(
|
|
(key: string, vars?: Record<string, string | number>) => {
|
|
const raw = resolveMessage(MESSAGES[locale], key);
|
|
return interpolate(raw, vars);
|
|
},
|
|
[locale],
|
|
);
|
|
|
|
useEffect(() => {
|
|
document.documentElement.lang = locale === "en" ? "en" : "zh-CN";
|
|
}, [locale]);
|
|
|
|
const value = useMemo(() => ({ locale, setLocale, t }), [locale, setLocale, t]);
|
|
|
|
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
|
|
}
|
|
|
|
export function useI18n() {
|
|
const ctx = useContext(I18nContext);
|
|
if (!ctx) throw new Error("useI18n must be used within I18nProvider");
|
|
return ctx;
|
|
}
|