perf(i18n): lazy-load non-zh-CN locales — shared index chunk 440KB→285KB
All 5 locales (en/ja/ko/zh-CN/zh-TW) were statically bundled into the index chunk that every one of the 5 webviews loads. Keep only zh-CN (fallback + source of truth) inline so init stays synchronous; lazy-load the other 4 as separate chunks on demand (initial non-zh-CN locale loads async after first paint, then changeLanguage; setLocalePreference awaits the locale before switching). Worst case = graceful zh-CN fallback, never broken keys. Cuts ~155KB raw / ~75KB gzip from the per-webview index; zh-CN users never load the other 4. Build-verified: index 285KB + en/ja/ko/zh-TW separate chunks. Runtime-sanity: switch language in settings (en/ja/ko/zh-TW) to confirm it loads + applies.
This commit is contained in:
1 parent
1c0f55716c
commit
cab7dc205d
1 file changed
+30
-13
@@ -10,11 +10,7 @@
|
||||
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import { en } from './en';
|
||||
import { ja } from './ja';
|
||||
import { ko } from './ko';
|
||||
import { zhCN } from './zh-CN';
|
||||
import { zhTW } from './zh-TW';
|
||||
import type { UserPreferences } from '../lib/types';
|
||||
import { setRemoteLocale } from '../lib/ipc';
|
||||
|
||||
@@ -49,22 +45,42 @@ function getStoredLocale(): SupportedLocale | null {
|
||||
|
||||
const initialLng: SupportedLocale = getStoredLocale() ?? detectSystemLocale();
|
||||
|
||||
// 只把 fallback / source-of-truth 的 zh-CN 静态打进 index —— 5 个 webview 都加载 index,
|
||||
// 把 en / ja / ko / zh-TW 四份语言包拆成按需 chunk,减小每个 webview 的常驻体积。
|
||||
// 代价:非 zh-CN 用户首屏先显示 zh-CN 一瞬,语言包异步到位后切换;最坏情况(加载失败)
|
||||
// 也只停在 zh-CN(可读),不会出现 key 字面量。
|
||||
const localeLoaders: Record<Exclude<SupportedLocale, 'zh-CN'>, () => Promise<typeof zhCN>> = {
|
||||
'zh-TW': () => import('./zh-TW').then(m => m.zhTW),
|
||||
en: () => import('./en').then(m => m.en),
|
||||
ja: () => import('./ja').then(m => m.ja),
|
||||
ko: () => import('./ko').then(m => m.ko),
|
||||
};
|
||||
|
||||
async function ensureLocaleLoaded(lng: SupportedLocale): Promise<void> {
|
||||
if (lng === 'zh-CN' || i18n.hasResourceBundle(lng, 'translation')) return;
|
||||
try {
|
||||
i18n.addResourceBundle(lng, 'translation', await localeLoaders[lng]());
|
||||
} catch (e) {
|
||||
console.warn('[i18n] load locale failed, staying on zh-CN fallback:', lng, e);
|
||||
}
|
||||
}
|
||||
|
||||
void i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
'zh-CN': { translation: zhCN },
|
||||
'zh-TW': { translation: zhTW },
|
||||
en: { translation: en },
|
||||
ja: { translation: ja },
|
||||
ko: { translation: ko },
|
||||
},
|
||||
// 仅内联 zh-CN(fallback)。init 仍同步完成,首屏 t() 立刻可用(非 zh-CN 暂回退 zh-CN)。
|
||||
resources: { 'zh-CN': { translation: zhCN } },
|
||||
lng: initialLng,
|
||||
fallbackLng: 'zh-CN',
|
||||
supportedLngs: SUPPORTED_LOCALES as unknown as string[],
|
||||
partialBundledLanguages: true, // 告诉 i18next 我们的内联资源已完整,无需 backend 拉取
|
||||
partialBundledLanguages: true, // 告诉 i18next 内联资源已完整,无需 backend 拉取
|
||||
interpolation: { escapeValue: false },
|
||||
react: { useSuspense: false }, // 不悬挂;首次渲染必须能拿到译文(无 backend 时 init 同步完成)
|
||||
react: { useSuspense: false }, // 不悬挂;首屏必须能拿到译文(zh-CN 已内联,同步完成)
|
||||
});
|
||||
|
||||
// 初始语言非 zh-CN:异步补加载该语言包后切换(首屏先 zh-CN,到位后无缝切过去)。
|
||||
if (initialLng !== 'zh-CN') {
|
||||
void ensureLocaleLoaded(initialLng).then(() => i18n.changeLanguage(initialLng));
|
||||
}
|
||||
|
||||
export default i18n;
|
||||
|
||||
/**
|
||||
@@ -88,6 +104,7 @@ export async function setLocalePreference(
|
||||
} else {
|
||||
window.localStorage.setItem(LOCALE_STORAGE_KEY, pref);
|
||||
}
|
||||
await ensureLocaleLoaded(resolved);
|
||||
await i18n.changeLanguage(resolved);
|
||||
syncRemoteLocale(resolved);
|
||||
return resolved;
|
||||
|
||||
Reference in new issue
Block a user