Offset в plural конструкциях

Назначение offset в ICU MessageFormat

В ICU MessageFormat механизм offset используется для корректировки значения счётчика в plural-правилах без изменения исходного числа в тексте. Это позволяет формировать более естественные фразы, когда часть элементов уже явно названа в сообщении.

Ключевая идея:

offset уменьшает значение переменной только внутри plural-логики, не влияя на отображаемое число.

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


Синтаксис offset в FormatJS

В FormatJS (через ICU MessageFormat) offset задаётся внутри plural-блока:

{value, plural, offset:1
  =0 {Нет элементов}
  =1 {Только # элемент}
  one {# элемент и ещё один}
  other {# элемента и ещё # дополнительных}
}

Здесь:

  • value — исходное число
  • offset:1 — вычитает 1 из значения при выборе формы
  • # — подставляет скорректированное значение (value - offset)

Механика работы offset

Если задано:

value = 5
offset = 1

то внутри plural-блока используется:

effectiveValue = 5 - 1 = 4

Однако важно различать:

  • выбор формы (one, other, =n) происходит на основе effectiveValue
  • # также отображает effectiveValue, а не исходное число

Практический смысл использования

Offset применяется, когда первый элемент уже явно вынесен из списка.

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


Пример без offset

const message = `
{count, plural,
  one {# участник}
  few {# участника}
  other {# участников}
}
`;

При count = 3:

3 участника

Но если один участник уже выведен отдельно, логика ломается: текст будет дублировать его.


Пример с offset

const message = `
{name} и ещё {count, plural, offset:1
  =0 {никого}
  one {# человек}
  other {# человека}
}
`;

При данных:

{
  name: "Анна",
  count: 3
}

Результат:

Анна и ещё 2 человека

Разбор поведения offset

Исходное значение: count = 3

  • offset = 1
  • внутри plural используется 2

Выбор формы:

  • one → не подходит (2 ≠ 1)
  • other → подходит

Подстановка #:

  • # = 2

Использование =exact значений с offset

Offset не отменяет возможность точных совпадений:

{count, plural, offset:1
  =0 {только {name}}
  =1 {{name} и ещё один человек}
  other {{name} и ещё # человека}
}

При count = 1:

  • effectiveValue = 0
  • срабатывает =0

Результат:

только Анна

Сочетание offset и выбор форм множественного числа

FormatJS сначала применяет offset, затем:

  1. вычисляет effectiveValue
  2. проверяет exact-match (=n)
  3. применяет plural rules языка

Это важно учитывать, так как логика может неожиданно переключиться на точное совпадение.


Типичные сценарии применения

1. “и ещё N”

Самый частый случай:

{user} и ещё {count, plural, offset:1
  one {# участник}
  other {# участников}
}

2. Списки с главным элементом

{leader} и ещё {members, plural, offset:1
  =0 {никого}
  one {# человек}
  other {# человека}
}

3. Уведомления

У вас {notifications, plural, offset:0
  =0 {нет новых уведомлений}
  one {# новое уведомление}
  few {# новых уведомления}
  other {# новых уведомлений}
}

Здесь offset не используется, но сравнение полезно для понимания контраста: offset применяют только когда часть данных уже выведена.


Поведение # при offset

Символ # всегда отображает уже скорректированное значение.

Пример:

{count, plural, offset:2
  other {осталось # элементов}
}

При count = 10:

осталось 8 элементов

Частые ошибки

1. Несоответствие логики offset и текста

{name} и ещё {count, plural, offset:1
  one {# пользователь}
}

Проблема: отсутствует other, при значениях > 2 сообщение ломается.


2. Использование offset без необходимости

Если нет вынесенного элемента, offset приводит к искажению числа.

{count, plural, offset:1
  other {# элементов}
}

При count = 3 вывод будет 2, что логически неверно.


3. Игнорирование exact-match

=0 {ничего}

может перекрывать ожидаемую plural-ветку после offset.


Поведение в разных локалях

Plural rules зависят от языка, но offset применяется одинаково — до определения формы.

Например:

  • английский: one / other
  • русский: one / few / many / other

Offset не изменяет правила языка, только входное значение.


Взаимодействие offset с вложенными структурами

Offset работает только внутри текущего plural-блока и не влияет на вложенные message-части.

{count, plural, offset:1
  other {{
    {name} и ещё # участников в группе
  }}
}

Поведение при отрицательных значениях

Если после offset значение становится отрицательным, оно всё равно участвует в plural-логике:

count = 0
offset = 1
effectiveValue = -1

Выбор формы зависит от правил языка, но обычно такие ситуации требуют ручного контроля через exact-match.


Рекомендации по использованию

  • применять offset только при наличии явно выделенного элемента
  • всегда добавлять other как базовый fallback
  • учитывать влияние offset на #
  • проверять exact-match сценарии отдельно
  • избегать отрицательных effectiveValue без явной обработки

Ментальная модель offset

Offset можно рассматривать как трансформацию входного значения:

value → (value - offset) → plural rules → rendering

При этом исходное значение остаётся доступным вне plural-блока, а внутри используется только скорректированная версия.