ICU Message Format: the braces a translation has to keep
ICU Message Format: the braces a translation has to keep
ICU Message Format is the pattern syntax inside strings like {count, plural, one {# item} other {# items}}. Curly braces mark the parts that change (a name, a number, a set of plural or select branches), so a translator rewrites one whole message instead of gluing sentence fragments together. Flutter .arb files and FormatJS JSON catalogs use the same shape.
Each argument in braces either inserts a value, like {name}, or picks a branch. A plural argument picks a branch by number, using the plural rules of the target language. A select argument picks a branch by a fixed keyword such as male, female or other. A plural message always needs an other branch.
The keyword one names a CLDR plural category and does not mean the number 1: in English it matches the integer 1 only, while in French it also covers 0 and 1.5. A translator keeps argument names and keywords as written and translates the text inside the branches.
ICU the library, MessageFormat the syntax
ICU stands for International Components for Unicode, a set of libraries (ICU4C and ICU4J) for handling Unicode text. MessageFormat is one of its formatting tools. Sorting and searching text belong to other parts of the library and have nothing to do with the braces in your strings file.
The ICU user guide page Formatting Messages describes messages as user-visible strings, often with variable elements like names, numbers and dates. You write and translate each message as a single unit, with placeholders in curly braces, so the translator can move the variable elements where the target grammar wants them. The page discourages the old choice argument type, which leaves plural and select as the two you'll write.
Three messages, labeled
All three examples below are classic MessageFormat, the syntax you'll find in .arb files and react-intl catalogs.
A placeholder
{name} shared a folder with you.
The translator can move {name} to the end of the sentence or the middle. The word name stays in English, because your code passes the value under that key.
A plural
{count, plural, =0 {No photos} one {# photo} other {# photos}}
count is the argument, plural is the type, and the rest are branches. =0 matches the exact value 0. one and other are plural categories. FormatJS documents that # is replaced with the formatted number, so you don't need to repeat {count} inside a branch.
The ICU guide adds two rules. An English message with both =0 and =1 doesn't need a one variant, because one would never be selected, but it always needs other. And the translation system and the translator together need to add one, few and so on "if and as necessary per target language".
A select
{gender, select, male {He left a comment} female {She left a comment} other {They left a comment}}
Each branch holds a full sentence. That matches the guide's advice: "If possible, use complex arguments as the outermost structure of a message, and write full sentences in their sub-messages. If you have nested select and plural arguments, place the select arguments (with their fixed sets of choices) on the outside and nest the plural arguments (hopefully at most one) inside."
Numbers and dates inside a message
An argument can carry a style. The guide lists short, medium, long and full for dates and times, and integer, currency and percent for numbers. A skeleton in the style position starts with :: so ICU doesn't read it as a pattern. The guide's own C++ example:
At {1,time,::jmm} on {1,date,::dMMMM}, there was {2} on planet {0,number,integer}.
The guide discourages hand-written number and date patterns, since they are locale-sensitive and would need to be localized themselves. For a translator, ::jmm and integer count as code and stay untouched.
The keyword one is not the number 1
CLDR's Plural Rules define six categories: zero, one (singular), two (dual), few (paucal), many and other. other is required. It's the general plural form, and the only form in a language that has a single one. CLDR says the categories don't follow traditional grammar; they describe the changes a phrase needs when a number in it changes value.
The Language Plural Rules chart (the CLDR 49 chart, checked November 2026) shows how far apart five languages sit for cardinal numbers:
| Language | Cardinal categories | Chart example that surprises English speakers |
|---|---|---|
| English | one, other | one is the integer 1 only. 0 and 1.5 are other ("1.5 days"). |
| French | one, many, other | one covers 0 and 1, plus decimals from 0.0 to 1.5 ("1,5 jour"). many is for large round numbers such as 1000000. |
| Arabic | zero, one, two, few, many, other | 3 is few, 11 is many, 100 is other. |
| Japanese | other | 15 and 1.5 both use other. There is no one. |
| Polish | one, few, many, other | 2 to 4 and 22 to 24 are few, but 12 to 14 are not. 0 and 5 to 19 are many. 1.5 is other. |
An English one/other pair is a starting point. A Polish translation of the photos message needs one, few, many and other. An Arabic one can use all six. A Japanese one can collapse to other alone, and a French one has to read right for 0, because French puts 0 in one.
The chart also explains a dead branch you'll see in English files. English puts 0 in other, so a zero {...} branch never matches 0 in English. Write =0 when you want special text for zero.
Where the same braces show up
A Flutter .arb value
Flutter's internationalization guide wraps each placeholder, a valid Dart identifier, in curly braces and declares it in the placeholders object of the .arb file. Its plural example:
"nWombats": "{count, plural, =0{no wombats} =1{1 wombat} other{{count} wombats}}"
The guide's plural form accepts =0, =1, =2, few, many and other, and only other is required. Select looks the same as in ICU, {gender, select, male{he} female{she} other{they}}, and Flutter's select comparison is case-sensitive. Setup, l10n.yaml and generator errors are covered in ARB files and gen-l10n.
A FormatJS JSON value
FormatJS's migration note from i18next keeps the whole plural in one string:
"itemCount": "{count, plural, zero {No items} one {# item} other {# items}}"
That zero branch is the English dead branch from the chart: in an English locale it won't match 0. Swap it for =0 if your source language is English.
Angular's i18n guide uses the same plural and select clauses, falls back to other when no category matches, and notes that the en-US plural function doesn't support few.
A JSON file isn't ICU by default. If you see {{name}}, you're looking at i18next. Check which file your stack actually ships before you translate, and see JSON catalogs for i18next and react-intl for the library side.
Formats that look similar and use other syntax
Android quantity strings
Android's string resources guide uses XML. A <plurals> element holds <item> children whose quantity is zero, one, two, few, many or other. It uses the same category names with no braces:
<plurals name="songs_found">
<item quantity="one">%d song found.</item>
<item quantity="other">%d songs found.</item>
</plurals>
The guide says to always supply one and other, and that languages such as Chinese don't make these distinctions, so they always get other. More in Android quantity strings.
Apple .stringsdict
Apple's plurals page puts strings with variable amounts in a .stringsdict property list. Its English example uses NSStringPluralRuleType with zero, one and other keys for "No homes found", "%d home found" and "%d homes found". It uses the same category names in plist syntax.
i18next JSON
i18next interpolates with double braces, {{name}}, and splits plurals into separate keys: item, item_plural, item_zero. Gender goes in a context suffix on the key. FormatJS writes the same gender message as {gender, select, male {He left} female {She left} other {They left}}.
MessageFormat 2.0
MF2 is a newer, different syntax. Unicode's CLDR 47 release post of 13 March 2025 says the spec moved from Final Candidate to Stable, with tech-preview implementations in ICU4J and ICU4C (ICU 76) and in JavaScript (messageformat 4.0). ICU's user guide still calls its MF2 MessageFormatter a Technical Preview API. MF2 placeholders look like this, and selection uses .match:
Hello {$name}, your card expires on {$exp :datetime skeleton=yMMMdE}!
The braces in your .arb file or react-intl catalog are classic MessageFormat. A working file in that syntax needs no conversion.
The apostrophe rule
Syntax characters inside the text need quoting with pairs of ASCII apostrophes, and two apostrophes in a row always stand for one. The ICU guide's illustration: This '{isn''t}' obvious comes out as This {isn't} obvious. Before ICU 4.8, every ASCII apostrophe started quoted text. Since 4.8, one starts quoted text only when it sits right before a character that requires quoting, and nested messages follow the same rule.
The guide recommends the real apostrophe (U+2019) for human-readable text and the ASCII one (U+0027) only for syntax. Flutter follows the same rule with use-escaping on: Hello! '{Isn''t}' this a wonderful day? prints Hello! {Isn't} this a wonderful day?.
Translating ICU strings with Localize Your App
Localize Your App is a web tool that translates app localization files with AI, and it accepts .arb and i18n JSON. Placeholders are locked. A glossary keeps product terms consistent, and translation memory reuses approved strings, with those repeats free. You review source and target side by side, edit any line, and download the same format.
It isn't an ICU editor, so don't count on it to add a missing other, repair apostrophes, or check that a Polish file has few and many. Read those branches yourself in the side-by-side view before you ship.
Credits are one-time and never expire, with no subscription. 1 credit translates 1 string of up to 500 characters into 1 language, and Starter is $9 for 8,000 credits. You get a 14-day full refund if the files don't compile or you're not happy. It has no CLI or git sync, and it doesn't store the uploaded file. You can translate the file without a subscription tonight.
FAQ
What is ICU in text?
In localization, ICU text usually means a string written in ICU's MessageFormat syntax: curly-brace placeholders, plus plural and select branches that hold whole sentences.
What is ICU in programming?
ICU is International Components for Unicode, a set of libraries (ICU4C and ICU4J) for Unicode text. MessageFormat is one of its formatting tools.
What is the ICU plural?
A {count, plural, ...} argument picks a sub-message based on the number and the plural rules of the language. Its branches are CLDR categories (zero, one, two, few, many, other) or exact values like =0, and other is required.
What is the ICU format?
People usually mean ICU Message Format: one user-visible string per message, with variable parts in curly braces, so a translator can reorder the sentence without breaking the code that fills it in.
If you have an .arb or JSON file with plural branches waiting, translate your first file and check each branch side by side before you ship.
Ship your app in 50 languages by tonight
Upload your localization file, review side by side, download ready-to-import files for every language. One-time credits from $9 — no subscription.
No subscription. Credits never expire.