Дебаг отсутствующих переводов

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

Поведение определяется несколькими уровнями: runtime-библиотекой (react-intl или @formatjs/intl), конфигурацией провайдера и инструментами извлечения сообщений. Отсутствие единого контроля на всех уровнях приводит к появлению «тихих» дефектов, которые проявляются только в продакшене или при смене локали.


Механизм формирования ключей и источники потерь

Сообщения в FormatJS идентифицируются через id. Типичная структура:

{
  id: "user.profile.title",
  defaultMessage: "Profile"
}

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

  • отсутствует запись в JSON-файле локали
  • не выполнен экспорт сообщений из исходного кода
  • изменён id, но старые файлы локализации не синхронизированы
  • часть сообщений генерируется динамически и не попадает в extraction pipeline

Особую сложность создают динамические идентификаторы:

intl.formatMessage({ id: `error.${code}` })

Такие конструкции часто выпадают из статического анализа, что приводит к неполным каталогам переводов.


Поведение react-intl при отсутствии перевода

В react-intl поведение зависит от конфигурации IntlProvider.

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

Если указан defaultMessage, он используется как fallback:

intl.formatMessage({
  id: "dashboard.title",
  defaultMessage: "Dashboard"
})

При отсутствии перевода возвращается defaultMessage.

2. Отсутствие defaultMessage

Если перевод отсутствует и defaultMessage не задан, поведение зависит от onError:

  • в development — предупреждение в консоль
  • в production — возврат id или пустой строки (в зависимости от конфигурации)

Конфигурация обработки ошибок intl

Ключевым механизмом контроля является onError в IntlProvider:

<IntlProvider
  locale="ru"
  messages={messages}
  onEr ror={(err) => {
    if (err.code === "MISSING_TRANSLATION") {
      logMissingTranslation(err.message);
      return;
    }
    throw err;
  }}
>

На уровне архитектуры этот хук используется для:

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

Стратегии обнаружения отсутствующих переводов

Статический анализ через babel-plugin-formatjs

babel-plugin-formatjs позволяет извлекать сообщения во время сборки:

{
  "plugins": [
    ["formatjs", {
      "idInterpolationPattern": "[sha512:contenthash:base64:6]"
    }]
  ]
}

Экстракция выполняется командой:

formatjs extract "src/**/*.ts" --out-file messages.json

Результат используется как источник истины для всех локалей.

Критически важный аспект — синхронизация:

  • en.json считается базовым
  • все остальные локали проверяются на полноту относительно него

Проверка полноты переводов на CI

Типовая проверка включает сравнение ключей:

const base = require("./en.json");
const ru = require("./ru.json");

const missing = Object.keys(base).filter(
  (key) => !(key in ru)
);

if (missing.length > 0) {
  console.error("Missing translations:", missing);
  process.exit(1);
}

Такая проверка предотвращает попадание неполных локалей в production.


Runtime-детектирование отсутствующих ключей

В дополнение к CI применяется runtime-логирование. В react-intl возможно перехватывать форматирование через обёртку:

function safeFormatMessage(intl, descriptor) {
  const message = intl.messages?.[descriptor.id];

  if (!message) {
    reportMissing(descriptor.id);
  }

  return intl.formatMessage(descriptor);
}

Этот слой полезен для выявления:

  • редко используемых экранов
  • языков с низким покрытием
  • динамически формируемых ключей

Типизация сообщений и предотвращение ошибок

TypeScript позволяет снизить количество отсутствующих переводов через строгие типы:

type MessageIds =
  | "dashboard.title"
  | "user.profile.title";

const messages: Record<MessageIds, string> = {
  "dashboard.title": "Dashboard",
  "user.profile.title": "Profile"
};

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


Инструменты проверки экосистемы FormatJS

eslint-plugin-formatjs

Линтер обеспечивает контроль использования сообщений:

Основные правила:

  • no-missing-message-id
  • enforce-default-message
  • no-id

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

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/enforce-default-message": "error",
    "formatjs/no-missing-message-id": "error"
  }
}

i18n-аналитика и мониторинг пропусков

В production-системах отсутствующие переводы часто фиксируются как события телеметрии:

  • идентификатор сообщения
  • локаль
  • путь интерфейса
  • версия приложения

Пример структуры события:

{
  "type": "missing_translation",
  "id": "settings.notifications.title",
  "locale": "ru",
  "route": "/settings"
}

Агрегация таких данных позволяет выявлять:

  • забытые экраны
  • недопереведённые фичи
  • деградацию локалей после релиза

Динамические сообщения и проблемы статического анализа

Наиболее сложный класс ошибок связан с динамическими ключами:

const id = `validation.${field}.${rule}`;
intl.formatMessage({ id });

Такие конструкции не могут быть корректно извлечены babel-plugin-formatjs, что приводит к:

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

Решения:

  • ограничение набора возможных ключей через enum
  • явное перечисление сообщений
  • генерация типов на основе extracted messages

Генерация справочника сообщений

FormatJS extraction pipeline позволяет формировать единый каталог:

formatjs compile messages.json --out-file compiled.json

Результат используется как runtime-источник:

  • ускоренный доступ к переводам
  • валидация структуры ICU сообщений
  • контроль целостности локалей

ICU-синтаксис и его влияние на ошибки перевода

FormatJS использует ICU MessageFormat:

{
  id: "cart.items",
  defaultMessage: "There are {count, plural, one {# item} other {# items}}"
}

Ошибки отсутствующих переводов часто сопровождаются ошибками ICU:

  • некорректные plural rules
  • отсутствие переменных
  • несовместимость локали и формата

При отсутствии перевода ICU-строка может не интерпретироваться, если fallback не предусмотрен.


Стратегии устойчивости интерфейса

Для предотвращения деградации UI при отсутствии переводов используются подходы:

fallback chain

  • локаль пользователя
  • базовая локаль (en)
  • defaultMessage
  • id

safe rendering layer

const text = message ?? `[${id}]`;

Такой подход предотвращает «пустые» интерфейсы.


Контроль изменений ключей

Переименование id является одной из основных причин потери переводов. Для управления используется:

  • стабильная схема именования (domain.entity.property)
  • автоматические refactor-инструменты
  • запрет ручного изменения ключей без миграции

Связка FormatJS и CI/CD пайплайна

Полный контроль достигается при интеграции:

  • extraction на этапе build
  • проверка полноты в CI
  • линтинг сообщений
  • runtime-логирование отсутствий
  • мониторинг в продакшене

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