Локализация соединительных элементов

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

В локализации именно соединительные элементы часто становятся источником ошибок, потому что их нельзя жестко зафиксировать строкой: в разных языках изменяется не только слово-связка, но и пунктуация, порядок элементов и даже тип используемого разделителя.

Типичные случаи:

  • перечисления: «яблоки, груши и бананы»
  • альтернативы: «да или нет»
  • технические списки: «1 kW / 2 kW / 3 kW»
  • комбинированные конструкции в UI: «Файл, папка и архив»

FormatJS решает эту задачу через интеграцию с возможностями Intl и ICU MessageFormat, делая соединительные элементы локализуемой сущностью, а не строковой константой.


Ограничения ICU MessageFormat при работе со списками

ICU MessageFormat, используемый в экосистеме FormatJS, ориентирован на интерполяцию и грамматику внутри предложения: pluralization, select, вложенные условия. Однако он изначально не предназначен для форматирования списков как самостоятельной структуры.

Пример обычного сообщения:

const msg = "В корзине {count, plural, one {# товар} few {# товара} many {# товаров}}";

Но при попытке выразить список:

const msg = "Вы выбрали {items}";

ICU не предоставляет встроенного механизма для правильного соединения элементов массива с учетом языка. В результате разработчики либо:

  • вручную склеивают строки через join(", ")
  • либо хранят готовые строки в переводах
  • либо используют небезопасные универсальные соединители

Все эти подходы нарушают локализационную корректность.


Intl.ListFormat как основа соединительных элементов

В современном 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 с форматированием списков

FormatJS предоставляет поддержку Intl.ListFormat через собственные обертки и полифиллы, обеспечивая единое поведение в средах, где нативная реализация отсутствует.

В React-ориентированной экосистеме используется react-intl, где доступ к форматированию осуществляется через useIntl.

Форматирование списков через useIntl

import { 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»

Эти различия невозможно корректно поддерживать через статические строки или простые шаблоны.


Типы соединительных конструкций

Conjunction (соединение)

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

new Intl.ListFormat("ru-RU", { type: "conjunction" })

Пример:

  • «редактор, компилятор и линтер»

Disjunction (разделение)

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

new Intl.ListFormat("ru-RU", { type: "disjunction" })

Пример:

  • «создать файл или папку»

В английском:

  • «file or folder»

Unit (единицы)

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

new Intl.ListFormat("ru-RU", { type: "unit" })

Пример:

  • «10 кг, 20 г, 5 мг»

Порядок элементов и пунктуация как часть соединительных правил

Соединительные элементы включают не только союзы, но и:

  • запятые (разделители)
  • пробелы вокруг элементов
  • наличие или отсутствие последней запятой
  • стилистические особенности (формальные/неформальные списки)

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


Использование в ICU-сообщениях и ограничения

В ICU MessageFormat нет стандартного типа list, поэтому списки обычно передаются как уже отформатированная строка:

const message = "{items}";

Где items предварительно формируется через Intl.ListFormat.

Это приводит к важному архитектурному правилу:

форматирование списков должно происходить до передачи в ICU-шаблон, а не внутри него


Комбинирование списков с plural и select

В реальных интерфейсах соединительные элементы часто используются вместе с другими механизмами ICU.

Пример сложного сообщения

const message =
  "Вы выбрали {count, plural, one {# файл: {files}} few {# файла: {files}} many {# файлов: {files}}}";

Здесь files должен быть заранее преобразован:

intl.formatList(files, { type: "conjunction" });

Таким образом разделяются ответственности:

  • plural отвечает за грамматику числа
  • list format отвечает за соединение элементов

Локализация UI-компонентов и соединительные элементы

В интерфейсах соединительные элементы особенно важны в:

  • списках фильтров
  • breadcrumb-навигации
  • тегах и метках
  • уведомлениях
  • summary-блоках
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]
);

Архитектурные принципы работы с соединительными элементами

При проектировании локализуемого интерфейса важно соблюдать разделение:

  1. данные (массив элементов)
  2. семантика соединения (and/or/unit)
  3. локализация (CLDR правила)

Соединительные элементы не должны быть частью бизнес-логики. Их задача — представление.


Типичные ошибки реализации

  • использование array.join(", ")
  • жестко заданные «и» или «or»
  • игнорирование локали интерфейса
  • попытка формировать список внутри ICU строки
  • смешивание plural и list логики

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


Роль FormatJS в унификации соединительных элементов

Экосистема FormatJS обеспечивает единый слой между:

  • ICU MessageFormat
  • Intl API
  • React компонентами
  • полифиллами для старых окружений

Соединительные элементы становятся частью формализованной системы локализации, а не строковой логики приложения.