Логирование проблем локализации

Проблемы локализации в приложениях часто проявляются не как явные ошибки, а как «тихие» расхождения в отображении данных: разные форматы дат, неожиданное округление чисел, некорректные валютные символы, смещённые временные зоны. Библиотека Intl API в JavaScript предоставляет инструменты для стандартизированной работы с локалями, но без системного логирования её поведения диагностика становится затруднительной.

Любой объект Intl после инициализации может возвращать информацию о том, какие параметры были реально применены. Это критично, поскольку запрошенная локаль не всегда совпадает с фактически использованной.

const formatter = new Intl.NumberFormat('ru-KZ');

console.log(formatter.resolvedOptions());

Результат содержит ключевые поля:

  • locale — фактически выбранная локаль
  • numberingSystem — система счисления
  • style и currency (если применимо)

Логирование resolvedOptions() позволяет фиксировать расхождения между ожидаемыми и реальными настройками:

const formatter = new Intl.DateTimeFormat('fr-FR', {
  timeZone: 'Asia/Almaty'
});

const options = formatter.resolvedOptions();

console.log(JSON.stringify({
  requestedLocale: 'fr-FR',
  resolvedLocale: options.locale,
  timeZone: options.timeZone
}));

Такой подход позволяет обнаруживать случаи fallback-локалей, когда запрошенная локаль недоступна в среде выполнения.

Валидация и нормализация локалей

Перед использованием локали полезно фиксировать её каноническую форму. Это снижает риск несоответствий между сервером и клиентом.

const locales = Intl.getCanonicalLocales(['ru-kz', 'RU-KZ', 'ru']);

console.log(locales);

Функция Intl.getCanonicalLocales помогает:

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

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

Логирование числового форматирования

Ошибки в числовом представлении часто связаны с разделителями, округлением и валютами. Intl.NumberFormat предоставляет способ детализировать результат через formatToParts.

const formatter = new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: 'EUR'
});

const parts = formatter.formatToParts(123456.78);

console.log(parts);

Для логирования важно не только итоговое значение, но и структура:

function logNumberFormatting(value, locale, options) {
  const formatter = new Intl.NumberFormat(locale, options);

  console.log(JSON.stringify({
    input: value,
    locale,
    resolved: formatter.resolvedOptions(),
    formatted: formatter.format(value),
    parts: formatter.formatToParts(value)
  }));
}

Разбор formatToParts позволяет выявить:

  • неправильные разделители групп
  • неожиданные символы валюты
  • отличия между окружениями (Node.js vs браузер)

Диагностика форматирования дат и времени

Intl.DateTimeFormat чувствителен к часовым поясам, системным настройкам и политике окружения. Ошибки часто проявляются как «сдвиги» даты или времени.

const dtf = new Intl.DateTimeFormat('en-GB', {
  timeZone: 'UTC',
  dateStyle: 'full',
  timeStyle: 'short'
});

console.log(dtf.format(new Date()));
console.log(dtf.resolvedOptions());

При логировании важно фиксировать:

  • исходную временную метку (timestamp)
  • применённую временную зону
  • итоговую локаль
function logDateFormatting(date, locale, options) {
  const formatter = new Intl.DateTimeFormat(locale, options);

  console.log(JSON.stringify({
    inputTimestamp: date.getTime(),
    iso: date.toISOString(),
    localeRequested: locale,
    localeResolved: formatter.resolvedOptions().locale,
    timeZone: formatter.resolvedOptions().timeZone,
    output: formatter.format(date)
  }));
}

Такой лог помогает выявлять:

  • расхождение между UTC и локальным временем
  • неправильные timezone-конфигурации сервера
  • различия ICU-библиотек в окружениях

Проблемы fallback-локалей

Intl API использует цепочки fallback, если конкретная локаль недоступна. Это может приводить к неожиданным результатам, особенно в минималистичных средах выполнения.

const f = new Intl.NumberFormat('xx-YY');

console.log(f.resolvedOptions().locale);

Если локаль недоступна, система может заменить её на базовую (например, en-US). Без логирования это поведение остаётся незаметным.

Практика фиксации:

function createFormatter(locale, options) {
  const formatter = new Intl.NumberFormat(locale, options);

  const resolved = formatter.resolvedOptions().locale;

  if (resolved !== locale) {
    console.warn(JSON.stringify({
      message: 'Locale fallback occurred',
      requested: locale,
      resolved
    }));
  }

  return formatter;
}

Сравнение локалей через Collator

Intl.Collator часто используется для сортировки, но различия в локалях могут радикально менять порядок элементов.

const collator = new Intl.Collator('tr', { sensitivity: 'base' });

console.log(['i', 'I', 'İ', 'ı'].sort(collator.compare));

Логирование важно для:

  • диагностики пользовательской сортировки
  • выявления несогласованности UI между регионами
  • контроля стабильности сортировок
function logSorting(locale, array) {
  const collator = new Intl.Collator(locale);

  console.log(JSON.stringify({
    locale,
    input: array,
    output: [...array].sort(collator.compare),
    resolved: collator.resolvedOptions().locale
  }));
}

Различия окружений и ICU

Одной из скрытых проблем является различие ICU-данных между средами:

  • Node.js с full-ICU
  • Node.js с minimal-ICU
  • браузеры разных версий

Это приводит к тому, что одна и та же локаль может давать разные результаты форматирования.

Логирование должно включать идентификацию среды:

function logIntlEnvironment() {
  const nf = new Intl.NumberFormat('ru-RU');

  console.log(JSON.stringify({
    engine: typeof process !== 'undefined' ? 'node' : 'browser',
    locale: nf.resolvedOptions().locale,
    hasIntl: typeof Intl !== 'undefined'
  }));
}

Структурированное логирование ошибок локализации

Эффективный подход — единый формат логов для всех Intl-операций.

function intlLog(event, payload) {
  console.log(JSON.stringify({
    event,
    timestamp: Date.now(),
    ...payload
  }));
}

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

const formatter = new Intl.NumberFormat('ru-KZ');

intlLog('number_format', {
  requested: 'ru-KZ',
  resolved: formatter.resolvedOptions().locale,
  sample: formatter.format(12345.67)
});

Такой формат позволяет:

  • агрегировать ошибки локализации
  • строить метрики fallback-локалей
  • отслеживать деградации UI

Частые ошибки при логировании Intl

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

console.log(new Intl.DateTimeFormat('ru-RU').format(new Date()));

Такой лог не содержит информации о:

  • фактически выбранной локали
  • временной зоне
  • параметрах форматирования

Другая ошибка — игнорирование formatToParts, из-за чего теряется структура результата и становится невозможным анализ причин визуальных расхождений.

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

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

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

function compareIntlOnServerAndClient(locale) {
  const server = new Intl.NumberFormat(locale).resolvedOptions().locale;

  // имитация клиентского окружения
  const client = new Intl.NumberFormat(locale).resolvedOptions().locale;

  intlLog('ssr_locale_check', {
    server,
    client,
    match: server === client
  });
}

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

Логирование поведения fallback-цепочек

Некоторые среды используют сложные цепочки замены локалей. Это может приводить к неожиданным региональным форматам.

function traceLocaleFallback(locale) {
  const formatter = new Intl.DateTimeFormat(locale);

  const resolved = formatter.resolvedOptions().locale;

  intlLog('locale_fallback_trace', {
    requested: locale,
    resolved
  });
}

Регулярное накопление таких логов позволяет выявлять системные несоответствия конфигурации локалей и ICU-пакетов.