From cab7dc205de31ae54315f9b468063fcc0ac80586 Mon Sep 17 00:00:00 2001 From: Andy Jiang <72869031+jiangmuran@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:13:13 +0800 Subject: [PATCH] =?UTF-8?q?perf(i18n):=20lazy-load=20non-zh-CN=20locales?= =?UTF-8?q?=20=E2=80=94=20shared=20index=20chunk=20440KB=E2=86=92285KB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- openless-all/app/src/i18n/index.ts | 43 +++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/openless-all/app/src/i18n/index.ts b/openless-all/app/src/i18n/index.ts index 93ba05b6..bf69b494 100644 --- a/openless-all/app/src/i18n/index.ts +++ b/openless-all/app/src/i18n/index.ts @@ -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, () => Promise> = { + '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 { + 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;