Intl.ListFormat полифилл

Intl.ListFormat polyfill из экосистемы FormatJS обеспечивает воспроизведение стандарта ECMAScript Internationalization API в средах, где нативная реализация отсутствует или работает неполно. Основная цель — унифицировать форматирование списков (перечислений) в разных локалях с учётом грамматических правил языка, а не просто склеивать элементы через запятую или союз.

Стандартный Intl.ListFormat позволяет формировать корректные списки элементов с учётом языка и контекста:

  • различие между «A, B и C» и «A, B или C»
  • адаптация под локаль (en, ru, ja и др.)
  • корректная пунктуация и союзы
  • поддержка различных типов списков (conjunction, disjunction, unit)

Проблема заключается в том, что не все среды исполнения JavaScript имеют полноценную реализацию этого API. Полифилл из FormatJS закрывает этот пробел, предоставляя одинаковое поведение в браузерах и Node.js.

Установка и подключение полифилла

Библиотека поставляется как часть пакета @formatjs/intl-listformat.

npm install @formatjs/intl-listformat

Подключение полифилла выполняется до использования Intl.ListFormat:

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

Для приложений, работающих с несколькими локалями, подключаются соответствующие наборы данных:

import '@formatjs/intl-listformat/locale-data/en';
import '@formatjs/intl-listformat/locale-data/de';
import '@formatjs/intl-listformat/locale-data/ru';

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

Базовое использование

После подключения полифилла API полностью повторяет стандартный Intl.ListFormat:

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

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

Разные типы списков:

const disjunction = new Intl.ListFormat('ru', {
  type: 'disjunction'
});

disjunction.format(['яблоки', 'груши', 'сливы']);
// "яблоки, груши или сливы"
const unit = new Intl.ListFormat('en', {
  type: 'unit'
});

unit.format(['5 ft', '10 in']);
// "5 ft, 10 in"

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

Intl.ListFormat принимает два основных параметра конфигурации: locale и options.

locale

Определяет язык форматирования:

new Intl.ListFormat('ru');
new Intl.ListFormat('en-US');
new Intl.ListFormat('fr');

Если локаль не поддерживается напрямую, активируется fallback-цепочка, определяемая ICU-данными внутри полифилла.

options.style

Определяет степень формальности и формат вывода:

  • long — полный вариант («A, B и C»)
  • short — сокращённый вариант («A, B & C»)
  • narrow — максимально компактный формат
new Intl.ListFormat('en', { style: 'short' });

options.type

Определяет семантику списка:

  • conjunction — логическое «и»
  • disjunction — логическое «или»
  • unit — единицы измерения или однородные элементы

Поведение в разных локалях

Форматирование списков строго зависит от языковых правил.

Русский язык

new Intl.ListFormat('ru', { type: 'conjunction' })
  .format(['HTML', 'CSS', 'JavaScript']);

Результат:

HTML, CSS и JavaScript

Английский язык

new Intl.ListFormat('en', { type: 'conjunction' })
  .format(['HTML', 'CSS', 'JavaScript']);
HTML, CSS, and JavaScript

Немецкий язык

new Intl.ListFormat('de', { type: 'disjunction' })
  .format(['A', 'B', 'C']);
A, B oder C

Внутренний механизм полифилла

FormatJS использует набор CLDR-данных (Unicode Common Locale Data Repository), из которых извлекаются правила построения списков. Эти правила включают:

  • шаблоны соединений (pattern-based formatting)
  • позиции союзов
  • правила пунктуации
  • контекстные формы для разных типов списков

Полифилл фактически эмулирует поведение ICU, применяя заранее подготовленные шаблоны.

Сравнение с ручной реализацией

Простейшая реализация без Intl:

function formatList(arr) {
  return arr.slice(0, -1).join(', ') + ' и ' + arr[arr.length - 1];
}

Проблемы такого подхода:

  • не учитываются разные языки
  • отсутствует поддержка «или»
  • нет гибкости для short/narrow стилей
  • ломается на пустых и одноэлементных списках
  • невозможна локализация без переписывания логики

Intl.ListFormat решает эти проблемы за счёт декларативного API.

Пустые и короткие списки

Поведение стандартизировано:

new Intl.ListFormat('ru').format([]);
// ""

new Intl.ListFormat('ru').format(['A']);
// "A"

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

Использование в React и FormatJS экосистеме

В приложениях, использующих FormatJS как основу интернационализации, Intl.ListFormat часто применяется совместно с:

  • react-intl
  • @formatjs/intl-pluralrules
  • @formatjs/intl-displaynames

Пример в компоненте:

const formatter = new Intl.ListFormat(locale, {
  type: 'conjunction'
});

const text = formatter.format(items);

Важно учитывать, что форматтер лучше создавать вне рендера или мемоизировать, чтобы избежать лишних вычислений.

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

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

  • импорт только нужных локалей
  • tree-shaking
  • разделение кода (code splitting)
import '@formatjs/intl-listformat/locale-data/ru';

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

SSR и Node.js особенности

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

Типичная схема:

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

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

Ограничения полифилла

Несмотря на высокую совместимость, существуют ограничения:

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

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

Практические сценарии применения

Intl.ListFormat особенно полезен в интерфейсах, где списки формируются динамически:

  • отображение выбранных фильтров
  • перечисление ошибок формы
  • вывод тегов и категорий
  • формирование описаний товаров
  • UI уведомлений и логов

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