Механизм множественного числа в FormatJS основан на стандарте ICU
MessageFormat и правилах CLDR (Common Locale Data Repository), где выбор
формы слова зависит от языка, числового значения и контекста локали. В
JavaScript-экосистеме это реализовано через библиотеки
intl-messageformat и react-intl, которые
интерпретируют plural-выражения внутри сообщений и выбирают корректную
грамматическую форму динамически.
Форма множественного числа задаётся через конструкцию:
{count, plural,
one {форма для одного}
other {форма для остальных}
}
Ключевые элементы:
count — числовая переменнаяplural — тип форматированияone, few, many,
other — категории CLDRВ JavaScript это обычно используется так:
import { createIntl, createIntlCache } from 'react-intl';
const cache = createIntlCache();
const intl = createIntl(
{
locale: 'en',
messages: {
apples: '{count, plural, one {# apple} other {# apples}}'
}
},
cache
);
intl.formatMessage({ id: 'apples' }, { count: 1 }); // 1 apple
intl.formatMessage({ id: 'apples' }, { count: 5 }); // 5 apples
Символ # внутри шаблона автоматически заменяется
значением переменной.
FormatJS не использует фиксированные правила языка вручную — вместо этого применяется CLDR, где каждая локаль имеет свой набор категорий.
Основные категории:
zero — ноль (используется в некоторых языках)one — один предметtwo — два предмета (например, арабские языки)few — несколько предметов (зависит от языка)many — много предметов (в некоторых языках)other — универсальная категория (почти всегда
обязательна)Важно: всегда должна быть категория
other, даже если остальные не используются.
Для английского правила просты:
one — 1other — всё остальноеconst messages = {
items: '{count, plural, one {# item} other {# items}}'
};
Примеры:
Русский язык имеет более сложную систему, где учитываются последние цифры числа и исключения для диапазона 11–14.
CLDR категории для русского:
one — 1, 21, 31, 41…few — 2–4, 22–24, 32–34…many — 0, 5–20, 25–30…other — дробные значенияПример:
const messages = {
apples: '{count, plural,
one {# яблоко}
few {# яблока}
many {# яблок}
other {# яблока}
}'
};
Использование:
intl.formatMessage({ id: 'apples' }, { count: 1 }); // 1 яблоко
intl.formatMessage({ id: 'apples' }, { count: 2 }); // 2 яблока
intl.formatMessage({ id: 'apples' }, { count: 5 }); // 5 яблок
intl.formatMessage({ id: 'apples' }, { count: 21 }); // 21 яблоко
Ключевой момент: разработчику не нужно вручную вычислять правила языка — FormatJS делает это через Intl API.
ICU MessageFormat позволяет задавать точные числовые совпадения:
const messages = {
points: '{count, plural,
=0 {нет очков}
one {# очко}
other {# очков}
}'
};
Здесь =0 перекрывает стандартную категорию
zero.
Пример поведения:
Plural можно комбинировать с другими типами форматирования, включая select:
const messages = {
notification: '{gender, select,
male {{count, plural, one {он получил # сообщение} other {он получил # сообщений}}}
female {{count, plural, one {она получила # сообщение} other {она получила # сообщений}}}
other {{count, plural, one {получено # сообщение} other {получено # сообщений}}}
}'
};
Такие конструкции используются в сложных интерфейсах, где требуется учитывать и грамматику числа, и грамматический род.
Для дробных значений всегда применяется категория
other:
const messages = {
weight: '{count, plural, one {# килограмм} other {# килограмма}}'
};
intl.formatMessage({ id: 'weight' }, { count: 1.5 });
Результат:
CLDR не относит дроби к one, few или
many.
Один и тот же шаблон может давать разные результаты в зависимости от языка.
const intlEn = createIntl({ locale: 'en', messages }, cache);
const intlRu = createIntl({ locale: 'ru', messages }, cache);
Для числа 5:
5 items5 предметовFormatJS автоматически подгружает правила языка из Intl API, не требуя ручного описания логики.
Внутри шаблонов можно использовать не только #, но и
именованные переменные:
const messages = {
inbox: '{count, plural,
one {У вас # новое сообщение в # папке}
other {У вас # новых сообщений в # папках}
}'
};
Передача данных:
intl.formatMessage(
{ id: 'inbox' },
{ count: 3 }
);
1. Отсутствие категории other
Неправильно:
{count, plural, one {...}}
Правильно:
{count, plural, one {...} other {...}}
2. Попытка вручную реализовать правила языка
FormatJS уже учитывает CLDR, поэтому условия вида
count === 1 ? ... внутри строки не применяются.
3. Игнорирование дробных значений Дроби всегда
попадают в other, что может неожиданно ломать текст, если
это не учтено.
Если для локали нет подходящей категории в сообщении, используется
other. Это обязательный fallback-механизм ICU.
{count, plural,
one {# file}
other {# files}
}
Даже если локаль имеет категории few или
many, но они не указаны в шаблоне, система не падает —
применяется other.
FormatJS предварительно компилирует ICU-строки в функции форматирования. Это означает:
Это особенно важно в React-приложениях с частыми обновлениями интерфейса.
При проектировании сообщений учитываются:
Типовой шаблон для интерфейсов:
'{count, plural,
one {# элемент}
few {# элемента}
many {# элементов}
other {# элемента}
}'
Такой подход гарантирует корректную работу UI независимо от языка пользователя.