Add a lightweight i18n system to the web dashboard with English (default) and Chinese language support. A language switcher with flag icons is placed in the header bar, allowing users to toggle between languages. The choice persists to localStorage. Implementation: - src/i18n/ — types, translation files (en.ts, zh.ts), React context + hook - LanguageSwitcher component shows the *other* language's flag as the toggle - I18nProvider wraps the app in main.tsx - All 8 pages + OAuth components updated to use t() translation calls - Zero new dependencies — pure React context + localStorage
59 lines
1.3 KiB
TypeScript
59 lines
1.3 KiB
TypeScript
import { createContext, useContext, useState, useCallback, type ReactNode } from "react";
|
|
import type { Locale, Translations } from "./types";
|
|
import { en } from "./en";
|
|
import { zh } from "./zh";
|
|
|
|
const TRANSLATIONS: Record<Locale, Translations> = { en, zh };
|
|
const STORAGE_KEY = "hermes-locale";
|
|
|
|
function getInitialLocale(): Locale {
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
if (stored === "en" || stored === "zh") return stored;
|
|
} catch {
|
|
// SSR or privacy mode
|
|
}
|
|
return "en";
|
|
}
|
|
|
|
interface I18nContextValue {
|
|
locale: Locale;
|
|
setLocale: (l: Locale) => void;
|
|
t: Translations;
|
|
}
|
|
|
|
const I18nContext = createContext<I18nContextValue>({
|
|
locale: "en",
|
|
setLocale: () => {},
|
|
t: en,
|
|
});
|
|
|
|
export function I18nProvider({ children }: { children: ReactNode }) {
|
|
const [locale, setLocaleState] = useState<Locale>(getInitialLocale);
|
|
|
|
const setLocale = useCallback((l: Locale) => {
|
|
setLocaleState(l);
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, l);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}, []);
|
|
|
|
const value: I18nContextValue = {
|
|
locale,
|
|
setLocale,
|
|
t: TRANSLATIONS[locale],
|
|
};
|
|
|
|
return (
|
|
<I18nContext.Provider value={value}>
|
|
{children}
|
|
</I18nContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useI18n() {
|
|
return useContext(I18nContext);
|
|
}
|