В экосистеме i18next динамическая загрузка переводов используется для сокращения размера начального бандла, ускорения первичной загрузки приложения и разделения локализационных ресурсов по языкам и модулям интерфейса. Подход основан на идее загрузки только тех переводов, которые необходимы в текущем контексте выполнения, а не включения всех языков в итоговую сборку.
Базовая модель i18next предполагает наличие ресурсов перевода в памяти:
i18n.init({
resources: {
en: {
common: {
welcome: "Welcome"
}
}
},
lng: "en",
ns: ["common"],
defaultNS: "common"
});
Такой подход становится неэффективным при большом количестве языков и пространств имён. Динамический импорт переносит загрузку ресурсов в момент их фактической необходимости.
Ключевые механизмы:
Динамический импорт невозможен без структурирования переводов. Наиболее распространённая модель — разделение по namespace:
locales/
en/
common.json
dashboard.json
ru/
common.json
dashboard.json
Пример содержимого:
// locales/ru/common.json
{
"welcome": "Добро пожаловать"
}
// locales/ru/dashboard.json
{
"title": "Панель управления"
}
Инициализация i18next с поддержкой namespace:
i18n.init({
lng: "ru",
fallbackLng: "en",
ns: ["common", "dashboard"],
defaultNS: "common"
});
Наиболее универсальный способ динамической загрузки — использование backend-модуля:
npm install i18next-http-backend
Конфигурация:
import i18n from "i18next";
import HttpBackend from "i18next-http-backend";
i18n
.use(HttpBackend)
.init({
lng: "ru",
fallbackLng: "en",
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json"
}
});
При таком подходе перевод загружается только при первом обращении к namespace:
i18n.t("dashboard:title");
Если namespace dashboard не загружен, библиотека
автоматически выполнит HTTP-запрос:
GET /locales/ru/dashboard.json
В некоторых архитектурах требуется явный контроль загрузки переводов.
await i18n.loadNamespaces("dashboard");
i18n.setDefaultNamespace("dashboard");
console.log(i18n.t("title"));
Метод loadNamespaces инициирует подгрузку ресурсов и
интегрирует их в текущий instance i18next.
При использовании Vite или современных bundler-ов возможен отказ от
HTTP-загрузки в пользу import():
async function loadLocale(locale, namespace) {
const messages = await import(
`./locales/${locale}/${namespace}.json`
);
i18n.addResourceBundle(
locale,
namespace,
messages.default,
true,
true
);
}
Использование:
await loadLocale("ru", "dashboard");
Преимущество такого подхода заключается в:
В Vite-экосистеме динамический импорт переводов часто строится через
import.meta.glob:
const locales = import.meta.glob("./locales/**/!.json");
Загрузка конкретного файла:
async function loadLocale(locale, ns) {
const path = `./locales/${locale}/${ns}.json`;
const loader = locales[path];
if (!loader) return;
const module = await loader();
i18n.addResourceBundle(locale, ns, module.default);
}
Такой подход позволяет:
Webpack позволяет вынести переводы в отдельные чанки:
function loadLocale(locale, ns) {
return import(
/* webpackChunkName: "i18n-[request]" */
`./locales/${locale}/${ns}.json`
).then((module) => {
i18n.addResourceBundle(locale, ns, module.default);
});
}
Каждый язык или namespace превращается в отдельный chunk:
i18n-ru-dashboard.js
i18n-en-common.js
Механизм смены языка часто связывается с динамической подгрузкой:
async function changeLanguage(lng) {
await i18n.changeLanguage(lng);
const namespaces = i18n.options.ns;
await Promise.all(
namespaces.map((ns) =>
i18n.loadNamespaces(ns)
)
);
}
В более строгой архитектуре загрузка происходит до смены языка:
async function switchLanguage(lng) {
const namespaces = ["common", "dashboard"];
for (const ns of namespaces) {
await loadLocale(lng, ns);
}
await i18n.changeLanguage(lng);
}
i18next по умолчанию хранит загруженные ресурсы в памяти:
i18n.hasResourceBundle("ru", "dashboard");
Проверка позволяет избежать повторных загрузок:
if (!i18n.hasResourceBundle(lng, ns)) {
await loadLocale(lng, ns);
}
Для HTTP-backend кэширование может усиливаться через HTTP cache headers:
Cache-Control: public, max-age=31536000, immutable
При отсутствии файла перевода или сетевой ошибке используется fallback language:
i18n.init({
fallbackLng: "en",
saveMissing: false
});
Обработка отсутствующих ресурсов:
i18n.on("failedLoading", (lng, ns, msg) => {
console.error(`Ошибка загрузки ${lng}/${ns}: ${msg}`);
});
В SSR-окружениях динамическая загрузка требует предварительного наполнения ресурсов:
await i18n.init({
lng: "ru",
ns: ["common", "dashboard"],
preload: ["ru"]
});
При гидрации клиент должен получить уже загруженные ресурсы:
i18n.addResourceBundle("ru", "common", serverState.common);
Эффективная схема динамического импорта переводов обычно комбинирует:
На практике часто используется гибридная модель:
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
allowMultiLoading: true
}
с дополнительным предзагрузчиком:
i18n.loadLanguages(["en", "ru"]);