FormatJS Intl полифил

Intl API в JavaScript представляет собой стандартный набор инструментов для локализации: форматирования чисел, дат, времени, списков, сообщений и работы с языковыми особенностями. В современных браузерах и средах выполнения он реализован нативно через ICU (International Components for Unicode), однако исторически поддержка была неполной, а поведение — фрагментированным. Это привело к появлению полифилов, среди которых ключевую роль занимает экосистема FormatJS FormatJS.

Стандарт Intl включает набор конструкторов:

  • Intl.DateTimeFormat
  • Intl.NumberFormat
  • Intl.PluralRules
  • Intl.RelativeTimeFormat
  • Intl.ListFormat
  • Intl.Segmenter

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

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

  • устаревшие браузеры без полной реализации Intl;
  • Node.js версии, собранные без полного ICU (small-icu);
  • различия в версиях ICU между окружениями;
  • отсутствие новых API (например, RelativeTimeFormat или ListFormat) в старых runtime.

Полифил решает задачу унификации поведения: один и тот же код работает одинаково в разных средах.


FormatJS Intl полифил как экосистема

FormatJS FormatJS представляет собой набор модульных пакетов, реализующих отдельные части Intl API. Вместо монолитного полифила используется композиционная модель: подключаются только необходимые части.

Официальная документация и исходные материалы доступны на FormatJS documentation.

Ключевые пакеты:

  • @formatjs/intl-numberformat
  • @formatjs/intl-datetimeformat
  • @formatjs/intl-pluralrules
  • @formatjs/intl-relativetimeformat
  • @formatjs/intl-listformat
  • @formatjs/intl-segmenter

Каждый пакет повторяет интерфейс соответствующего стандарта Intl.*.


Принцип работы полифила

Механика работы строится вокруг подмены глобального объекта Intl или его отдельных конструкторов.

Базовый сценарий:

  1. Проверяется наличие нативной реализации:

    if (!Intl.NumberFormat) {
      // подключение полифила
    }
  2. При отсутствии API подключается реализация из @formatjs/*.

  3. Данные локалей подгружаются отдельно (часто в виде JSON или встроенных таблиц).

  4. Глобальный Intl дополняется или переопределяется.


Установка и базовая интеграция

Модульный подход позволяет подключать только нужные компоненты.

Пример установки:

npm install @formatjs/intl-numberformat

Инициализация:

import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/ru';

После подключения код, использующий Intl.NumberFormat, начинает работать одинаково во всех окружениях.


Полифил NumberFormat

Intl.NumberFormat отвечает за форматирование чисел, валют и процентов.

Пример использования:

const formatter = new Intl.NumberFormat('ru-RU', {
  style: 'currency',
  currency: 'KZT'
});

formatter.format(123456.78);

Полифил реализует:

  • локализацию разделителей (, vs .);
  • правила округления;
  • формат валют;
  • компактные формы (1K, 1M);
  • scientific notation (частично, в зависимости от реализации).

Полифил DateTimeFormat

Intl.DateTimeFormat — один из наиболее сложных компонентов.

const dt = new Intl.DateTimeFormat('ru-RU', {
  dateStyle: 'full',
  timeStyle: 'short'
});

dt.format(new Date());

Реализация FormatJS:

  • поддержка календарей;
  • локализация месяцев и дней недели;
  • форматирование времени суток;
  • поддержка часовых поясов (частично зависит от ICU данных).

PluralRules и языковая морфология

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

const pr = new Intl.PluralRules('ru-RU');

pr.select(1); // one
pr.select(2); // few
pr.select(5); // many

Полифил реализует:

  • категории множественного числа;
  • правила CLDR;
  • сопоставление чисел и грамматических форм.

RelativeTimeFormat

Используется для выражений типа «2 дня назад», «через 5 минут».

const rtf = new Intl.RelativeTimeFormat('ru', { numeric: 'auto' });

rtf.format(-2, 'day');

Особенности реализации:

  • языковые шаблоны для разных временных единиц;
  • поддержка автоформ («вчера», «завтра»);
  • правила согласования.

ListFormat

Intl.ListFormat отвечает за форматирование списков:

const lf = new Intl.ListFormat('ru', {
  style: 'long',
  type: 'conjunction'
});

lf.format(['яблоки', 'груши', 'сливы']);

Результат зависит от локали:

  • запятые;
  • союзы;
  • правила последнего элемента списка.

Segmenter и работа с текстом

Intl.Segmenter используется для разбиения текста на:

  • слова;
  • предложения;
  • графемы.
const segmenter = new Intl.Segmenter('ru', { granularity: 'word' });

[...segmenter.segment('Пример текста')];

Полифил реализует алгоритмы Unicode Text Segmentation.


Подключение в сборщиках (Webpack, Vite)

В современных сборках важна оптимизация размера бандла.

Пример Webpack:

module.exports = {
  resolve: {
    alias: {
      '@formatjs/intl-relativetimeformat': '@formatjs/intl-relativetimeformat/polyfill'
    }
  }
};

Подходы оптимизации:

  • подключение только нужных API;
  • загрузка локалей по требованию;
  • code splitting по языкам;
  • динамический импорт:
await import('@formatjs/intl-datetimeformat/locale-data/ru');

Node.js и ICU зависимости

Node.js может работать в разных режимах ICU:

  • full-icu
  • small-icu
  • system-icu

В случае small-icu часть локалей отсутствует, что приводит к неполной локализации.

Полифил FormatJS компенсирует это:

  • поставляет собственные CLDR данные;
  • обеспечивает консистентность между окружениями;
  • устраняет различия между CI, production и development средами.

Стратегии загрузки локалей

Локали являются отдельным слоем данных.

Подходы:

  1. Явная загрузка:
import '@formatjs/intl-numberformat/locale-data/ru';
  1. Lazy loading:
async function loadLocale(locale) {
  await import(`@formatjs/intl-numberformat/locale-data/${locale}`);
}
  1. Предзагрузка набора локалей:
import '@formatjs/intl-numberformat/locale-data/en';
import '@formatjs/intl-numberformat/locale-data/ru';
import '@formatjs/intl-numberformat/locale-data/de';

Совместимость и fallback-логика

Типичная стратегия работы:

  • использовать нативный Intl, если он полностью поддерживает нужные API;
  • использовать полифил только для недостающих функций;
  • избегать полного переопределения глобального объекта без необходимости.

Проверки:

const hasRtf = typeof Intl.RelativeTimeFormat !== 'undefined';

Производительность и размер

Полифилы Intl могут быть тяжёлыми, так как включают:

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

Оптимизация:

  • tree-shaking отдельных пакетов;
  • подключение только используемых локалей;
  • избегание глобального импорта @formatjs/intl.

Отличия полифила от нативного Intl

Нативная реализация:

  • использует ICU внутри движка;
  • быстрее;
  • обновляется вместе с браузером;
  • глубоко интегрирована в runtime.

Полифил FormatJS:

  • реализован на JavaScript;
  • использует внешние данные CLDR;
  • более предсказуем в кросс-окружениях;
  • позволяет использовать новые API до их появления в браузерах.

Типовые ошибки интеграции

  • смешивание нативных и полифил-реализаций без контроля;
  • отсутствие загрузки локалей;
  • двойное подключение пакетов;
  • отсутствие согласованной стратегии ICU;
  • использование устаревших форматов locales (en_US вместо en-US).

Использование в многоязычных приложениях

В приложениях с динамическим языком интерфейса:

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

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

  • слой i18n (переводы);
  • слой formatting (Intl);
  • слой данных локалей;
  • runtime переключение языка.

Модульность и современный подход FormatJS

Современная версия FormatJS ориентирована на:

  • ESM-модули;
  • минимальные бандлы;
  • явные импорты;
  • отсутствие глобальных побочных эффектов;
  • совместимость с tree-shaking.

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