Множественное число и правила plural

Механизм множественного числа в 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

Символ # внутри шаблона автоматически заменяется значением переменной.


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

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

Основные категории:

  • zero — ноль (используется в некоторых языках)
  • one — один предмет
  • two — два предмета (например, арабские языки)
  • few — несколько предметов (зависит от языка)
  • many — много предметов (в некоторых языках)
  • other — универсальная категория (почти всегда обязательна)

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


Английский язык как базовый пример

Для английского правила просты:

  • one — 1
  • other — всё остальное
const messages = {
  items: '{count, plural, one {# item} other {# items}}'
};

Примеры:

  • 1 item
  • 2 items
  • 0 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.


Использование точных значений (Exact Match)

ICU MessageFormat позволяет задавать точные числовые совпадения:

const messages = {
  points: '{count, plural,
    =0 {нет очков}
    one {# очко}
    other {# очков}
  }'
};

Здесь =0 перекрывает стандартную категорию zero.

Пример поведения:

  • 0 → нет очков
  • 1 → 1 очко
  • 5 → 5 очков

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

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 });

Результат:

  • 1.5 килограмма

CLDR не относит дроби к one, few или many.


Влияние локали на выбор формы

Один и тот же шаблон может давать разные результаты в зависимости от языка.

const intlEn = createIntl({ locale: 'en', messages }, cache);
const intlRu = createIntl({ locale: 'ru', messages }, cache);

Для числа 5:

  • английский: 5 items
  • русский: 5 предметов

FormatJS автоматически подгружает правила языка из Intl API, не требуя ручного описания логики.


Использование переменных внутри plural

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

const messages = {
  inbox: '{count, plural,
    one {У вас # новое сообщение в # папке}
    other {У вас # новых сообщений в # папках}
  }'
};

Передача данных:

intl.formatMessage(
  { id: 'inbox' },
  { count: 3 }
);

Частые ошибки при работе с plural

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-строки в функции форматирования. Это означает:

  • plural-разбор происходит один раз при инициализации
  • во время рендера выполняется только выбор ветки
  • нет регулярных выражений на каждом вызове

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


Практика проектирования plural-сообщений

При проектировании сообщений учитываются:

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

Типовой шаблон для интерфейсов:

'{count, plural,
  one {# элемент}
  few {# элемента}
  many {# элементов}
  other {# элемента}
}'

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