'多语言'
This commit is contained in:
100
app/context/LocaleContext.tsx
Normal file
100
app/context/LocaleContext.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
export type Locale = "zh" | "en";
|
||||
|
||||
const LOCALES: Locale[] = ["zh", "en"];
|
||||
const DEFAULT_LOCALE: Locale = "zh";
|
||||
|
||||
type Messages = Record<string, unknown>;
|
||||
|
||||
type LocaleContextValue = {
|
||||
locale: Locale;
|
||||
messages: Messages;
|
||||
t: (key: string) => string;
|
||||
setLocale: (locale: Locale) => void;
|
||||
};
|
||||
|
||||
const LocaleContext = createContext<LocaleContextValue | null>(null);
|
||||
|
||||
function getNested(obj: unknown, path: string): string | undefined {
|
||||
const keys = path.split(".");
|
||||
let current: unknown = obj;
|
||||
for (const key of keys) {
|
||||
if (current == null || typeof current !== "object") return undefined;
|
||||
current = (current as Record<string, unknown>)[key];
|
||||
}
|
||||
return typeof current === "string" ? current : undefined;
|
||||
}
|
||||
|
||||
export function LocaleProvider({
|
||||
locale,
|
||||
messages,
|
||||
children,
|
||||
}: {
|
||||
locale: Locale;
|
||||
messages: Messages;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const t = useCallback(
|
||||
(key: string) => {
|
||||
const value = getNested(messages, key);
|
||||
return value ?? key;
|
||||
},
|
||||
[messages]
|
||||
);
|
||||
|
||||
const setLocale = useCallback((newLocale: Locale) => {
|
||||
const path = window.location.pathname;
|
||||
const newPath = path.replace(/^\/(zh|en)/, `/${newLocale}`);
|
||||
window.location.href = newPath || `/${newLocale}`;
|
||||
}, []);
|
||||
|
||||
const value: LocaleContextValue = {
|
||||
locale,
|
||||
messages,
|
||||
t,
|
||||
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.locale;
|
||||
}
|
||||
|
||||
export function useTranslation(namespace?: string) {
|
||||
const ctx = useContext(LocaleContext);
|
||||
if (!ctx) throw new Error("useTranslation must be used within LocaleProvider");
|
||||
return {
|
||||
t: (key: string) => {
|
||||
const fullKey = namespace ? `${namespace}.${key}` : key;
|
||||
return ctx.t(fullKey);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function useLocaleRouter() {
|
||||
const locale = useLocale();
|
||||
return {
|
||||
replace: (pathname: string, opts?: { locale?: Locale }) => {
|
||||
const newLocale = opts?.locale ?? locale;
|
||||
const cleanPath = pathname.replace(/^\/(zh|en)/, "") || "/";
|
||||
const newPath = `/${newLocale}${cleanPath === "/" ? "" : cleanPath}`;
|
||||
window.location.href = newPath;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { LOCALES, DEFAULT_LOCALE };
|
||||
Reference in New Issue
Block a user