diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 5fb6af2..78707c2 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -22,6 +22,9 @@ export const metadata: Metadata = {
description: "Lottery administration console",
};
+/** 在 React 水合前恢复 ``,避免刷新后先闪中文再切换 */
+const ADMIN_LOCALE_BOOTSTRAP = `(function(){try{var s=localStorage.getItem("lottery_admin_ui_locale");var m={zh:"zh-Hans",en:"en",ne:"ne"};if(s&&m[s])document.documentElement.lang=m[s];}catch(e){}})();`;
+
export default function RootLayout({
children,
}: Readonly<{
@@ -33,6 +36,9 @@ export default function RootLayout({
suppressHydrationWarning
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
+
+
+
{children}
diff --git a/src/components/providers.tsx b/src/components/providers.tsx
index a88fcd5..2a25f39 100644
--- a/src/components/providers.tsx
+++ b/src/components/providers.tsx
@@ -13,13 +13,29 @@ type ProvidersProps = {
children: ReactNode;
};
+function applyStoredAdminLanguage() {
+ const locale = hydrateAdminUiLocale();
+ if (!locale) {
+ return;
+ }
+ const current = i18n.resolvedLanguage ?? i18n.language;
+ if (locale !== current) {
+ void i18n.changeLanguage(locale);
+ }
+}
+
function AdminSessionHydrator() {
useEffect(() => {
- const locale = hydrateAdminUiLocale();
- if (locale && i18n.resolvedLanguage !== locale) {
- void i18n.changeLanguage(locale);
+ if (i18n.isInitialized) {
+ applyStoredAdminLanguage();
+ } else {
+ i18n.on("initialized", applyStoredAdminLanguage);
}
useAdminSessionStore.getState().rehydrate();
+
+ return () => {
+ i18n.off("initialized", applyStoredAdminLanguage);
+ };
}, []);
return null;
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index b780f04..f9d1eeb 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -3,7 +3,13 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
-import { adminHtmlLang, applyAdminUiLocale, type AdminApiLocale } from "@/lib/admin-locale";
+import {
+ adminHtmlLang,
+ applyAdminUiLocale,
+ getStoredAdminUiLocale,
+ hydrateAdminUiLocale,
+ type AdminApiLocale,
+} from "@/lib/admin-locale";
import enAudit from "@/i18n/locales/en/audit.json";
import enAdminUsers from "@/i18n/locales/en/adminUsers.json";
import enAuth from "@/i18n/locales/en/auth.json";
@@ -118,10 +124,15 @@ export function normalizeAdminLanguage(lang: string | undefined): AdminLanguage
}
function getInitialAdminLanguage(): AdminLanguage {
- if (typeof document === "undefined") {
+ if (typeof window === "undefined") {
return ADMIN_DEFAULT_LANGUAGE;
}
+ const stored = getStoredAdminUiLocale();
+ if (stored) {
+ return stored;
+ }
+
return normalizeAdminLanguage(document.documentElement.lang);
}
@@ -133,11 +144,17 @@ function syncAdminLanguage(lang: AdminLanguage): void {
}
if (!i18n.isInitialized) {
+ if (typeof window !== "undefined") {
+ hydrateAdminUiLocale();
+ }
+
+ const initialLanguage = getInitialAdminLanguage();
+
void i18n
.use(initReactI18next)
.init({
resources,
- lng: getInitialAdminLanguage(),
+ lng: initialLanguage,
fallbackLng: ADMIN_DEFAULT_LANGUAGE,
supportedLngs: [...ADMIN_SUPPORTED_LANGUAGES],
defaultNS: "common",
@@ -149,9 +166,16 @@ if (!i18n.isInitialized) {
react: {
useSuspense: false,
},
+ })
+ .then(() => {
+ const lang = getInitialAdminLanguage();
+ if (normalizeAdminLanguage(i18n.language) !== lang) {
+ void i18n.changeLanguage(lang);
+ }
+ syncAdminLanguage(normalizeAdminLanguage(i18n.resolvedLanguage ?? i18n.language));
});
- syncAdminLanguage(normalizeAdminLanguage(i18n.resolvedLanguage ?? i18n.language));
+ syncAdminLanguage(initialLanguage);
i18n.on("languageChanged", (lang) => {
syncAdminLanguage(normalizeAdminLanguage(lang));
});
diff --git a/src/lib/admin-locale.ts b/src/lib/admin-locale.ts
index 221374a..6417f6d 100644
--- a/src/lib/admin-locale.ts
+++ b/src/lib/admin-locale.ts
@@ -69,7 +69,8 @@ export function adminHtmlLang(loc: AdminApiLocale): string {
return "en";
}
-function readStoredUiLocale(): AdminApiLocale | null {
+/** 读取用户上次选择的界面语言(无则 null) */
+export function getStoredAdminUiLocale(): AdminApiLocale | null {
if (typeof window === "undefined") {
return null;
}
@@ -104,7 +105,7 @@ export function hydrateAdminUiLocale(): AdminApiLocale | null {
if (typeof window === "undefined") {
return null;
}
- const stored = readStoredUiLocale();
+ const stored = getStoredAdminUiLocale();
if (stored) {
setAdminRequestLocale(stored);
document.documentElement.lang = adminHtmlLang(stored);