В системах локализации числовые значения рассматриваются не как абстрактные числа, а как элементы языковой грамматики. В разных языках одно и то же значение может требовать различных форм слов, согласующихся с числом. Кардинальная категория описывает поведение чисел в контексте количественного счёта: один объект, несколько объектов, ноль объектов и их грамматические вариации.
В JavaScript работа с кардинальными числительными реализована через
Intl.PluralRules, где определяется набор правил выбора
грамматической формы в зависимости от числового значения и локали.
Механизм Intl.PluralRules оперирует двумя основными
типами:
Кардинальная категория используется по умолчанию и отражает повседневные числовые выражения: количество товаров, сообщений, пользователей, файлов.
const rules = new Intl.PluralRules('en', { type: 'cardinal' });
rules.select(0); // "other"
rules.select(1); // "one"
rules.select(2); // "other"
Тип cardinal задаёт модель, в которой число
сопоставляется с грамматической категорией, а не с конкретной
словоформой.
Каждая локаль определяет собственный набор категорий множественного числа. В спецификации Unicode CLDR выделяются следующие основные формы:
zeroonetwofewmanyotherФактическое количество используемых форм зависит от языка. Например, в английском обычно используются только:
oneotherconst en = new Intl.PluralRules('en');
en.select(1); // "one"
en.select(5); // "other"
en.select(0); // "other"
Английский язык использует минимальную систему различий:
const en = new Intl.PluralRules('en');
const samples = [0, 1, 2, 10];
samples.map(n => [n, en.select(n)]);
// [
// [0, "other"],
// [1, "one"],
// [2, "other"],
// [10, "other"]
// ]
Единственное значение, выделяемое грамматически — единица.
Русский язык использует трёхчастную систему:
one — 1, 21, 31…few — 2–4, 22–24…many — 0, 5–20, 25–30…other — используется редко, в основном для дробных
значенийconst ru = new Intl.PluralRules('ru');
[
1, 2, 5, 11, 21, 22, 25
].map(n => [n, ru.select(n)]);
Результат:
Такая система отражает морфологическую структуру славянских языков.
Арабская система является одной из наиболее сложных:
zeroonetwofewmanyotherconst ar = new Intl.PluralRules('ar');
[0, 1, 2, 3, 11, 100].map(n => [n, ar.select(n)]);
Intl.PluralRules не выполняет простое математическое
сравнение. Вычисление категории основано на CLDR-правилах,
учитывающих:
n % 10)Пример дробных чисел:
const ru = new Intl.PluralRules('ru');
ru.select(1.5); // "many"
ru.select(0.1); // "many"
Дробные значения почти всегда переходят в категорию many
или other, в зависимости от языка.
Intl.PluralRules не формирует строки самостоятельно. Его
задача — определить категорию, которая затем используется для выбора
правильной формы слова.
const ru = new Intl.PluralRules('ru');
const forms = {
one: 'сообщение',
few: 'сообщения',
many: 'сообщений'
};
function formatMessages(count) {
const form = ru.select(count);
return `${count} ${forms[form]}`;
}
Примеры результатов:
Хотя Intl.NumberFormat не определяет грамматические
категории, он часто используется совместно с PluralRules
для построения локализованных строк.
const nf = new Intl.NumberFormat('ru');
const pr = new Intl.PluralRules('ru');
const forms = {
one: 'файл',
few: 'файла',
many: 'файлов'
};
function formatFiles(n) {
return `${nf.format(n)} ${forms[pr.select(n)]}`;
}
Intl.NumberFormat отвечает за визуальное представление
числа, включая:
Intl.PluralRules отвечает за грамматическую
корректность.
Создание Intl.PluralRules может быть затратным при
массовом использовании. Поэтому часто применяется кэширование:
const cache = new Map();
function getRules(locale) {
if (!cache.has(locale)) {
cache.set(locale, new Intl.PluralRules(locale));
}
return cache.get(locale);
}
Такой подход особенно актуален при рендеринге больших списков или серверной генерации текста.
| Локаль | Категории |
|---|---|
| en | one, other |
| ru | one, few, many, other |
| fr | one, other |
| ar | zero, one, two, few, many, other |
| sl | one, two, few, other |
Чем сложнее морфология языка, тем больше категорий требуется для корректного отображения чисел.
Кардинальные правила не зависят от абсолютной величины числа, если локаль не требует иного поведения.
const en = new Intl.PluralRules('en');
en.select(1000001); // "one"
en.select(1000002); // "other"
В языках с модульной логикой важны последние цифры:
const ru = new Intl.PluralRules('ru');
ru.select(101); // "one"
ru.select(111); // "many"
Кардинальные числительные часто используются в интерфейсах:
Типовой паттерн формирования строки:
function createLabel(locale, count, forms) {
const pr = new Intl.PluralRules(locale);
return `${count} ${forms[pr.select(count)]}`;
}
При этом логика выбора формы отделяется от представления числа, что упрощает поддержку локализаций.
Отрицательные числа обрабатываются так же, как положительные, поскольку учитывается абсолютное значение.
const ru = new Intl.PluralRules('ru');
ru.select(-1); // "one"
ru.select(-5); // "many"
Дробные значения чаще всего попадают в обобщённые категории:
ru.select(2.3); // "many"
Модель кардинальных числительных не решает задачи:
Она строго ограничена задачей выбора числовой формы существительного.
При работе с API часто числовые значения приходят в виде строк. Перед
использованием в PluralRules происходит приведение к
числу:
const pr = new Intl.PluralRules('ru');
pr.select(Number("21")); // "one"
Некорректные значения приводят к NaN, который обычно
трактуется как other.
pr.select(Number("abc")); // "other"