Плюрализация (склонение) в JS

Плюрализация в JS – выбор правильной формы слова под число: 1 товар, 2 товара, 5 товаров. Классика рунета – самописная функция с n % 10, современный стандарт – встроенный API Intl.PluralRules с правилами CLDR.

4 минуты чтения

Термин пришёл из интернационализации (i18n): plural – множественное число, плюрализация – выбор корректной формы слова под конкретное число. В интерфейсах задача встречается на каждом шагу: счётчик корзины («5 товаров»), комментарии («3 комментария»), таймеры обратного отсчёта на setInterval («осталось 2 дня»), интервалы времени, посчитанные через Date.now() («4 часа назад»). В английском форм всего две (product/products), поэтому наивные библиотеки просто прибавляют «s». В русском для целых чисел форм три, и выбор зависит не от величины числа, а от его последних цифр: 1 товар, 2 товара, 5 товаров, затем снова 21 товар – но 11 товаров.

Как это работает

Правила множественного числа для языков мира собраны в CLDR (Unicode Common Locale Data Repository) – базе локалей, которой пользуются операционные системы, браузеры и i18n-библиотеки. Для русского CLDR определяет четыре категории. Выбор зависит от последней цифры целой части числа (i % 10), двух последних цифр (i % 100) и наличия дробной части (v – число знаков после запятой).

Категории множественного числа для русского языка по CLDR
КатегорияУсловие CLDRПримерыФорма
onei % 10 = 1 и i % 100 ≠ 11, v = 01, 21, 31, 101, 1001товар
fewi % 10 = 2..4 и i % 100 ≠ 12..14, v = 02–4, 22–24, 102товара
manyi % 10 = 0, i % 10 = 5..9 или i % 100 = 11..14, v = 00, 5–19, 100, 1000товаров
otherдробные значения (v > 0)0,5; 1,5; 10,0товара

Классическая самописная функция

Рунет с 2011 года решает задачу одной и той же функцией, кочующей по сниппетам и Q&A: сначала отсекается исключение 11–19, затем последняя цифра выбирает один из трёх элементов массива форм.

function numWord(value, words) {
  value = Math.abs(value) % 100;
  const num = value % 10;
  if (value > 10 && value < 20) return words[2]; // 11–19 → товаров
  if (num > 1 && num < 5) return words[1];       // 2–4 → товара
  if (num === 1) return words[0];                // 1 → товар
  return words[2];                               // 0, 5–9 → товаров
}

numWord(1, ['товар', 'товара', 'товаров']);  // 'товар'
numWord(21, ['товар', 'товара', 'товаров']); // 'товар'
numWord(11, ['товар', 'товара', 'товаров']); // 'товаров'

Intl.PluralRules и самописная функция – в чём разница

Писать эти правила руками больше не нужно: в JavaScript есть стандартный API Intl.PluralRules. Конструктор принимает локаль, метод select(n) возвращает одну из категорий CLDR (zero/one/two/few/many/other) – остаётся сопоставить её со словарём форм.

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

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

API входит в Baseline Widely available с сентября 2019 года: Chrome 63+, Firefox 58+, Safari 13+, Edge 79+, а на сервере – Node.js 10+, Deno и Bun; по caniuse глобальная поддержка около 94%, полифилл для современных таргетов не нужен.

Самописная функция и Intl.PluralRules
КритерийСамописная функцияIntl.PluralRules
Языкитолько русский и близкие славянские: в польском 21 склоняется как 5, а не как 1любая локаль CLDR, язык меняется аргументом конструктора
Правилазашиты в код проекта, поддерживать самомувстроены в движок, не попадают в бандл
Дробиобычно не учтеныотдельная категория other
Порядковыеещё одна функцияопция { type: 'ordinal' } (1-й, 2-й)

Отдельная ловушка – дробные числа. У русского категория other покрывает именно дроби: select(1.5) возвращает 'other' («полтора товара» – 1,5 товара). При этом other – единственная категория, гарантированная во всех локалях, поэтому ключ other в словаре форм обязателен: без него на первой же дроби интерфейс выведет undefined.

Зачем это нужно

Плюрализация – место, где качество интерфейса видно сразу: строка «5 товар(ов)» выдаёт сделанный наспех сайт не хуже битой вёрстки.

  • Чистый текст интерфейса – без костылей вида «товар(ов)» и «дней: 5»; счётчики, таймеры и уведомления читаются как нормальный русский язык.
  • Мультиязычность – «славянская» формула не переносится на другие языки, а Intl.PluralRules переключается на любой язык сменой локали, код не меняется.
  • Меньше кода – правила CLDR уже встроены в браузер и Node.js, их не нужно тащить в бандл и обновлять при изменениях.

Если проект использует i18n-библиотеку, логика уже внутри: i18next, FormatJS/react-intl и Lingui сами вызывают Intl.PluralRules. В i18next (формат JSON v4, с версии 21.0.0) формы задаются суффиксами ключей _one, _few, _many, _other, а число передаётся обязательным параметром count:

{
  "product_one": "{{count}} товар",
  "product_few": "{{count}} товара",
  "product_many": "{{count}} товаров",
  "product_other": "{{count}} товара"
}

i18next.t('product', { count: 5 }); // '5 товаров'

Пример

Готовый сниппет для счётчика товаров: словарь форм плюс функция из трёх строк. Экземпляр Intl.PluralRules создаётся один раз и переиспользуется.

const forms = { one: 'товар', few: 'товара', many: 'товаров', other: 'товара' };
const pr = new Intl.PluralRules('ru');
const label = (n) => `${n} ${forms[pr.select(n)]}`;

label(1);   // '1 товар'
label(3);   // '3 товара'
label(25);  // '25 товаров'
label(11);  // '11 товаров'
label(21);  // '21 товар'
label(1.5); // '1.5 товара'

Тот же словарь работает для любых сущностей – дней, комментариев, отзывов: меняются слова, логика остаётся. Для диапазонов («1–3 товара») у API есть метод selectRange(), а число с разделителями тысяч форматирует парный Intl.NumberFormat. На Битрикс-сайтах такой сниппет закрывает типовые места: счётчик корзины в шапке, «Найдено N товаров» в фильтре каталога, количество отзывов в карточке – включая счётчики, которые обновляются без перезагрузки страницы через делегирование событий.

Частые вопросы

Почему нельзя склонять слово простым условием n > 1?

В русском три формы для целых чисел: 1 товар, 2 товара, 5 товаров. Выбор зависит от последних цифр числа, а не от его величины: 21 товар (снова первая форма), но 11 товаров – числа 11–14 всегда исключение. Условие n > 1 даёт только две формы и ломается уже на числе 2.

Как работает Intl.PluralRules для русского языка?

new Intl.PluralRules('ru').select(n) возвращает категорию CLDR: 'one' для 1, 21, 101, 'few' для 2–4 и 22–24, 'many' для 0, 5–20 и чисел, оканчивающихся на 11–14, 'other' для дробных. Категория служит ключом в словаре форм { one: 'товар', few: 'товара', many: 'товаров', other: 'товара' }.

Во всех ли браузерах поддерживается Intl.PluralRules?

Да, это Baseline Widely available с сентября 2019 года: Chrome 63+, Firefox 58+, Safari 13+, Edge 79+. API работает и на сервере – в Node.js 10+, Deno и Bun. Полифилл для современных проектов не нужен.

Зачем в словаре форм ключ other, если русскому хватает one, few и many?

По CLDR категория other у русского покрывает дробные числа: для 1,5 метод select() вернёт 'other' (1,5 товара). Кроме того, other – единственная категория, гарантированная во всех языках. Без этого ключа код вернёт undefined на первой же дроби.

Чем Intl.PluralRules лучше самописной функции с n % 10?

Самописная формула верна только для русского и части славянских языков: в польском 21 склоняется как 5, а не как 1. Intl.PluralRules знает правила CLDR для любой локали, встроен в движок и не увеличивает бандл. Смена языка – это смена одного аргумента конструктора.

Как склонять порядковые числительные (1-й, 2-й) в JavaScript?

У Intl.PluralRules есть опция type: по умолчанию действует 'cardinal' (количественные: 1 товар), а { type: 'ordinal' } переключает на правила порядковых форм (1-й, 2-й). Для диапазонов вида «1–3 товара» есть отдельный метод selectRange().

Нужно ли писать плюрализацию вручную, если проект на i18next или react-intl?

Нет. i18next (формат JSON v4, с версии 21.0.0) использует суффиксы ключей _one, _few, _many, _other и внутри сам вызывает Intl.PluralRules – достаточно заполнить формы в переводах и передавать обязательный параметр count. FormatJS/react-intl и Lingui устроены так же.

Материалы по теме

Валентина Меланина

Нужна консультация?

Разберу ваш сайт и покажу точки роста

Если хотите понять, как этот термин применить к вашему проекту — начнём с аудита.