Работа с перечнями значений в интерфейсах требует не только простой
конкатенации строк, но и учета правил конкретного языка: разделители,
союзы, порядок элементов и пунктуация могут существенно отличаться. В
экосистеме FormatJS для этого используется API
Intl.ListFormat, обеспечивающий локализованное
форматирование списков.
Вместо ручного склеивания элементов через запятую или другие символы применяется объект форматирования, который учитывает:
Основная идея заключается в том, что список рассматривается как языковая конструкция, а не просто массив строк.
Объект Intl.ListFormat создается с указанием локали и
параметров форматирования:
const formatter = new Intl.ListFormat('ru', {
style: 'long',
type: 'conjunction'
});
Параметры:
locale — строка локали (ru,
en, de, fr-CA и т.д.);style — визуальная плотность представления;type — логика соединения элементов.Используется для перечисления с союзом «и»:
const formatter = new Intl.ListFormat('ru', {
style: 'long',
type: 'conjunction'
});
formatter.format(['яблоки', 'груши', 'сливы']);
// "яблоки, груши и сливы"
Особенности:
Используется для альтернативы («или»):
const formatter = new Intl.ListFormat('ru', {
style: 'long',
type: 'disjunction'
});
formatter.format(['HTML', 'PDF', 'DOCX']);
// "HTML, PDF или DOCX"
Применяется в UI, где требуется выбор одного варианта из нескольких.
Используется для единиц измерения или технических списков:
const formatter = new Intl.ListFormat('en', {
style: 'long',
type: 'unit'
});
formatter.format(['5 кг', '200 г', '1 л']);
Такой формат избегает союзов и делает акцент на перечислении значений как набора параметров.
Полноформатное отображение с традиционными союзами:
new Intl.ListFormat('ru', { style: 'long' });
Результат:
Используется по умолчанию для большинства интерфейсов.
Более компактное отображение, часто с сокращениями или упрощенными правилами:
new Intl.ListFormat('ru', { style: 'short' });
Результат может быть менее формальным, например:
Максимально компактный вариант без союзов:
new Intl.ListFormat('ru', { style: 'narrow' });
Используется в ограниченном пространстве интерфейса, например в мобильных таблицах или виджетах.
Одно из ключевых преимуществ Intl.ListFormat заключается
в полной локализационной адаптации.
Пример для английского языка:
const en = new Intl.ListFormat('en', { type: 'conjunction' });
en.format(['apples', 'bananas', 'oranges']);
// "apples, bananas, and oranges"
Пример для немецкого:
const de = new Intl.ListFormat('de', { type: 'conjunction' });
de.format(['Äpfel', 'Birnen', 'Pflaumen']);
// "Äpfel, Birnen und Pflaumen"
Различия:
В экосистеме FormatJS Intl.ListFormat часто используется
напрямую или через интеграцию с react-intl.
Прямое использование:
import '@formatjs/intl-listformat/polyfill';
import '@formatjs/intl-listformat/locale-data/ru';
const formatter = new Intl.ListFormat('ru', {
style: 'long',
type: 'conjunction'
});
formatter.format(['один', 'два', 'три']);
В React Intl логика может быть обернута в утилитарные функции:
import { useIntl } from 'react-intl';
function useFormattedList() {
const intl = useIntl();
return (items) =>
new Intl.ListFormat(intl.locale, {
style: 'long',
type: 'conjunction'
}).format(items);
}
Такой подход обеспечивает согласованность локали во всём приложении.
Создание Intl.ListFormat — относительно тяжелая
операция, особенно при частом рендеринге интерфейсов. Поэтому форматтеры
рекомендуется кешировать.
const cache = new Map();
function getListFormatter(locale, options) {
const key = `${locale}-${options.type}-${options.style}`;
if (!cache.has(key)) {
cache.set(key, new Intl.ListFormat(locale, options));
}
return cache.get(key);
}
Это особенно важно в React-приложениях, где повторные рендеры могут приводить к пересозданию объектов.
В реальных интерфейсах списки часто формируются динамически:
const errors = [];
if (!email) errors.push('Email');
if (!password) errors.push('Пароль');
if (!name) errors.push('Имя');
const formatter = new Intl.ListFormat('ru', {
style: 'long',
type: 'conjunction'
});
const message = `Не заполнены поля: ${formatter.format(errors)}`;
Результат:
Такой подход улучшает читаемость сообщений без ручной логики форматирования.
Режим unit применяется не только к физическим единицам,
но и к любым структурным наборам данных:
const formatter = new Intl.ListFormat('en', {
style: 'long',
type: 'unit'
});
formatter.format(['10 ms', '20 ms', '30 ms']);
// "10 ms, 20 ms, 30 ms"
Особенность заключается в том, что форматтер не добавляет логических связок, сохраняя техническую нейтральность вывода.
Intl.ListFormat основан на ICU (International Components
for Unicode), поэтому:
В старых браузерах может отсутствовать нативная поддержка, поэтому FormatJS предоставляет полифил:
import '@formatjs/intl-listformat/polyfill';
Также подключаются данные локалей:
import '@formatjs/intl-listformat/locale-data/ru';
import '@formatjs/intl-listformat/locale-data/en';
Хотя Intl.ListFormat работает со строками, на практике
часто требуется предварительная трансформация объектов:
const users = [
{ name: 'Алексей' },
{ name: 'Мария' },
{ name: 'Иван' }
];
const formatter = new Intl.ListFormat('ru', {
style: 'long',
type: 'conjunction'
});
const result = formatter.format(users.map(u => u.name));
Такой подход сохраняет разделение ответственности: данные преобразуются отдельно от локализации.
Intl.ListFormat часто используется вместе с:
Intl.NumberFormat для числовых значений в списках;Intl.DateTimeFormat для дат;Пример комбинированного использования:
const numberFormatter = new Intl.NumberFormat('ru');
const listFormatter = new Intl.ListFormat('ru', { type: 'conjunction' });
const values = [1000, 2500, 500];
const formatted = listFormatter.format(
values.map(v => numberFormatter.format(v))
);
Результат:
Несмотря на универсальность, существуют особенности:
При необходимости сложной логики форматирования требуется
предварительная обработка данных до передачи в
Intl.ListFormat.