# App Localization: ICU Plurals

Part of the HebrewCore documentation. Web: https://hc.itsbaba.com/docs#icu · Whole docs: https://hc.itsbaba.com/docs.md · Index: https://hc.itsbaba.com/llms.txt

Interface strings with plurals cannot be translated word for word: English has two plural forms, Hebrew has a separate form for two, and Russian has four. Set `message_format: "icu"` on `/v1/translate` or `/v1/translate/batch` and send an ICU MessageFormat string. You get back a valid ICU MessageFormat string in the target language, ready for your i18n library (FormatJS, i18next ICU, ICU4J, Android and Apple string catalogs that accept ICU).

| Rule | Detail |
| --- | --- |
| Arguments | `{name}`, `{count, number}` and `{date, date, short}` are kept exactly. |
| Plural branches | `plural` and `selectordinal` branches are rewritten for the target language's CLDR plural categories. Exact matches such as `=0` and `=1` are kept when the source has them. |
| `#` | Kept wherever the source used it. |
| `select` | Branch keys (`female`, `male`, `other`) are kept; each branch is translated. |
| Nesting | Nested messages are supported, for example a `select` inside a `plural` branch. |
| Validation | The input is parsed first: invalid ICU returns `400 invalid_request` with code `invalid_message_format`. The output is parsed and validated before it is returned; if it cannot be made valid, the source string comes back with `protection.status: "fallback"`. |
| Response | The usual translate response. `translation.text` is the ICU string. In a batch, every item is one message. |

## Plural Categories

| Language | Categories | Example for `{count}` days |
| --- | --- | --- |
| English (`en`) | `one`, `other` | 1 day · 2 days · 5 days |
| Hebrew (`he`) | `one`, `two`, `other` | 1 יום · 2 ימים · 5 ימים |
| Russian (`ru`) | `one`, `few`, `many`, `other` | 1 день · 2 дня · 5 дней · 1,5 дня |

## English to Hebrew

```http
POST /v1/translate
{
  "text": "{count, plural, one {# day left in your trial} other {# days left in your trial}}",
  "source_lang": "en",
  "target_lang": "he",
  "message_format": "icu"
}

{
  "translation": {
    "text": "{count, plural, one {נותר # יום לתקופת הניסיון} two {נותרו # ימים לתקופת הניסיון} other {נותרו # ימים לתקופת הניסיון}}",
    "lang": "he", "dir": "rtl",
    …
  },
  "usage": { "characters": 82 }
}
```

## English to Russian, with an Exact Match

```http
POST /v1/translate
{ "text": "{count, plural, =0 {Your cart is empty} one {# item in your cart} other {# items in your cart}}",
  "target_lang": "ru", "message_format": "icu" }

→ "{count, plural, =0 {Ваша корзина пуста} one {# товар в корзине} few {# товара в корзине} many {# товаров в корзине} other {# товара в корзине}}"
```

## Select Messages

```http
POST /v1/translate
{ "text": "{gender, select, female {{name} invited you to her workspace} male {{name} invited you to his workspace} other {{name} invited you to their workspace}}",
  "target_lang": "he", "message_format": "icu", "listener_gender": "female" }

→ "{gender, select, female {{name} הזמינה אותך לסביבת העבודה שלה} male {{name} הזמין אותך לסביבת העבודה שלו} other {{name} הזמינו אותך לסביבת העבודה}}"
```

> Without `message_format: "icu"`, arguments such as `{count}` are still protected, but plural branches are translated as plain text and keep the source language's categories. Always set it for ICU strings.
