Стратегии разделения переводов

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

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


Разделение по namespace (основной механизм i18next)

Ключевая стратегия в i18next — использование namespace (пространств имён).

Суть подхода

Переводы группируются в логические блоки:

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

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

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

Пример структуры JSON

auth.json:

{
  "login": "Вход",
  "logout": "Выход",
  "emailPlaceholder": "Введите email",
  "passwordPlaceholder": "Введите пароль"
}

common.json:

{
  "save": "Сохранить",
  "cancel": "Отмена",
  "loading": "Загрузка..."
}

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

i18next.t('login', { ns: 'auth' });
i18next.t('save', { ns: 'common' });

Или при настройке fallback namespace:

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

Преимущество подхода

  • снижение размера загружаемых переводов
  • параллельная работа команд над разными модулями
  • уменьшение конфликтов в Git
  • возможность ленивой загрузки

Функциональное разделение (feature-based)

В современных фронтенд-архитектурах чаще используется разбиение по фичам, а не по типам строк.

Идея

Каждая бизнес-функция приложения получает собственный набор переводов.

locales/
  ru/
    feature-auth.json
    feature-cart.json
    feature-checkout.json

Пример

feature-cart.json:

{
  "title": "Корзина",
  "empty": "Корзина пуста",
  "removeItem": "Удалить товар",
  "total": "Итого"
}

Подключение namespace

i18next.init({
  ns: ['feature-auth', 'feature-cart', 'feature-checkout'],
  defaultNS: 'feature-auth'
});

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

i18next.t('title', { ns: 'feature-cart' });

Особенность

Функциональное разделение лучше соответствует компонентной архитектуре:

  • React/Vue компоненты могут сопровождаться собственными переводами
  • легко переносить фичи между проектами
  • упрощается code-splitting

Разделение по слоям интерфейса

Подход применяется в более крупных системах, где важна унификация терминов.

Слои:

  • UI слой (кнопки, формы, элементы управления)
  • Domain слой (бизнес-термины)
  • System слой (ошибки, логирование)

Пример структуры

locales/
  ru/
    ui.json
    domain.json
    system.json

Пример ui.json

{
  "button.save": "Сохранить",
  "button.delete": "Удалить",
  "input.search": "Поиск"
}

Пример domain.json

{
  "order.status.pending": "В обработке",
  "order.status.completed": "Завершён",
  "payment.method.card": "Банковская карта"
}

Особенности

  • единообразие терминологии
  • централизованный контроль UI-строк
  • удобство для продуктовых редакторов

Недостаток — усложнение структуры для небольших приложений.


Ленивое подключение переводов (lazy loading)

Разделение переводов становится особенно эффективным при использовании динамической загрузки.

Принцип

Переводы загружаются только при обращении к маршруту или компоненту.

Пример конфигурации backend

import Backend from 'i18next-http-backend';

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

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

i18next.loadNamespaces('profile');

Или в React:

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

Архитектурный эффект

  • уменьшение initial bundle size
  • ускорение первого рендера
  • масштабируемость на десятки и сотни экранов

Разделение по языкам и fallback-стратегия

Структура локалей почти всегда включает уровень языка:

locales/
  en/
  ru/
  de/

Fallback цепочка

i18next поддерживает fallback:

i18next.init({
  fallbackLng: 'en'
});

Практическая стратегия

  • основной язык (ru) — полный набор переводов
  • fallback (en) — страховочный слой
  • дополнительные языки — частичные переводы

Важный принцип

Структура ключей должна быть идентичной во всех языках.

Иначе fallback теряет смысл.


Разделение по контексту и доменам данных

В сложных системах один и тот же термин может иметь разные значения.

Пример проблемы

Слово “status”:

  • статус заказа
  • статус пользователя
  • статус задачи

Решение: контекстные ключи

{
  "order.status": "Статус заказа",
  "user.status": "Статус пользователя",
  "task.status": "Статус задачи"
}

Альтернативный подход — вложенные ключи

{
  "order": {
    "status": "Статус заказа"
  },
  "user": {
    "status": "Статус пользователя"
  }
}

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

i18next.t('order.status');
i18next.t('user.status');

Преимущество

  • устранение неоднозначности
  • удобство автодополнения
  • читаемость структуры

Разделение с учётом интерполяции и шаблонов

Переводы часто содержат динамические значения.

Пример

{
  "welcome": "Привет, {{name}}",
  "cart.items": "Товаров: {{count}}"
}

Стратегия разделения

Интерполяционные строки стоит выделять в отдельные группы:

  • messages — текстовые сообщения
  • labels — подписи
  • templates — сложные шаблоны

Пример структуры

locales/
  ru/
    messages.json
    labels.json
    templates.json

Причина

Шаблоны обычно:

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

Разделение для больших монорепозиториев

В монорепо переводами часто пользуются несколько приложений.

Подход

packages/
  app-web/
    locales/
  app-admin/
    locales/
  shared/
    locales/

shared слой

Содержит:

  • общие кнопки
  • системные сообщения
  • базовые термины

Проблема дублирования

Без shared слоя возникает:

  • копирование переводов
  • расхождение терминов
  • сложность синхронизации

Версионирование переводов

При активной разработке структура ключей меняется.

Подход через версионные namespaces

locales/
  ru/
    v1/
    v2/

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

i18next.init({
  ns: ['v2/common', 'v2/auth']
});

Когда необходимо

  • редизайн интерфейса
  • изменение продуктовой логики
  • миграция ключей

Комбинированные стратегии

На практике почти всегда используется гибрид:

  • namespace (основа)
  • feature-based (архитектура)
  • lazy loading (оптимизация)
  • domain separation (семантика)

Пример итоговой структуры

locales/
  ru/
    common.json
    errors.json
    features/
      auth.json
      cart.json
      checkout.json
    domains/
      order.json
      user.json

Практические критерии выбора стратегии

Размер проекта

  • малый: common + feature
  • средний: namespace + lazy loading
  • крупный: domain + feature + versioning

Количество команд

  • одна команда: простая структура
  • несколько команд: строгие namespaces

Частота изменений

  • стабильный UI: минимальное дробление
  • активная разработка: feature-based

Ошибки при разделении переводов

Слишком мелкие файлы

Приводит к:

  • перегрузке структуры
  • сложной навигации

Смешение уровней абстракции

Когда в одном файле:

  • UI строки
  • бизнес-термины
  • системные ошибки

Отсутствие единых ключей

Разные языки содержат:

  • разные структуры
  • несовместимый fallback

Игнорирование lazy loading

Даже при правильной структуре весь перевод может попадать в bundle.


Оптимизация структуры ключей

Рекомендуемый формат

entity.action.context

Примеры:

{
  "cart.remove.item": "Удалить товар",
  "auth.login.submit": "Войти",
  "order.status.pending": "Ожидает обработки"
}

Преимущество

  • предсказуемость
  • масштабируемость
  • отсутствие конфликтов имён