From 9ae9cccbd96408c05f70163b50a63d7a33061a75 Mon Sep 17 00:00:00 2001 From: Fringg Date: Fri, 13 Mar 2026 06:16:27 +0300 Subject: [PATCH] perf: lazy-load locale files per language instead of bundling all Split 574KB monolithic locales chunk into per-language lazy chunks: - ru: 211KB (loaded on startup as fallback) - fa: 143KB, en: 121KB, zh: 101KB (loaded on demand) Russian users save 63% bandwidth. Other languages loaded only when user switches via LanguageSwitcher. --- src/i18n.ts | 48 +++++++++++++++++++++++++++++++++++------------- vite.config.ts | 1 - 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/i18n.ts b/src/i18n.ts index 5c21382..1f803fd 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -2,25 +2,36 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; -import ru from './locales/ru.json'; -import en from './locales/en.json'; -import zh from './locales/zh.json'; -import fa from './locales/fa.json'; - -const resources = { - ru: { translation: ru }, - en: { translation: en }, - zh: { translation: zh }, - fa: { translation: fa }, +const localeLoaders: Record Promise<{ default: Record }>> = { + ru: () => import('./locales/ru.json'), + en: () => import('./locales/en.json'), + zh: () => import('./locales/zh.json'), + fa: () => import('./locales/fa.json'), }; +const SUPPORTED_LANGS = Object.keys(localeLoaders); +const FALLBACK_LNG = 'ru'; + +const loadedLanguages = new Set(); + +async function loadLanguage(lng: string): Promise { + if (loadedLanguages.has(lng)) return; + + const loader = localeLoaders[lng]; + if (!loader) return; + + const mod = await loader(); + i18n.addResourceBundle(lng, 'translation', mod.default, true, true); + loadedLanguages.add(lng); +} + i18n .use(LanguageDetector) .use(initReactI18next) .init({ - resources, - fallbackLng: 'ru', - supportedLngs: ['ru', 'en', 'zh', 'fa'], + fallbackLng: FALLBACK_LNG, + supportedLngs: SUPPORTED_LANGS, + partialBundledLanguages: true, detection: { order: ['localStorage', 'navigator'], @@ -37,4 +48,15 @@ i18n }, }); +// Load detected language + fallback on startup +const detectedLng = i18n.language?.split('-')[0] || FALLBACK_LNG; +const langsToLoad = [FALLBACK_LNG, ...(detectedLng !== FALLBACK_LNG ? [detectedLng] : [])]; +Promise.all(langsToLoad.map(loadLanguage)); + +// Lazy-load on language change +i18n.on('languageChanged', (lng: string) => { + const code = lng.split('-')[0]; + loadLanguage(code); +}); + export default i18n; diff --git a/vite.config.ts b/vite.config.ts index ce18db2..eb6ae11 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -36,7 +36,6 @@ export default defineConfig({ rollupOptions: { output: { manualChunks(id) { - if (id.includes('/src/locales/')) return 'locales'; if (!id.includes('node_modules')) return; if ( id.includes('react-dom') ||