В приложениях с поддержкой нескольких языков объем переводов часто становится значительным фактором, влияющим на размер бандла и скорость первоначальной загрузки. Полная загрузка всех локалей на старте приводит к избыточному потреблению памяти и увеличению времени загрузки, особенно при большом количестве языков и модулей.
Подход lazy loading в связке с i18next заключается в том, что переводы подгружаются только в момент их фактической необходимости — при переходе в соответствующий раздел приложения, активации определённого функционального модуля или смене языка.
Ключевая идея:
переводы должны быть частью кода, который их использует, а не глобальным статическим ресурсом приложения
i18next предоставляет механизм разделения переводов на namespaces (пространства имён). Это основной инструмент для ленивой загрузки.
Обычно структура выглядит так:
locales/
en/
common.json
auth.json
dashboard.json
ru/
common.json
auth.json
dashboard.json
Каждый namespace соответствует отдельному модулю приложения.
Инициализация i18next с поддержкой загрузчика:
import i18n from 'i18next';
import HttpBackend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';
i18n
.use(HttpBackend)
.use(initReactI18next)
.init({
fallbackLng: 'en',
lng: 'en',
ns: ['common'],
defaultNS: 'common',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
},
interpolation: {
escapeValue: false
},
react: {
useSuspense: true
}
});
export default i18n;
Namespaces являются ключевым механизмом ленивой загрузки.
Каждый модуль приложения должен иметь собственный набор переводов:
auth — авторизация и регистрацияdashboard — главная панельprofile — профиль пользователяsettings — настройкиИспользование namespace в компоненте:
import { useTranslation } from 'react-i18next';
function Profile() {
const { t } = useTranslation('profile');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
Если namespace ещё не загружен, i18next автоматически инициирует его загрузку через backend.
i18next-http-backend позволяет загружать переводы по HTTP запросу только при необходимости.
Механизм работы:
Компонент запрашивает namespace
i18next проверяет наличие в кеше
При отсутствии отправляется запрос:
/locales/ru/profile.jsonРезультат сохраняется в памяти
Это позволяет избежать загрузки лишних JSON-файлов при старте приложения.
В некоторых сценариях требуется заранее загрузить переводы, например при входе в модуль:
import i18n from './i18n';
async function loadProfileModule() {
await i18n.loadNamespaces('profile');
}
Можно загружать несколько namespace одновременно:
await i18n.loadNamespaces(['profile', 'settings']);
Этот подход полезен при предзагрузке данных перед переходом на страницу.
Lazy loading переводов часто используется вместе с динамическими импортами модулей.
Пример с React и динамическим импортом:
import { Suspense, lazy } from 'react';
const Profile = lazy(() => import('./Profile'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Profile />
</Suspense>
);
}
Внутри Profile автоматически загружается namespace
profile, что синхронизируется с код-сплитингом.
Это позволяет добиться:
i18next сохраняет загруженные ресурсы в памяти. Повторный запрос namespace не вызывает HTTP загрузку.
Поведение:
Это особенно важно при частых переключениях языков или маршрутов.
В некоторых случаях переводы поставляются вместе с кодом модуля:
import i18n from './i18n';
import profileEN from './locales/en/profile.json';
import profileRU from './locales/ru/profile.json';
i18n.addResourceBundle('en', 'profile', profileEN);
i18n.addResourceBundle('ru', 'profile', profileRU);
Этот подход исключает HTTP запросы, но требует включения переводов в бандл модуля.
При использовании роутеров lazy loading переводов часто привязывается к маршрутам.
Пример логики:
import i18n from './i18n';
router.beforeEach(async (to) => {
const namespace = to.meta.namespace;
if (namespace) {
await i18n.loadNamespaces(namespace);
}
});
Каждый маршрут содержит информацию о необходимых переводах:
{
path: '/dashboard',
component: Dashboard,
meta: {
namespace: 'dashboard'
}
}
В серверном рендеринге lazy loading требует предварительного разрешения всех namespace до генерации HTML.
Пример:
await i18n.loadNamespaces(['common', 'dashboard']);
const html = renderToString(app);
Без этого возможна ситуация, когда клиент получает HTML с отсутствующими переводами и происходит гидратационная рассинхронизация.
При ленивой загрузке возможны следующие сценарии:
Настройки fallback:
i18n.init({
fallbackLng: 'en',
fallbackNS: 'common',
load: 'languageOnly'
});
Также можно обработать ошибки загрузки backend:
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
requestOptions: {
timeout: 5000
}
}
При большом количестве модулей важно избегать избыточных HTTP вызовов.
Подходы:
dashboard,
dashboard.widgets)Типичная структура крупного проекта:
i18n/
en/
common.json
auth.json
dashboard.json
admin.json
billing.json
И загрузка по требованию:
common — всегда загружаетсяauth — при входеdashboard — после авторизацииadmin — только для админовbilling — при переходе в платежиТакой подход обеспечивает линейный рост приложения без деградации стартовой производительности.
При смене языка необходимо перезагружать только активные namespaces:
i18n.changeLanguage('ru').then(() => {
return i18n.reloadResources();
});
Либо точечно:
await i18n.loadNamespaces(currentNamespaces);
Это предотвращает полную перезагрузку всех переводов и снижает нагрузку на сеть.
React Suspense позволяет естественно интегрировать загрузку переводов в жизненный цикл UI:
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
При этом i18next блокирует рендер компонента до завершения загрузки namespace.
Поведение:
Lazy loading переводов становится частью общей модульной архитектуры:
Такой подход снижает связность системы и упрощает масштабирование интерфейса на новые языки и домены.