110 lines
2.7 KiB
TypeScript
110 lines
2.7 KiB
TypeScript
"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}`);
|
|
void import("@/app/lib/navigation-loading").then(({ startNavigation }) => {
|
|
startNavigation();
|
|
window.location.href = newPath || `/${newLocale}/digital`;
|
|
});
|
|
}, []);
|
|
|
|
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 digitalPath = cleanPath.startsWith("/digital")
|
|
? cleanPath
|
|
: `/digital${cleanPath === "/" ? "" : cleanPath}`;
|
|
const newPath = `/${newLocale}${digitalPath}`;
|
|
void import("@/app/lib/navigation-loading").then(({ startNavigation }) => {
|
|
startNavigation();
|
|
window.location.href = newPath;
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
export { LOCALES, DEFAULT_LOCALE };
|