# What Is ICU MessageFormat? Plurals and Select Explained

> ICU MessageFormat is the standard syntax for strings that change with a number or a choice: plurals, select and placeholders. How it works, and where it bites.

Source: https://stringlane.app/docs/icu-messageformat-explained
Last updated: 2026-08-19

ICU MessageFormat is a syntax for writing strings that vary based on a number or a category: "1 item" versus "2 items", or gender- and platform-specific phrasing. It comes from the **International Components for Unicode (ICU)** project, and it is supported across Flutter ARB, iOS `.stringsdict` / String Catalogs, and i18next. If you localize an app, you will eventually write it.

## The problem ICU solves

You cannot translate plurals by gluing a number to a noun. English has two forms ("1 item", "2 items"); Ukrainian has four; Arabic has six. Hard-coding `"$count items"` produces broken grammar in most of the world. ICU lets one string express every form, and the runtime picks the right one for the active locale and number.

## Plural syntax

```
{count, plural, one {# item} other {# items}}
```

- **`count`**: the variable the rule keys off.
- **`plural`**: the message type.
- **`one`, `other`**: plural categories. `#` is replaced by the number at runtime.

ICU defines six plural categories: `zero`, `one`, `two`, `few`, `many`, and `other`. Languages use different subsets: English uses only `one` and `other`; Ukrainian uses `one`, `few`, `many`, and `other`; Arabic uses all six. **`other` is always required**. It is the fallback when no other category matches.

## Select syntax

`select` branches on an arbitrary string value rather than a number: most often grammatical gender:

```
{gender, select, male {He liked this} female {She liked this} other {They liked this}}
```

Like `plural`, `select` requires an `other` branch.

## Where ICU bites

The syntax is unforgiving. A missing brace, a dropped category, or an `other` branch that got lost in translation produces a string that compiles but renders wrong, or crashes the formatter. The common failure modes are:

- A translator deletes the `other` branch a language needs.
- A required plural category for the target language is missing.
- Braces get unbalanced during editing.
- A placeholder like `{count}` survives in the source but vanishes from a translation.

These are hard to catch by reading raw files, because the error is structural, not a typo you can see.

## How StringLane works with ICU

StringLane validates ICU MessageFormat as you type. Its validation engine checks that each locale carries the plural categories its language requires, flags malformed or unbalanced syntax, and surfaces missing placeholders: all inline, per cell. When the structure is broken, [Fix with AI](/docs/fix-with-ai) repairs the ICU skeleton while preserving your translated text, so you do not have to retranslate.

## Next steps

- **[Working with ICU Plurals and Select Forms](/docs/working-with-icu-plurals)**: write and validate ICU strings hands-on.
- **[How to Read Validation Badges](/docs/read-validation-badges)**: what the ICU, Param, and Missing badges mean.
- **[How to Fix Issues with AI](/docs/fix-with-ai)**: repair ICU and placeholder errors without retranslating.

## Frequently asked questions

### What is the difference between plural and select in ICU?

plural branches on a number and uses the CLDR categories for the target language (zero, one, two, few, many, other). select branches on an arbitrary string value you supply, such as a gender or a role, and you define the option names yourself. Both require an other branch as the fallback.

### Why do I need a few or many plural category?

Because the target language has grammatical forms English does not. Ukrainian and Polish need few and many, Arabic needs all six. Translating one and other alone leaves those forms to fall through to other, which produces grammatically wrong text for whole ranges of numbers.

### Why does # show up literally in my Flutter app?

Because Flutter's gen-l10n does not implement ICU's pound token. In most ICU runtimes # is replaced by the number, but in Flutter it renders as a literal #. Use the explicit variable — {count} instead of # — in ARB files.

### Is the other branch really always required?

Yes, for both plural and select. It is the fallback used when no other branch matches, and a message without one fails to parse. This is the single most common ICU error.
