# General Translation Knowledge Base: Что такое формат сообщений ICU?
URL: https://generaltranslation.com/ru/kb/icu-message-format.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Формат сообщений ICU — это синтаксис, позволяющий уместить в одной переводимой строке переменные, формы множественного числа, варианты выбора (select) и порядковые числительные, например {count, plural, one {# file} other {# files}}. Каждый язык использует только нужные ему категории множественного числа CLDR: в английском это one и other, а в польском к ним добавляются few и many.
Category: Форматирование с учётом локали
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

Этот синтаксис восходит к классу MessageFormat из ICU (International Components for Unicode) — набора библиотек интернационализации от консорциума Unicode для C/C++ и Java. Ту же грамматику реализуют и JavaScript-библиотеки, например FormatJS. Сейчас его также называют MessageFormat 1, поскольку появилась переработанная версия — MessageFormat 2.

## Какую проблему решает формат сообщений ICU?

Код, который собирает предложения из фрагментов, работает в английском, но ломается в других языках. `'You have ' + n + ' files'` жёстко задаёт английский порядок слов и две английские формы множественного числа. В польском для того же предложения нужны четыре формы: 1 plik, 2 pliki, 5 plików и 1,5 pliku. Во многих языках от рода в предложении («Она ответила» или «Он ответил») зависят и другие слова, а порядковые числительные (1-й, 2-й, 3-й) подчиняются собственным правилам.

Формат сообщений ICU объединяет все эти случаи в одной строке. Переводчик видит предложение целиком, со всеми ветками, переписывает его под свой язык и добавляет или удаляет ветки, как того требует грамматика. По той же причине [руководство пользователя ICU](https://unicode-org.github.io/icu/userguide/format_parse/messages/) рекомендует писать в каждой ветке полное предложение: переводчик не может исправить грамматику фрагмента, которого не видит.

## Синтаксис и примеры, которые можно запустить

Все примеры ниже проверены с [`intl-messageformat`](https://formatjs.github.io/docs/intl-messageformat) 12.1.4 от FormatJS на Node.js 24. Сообщение разбирается один раз, а затем форматируется с подстановкой значений:

```js
import { IntlMessageFormat } from 'intl-messageformat';

const msg = new IntlMessageFormat(
  '{count, plural, =0 {You have no messages.} one {You have # message.} other {You have # messages.}}',
  'en'
);

msg.format({ count: 0 });    // 'You have no messages.'
msg.format({ count: 1 });    // 'You have 1 message.'
msg.format({ count: 1234 }); // 'You have 1,234 messages.'
```

### Аргументы

`{name}` подставляет значение: `Hello, {name}!` с `{ name: 'Ada' }` даёт «Hello, Ada!». Типизированные аргументы форматируют значение в соответствии с локалью: например, `Total: {price, number, ::currency/EUR}` с `1234.5` даёт «Total: €1,234.50» для английского языка.

### plural, `#` и точные совпадения

`{count, plural, ...}` выбирает ветку по категории множественного числа, принятой в локали: `zero`, `one`, `two`, `few`, `many` или `other`. Внутри ветки `#` обозначает число, отформатированное по правилам локали; например, немецкое сообщение `{count, plural, one {Sie haben # Nachricht.} other {Sie haben # Nachrichten.}}` выводит «Sie haben 1.234 Nachrichten.» Точное совпадение, например `=0`, имеет приоритет над категорией — именно поэтому пример выше выводит «no messages», а не «0 messages».

То же предложение на польском — с теми ветками, которые используются в польском языке:

```text
{count, plural, one {# plik} few {# pliki} many {# plików} other {# pliku}}
```

Для локали `pl` это сообщение форматирует 1, 2, 5, 22 и 1,5 как «1 plik», «2 pliki», «5 plików», «22 pliki» и «1,5 pliku».

### select

`select` выбирает ветку по строковому значению, а ветка `other` срабатывает во всех остальных случаях:

```text
{gender, select,
  female {She replied to your comment.}
  male {He replied to your comment.}
  other {They replied to your comment.}}
```

`{ gender: 'female' }` даёт &quot;She replied to your comment.&quot;, а `{ gender: 'nonbinary' }` — &quot;They replied to your comment.&quot; Помещайте в каждую ветку предложение целиком: в других языках от рода может зависеть не только местоимение, но и форма глагола.

### selectordinal

`selectordinal` использует правила для порядковых числительных, которые в английском языке делятся на четыре категории:

```text
{place, selectordinal,
  one {You finished #st.}
  two {You finished #nd.}
  few {You finished #rd.}
  other {You finished #th.}}
```

Для мест 1, 2 и 11 результатом будут `You finished 1st.`, `You finished 2nd.` и `You finished 11th.` Для 3, 4, 22 и 103 используются суффиксы `rd`, `th`, `nd` и `rd` соответственно.

### offset

`offset:1` вычитает единицу из числа перед выбором категории и подстановкой `#`, что удобно для сообщений вида «вы и ещё N человек». Точные совпадения проверяются по исходному значению:

```text
{guests, plural, offset:1
  =0 {Nobody is going.}
  =1 {{host} is going.}
  one {{host} and # other person are going.}
  other {{host} and # other people are going.}}
```

При `host: 'Mia'` для 0, 1, 2 и 5 гостей выводятся строки &quot;Nobody is going.&quot;, &quot;Mia is going.&quot;, &quot;Mia and 1 other person are going.&quot; и &quot;Mia and 4 other people are going.&quot; соответственно.

### Вложенные аргументы

Аргументы можно вкладывать друг в друга. Руководство по ICU рекомендует размещать `select` снаружи, а `plural` — внутри, и в каждой ветке писать предложение целиком:

```text
{gender, select,
  female {{count, plural, one {She has # new follower} other {She has # new followers}}}
  other {{count, plural, one {They have # new follower} other {They have # new followers}}}}
```

### Апострофы и экранирование

ASCII-апостроф экранирует синтаксические символы, а два апострофа подряд выводят один. Начиная с ICU 4.8, одиночный апостроф открывает экранированный фрагмент, только если стоит непосредственно перед символом, требующим экранирования, поэтому обычные сокращения с апострофом (например, английские don&#39;t или it&#39;s) работают как есть:

```text
It's {name}'s turn. Type '{name}' to insert a name. I''m here.
```

С `{ name: 'Ada' }` выводится `It's Ada's turn. Type {name} to insert a name. I'm here.` Внутри plural `'#'` выводит символ `#` как есть. [Руководство по синтаксису ICU](https://formatjs.github.io/docs/core-concepts/icu-syntax) от FormatJS рекомендует использовать в человекочитаемом тексте типографский апостроф (’) — тогда этот вопрос просто не возникает.

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

Эти категории взяты из [правил множественного числа Unicode CLDR](https://cldr.unicode.org/index/cldr-spec/plural-rules) — тех же данных, на которых основан `Intl.PluralRules` в JavaScript. Вот что `Intl.PluralRules` возвращает для нескольких языков:

| Язык                            | Категории для количественных чисел           |
| ------------------------------- | -------------------------------------------- |
| Английский (`en`)               | `one`, `other`                               |
| Польский (`pl`), русский (`ru`) | `one`, `few`, `many`, `other`                |
| Арабский (`ar`)                 | `zero`, `one`, `two`, `few`, `many`, `other` |
| Японский (`ja`)                 | `other`                                      |

`one` не означает строго 1: в русском языке 21 тоже относится к `one`. Используйте `=1`, только если имеете в виду именно число 1. О том, как это реализуется на уровне компонентов в React, см. [Как работать с формами множественного числа в React?](/kb/how-to-handle-plurals-in-react).

## Типичные проблемы

* **Синтаксические ошибки всплывают поздно.** Пропущенная фигурная скобка остаётся обычной строкой, пока сообщение не будет разобрано, — и тогда `intl-messageformat` выбрасывает исключение `EXPECT_ARGUMENT_CLOSING_BRACE`. Разбирайте сообщения или проверяйте их линтером в CI либо компилируйте их заранее.
* **Нет ветки `other`.** `intl-messageformat` выбрасывает исключение `MISSING_OTHER_CLAUSE` для plural без `other`, а FormatJS требует эту ветку и для `select`.
* **Ветки, которые не подходят для языка перевода.** В английском сообщении есть `one` и `other`; польскому переводу нужны ещё `few` и `many`, а японскому — только `other`. Ваш процесс перевода должен уметь добавлять и удалять ветки, а не только переводить текст внутри них.
* **Фрагменты.** `{count} {count, plural, one {file} other {files}} deleted` оставляет переводчикам разрозненные куски, которые приходится переставлять. Помещайте в каждую ветку предложение целиком.
* **Смешение диалектов.** По умолчанию i18next использует синтаксис `{{name}}` с ключами множественного числа вида `key_one`, а не ICU. Команды, которым нужен ICU, подключают плагин [`i18next-icu`](https://github.com/i18next/i18next-icu).

## Какие библиотеки поддерживают формат сообщений ICU?

* **ICU4J и ICU4C** — эталонные реализации на Java и C/C++.
* **FormatJS**: [`intl-messageformat`](https://formatjs.github.io/docs/intl-messageformat) — парсер и форматтер, на котором построен react-intl. Поддерживает скелетоны чисел и дат (например, `::currency/EUR`) и теги форматированного текста. (См. [General Translation vs. react-intl](/kb/general-translation-vs-react-intl).)
* **messageformat**: [`@messageformat/core`](https://messageformat.github.io/messageformat/) компилирует ICU-сообщения в функции JavaScript; при этом версия 4 пакета `messageformat` реализует уже MessageFormat 2.
* **i18next-icu**: плагин для i18next, который переводит i18next на синтаксис ICU; построен на `intl-messageformat`.
* **Lingui**: [макросы](https://lingui.dev/ref/macro) Lingui на этапе компиляции преобразуют JSX и тегированные шаблоны в сообщения ICU MessageFormat.
* **next-intl**: [сообщения в next-intl используют синтаксис ICU](https://next-intl.dev/docs/usage/messages).

## Что меняется в MessageFormat 2?

MessageFormat 2 (MF2) — это переработанная консорциумом Unicode версия формата с новым синтаксисом: объявлениями вроде `.input` и `.match` и шаблонами в двойных фигурных скобках. Его спецификация — [Part 9 of Unicode LDML](https://www.unicode.org/reports/tr35/tr35-messageFormat.html) — имеет статус стабильного документа (хотя некоторые функции всё ещё помечены как Draft), а сам формат уже встроен в ICU4C и ICU4J. В JavaScript [предложение `Intl.MessageFormat`](https://github.com/tc39/proposal-intl-messageformat) от TC39 находится на Stage 1, поэтому в язык оно пока не входит; в качестве полифила используется пакет `messageformat` 4.x. Пример с множественным числом в MF2 выглядит так:

```text
.input {$count :number}
.match $count
0   {{You have no messages.}}
one {{You have {$count} message.}}
*   {{You have {$count} messages.}}
```

При запуске с `messageformat` 4.1.0 этот код выводит &quot;You have no messages.&quot;, &quot;You have 1 message.&quot; и &quot;You have 1,234 messages.&quot; для 0, 1 и 1234. Сообщения MF2 несовместимы со строками MessageFormat 1, а все перечисленные выше библиотеки работают именно с MessageFormat 1.

## Как General Translation обрабатывает ICU

В `gt-react` и `gt-next` хук [`useGT`](/docs/react/reference/hooks/use-gt) возвращает функцию, которая принимает ICU-сообщение и значения для него:

```tsx title="Inbox.tsx"
import { useGT } from 'gt-react';

export function Inbox({ count }: { count: number }) {
  const gt = useGT();
  return (
    <p>
      {gt('{count, plural, =0 {You have no messages.} one {You have # message.} other {You have # messages.}}', {
        count,
        $context: 'Unread message count in the inbox',
      })}
    </p>
  );
}
```

[`npx gt translate`](/docs/cli/reference/commands/translate) извлекает эти строки и генерирует перевод для каждой целевой локали, используя в качестве контекста `$context`, ваш Glossary и пользовательские промпты. В исходной локали `gt-react` 11.4.12 выводит те же три результата, что и пример с `intl-messageformat` выше.

* **Словари.** Записи, считываемые через [`useTranslations`](/docs/react/reference/hooks/use-translations), поддерживают форматирование ICU, например `{dollars, number, ::currency/USD}`.
* **Существующие JSON-каталоги.** CLI определяет next-intl и i18next (включая `i18next-icu`) по `package.json` и переводит их JSON-файлы с сохранением синтаксиса соответствующей библиотеки. (См. [JSON-файлы](/docs/cli/reference/formats/json-files).)
* **Форматирование без перевода.** Функция [`formatMessage`](/docs/platform/core/reference/utility-functions/formatting/format-message) из основной библиотеки форматирует ICU-сообщение для заданной локали.
* **Линтинг.** Правило `static-string` из `@generaltranslation/react-core-linter` выявляет некорректные ICU-сообщения. (См. [Правила линтинга](/docs/react/reference/lint-rules#static-string).)

Не все API разбирают ICU: [`tx`](/docs/node/reference/functions/tx) из `gt-node` воспринимает фигурные скобки как обычный текст. В JSX компоненты [`<Plural>`](/docs/react/reference/components/plural) и [`<Branch>`](/docs/react/reference/components/branch) внутри [`<T>`](/docs/react/reference/components/t) покрывают те же случаи plural и select, избавляя от необходимости писать синтаксис вручную.

## Ключевые выводы

* Формат сообщений ICU объединяет переменные, `plural`, `select`, `selectordinal` и `offset` в одной переводимой строке: `#` обозначает отформатированное число, а конструкции вида `=0` задают точные совпадения.
* Категории множественного числа берутся из CLDR: в английском 2 количественные формы, в польском и русском — 4, в арабском — 6, а в японском — 1, поэтому при переводе ветки приходится добавлять или удалять.
* Пишите в каждой ветке полные предложения, всегда добавляйте `other` и проверяйте синтаксис до того, как строка попадёт в рабочую среду.
* MessageFormat 2 — стабильная спецификация Unicode, встроенная в ICU4C и ICU4J, однако `Intl.MessageFormat` пока остаётся предложением TC39 на стадии Stage 1.
* [`useGT`](/docs/react/reference/hooks/use-gt) от General Translation принимает строки ICU в `gt-react` и `gt-next`, а [`npx gt translate`](/docs/cli/reference/commands/translate) генерирует переводы.

## Related questions

- [Как работать с формами множественного числа в React?](https://generaltranslation.com/ru/kb/how-to-handle-plurals-in-react.mdx): Чтобы работать с формами множественного числа в React, задайте для каждого языка по одному предложению на каждую форму множественного числа и выбирайте нужное по правилам множественного числа этого языка — ведь в английском 2 формы, в русском 4, а в арабском 6. Компонент `<Plural>` от General Translation в gt-react и gt-next принимает формы на исходном языке, а General Translation генерирует формы, нужные каждому языку перевода.
- [Альтернативы react-intl: General Translation или react-intl](https://generaltranslation.com/ru/kb/general-translation-vs-react-intl.mdx): General Translation позволяет React-командам сохранить синтаксис ICU-сообщений и при этом получить генерацию переводов, общую терминологию и проверку в панели управления. react-intl отвечает за форматирование и предоставляет инструменты FormatJS для работы с каталогами, а General Translation ещё и создаёт сами переводы.
- [Как перевести JSON-файлы i18n?](https://generaltranslation.com/ru/kb/translate-json-files.mdx): Переводите только строковые значения в JSON-файле локали. Ключи, не связанные с множественным числом, заполнители вроде {{name}} и теги оставляйте без изменений, но учтите, что ключи для форм множественного числа зависят от языка: например, для польского нужны inbox_few и inbox_many. Используйте инструмент или API с поддержкой формата, а затем разберите и проверьте результат перед вычиткой.
- [Альтернативы i18next: General Translation или i18next](https://generaltranslation.com/ru/kb/general-translation-vs-i18next.mdx): Выбирайте General Translation, если хотите хранить тексты прямо в React-компонентах и создавать переводы с учётом их JSX-структуры и контекста продукта. Можно также оставить i18next и переводить существующие каталоги с помощью General Translation. i18next отвечает за рендеринг сообщений и выбор форм множественного числа, а сами переводы предоставляет отдельный сервис.
- [Как называть ключи переводов i18n?](https://generaltranslation.com/ru/kb/i18n-key-naming.mdx): Называйте ключи по тому, где отображается текст и что он означает, например checkout.summary.title, а не по его английской формулировке. Для каждого отдельного случая использования заводите собственный ключ, даже если английский текст совпадает: в немецком кнопка Open переводится как Öffnen, а статус Open — как Offen.

## Previous and next questions

- Previous question: [Что такое система управления переводами (TMS)?](https://generaltranslation.com/ru/kb/what-is-a-translation-management-system.mdx)
- Next question: [Языковые перенаправления и SEO: URL, cookie-файл или Accept-Language?](https://generaltranslation.com/ru/kb/locale-routing-seo.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/ru/kb/sitemap.md) for every article.
