feat(i18n): 增强管理端多语言支持,优化语言初始化与恢复逻辑
This commit is contained in:
@@ -22,6 +22,9 @@ export const metadata: Metadata = {
|
|||||||
description: "Lottery administration console",
|
description: "Lottery administration console",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 在 React 水合前恢复 `<html lang>`,避免刷新后先闪中文再切换 */
|
||||||
|
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({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
@@ -33,6 +36,9 @@ export default function RootLayout({
|
|||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||||
>
|
>
|
||||||
|
<head>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: ADMIN_LOCALE_BOOTSTRAP }} />
|
||||||
|
</head>
|
||||||
<body className="flex min-h-full flex-col">
|
<body className="flex min-h-full flex-col">
|
||||||
<Providers>{children}</Providers>
|
<Providers>{children}</Providers>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -13,13 +13,29 @@ type ProvidersProps = {
|
|||||||
children: ReactNode;
|
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() {
|
function AdminSessionHydrator() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const locale = hydrateAdminUiLocale();
|
if (i18n.isInitialized) {
|
||||||
if (locale && i18n.resolvedLanguage !== locale) {
|
applyStoredAdminLanguage();
|
||||||
void i18n.changeLanguage(locale);
|
} else {
|
||||||
|
i18n.on("initialized", applyStoredAdminLanguage);
|
||||||
}
|
}
|
||||||
useAdminSessionStore.getState().rehydrate();
|
useAdminSessionStore.getState().rehydrate();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
i18n.off("initialized", applyStoredAdminLanguage);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,7 +3,13 @@
|
|||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import { initReactI18next } from "react-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 enAudit from "@/i18n/locales/en/audit.json";
|
||||||
import enAdminUsers from "@/i18n/locales/en/adminUsers.json";
|
import enAdminUsers from "@/i18n/locales/en/adminUsers.json";
|
||||||
import enAuth from "@/i18n/locales/en/auth.json";
|
import enAuth from "@/i18n/locales/en/auth.json";
|
||||||
@@ -118,10 +124,15 @@ export function normalizeAdminLanguage(lang: string | undefined): AdminLanguage
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getInitialAdminLanguage(): AdminLanguage {
|
function getInitialAdminLanguage(): AdminLanguage {
|
||||||
if (typeof document === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return ADMIN_DEFAULT_LANGUAGE;
|
return ADMIN_DEFAULT_LANGUAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stored = getStoredAdminUiLocale();
|
||||||
|
if (stored) {
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
|
||||||
return normalizeAdminLanguage(document.documentElement.lang);
|
return normalizeAdminLanguage(document.documentElement.lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +144,17 @@ function syncAdminLanguage(lang: AdminLanguage): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!i18n.isInitialized) {
|
if (!i18n.isInitialized) {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
hydrateAdminUiLocale();
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialLanguage = getInitialAdminLanguage();
|
||||||
|
|
||||||
void i18n
|
void i18n
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources,
|
resources,
|
||||||
lng: getInitialAdminLanguage(),
|
lng: initialLanguage,
|
||||||
fallbackLng: ADMIN_DEFAULT_LANGUAGE,
|
fallbackLng: ADMIN_DEFAULT_LANGUAGE,
|
||||||
supportedLngs: [...ADMIN_SUPPORTED_LANGUAGES],
|
supportedLngs: [...ADMIN_SUPPORTED_LANGUAGES],
|
||||||
defaultNS: "common",
|
defaultNS: "common",
|
||||||
@@ -149,9 +166,16 @@ if (!i18n.isInitialized) {
|
|||||||
react: {
|
react: {
|
||||||
useSuspense: false,
|
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) => {
|
i18n.on("languageChanged", (lang) => {
|
||||||
syncAdminLanguage(normalizeAdminLanguage(lang));
|
syncAdminLanguage(normalizeAdminLanguage(lang));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ export function adminHtmlLang(loc: AdminApiLocale): string {
|
|||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
|
|
||||||
function readStoredUiLocale(): AdminApiLocale | null {
|
/** 读取用户上次选择的界面语言(无则 null) */
|
||||||
|
export function getStoredAdminUiLocale(): AdminApiLocale | null {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -104,7 +105,7 @@ export function hydrateAdminUiLocale(): AdminApiLocale | null {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const stored = readStoredUiLocale();
|
const stored = getStoredAdminUiLocale();
|
||||||
if (stored) {
|
if (stored) {
|
||||||
setAdminRequestLocale(stored);
|
setAdminRequestLocale(stored);
|
||||||
document.documentElement.lang = adminHtmlLang(stored);
|
document.documentElement.lang = adminHtmlLang(stored);
|
||||||
|
|||||||
Reference in New Issue
Block a user