Динамическая загрузка namespace

Работа с i18next в крупных приложениях почти всегда приводит к разделению переводов на namespaces. Это позволяет разбивать локализационные файлы по функциональным областям: страницы, модули, компоненты. Однако при росте приложения становится критичным не загружать все переводы сразу, а подгружать их по мере необходимости. Именно это и составляет основу динамической загрузки namespace.


Архитектурная модель namespaces

Namespaces в i18next представляют собой логическое разделение переводов:

  • common — общие строки интерфейса
  • auth — авторизация и регистрация
  • profile — профиль пользователя
  • dashboard — панель управления

Каждый namespace соответствует отдельному файлу или источнику данных.

Структура ресурсов:

{
  "common": {
    "save": "Сохранить",
    "cancel": "Отмена"
  },
  "auth": {
    "login": "Вход",
    "logout": "Выход"
  }
}

При статической конфигурации все namespaces загружаются сразу, что увеличивает начальный вес приложения.


Принцип динамической загрузки

Динамическая загрузка namespaces заключается в том, что перевод подгружается только при первом обращении к нему.

Ключевые цели:

  • уменьшение размера initial bundle
  • ускорение времени первого рендера
  • разделение ответственности между модулями
  • ленивое подключение локализации

Базовая конфигурация с backend loader

Чаще всего используется i18next-http-backend, который позволяет загружать JSON-файлы по HTTP.

import i18n from 'i18next';
import HttpBackend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';

i18n
  .use(HttpBackend)
  .use(initReactI18next)
  .init({
    lng: 'ru',
    fallbackLng: 'en',
    ns: ['common'],
    defaultNS: 'common',
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    },
    partialBundledLanguages: true,
    react: {
      useSuspense: true
    }
  });

В этой конфигурации важны два момента:

  • ns задаёт начально загруженные namespaces
  • остальные namespaces могут быть загружены динамически

Добавление namespace в рантайме

Основной механизм динамической загрузки — метод loadNamespaces.

i18n.loadNamespaces('auth').then(() => {
  console.log('namespace auth загружен');
});

После загрузки можно безопасно использовать ключи:

i18n.t('auth:login');

Если namespace ещё не загружен, i18next инициирует загрузку через backend.


Использование нескольких namespaces

В реальных приложениях часто требуется несколько namespaces на одном экране:

i18n.loadNamespaces(['common', 'profile']).then(() => {
  console.log('переводы загружены');
});

И использование:

i18n.t('common:save');
i18n.t('profile:username');

Такой подход позволяет изолировать перевод каждого домена.


Lazy loading на уровне компонентов

В связке с React применяется подход загрузки namespace при монтировании компонента.

import { useTranslation } from 'react-i18next';
import { useEffect } from 'react';

function ProfilePage() {
  const { t, i18n } = useTranslation('profile');

  useEffect(() => {
    i18n.loadNamespaces('profile');
  }, [i18n]);

  return <div>{t('title')}</div>;
}

Здесь важно, что useTranslation('profile') уже определяет namespace, но загрузка может быть отложена до момента использования.


Suspense и асинхронная загрузка

При включённом useSuspense: true загрузка namespace становится синхронной с точки зрения UI.

const ProfilePage = React.lazy(() => import('./ProfilePage'));

И внутри i18next:

react: {
  useSuspense: true
}

Если namespace не загружен, React приостанавливает рендер до завершения загрузки ресурсов.


Ручное управление ресурсами

Помимо HTTP backend, можно управлять namespaces вручную:

i18n.addResourceBundle(
  'ru',
  'dashboard',
  {
    title: 'Панель управления',
    stats: 'Статистика'
  },
  true,
  true
);

Параметры:

  • первый true — deep merge
  • второй true — перезапись существующих ключей

Этот подход используется при динамической генерации переводов или получении их с API.


Проверка загрузки namespace

Для контроля состояния используется:

i18n.hasLoadedNamespace('auth');

Возвращает true или false.

Также можно подписаться на событие загрузки:

i18n.on('loaded', (loaded) => {
  console.log('загружено:', loaded);
});

Fallback поведение при отсутствии namespace

Если namespace не найден, поведение зависит от конфигурации:

{
  fallbackLng: 'en',
  saveMissing: true
}

При отсутствии ключа возможны сценарии:

  • возврат ключа
  • fallback на язык
  • отправка missing keys в backend

Оптимизация загрузки namespaces

Группировка по маршрутам

Каждый route соответствует набору namespaces:

const routeNamespaces = {
  '/profile': ['profile', 'common'],
  '/dashboard': ['dashboard', 'common']
};

Перед переходом:

i18n.loadNamespaces(routeNamespaces[path]);

Предзагрузка критических namespaces

Некоторые namespaces загружаются сразу:

ns: ['common', 'auth']

Остальные — лениво.


Кэширование на уровне backend

При использовании HTTP backend файлы кешируются браузером:

/locales/ru/auth.json
/locales/ru/profile.json

Важно правильно настроить заголовки:

  • Cache-Control
  • ETag
  • max-age

Динамическая загрузка в SSR

В серверном рендеринге namespaces должны быть загружены до рендера HTML:

await i18n.loadNamespaces(['common', 'profile']);

После этого выполняется:

i18n.init({
  lng: 'ru'
});

Это предотвращает гидрационные расхождения.


Типичные ошибки при динамической загрузке

Использование t до загрузки namespace

t('profile:title') // может вернуть ключ

Причина — namespace ещё не загружен.


Отсутствие loadPath

Без backend.loadPath динамическая загрузка невозможна:

backend: {
  loadPath: '/locales/{{lng}}/{{ns}}.json'
}

Пересечение namespaces

Если один ключ существует в нескольких namespaces, приоритет определяется порядком загрузки и defaultNS.


Стратегия масштабирования

При росте приложения структура namespaces обычно эволюционирует:

  • feature-based namespaces (auth, billing, settings)
  • component-based namespaces (button, modal)
  • hybrid подход

Feature-based модель чаще предпочтительна для динамической загрузки, так как соответствует маршрутизации и lazy loading.


Поведение при параллельной загрузке

i18next предотвращает дублирующие запросы:

  • если namespace уже загружается, новый запрос не создаётся
  • если загружен — используется кэш

Это особенно важно при одновременном рендере нескольких компонентов.


Интеграция с code splitting

При использовании dynamic import:

const loadProfile = async () => {
  await i18n.loadNamespaces('profile');
  return import('./ProfilePage');
};

Это позволяет синхронизировать загрузку кода и переводов.


Наблюдение за состоянием загрузки

Можно построить слой мониторинга:

i18n.on('failedLoading', (lng, ns, msg) => {
  console.error(lng, ns, msg);
});

Используется для отслеживания проблем CDN или отсутствующих файлов.


Контроль размера namespace

Практика разделения переводов влияет на производительность:

  • маленькие namespaces быстрее загружаются
  • крупные JSON-файлы увеличивают latency
  • чрезмерная дробность увеличивает количество запросов

Баланс достигается группировкой по функциональным зонам, а не по отдельным компонентам.


Поведение при смене языка

При смене языка все namespaces пересобираются:

await i18n.changeLanguage('en');

Если namespaces уже были загружены для нового языка, повторная загрузка не происходит.