Правила множественного числа в разных языках

Множественное число в ICU MessageFormat в FormatJS основано на стандартизации правил CLDR (Common Locale Data Repository), где каждая локаль описывается через набор категорий множественного числа. Эти категории интерпретируются движком Intl.PluralRules, а затем используются в react-intl и других пакетах FormatJS для выбора корректной языковой формы.

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

Основные категории множественного числа:

  • zero
  • one
  • two
  • few
  • many
  • other

Не каждая локаль использует все категории. Конкретный набор зависит от языка и правил CLDR.


В основе механизма лежит Intl.PluralRules, который определяет категорию числа:

const pr = new Intl.PluralRules('en');
pr.select(1);   // "one"
pr.select(2);   // "other"

FormatJS использует этот механизм внутри ICU-сообщений, где разработчик описывает все возможные формы:

import { defineMessages } from 'react-intl';

const messages = defineMessages({
  apples: {
    id: 'fruits.apples',
    defaultMessage: 'Есть {count, plural, one {# яблоко} other {# яблок}}'
  }
});

Знак # автоматически подставляет текущее значение count.


Английская модель множественного числа

Английский язык использует минимальную систему категорий:

  • one — 1
  • other — все остальные значения

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

const pr = new Intl.PluralRules('en');

pr.select(1); // one
pr.select(0); // other
pr.select(5); // other

ICU-сообщение:

{count, plural,
  one {# file}
  other {# files}
}

Особенность английской модели заключается в отсутствии различий между нулём и множественными формами — всё, кроме единицы, считается other.


Русский язык и сложная система форм

Русский язык использует четыре категории:

  • one — 1, 21, 31, 41…
  • few — 2-4, 22-24, 32-34…
  • many — 0, 5-20, 25-30…
  • other — дробные значения

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

const pr = new Intl.PluralRules('ru');

pr.select(1);  // one
pr.select(2);  // few
pr.select(5);  // many
pr.select(21); // one

ICU-шаблон в FormatJS:

{count, plural,
  one {# файл}
  few {# файла}
  many {# файлов}
  other {# файла}
}

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


Польский язык и расширенная логика категорий

Польский язык также использует несколько категорий:

  • one — 1
  • few — 2-4, кроме 12-14
  • many — 0, 5-21 (с исключениями)
  • other — дробные значения

Пример:

const pr = new Intl.PluralRules('pl');

pr.select(1);  // one
pr.select(2);  // few
pr.select(5);  // many
pr.select(12); // many

ICU-конструкция:

{count, plural,
  one {# plik}
  few {# pliki}
  many {# plików}
  other {# pliku}
}

Сложность польской системы связана с исключениями в диапазонах 12–14, которые не следуют базовому правилу малых чисел.


Арабская модель множественного числа

Арабский язык обладает одной из самых детализированных систем:

  • zero
  • one
  • two
  • few (3–10)
  • many (11–99)
  • other (100+ и дробные)
const pr = new Intl.PluralRules('ar');

pr.select(0);   // zero
pr.select(1);   // one
pr.select(2);   // two
pr.select(3);   // few
pr.select(11);  // many
pr.select(100); // other

ICU-форма:

{count, plural,
  zero {لا ملفات}
  one {ملف واحد}
  two {ملفان}
  few {# ملفات}
  many {# ملفاً}
  other {# ملف}
}

Арабская система требует полного покрытия всех категорий, иначе часть чисел может отображаться некорректно.


Языки без множественного числа

Некоторые языки не различают формы единственного и множественного числа в грамматике. Примером служат восточноазиатские языки, включая японский и китайский.

В таких локалях Intl.PluralRules всегда возвращает other:

const pr = new Intl.PluralRules('ja');

pr.select(1); // other
pr.select(5); // other

ICU-сообщение упрощается до одной формы:

{count} ファイル

В FormatJS условное ветвление фактически не требуется, однако синтаксис plural сохраняется для унификации.


Внутренняя модель ICU MessageFormat

FormatJS использует синтаксис ICU, где plural — это выражение с ключами категорий:

{variable, plural,
  category {message}
}

Алгоритм обработки включает несколько этапов:

  1. Извлечение числового значения
  2. Передача в Intl.PluralRules
  3. Получение категории
  4. Выбор соответствующего блока
  5. Подстановка # и переменных

Использование offset в plural-форматах

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

{count, plural, offset:1
  one {Ты и ещё # человек}
  other {Ты и ещё # человек}
}

Если count = 1, то отображается:

Ты и ещё 0 человек

Это используется в сценариях, где текущее значение уже учитывается в тексте.


Вложенные plural-конструкции

FormatJS позволяет комбинировать plural с другими ICU-конструкциями:

{gender, select,
  male {{count, plural, one {Он добавил # файл} other {Он добавил # файлов}}}
  female {{count, plural, one {Она добавила # файл} other {Она добавила # файлов}}}
  other {{count, plural, one {Добавлено # файл} other {Добавлено # файлов}}}
}

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


Intl.PluralRules как основа локализации

Intl.PluralRules поддерживает параметризацию:

new Intl.PluralRules('ru', { type: 'cardinal' });
new Intl.PluralRules('ru', { type: 'ordinal' });

Ordinal-формы используются для порядковых числительных:

const pr = new Intl.PluralRules('en', { type: 'ordinal' });

pr.select(1); // "one" -> 1st
pr.select(2); // "two" -> 2nd

FormatJS может комбинировать cardinal и ordinal правила в зависимости от контекста интерфейса.


CLDR как источник правил множественного числа

CLDR определяет правила через выражения диапазонов:

one: n mod 10 = 1 and n mod 100 != 11
few: n mod 10 in 2..4 and n mod 100 not in 12..14
many: n mod 10 = 0 or n mod 10 in 5..9 or n mod 100 in 11..14

Эти правила интерпретируются движком JavaScript и не требуют ручной реализации.


Обработка чисел с плавающей точкой

Для дробных значений большинство языков используют категорию other:

const pr = new Intl.PluralRules('ru');

pr.select(1.5); // other
pr.select(2.3); // other

ICU-шаблоны обычно не предусматривают отдельные формы для дробных чисел, за исключением отдельных локалей с особыми правилами.


Поведение FormatJS при отсутствии категории

Если в ICU-шаблоне отсутствует подходящая категория, используется other как fallback:

{count, plural,
  one {# item}
  other {# items}
}

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


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

Внутри FormatJS объекты Intl.PluralRules обычно кешируются по локали, поскольку их создание может быть затратным:

const cache = new Map();

function getPluralRules(locale) {
  if (!cache.has(locale)) {
    cache.set(locale, new Intl.PluralRules(locale));
  }
  return cache.get(locale);
}

Это особенно важно при серверном рендеринге и частых вычислениях интерфейса.


Взаимодействие plural с форматированием чисел

Plural-формы не форматируют числа автоматически. Для отображения чисел с локализацией используется Intl.NumberFormat:

{count, plural,
  one {{count, number} файл}
  other {{count, number} файлов}
}

Это позволяет одновременно применять языковые правила и локализованное форматирование чисел (разделители тысяч, десятичные знаки).