perf(frontend): U48 i18n locale 懒加载 — en-US JSON 动态导入拆分为独立 chunk
问题: src/i18n/index.ts 静态导入 zh-CN.json (200KB compact) + en-US.json (292KB compact),两者共 486KB 全部内联到主 index chunk,致主 chunk 696KB 超过 Vite 600KB 警告阈值。 修复: - src/i18n/index.ts: 移除 en-US.json 静态导入,改为 ensureLocaleLoaded() 动态导入 + i18n.addResourceBundle() 按需加载 - zh-CN (默认/回退语言) 保持静态内联,无行为变化 - en-US 仅在用户切换英文时按需 fetch (290KB 独立 chunk) - 已选英文的用户刷新页面时异步加载 en-US (短暂中文闪烁,可接受) - src/store/index.ts: setLanguage 调用 ensureLocaleLoaded 确保目标语言包 加载完成后再切换 结果: - 主 index chunk: 696KB → 423KB (-39%, -273KB) - en-US chunk: 290KB 独立懒加载 (仅英文用户首次切换时 fetch) - Vite 600KB chunk 警告消除 - tsc -b=0, npm build✓ (18.18s), ESLint=0/43/any=0, go build=0 - 17/17 集成测试通过, Docker healthy
This commit is contained in:
1 parent
ca036a85ca
commit
8e211b64af
2 files changed
+35
-5
No files matched your search
@@ -1,7 +1,6 @@
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
import zhCN from '@/locales/zh-CN.json';
|
import zhCN from '@/locales/zh-CN.json';
|
||||||
import enUS from '@/locales/en-US.json';
|
|
||||||
|
|
||||||
export type AppLanguage = 'zh-CN' | 'en-US';
|
export type AppLanguage = 'zh-CN' | 'en-US';
|
||||||
|
|
||||||
@@ -12,6 +11,24 @@ export const SUPPORTED_LANGUAGES: { code: AppLanguage; label: string; flag: stri
|
|||||||
|
|
||||||
export const DEFAULT_LANGUAGE: AppLanguage = 'zh-CN';
|
export const DEFAULT_LANGUAGE: AppLanguage = 'zh-CN';
|
||||||
|
|
||||||
|
/** Track which locale resource bundles have been registered with i18next. */
|
||||||
|
const loadedLocales = new Set<string>(['zh-CN']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure a locale resource bundle is loaded before use.
|
||||||
|
* zh-CN is bundled inline (default/fallback language).
|
||||||
|
* en-US is lazy-loaded via dynamic import to reduce the main bundle by ~290 kB.
|
||||||
|
* Subsequent calls for an already-loaded locale are instant no-ops.
|
||||||
|
*/
|
||||||
|
export async function ensureLocaleLoaded(lang: string): Promise<void> {
|
||||||
|
if (loadedLocales.has(lang)) return;
|
||||||
|
if (lang === 'en-US') {
|
||||||
|
const mod = await import('@/locales/en-US.json');
|
||||||
|
i18n.addResourceBundle('en-US', 'translation', mod.default, true, true);
|
||||||
|
loadedLocales.add('en-US');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Read persisted language from localStorage (zustand persist key) */
|
/** Read persisted language from localStorage (zustand persist key) */
|
||||||
function getPersistedLanguage(): AppLanguage {
|
function getPersistedLanguage(): AppLanguage {
|
||||||
try {
|
try {
|
||||||
@@ -27,12 +44,15 @@ function getPersistedLanguage(): AppLanguage {
|
|||||||
return DEFAULT_LANGUAGE;
|
return DEFAULT_LANGUAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const persistedLang = getPersistedLanguage();
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
resources: {
|
resources: {
|
||||||
'zh-CN': { translation: zhCN },
|
'zh-CN': { translation: zhCN },
|
||||||
'en-US': { translation: enUS },
|
|
||||||
},
|
},
|
||||||
lng: getPersistedLanguage(),
|
// Start with zh-CN if the persisted language (en-US) hasn't been loaded yet.
|
||||||
|
// en-US is fetched asynchronously below and the language is switched once ready.
|
||||||
|
lng: persistedLang === 'en-US' ? 'zh-CN' : persistedLang,
|
||||||
fallbackLng: 'zh-CN',
|
fallbackLng: 'zh-CN',
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false, // React already escapes
|
escapeValue: false, // React already escapes
|
||||||
@@ -42,6 +62,15 @@ i18n.use(initReactI18next).init({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If the user previously selected en-US, load it asynchronously then switch.
|
||||||
|
// There is a brief flash of Chinese before English loads — acceptable trade-off
|
||||||
|
// for a ~290 kB reduction in the main entry chunk.
|
||||||
|
if (persistedLang === 'en-US') {
|
||||||
|
void ensureLocaleLoaded('en-US').then(() => {
|
||||||
|
i18n.changeLanguage('en-US');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Sync <html lang="..."> with current language
|
// Sync <html lang="..."> with current language
|
||||||
i18n.on('languageChanged', (lng) => {
|
i18n.on('languageChanged', (lng) => {
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ export const useLanguageStore = create<LanguageState>()(
|
|||||||
(set) => ({
|
(set) => ({
|
||||||
language: "zh-CN" as AppLanguage,
|
language: "zh-CN" as AppLanguage,
|
||||||
setLanguage: (lang) => {
|
setLanguage: (lang) => {
|
||||||
// 同步切换 i18next 实例
|
// Ensure the target locale bundle is loaded (en-US is lazy) before switching.
|
||||||
import("@/i18n")
|
import("@/i18n")
|
||||||
.then(({ default: i18n }) => {
|
.then(async ({ default: i18n, ensureLocaleLoaded }) => {
|
||||||
|
await ensureLocaleLoaded(lang);
|
||||||
i18n.changeLanguage(lang);
|
i18n.changeLanguage(lang);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
Reference in new issue
Block a user