Стили отображения списков

В JavaScript для форматирования списков в зависимости от локали используется объект Intl.ListFormat. Он решает задачу, которая раньше требовала ручной склейки строк с учётом запятых, союзов и особенностей языка. Главная ценность заключается в том, что форматирование списка становится зависимым от правил конкретной локали, а не от жёстко заданной логики приложения.

Базовая концепция форматирования списков

Список в контексте Intl.ListFormat — это набор значений, которые должны быть объединены в человекочитаемую строку. Например:

  • товары в корзине
  • имена пользователей
  • единицы измерения
  • альтернативные варианты

Разные языки используют разные правила соединения элементов. Например, в английском:

  • A, B, and C

В русском:

  • A, B и C

При этом в зависимости от стиля список может быть оформлен по-разному: с союзами, с разделителями или в компактной форме.

Конструктор Intl.ListFormat

Создание форматтера происходит через конструктор:

new Intl.ListFormat(locale, options)

Параметры:

  • locale — строка локали (например, "ru", "en", "de"), либо массив локалей
  • options — объект конфигурации форматирования

Основные режимы type

Ключевой параметр, определяющий смысловую нагрузку списка:

conjunction

Используется для обычных перечислений.

Пример:

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

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

Смысл — объединение элементов как единого набора.

disjunction

Используется для альтернатив.

Пример:

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

formatter.format(["Chrome", "Firefox", "Safari"]);
// "Chrome, Firefox или Safari"

Смысл — выбор одного из вариантов.

unit

Используется для единиц измерения и технических значений.

Пример:

const formatter = new Intl.ListFormat("ru", { type: "unit" });

formatter.format(["5 кг", "200 г"]);
// "5 кг, 200 г"

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

Стили отображения (style)

Параметр style определяет степень полноты форматирования.

long

Полный, развернутый стиль. Используются все грамматические элементы языка.

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

formatter.format(["HTML", "CSS", "JavaScript"]);
// "HTML, CSS и JavaScript"

Особенности:

  • полные союзы
  • корректная пунктуация
  • максимальная читаемость

Это стиль по умолчанию в большинстве локалей.

short

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

const formatter = new Intl.ListFormat("en", {
  style: "short",
  type: "conjunction"
});

formatter.format(["apples", "bananas", "oranges"]);
// "apples, bananas & oranges"

Особенности:

  • сокращённые союзы (например, &)
  • более плотное представление текста
  • часто используется в интерфейсах

narrow

Максимально компактный стиль.

const formatter = new Intl.ListFormat("en", {
  style: "narrow",
  type: "conjunction"
});

formatter.format(["A", "B", "C"]);
// "A B C"

Особенности:

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

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

Один и тот же код даёт разные результаты в разных языках.

const values = ["red", "green", "blue"];

new Intl.ListFormat("en", { type: "conjunction" }).format(values);
// "red, green, and blue"

new Intl.ListFormat("ru", { type: "conjunction" }).format(values);
// "red, green и blue"

new Intl.ListFormat("de", { type: "conjunction" }).format(values);
// "red, green und blue"

Логика соединений определяется языковыми нормами, а не разработчиком.

Работа с двумя элементами

Отдельное внимание уделяется спискам из двух элементов, так как в разных языках для них часто используются особые правила.

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

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

Даже при двух элементах применяется союз, а не запятая.

Пустые и одиночные списки

Один элемент

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

formatter.format(["яблоко"]);
// "яблоко"

Форматирование не добавляет лишних символов.

Пустой массив

formatter.format([]);
// ""

Результат — пустая строка, без ошибок и дополнительных символов.

Использование метода formatToParts

Метод formatToParts возвращает структурированное представление списка, а не строку.

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

formatter.formatToParts(["A", "B", "C"]);

Результат:

[
  { type: "element", value: "A" },
  { type: "literal", value: ", " },
  { type: "element", value: "B" },
  { type: "literal", value: " и " },
  { type: "element", value: "C" }
]

Типы:

  • element — элемент списка
  • literal — разделители и союзы

Это позволяет:

  • кастомизировать отображение
  • оборачивать элементы в HTML
  • добавлять стили без разрушения логики форматирования

Комбинация style и type

Разные комбинации параметров дают различные эффекты.

new Intl.ListFormat("ru", {
  style: "long",
  type: "disjunction"
});

или

new Intl.ListFormat("en", {
  style: "short",
  type: "unit"
});

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

  • локали
  • смыслового типа списка
  • выбранного уровня компактности

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

Создание Intl.ListFormat — относительно дорогая операция. В высоконагруженных приложениях форматтеры обычно создаются один раз и переиспользуются.

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

function render(items) {
  return formatter.format(items);
}

Это снижает накладные расходы при рендеринге списков.

Особенности единиц измерения

Режим unit часто используется в технических интерфейсах.

new Intl.ListFormat("ru", {
  type: "unit",
  style: "long"
}).format(["10 км", "5 м"]);
// "10 км, 5 м"

В некоторых локалях могут применяться специальные правила группировки или сокращения.

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

Ручная сборка списка обычно выглядит так:

function join(items) {
  if (items.length === 0) return "";
  if (items.length === 1) return items[0];
  if (items.length === 2) return items.join(" и ");
  return items.slice(0, -1).join(", ") + " и " + items.at(-1);
}

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

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

Intl.ListFormat устраняет необходимость подобной логики.

Взаимодействие с динамическими данными

При работе с API или пользовательским вводом список может содержать:

  • строки разной длины
  • числа
  • уже локализованные значения

Форматтер не изменяет типы данных, а приводит их к строковому представлению через стандартные механизмы ToString.

formatter.format([1, 2, 3]);
// "1, 2 и 3"

Ограничения модели форматирования

  • отсутствие контроля над конкретным символом союза
  • зависимость от реализации браузера/движка
  • невозможность точечно переопределить грамматику
  • поведение определяется спецификацией ICU

Несмотря на это, результат остаётся согласованным внутри каждой локали.

Поведение в сложных интерфейсах

В интерфейсах с высокой плотностью информации списки часто комбинируются:

  • фильтры
  • теги
  • статусы
  • категории

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