Постепенная миграция больших проектов

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

Первым шагом создаётся минимальная конфигурация i18n-слоя, которая не ломает текущую систему строк. На этом этапе важно зафиксировать:

  • базовый язык (обычно en как fallback или текущая основная локаль продукта);
  • стратегию загрузки переводов (локальные JSON, динамические загрузчики, API);
  • механизм fallback-цепочки;
  • единый экземпляр i18n для всего приложения.

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

Инвентаризация строк и классификация

Перед внедрением i18n в код проводится структурный анализ интерфейса. Все текстовые строки делятся на категории:

  • статические UI-элементы (кнопки, подписи, заголовки);
  • динамические строки (ошибки, уведомления, сообщения API);
  • составные строки с параметрами;
  • строки, зависящие от бизнес-логики.

На этом этапе формируется первичная карта локализации, которая позже превращается в namespaces i18n. Например:

  • common — общие элементы интерфейса;
  • auth — авторизация;
  • dashboard — основной интерфейс;
  • errors — сообщения ошибок.

Такое разбиение позволяет избежать монолитного translation.json и упрощает миграцию по модулям.

Введение ключей без переписывания UI

На раннем этапе миграции код не переписывается радикально. Вместо этого вводится слой ключей, который временно дублирует существующие строки.

Пример переходного состояния:

// было
button.textContent = "Сохранить";

// стало (переходный режим)
button.textContent = i18n.t("common.save");

При этом перевод common.save может возвращать ту же строку “Сохранить”. Это создаёт важное свойство — функциональную эквивалентность старого и нового подхода.

Инкрементальная замена через компоненты

Наиболее устойчивый подход — миграция по компонентам, а не по всему приложению сразу. Выделяются независимые UI-блоки:

  • формы;
  • модальные окна;
  • панели навигации;
  • страницы.

Каждый компонент переводится полностью, включая все строки, связанные с ним. После миграции компонент становится «i18n-осознанным» и больше не использует прямые строковые литералы.

Особое внимание уделяется изоляции: компонент не должен зависеть от глобальных строковых констант.

Оборачивание строк и контроль технического долга

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

  • только через t() или аналогичный метод;
  • запрет на новые строковые литералы в UI-слое (через линтеры);
  • временные ключи-заглушки.

Типичный пример:

const title = i18n.t("dashboard.title", "Dashboard");

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

Управление namespaces и ростом проекта

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

  • разделение по доменам;
  • ленивый импорт переводов;
  • динамическая регистрация namespaces.

Пример конфигурационной логики:

i18n.init({
  fallbackLng: "en",
  ns: ["common"],
  defaultNS: "common",
  backend: {
    loadPath: "/locales/{{lng}}/{{ns}}.json"
  }
});

Позже namespaces подключаются по мере загрузки модулей:

i18n.loadNamespaces("dashboard");

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

Параллельное существование старой системы строк

В крупных проектах неизбежен период, когда старая система строк ещё присутствует. В этот период вводятся правила совместимости:

  • новые строки только через i18n;
  • старые строки не модифицируются без необходимости;
  • запрещается смешивание подходов внутри одного компонента.

Часто используется «адаптерный слой», который временно маппит старые константы на i18n-ключи:

const LEGACY_TEXT = {
  SAVE: i18n.t("common.save"),
  CANCEL: i18n.t("common.cancel")
};

Это позволяет ускорить миграцию без разрушения логики приложения.

Обработка параметризованных строк

Переход к i18n почти всегда выявляет необходимость параметров в строках. Вместо конкатенации вводится интерполяция:

i18n.t("errors.required", { field: "Email" });

Строки вида:

"Поле Email обязательно"

заменяются на шаблон:

"Поле {{field}} обязательно"

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

SSR и серверная синхронизация

В приложениях с серверным рендерингом важным этапом становится синхронизация состояния i18n между сервером и клиентом:

  • сериализация текущего языка;
  • передача загруженных namespaces;
  • гидратация состояния на клиенте.

Это предотвращает «мигание» локалей при загрузке страницы.

Автоматизация извлечения строк

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

  • сканирование AST;
  • поиск строковых литералов;
  • генерация JSON-структур переводов.

На этом этапе формируется первичный набор ключей, который затем уточняется вручную.

Тестирование локализации в переходный период

Особенность постепенной миграции — нестабильное состояние переводов. Для контроля вводятся проверки:

  • отсутствие «голых» строк в UI;
  • наличие fallback-ключей;
  • проверка существования переводов в runtime.

Автоматические тесты часто проверяют:

  • что каждый t() возвращает строку;
  • что нет undefined-значений;
  • что namespaces загружены корректно.

Feature flags для управления миграцией

В больших системах миграция часто управляется через флаги:

  • включение i18n для отдельных модулей;
  • A/B тестирование локализации;
  • постепенное включение языков.

Пример логики:

if (flags.i18nDashboardEnabled) {
  return i18n.t("dashboard.title");
} else {
  return "Dashboard";
}

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

Типичные проблемы переходного периода

При миграции часто возникают системные ошибки архитектуры:

  • дублирование ключей в разных namespaces;
  • потеря контекста в динамических строках;
  • несогласованность fallback-языков;
  • рост технического долга из-за частично переведённых модулей.

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

Постепенное устранение legacy-слоя

Финальная стадия миграции не является мгновенной заменой старой системы. Она выражается в постепенном:

  • удалении констант строк;
  • упрощении компонентов до вызовов t();
  • унификации структуры переводов;
  • выносе всех текстов в централизованные ресурсы.

Архитектура постепенно переходит в состояние, где UI полностью зависит от i18n-слоя, а строковые литералы исчезают из бизнес-кода.