Логирование и дебаг

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

Ключевое разделение:

  • Ошибки парсинга сообщений — некорректный ICU-синтаксис, несоответствие фигурных скобок, неверные селекторы.
  • Ошибки выполнения форматирования — отсутствие данных для плейсхолдеров, неправильные типы значений.
  • Ошибки локализации — отсутствующие ключи сообщений, fallback на defaultMessage.
  • Ошибки сборки — потеря сообщений при extraction, проблемы с AST при использовании @formatjs/cli.

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


Перехват ошибок форматирования через IntlMessageFormat

Базовый механизм FormatJS — класс IntlMessageFormat. Он выбрасывает исключения при некорректных ICU-строках и неконсистентных данных.

Типовой подход к перехвату:

import { IntlMessageFormat } from 'intl-messageformat';

try {
  const msg = new IntlMessageFormat(
    'Hello {name}, you have {count, plural, one {# message} other {# messages}}',
    'en'
  );

  const output = msg.format({ name: 'Alex', count: 2 });
} catch (e) {
  console.error('Ошибка форматирования сообщения:', e);
}

Важный момент: исключения здесь не всегда критические. В ряде случаев FormatJS возвращает fallback-строку или частично отформатированный результат. Поэтому логирование должно различать:

  • синтаксические ошибки ICU
  • ошибки подстановки данных
  • ошибки типов аргументов

Логирование отсутствующих сообщений

В react-intl ключевой источник диагностической информации — отсутствие перевода для id.

Механизм fallback:

  • если id не найден → используется defaultMessage
  • если defaultMessage отсутствует → возвращается id

Чтобы фиксировать такие ситуации, используется onError в IntlProvider.

import { IntlProvider } from 'react-intl';

function errorHandler(err) {
  console.warn('Intl error:', err.message);
}

<IntlProvider locale="en" messages={{}} onEr ror={errorHandler}>
  <App />
</IntlProvider>

Типы ошибок, которые поступают в onError:

  • MISSING_TRANSLATION
  • INVALID_FORMAT
  • MISSING_DATA
  • FORMATTING_ERROR

Логирование этих событий позволяет строить карту «дыр» в локализации, особенно при масштабных проектах с сотнями сообщений.


Поведение defaultMessage как источник диагностических сигналов

defaultMessage часто используется как fallback, но в режиме строгой локализации он становится индикатором проблемы.

Практика логирования:

  • фиксировать использование defaultMessage в production
  • различать dev и prod поведение
  • агрегировать статистику по fallback-использованию
const trackMissing = (id, message) => {
  console.log(`[i18n missing] ${id}: ${message}`);
};

В связке с HOC или hooks (useIntl) можно перехватывать форматирование на уровне обёртки.


ESLint как статический слой диагностики

Пакет eslint-plugin-formatjs позволяет выявлять проблемы до выполнения кода.

Основные проверки:

  • отсутствие id в сообщениях
  • неконсистентные ICU-синтаксисы
  • неиспользуемые ключи
  • динамически формируемые message descriptors

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

module.exports = {
  plugins: ['formatjs'],
  rules: {
    'formatjs/enforce-id': 'error',
    'formatjs/enforce-default-message': 'warn',
    'formatjs/no-missing-icu-plural': 'error'
  }
};

Логически ESLint становится первым уровнем «логирования», хотя работает статически.


CLI-инструменты и трассировка сообщений

@formatjs/cli используется для извлечения сообщений из кода:

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

Диагностически важные сценарии:

  • сообщение не попало в extraction → проблема AST
  • дублирующиеся id
  • неконсистентные описания ICU

При включении verbose-режима CLI выводит информацию о каждом найденном сообщении, что позволяет отслеживать:

  • пропущенные файлы
  • некорректные шаблоны
  • неожиданные структуры JSX

Логирование на уровне react-intl

В react-intl критический инструмент — перехват ошибок через IntlProvider.

Дополнительные стратегии:

Обёртка formatMessage

const intl = useIntl();

function safeFormatMessage(descriptor, values) {
  try {
    return intl.formatMessage(descriptor, values);
  } catch (e) {
    console.error('formatMessage error:', descriptor, e);
    return descriptor.defaultMessage || descriptor.id;
  }
}

Это позволяет фиксировать:

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

Типовые ошибки ICU и их диагностика

ICU MessageFormat имеет строгий синтаксис, и FormatJS чувствителен к следующим случаям:

Некорректные plural-правила

{count, plural, one {# item} other {# items}}

Ошибки возникают при:

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

Несовпадение аргументов

Hello {name}

Если name не передан, логирование фиксирует MISSING_DATA.


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

В крупных приложениях FormatJS логирование интегрируется с внешними системами:

  • Sentry
  • Datadog
  • custom logging pipelines

Общая схема:

  1. перехват через onError
  2. нормализация ошибки
  3. отправка в централизованный логгер
function intlErrorHandler(error) {
  const normalized = {
    message: error.message,
    code: error.code,
    timestamp: Date.now()
  };

  sendToLogger(normalized);
}

Разделение dev и production логики

В development режиме FormatJS должен быть максимально шумным:

  • логирование всех missing keys
  • предупреждения о fallback
  • ошибки ICU как console.error

В production:

  • только агрегированные события
  • подавление повторяющихся ошибок
  • batching логов
const isDev = process.env.NODE_ENV === 'development';

const errorHandler = (err) => {
  if (isDev) {
    console.error(err);
  } else {
    reportError(err);
  }
};

Трассировка сообщений через message descriptors

Message descriptor — центральная структура:

{
  id: 'user.greeting',
  defaultMessage: 'Hello {name}'
}

Логирование на этом уровне позволяет:

  • отслеживать использование конкретных id
  • строить coverage локализации
  • выявлять «мертвые» переводы

Расширенная практика — внедрение middleware:

function trackDescriptor(descriptor) {
  console.log('i18n usage:', descriptor.id);
  return descriptor;
}

Ошибки форматирования rich text

FormatJS поддерживает rich text форматирование:

Hello <b>{name}</b>

Типовые проблемы:

  • незакрытые теги
  • некорректные элементы
  • отсутствие defaultRichTextElements

Логирование таких ошибок особенно важно, поскольку они часто проявляются только в runtime.


Поведенческая модель fallback и её влияние на дебаг

Fallback-логика FormatJS влияет на диагностику:

  • silent fallback → риск скрытых ошибок
  • defaultMessage fallback → маскировка отсутствующих переводов
  • id fallback → потеря UX-логики локализации

Поэтому логирование должно фиксировать сам факт fallback-ветки, а не только ошибку.


Инструментирование через обёртки IntlProvider

Расширенная стратегия — замена провайдера на кастомный слой:

function LoggingIntlProvider(props) {
  return (
    <IntlProvider
      {...props}
      onEr ror={(err) => {
        console.warn('[i18n]', err);
      }}
    />
  );
}

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


Метрики качества локализации

Логирование в FormatJS часто превращается в систему метрик:

  • процент missing translations
  • частота fallback usage
  • количество ICU parsing errors
  • распределение ошибок по языкам

Такие метрики используются для оценки зрелости локализации и стабильности i18n слоя в приложении.