Множественные namespace в проекте

Архитектура переводов и роль namespace

В системах интернационализации ключевой проблемой становится масштабирование словаря переводов по мере роста проекта. При увеличении числа экранов, модулей и доменных областей приложения единый файл переводов начинает превращаться в узкое место: он усложняет поддержку, замедляет загрузку и увеличивает риск конфликтов ключей.

В i18next решение этой проблемы строится на концепции namespace (пространств имён) — логического разделения переводов на независимые группы. Каждый namespace представляет собой отдельный набор ключей, относящихся к конкретной части приложения.

Базовая идея заключается в том, что вместо монолитной структуры:

{
  "header.title": "Главная",
  "header.logout": "Выйти",
  "profile.title": "Профиль",
  "profile.edit": "Редактировать"
}

используется разбиение:

  • common.json
  • header.json
  • profile.json

Каждый файл становится самостоятельной единицей загрузки и обновления.


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

Внутренне i18next хранит переводы в виде вложенной структуры:

{
  en: {
    common: {
      welcome: "Welcome"
    },
    header: {
      title: "Home"
    },
    profile: {
      title: "Profile"
    }
  }
}

Здесь:

  • en — язык
  • common, header, profile — namespace

Доступ к ключам осуществляется через указание namespace либо явно, либо через конфигурацию по умолчанию.


Конфигурация нескольких namespace

Подключение namespace начинается с настройки i18next:

i18next.init({
  lng: 'ru',
  fallbackLng: 'en',
  ns: ['common', 'header', 'profile'],
  defaultNS: 'common',
  resources: {
    en: {
      common: {
        welcome: "Welcome"
      },
      header: {
        title: "Home"
      },
      profile: {
        title: "Profile"
      }
    },
    ru: {
      common: {
        welcome: "Добро пожаловать"
      },
      header: {
        title: "Главная"
      },
      profile: {
        title: "Профиль"
      }
    }
  }
});

Ключевые параметры:

  • ns — список доступных namespace
  • defaultNS — пространство имён, используемое по умолчанию
  • resources — локально заданные переводы (часто заменяются загрузчиком)

Обращение к ключам в разных namespace

i18next поддерживает два основных способа доступа к переводам.

Явное указание namespace

i18next.t('header:title');
i18next.t('profile:title');

Здесь используется разделитель : между namespace и ключом.

Использование namespace по умолчанию

i18next.t('welcome');

Если defaultNS = 'common', вызов интерпретируется как:

common.welcome

Динамическое переключение namespace

В реальных приложениях часто требуется переключать активные пространства имён в зависимости от контекста.

i18next.loadNamespaces(['profile', 'settings'], () => {
  console.log(i18next.t('profile:title'));
});

Этот механизм позволяет подгружать переводы лениво, снижая начальный объём загружаемых данных.


Разделение переводов по доменам приложения

Практическая архитектура namespace обычно отражает структуру продукта:

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

Пример структуры файлов:

locales/
  ru/
    common.json
    auth.json
    dashboard.json
  en/
    common.json
    auth.json
    dashboard.json

Каждый файл содержит только свой домен:

// auth.json
{
  "login": "Войти",
  "logout": "Выйти",
  "email": "Электронная почта"
}

Использование с backend-загрузчиком

При подключении HTTP backend namespace становятся ключевым элементом загрузки:

import Backend from 'i18next-http-backend';

i18next
  .use(Backend)
  .init({
    ns: ['common', 'auth', 'dashboard'],
    defaultNS: 'common',
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    }
  });

Здесь:

  • {{lng}} — язык
  • {{ns}} — namespace

При обращении к auth:login выполняется запрос:

/locales/ru/auth.json

Ленивые namespace и оптимизация загрузки

Крупные приложения редко загружают все переводы сразу. Вместо этого применяется стратегия lazy loading:

i18next.loadNamespaces(['dashboard'], () => {
  const title = i18next.t('dashboard:title');
});

Это позволяет:

  • уменьшить размер initial bundle
  • ускорить первую отрисовку
  • разделить переводы по маршрутам

Namespace в React-интеграции

В связке с React используется обёртка:

import { useTranslation } from 'react-i18next';

function Profile() {
  const { t } = useTranslation('profile');

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

Если требуется несколько namespace:

const { t } = useTranslation(['profile', 'common']);

t('profile:title');
t('common:welcome');

Первый элемент массива становится namespace по умолчанию внутри компонента.


Приоритет и fallback между namespace

i18next поддерживает цепочку поиска ключей:

  1. текущий namespace
  2. defaultNS
  3. fallback namespace (если настроен)

Пример:

i18next.init({
  ns: ['page', 'common'],
  defaultNS: 'page',
  fallbackNS: 'common'
});

Если ключ не найден в page, поиск продолжается в common.


Конфликты ключей и изоляция пространства имён

Использование namespace устраняет проблему конфликтов:

Без namespace:

{
  "title": "Dashboard"
}

и

{
  "title": "Profile"
}

привели бы к перезаписи одного значения другим.

С namespace:

dashboard.title
profile.title

ключи изолированы и не пересекаются.


Типизация namespace в TypeScript

При использовании TypeScript namespace можно типизировать для повышения безопасности:

interface Resources {
  common: {
    welcome: string;
  };
  auth: {
    login: string;
  };
}

declare module 'i18next' {
  interface CustomTypeOptions {
    defaultNS: 'common';
    resources: Resources;
  }
}

Это позволяет ловить ошибки ключей на этапе компиляции.


Композиция namespace в сложных приложениях

В крупных системах namespace часто строятся по иерархии:

  • global/common
  • feature/auth
  • feature/profile
  • feature/admin

Иногда применяется нейминг с разделителями:

auth.login
auth.register
profile.settings

Но даже в этом случае физическое разделение файлов остаётся предпочтительным, так как оно упрощает кеширование и сборку.


Асинхронная загрузка и жизненный цикл namespace

При использовании backend-решений namespace проходят несколько стадий:

  1. регистрация namespace в конфигурации
  2. запрос при первом обращении
  3. кэширование в памяти i18next
  4. повторное использование без сети

Поведение можно контролировать:

i18next.on('loaded', (loaded) => {
  console.log(loaded);
});

Использование namespace в модульной архитектуре

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

// module: comments
i18next.addResources('ru', 'comments', {
  add: 'Добавить комментарий',
  delete: 'Удалить'
});

Такой подход обеспечивает:

  • автономность модулей
  • независимую разработку
  • упрощение масштабирования

Объединение namespace при сборке

При использовании сборщиков (Webpack, Vite) namespace могут объединяться в чанки:

auth.chunk.js -> auth.json
dashboard.chunk.js -> dashboard.json

Это позволяет синхронизировать структуру переводов с кодовой базой, делая локализацию частью feature-based архитектуры.