# Working with ICU Plurals and Select Forms

> Write, validate, and fix ICU MessageFormat plural and select strings directly in StringLane, without leaving the editor.

Source: https://stringlane.app/docs/working-with-icu-plurals
Last updated: 2026-08-19

ICU MessageFormat is a standard for writing strings that change based on a number or a choice: things like "1 item" vs "2 items", or gender-specific phrasing. Flutter ARB, iOS `.stringsdict`, and i18next all support ICU. StringLane has built-in helpers to write and validate these strings correctly.

> New to the syntax? Start with [What Is ICU MessageFormat?](/docs/icu-messageformat-explained) for a plain-English overview, then come back here to write and validate it in StringLane.

## What is ICU MessageFormat?

A basic plural string looks like this:

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

Breaking it down:
- `count`: the variable name
- `plural`: the ICU message type
- `one`, `other`: plural categories that apply to different numbers
- `# item`: the text, where `#` is replaced by the number at runtime

### Plural categories

ICU defines six plural categories. Not all languages use all six:

| Category | When it applies (English) |
|---|---|
| `zero` | Exactly 0 |
| `one` | Exactly 1 |
| `two` | Exactly 2 |
| `few` | Small numbers (language-specific) |
| `many` | Large numbers (language-specific) |
| `other` | All other cases: **always required** |

English only uses `one` and `other`. Arabic uses all six. Ukrainian uses `one`, `few`, `many`, and `other`. StringLane's validation engine checks that each locale has the plural categories required for that language.

### Select forms

`select` works like a conditional:

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

Use `select` for grammatically gendered strings. Unlike `plural`, `select` has no standard category set. You define the options yourself. `other` is always required as a fallback.

## Writing plurals in StringLane

### Using the ICU Format Helper

When you're editing a cell, click the **`{}`** button next to the value (or press **⌘I**) to open the **ICU Format Helper**. It shows a guided form where you fill in each plural category. StringLane assembles the correct ICU syntax from your inputs.

The ICU button is visible on every row of an ICU-supporting format. You can use it to promote a plain string into an ICU plural without typing the syntax first.

![ICU editor raw tab showing the ICU MessageFormat string being edited](/docs-screenshots/icu-editor-raw-tab.webp)

### Using the Visual ICU Editor

For more complex strings, the **Visual ICU Editor** shows each category as its own field and previews the output in real time. The editor opens on the **Build** tab; switch to **Raw** to drop into the ICU textarea, or **Reference** for a copy-ready cheat-sheet of plural / select / selectordinal templates.

![Per-key detail pane showing a plural key with one locale panel per language and per-quantity rows](/docs-screenshots/detail-pane-plural.webp)

The visual editor validates as you type, if a placeholder like `#` is missing in one category but present in another, a warning appears immediately.

### Per-locale plural panels

Plural keys render as one panel per locale in the detail pane. Each panel has a **`{}`** button in its header that pre-fills the ICU editor's Build tab with all current plural forms for that locale. Each quantity row has its own value field, character-width bar, and validation rail.

When a locale needs a plural category that the base locale doesn't have (e.g. Ukrainian needs `few` and `many` but English only has `one`/`other`), the **Add form:** chips at the bottom of that locale's panel highlight the CLDR-required categories so they stand out from optional ones.

## Understanding ICU validation badges

StringLane validates every translation when a project loads and after each edit. ICU issues appear as a red **ICU** badge on the affected cell.

Common ICU errors and their fixes:

| Error | Cause | Fix |
|---|---|---|
| `ICU parse error` | Mismatched braces `{` `}`, or invalid syntax | Check that every opening `{` has a matching `}` |
| `Missing "other" category` | The `other` clause is absent | Add `other {…}`. It is always required |
| `Placeholder mismatch` | `#` or a named variable is in some categories but not others | Use the visual editor to add the placeholder to every category |
| `Literal # in plural` | A bare `#` in an ARB plural branch — Flutter will not substitute it | Replace `#` with `{yourVariable}`. See below |

Click the **ICU** badge to see the full error message in a tooltip.

![Issues panel listing every validation problem as its own row, each naming the key and locale with a Fix with AI button, and Fix all with AI at the foot](/docs-screenshots/issues-panel-right.webp)

### Fix-with-AI for ICU errors

Any plural or select string that fails to parse, or is missing the `other` category, gets a **Fix with AI** button on its row in the [Issues Panel](/docs/use-issues-panel). Clicking it asks the AI to repair the syntax while preserving your translation text. The footer **Fix all with AI** sweeps every affected row in one pass. See [How to Fix Issues with AI](/docs/fix-with-ai).

### CLDR-aware plural translate

When you click ✨ on a plural key, StringLane auto-fills every quantity the target language requires from CLDR rules, translating into Ukrainian from an English source that has only `one`/`other` produces all four forms (`one`, `few`, `many`, `other`) in one pass. No need to manually add `+ few` / `+ many` first.

## Flutter renders `#` literally

This one costs people a release, so it gets its own section.

In standard ICU, `#` inside a plural branch is replaced by the number at runtime. **Flutter's `gen-l10n` does not implement the pound token.** Write this in an ARB file:

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

…and your Flutter app displays a literal **"# items"** on screen. Not "3 items". The `#`.

Use the explicit variable instead:

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

StringLane checks for this on ARB projects. Every standalone `#` in a plural branch raises a **Literal # in plural** warning in the [Issues Panel](/docs/use-issues-panel) — in *every* locale including your base one, because the bug is in the string, not the translation. Each row has a **Fix with AI** button that rewrites `#` to the plural variable, and it tries that deterministically before calling the model.

Deliberate literals are safe: `rank #1` and `#{count}` are not flagged. Only a bare `#` standing where the number should be.

The ICU editor is format-aware too — open it on an ARB project and every template and example it offers uses `{count}` rather than `#`, so following the built-in guidance cannot produce the bug.

This applies to **ARB only**. iOS `.strings` / `.stringsdict`, Android XML and i18next all substitute `#` correctly and are not checked.

## Common mistakes

**Missing `other` category.** Every plural and select string must have an `other` clause: no exceptions. This is the most common ICU error. Fix:

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

**Wrong variable name.** In ARB, the placeholder in the ICU string must match the ARB metadata. If your key has `@description` saying the variable is `itemCount`, use `itemCount` in the ICU string, not `count` or `n`.

**Copying English plural to languages with more categories.** Ukrainian, Arabic, Polish, and others need `few` and `many` in addition to `one` and `other`. StringLane flags this as a validation warning. Use the visual editor to add the missing categories.

## What's next

- **[How to Read Validation Badges](/docs/read-validation-badges)**: all badge types explained
- **[How to Fix Placeholder Mismatches](/docs/fix-placeholder-mismatches)**: the Param badge
- **[Set Up AI Translation in 5 Minutes](/docs/set-up-ai-translation)**: let the model generate plural forms for each locale
