Кастомные правила плюрализации

В библиотеке i18next обработка множественных форм основана на встроенном PluralResolver, который опирается на правила CLDR (Unicode Common Locale Data Repository). Для большинства языков достаточно стандартных правил, однако в реальных приложениях часто возникает необходимость переопределять или расширять поведение, особенно при работе с нестандартными локалями, бизнес-логикой отображения или исторически сложившимися форматами перевода.

Плюрализация в i18next реализуется через комбинацию ключей ресурсов, количества (count) и набора правил, определяющих выбор формы.

Базовая модель множественных форм

i18next использует соглашение по ключам:

  • item
  • item_plural
  • item_two (для некоторых языков)
  • item_few, item_many
  • item_zero (опционально)

Выбор формы зависит от локали и значения count.

Пример структуры ресурсов:

{
  "item": "Элемент",
  "item_plural": "Элементы"
}

При вызове:

i18next.t("item", { count: 5 });

будет выбрана форма item_plural.

Алгоритм выбора формы

Внутренний PluralResolver выполняет следующие шаги:

  1. Определяет текущую локаль (lng)
  2. Получает правила плюрализации для языка
  3. Вычисляет индекс формы на основе count
  4. Формирует суффикс ключа (_plural, _few, _many и т.д.)
  5. Проверяет наличие соответствующего ключа в ресурсах
  6. Выполняет fallback на базовый ключ при отсутствии формы

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

Кастомизация правил плюрализации

i18next позволяет переопределять правила через addRule в pluralResolver.

Основной интерфейс:

i18next.services.pluralResolver.addRule(lng, {
  numbers: [],
  plurals: function (n) {
    return index;
  }
});

Структура кастомного правила

Каждое правило содержит:

  • numbers: массив допустимых значений count
  • plurals: функция, возвращающая индекс формы
  • append: (опционально) добавление суффиксов
  • coverage: описание диапазонов (используется внутренне)

Пример переопределения для локали

Рассмотрим упрощённый пример языка, где существует только две формы:

  • 1 → singular
  • все остальные → plural
i18next.services.pluralResolver.addRule("custom-lang", {
  numbers: [1, 2],
  plurals: function (n) {
    return n === 1 ? 0 : 1;
  }
});

В этом случае индекс 0 соответствует базовой форме, а 1 — множественной.

Ресурсы:

{
  "car": "машина",
  "car_plural": "машины"
}

Использование сложных правил

Некоторые языки требуют более детальной логики, например три формы:

  • 1 → singular
  • 2–4 → few
  • 5+ → many

Реализация:

i18next.services.pluralResolver.addRule("ru-custom", {
  numbers: [1, 2, 5],
  plurals: function (n) {
    if (n % 10 === 1 && n % 100 !== 11) return 0;
    if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return 1;
    return 2;
  }
});

Ресурсы:

{
  "comment": "комментарий",
  "comment_few": "комментария",
  "comment_many": "комментариев"
}

Интеграция с интерполяцией count

Ключевым механизмом является параметр count, который автоматически передаётся в функцию перевода:

i18next.t("comment", { count: 3 });

При этом:

  • значение count не только влияет на выбор формы
  • оно также может использоваться в интерполяции строки

Пример:

{
  "comment": "{{count}} комментарий",
  "comment_few": "{{count}} комментария",
  "comment_many": "{{count}} комментариев"
}

Переопределение поведения через pluralSeparator

По умолчанию i18next использует _ как разделитель:

key_plural
key_few

Его можно изменить:

i18next.init({
  pluralSeparator: "|"
});

Тогда структура ключей становится:

key|plural
key|few

Это важно при интеграции с системами, где символ _ уже зарезервирован.

Контекст и плюрализация

Плюрализация может комбинироваться с context, что создаёт дополнительный уровень выбора:

i18next.t("friend", { count: 2, context: "male" });

Ресурсы:

{
  "friend_male": "друг",
  "friend_male_plural": "друзья",
  "friend_female": "подруга",
  "friend_female_plural": "подруги"
}

Здесь сначала применяется контекст, затем плюрализация.

Порядок разрешения ключей

Внутренний механизм разрешения выполняется по следующей иерархии:

  1. key_context_plural
  2. key_context
  3. key_plural
  4. key

Это означает, что наиболее специфичная форма имеет приоритет.

Работа с нестандартными числовыми системами

Некоторые локали требуют обработки не только целых чисел, но и дробных значений.

Пример кастомного правила:

i18next.services.pluralResolver.addRule("xx", {
  numbers: [0, 1, 2],
  plurals: function (n) {
    if (n === 0) return 0;
    if (n === 1) return 1;
    return 2;
  }
});

Использование:

i18next.t("point", { count: 0.5 });

Перекрытие встроенных правил CLDR

Встроенные правила могут быть переопределены полностью, если зарегистрировать локаль с тем же кодом:

i18next.services.pluralResolver.addRule("en", {
  numbers: [1, 2],
  plurals: function (n) {
    return n === 1 ? 0 : 1;
  }
});

Такое переопределение требует аккуратности, так как затрагивает все переводы для языка.

Динамическая регистрация правил

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

function registerPluralRule(lng) {
  i18next.services.pluralResolver.addRule(lng, {
    numbers: [1, 2],
    plurals: n => (n === 1 ? 0 : 1)
  });
}

Это часто используется в плагинах или модульных системах.

Влияние fallback-языков

Если для текущей локали отсутствует правило плюрализации, используется fallback:

i18next.init({
  fallbackLng: "en"
});

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

Взаимодействие с форматтерами

Плюрализация может сочетаться с форматтерами:

i18next.t("balance", {
  count: 1500,
  formatParams: {
    count: {
      minimumFractionDigits: 0
    }
  }
});

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

Ограничения кастомных правил

При создании собственных правил важно учитывать:

  • индекс формы должен соответствовать структуре ключей
  • несоответствие numbers и возвращаемого индекса приводит к fallback
  • CLDR-совместимость теряется при полном переопределении языка
  • сложные условия могут ухудшить предсказуемость перевода

Поведение при отсутствии формы

Если соответствующий ключ отсутствует, i18next:

  1. пытается найти ближайшую форму
  2. использует базовый ключ
  3. применяет fallbackLng
  4. возвращает ключ как строку

Это важно при неполных ресурсах, особенно при кастомных правилах.

Расширенные сценарии использования

Кастомные правила часто применяются в следующих случаях:

  • локализация игровых интерфейсов с нестандартной логикой чисел
  • корпоративные системы с историческими формами слов
  • языки с контекстной морфологией, не совпадающей с CLDR
  • адаптация под UX-правила отображения (например, скрытие нулевых форм)

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