В интерфейсных текстах соединительные элементы определяют способ связывания перечислений, фрагментов предложений и составных выражений. К ним относятся союзы, запятые, разделители списков, а также правила их изменения в зависимости от языка, регистра, грамматического числа и контекста.
В локализации именно соединительные элементы часто становятся источником ошибок, потому что их нельзя жестко зафиксировать строкой: в разных языках изменяется не только слово-связка, но и пунктуация, порядок элементов и даже тип используемого разделителя.
Типичные случаи:
FormatJS решает эту задачу через интеграцию с возможностями
Intl и ICU MessageFormat, делая соединительные элементы
локализуемой сущностью, а не строковой константой.
ICU MessageFormat, используемый в экосистеме FormatJS, ориентирован на интерполяцию и грамматику внутри предложения: pluralization, select, вложенные условия. Однако он изначально не предназначен для форматирования списков как самостоятельной структуры.
Пример обычного сообщения:
const msg = "В корзине {count, plural, one {# товар} few {# товара} many {# товаров}}";
Но при попытке выразить список:
const msg = "Вы выбрали {items}";
ICU не предоставляет встроенного механизма для правильного соединения элементов массива с учетом языка. В результате разработчики либо:
join(", ")Все эти подходы нарушают локализационную корректность.
В современном JavaScript и в экосистеме FormatJS ключевым механизмом
становится Intl.ListFormat. Он стандартизирует правила
соединения элементов списка в зависимости от локали.
Intl.ListFormat поддерживает три типа соединений:
conjunction — логическое «и»disjunction — логическое «или»unit — единицы измерения или технические спискиconst formatter = new Intl.ListFormat("ru-RU", {
style: "long",
type: "conjunction"
});
formatter.format(["яблоки", "груши", "бананы"]);
// "яблоки, груши и бананы"
Для английского языка:
const formatter = new Intl.ListFormat("en-US", {
style: "long",
type: "conjunction"
});
formatter.format(["apples", "pears", "bananas"]);
// "apples, pears, and bananas"
Разница не только в слове «и/and», но и в наличии Oxford comma, которая зависит от локали и стиля.
FormatJS предоставляет поддержку Intl.ListFormat через
собственные обертки и полифиллы, обеспечивая единое поведение в средах,
где нативная реализация отсутствует.
В React-ориентированной экосистеме используется
react-intl, где доступ к форматированию осуществляется
через useIntl.
useIntlimport { useIntl } from "react-intl";
function ItemsList({ items }) {
const intl = useIntl();
const formatted = intl.formatList(items, {
type: "conjunction"
});
return <span>{formatted}</span>;
}
Здесь соединительные элементы не задаются вручную. Они определяются
локалью, установленной в IntlProvider.
Соединительные элементы зависят от CLDR-данных (Unicode Common Locale Data Repository). Это означает, что поведение определяется не разработчиком, а языковыми правилами.
| Локаль | Результат |
|---|---|
| ru-RU | «A, B и C» |
| en-US | «A, B, and C» |
| fr-FR | «A, B et C» |
| de-DE | «A, B und C» |
Эти различия невозможно корректно поддерживать через статические строки или простые шаблоны.
Используется для перечисления элементов, объединяемых логически.
new Intl.ListFormat("ru-RU", { type: "conjunction" })
Пример:
Используется для альтернатив.
new Intl.ListFormat("ru-RU", { type: "disjunction" })
Пример:
В английском:
Применяется для технических списков без союзов, где важна компактность.
new Intl.ListFormat("ru-RU", { type: "unit" })
Пример:
Соединительные элементы включают не только союзы, но и:
В некоторых локалях финальный разделитель может быть опущен или заменен иной конструкцией.
В ICU MessageFormat нет стандартного типа list, поэтому
списки обычно передаются как уже отформатированная строка:
const message = "{items}";
Где items предварительно формируется через
Intl.ListFormat.
Это приводит к важному архитектурному правилу:
форматирование списков должно происходить до передачи в ICU-шаблон, а не внутри него
В реальных интерфейсах соединительные элементы часто используются вместе с другими механизмами ICU.
const message =
"Вы выбрали {count, plural, one {# файл: {files}} few {# файла: {files}} many {# файлов: {files}}}";
Здесь files должен быть заранее преобразован:
intl.formatList(files, { type: "conjunction" });
Таким образом разделяются ответственности:
В интерфейсах соединительные элементы особенно важны в:
intl.formatList(["Главная", "Каталог", "Электроника"], {
type: "unit"
});
Результат может отличаться от обычного conjunction, потому что breadcrumb не является грамматическим перечислением.
Некоторые языки используют разные союзы в зависимости от контекста, например:
Это не поддерживается напрямую через Intl.ListFormat, но
может быть расширено через кастомные правила перед форматированием
списка.
Поведение соединительных элементов должно учитывать крайние случаи:
[] → пустая строка["A"] → «A»["A", "B"] → без запятой в некоторых локаляхFormatJS и Intl корректно обрабатывают эти случаи без дополнительной логики.
Intl.ListFormat оптимизирован для повторного
использования, но создание экземпляра форматтера в каждом рендере может
быть дорогостоящим.
Рекомендуемая модель:
const formatter = new Intl.ListFormat(locale, options);
и переиспользование экземпляра при повторных форматированиях.
В контексте React это часто решается через useMemo:
const formatter = useMemo(
() => new Intl.ListFormat(locale, { type: "conjunction" }),
[locale]
);
При проектировании локализуемого интерфейса важно соблюдать разделение:
Соединительные элементы не должны быть частью бизнес-логики. Их задача — представление.
array.join(", ")Такие ошибки приводят к несогласованной локализации и ухудшению читаемости интерфейса в разных языках.
Экосистема FormatJS обеспечивает единый слой между:
Соединительные элементы становятся частью формализованной системы локализации, а не строковой логики приложения.