Frontend Development › UI/UX for Engineers
Pluralization and ICU Messages
Handling plural rules that differ between languages.
Also known as: pluralization, icu messages, plurals i18n
Pluralization selects the right word form by count — and counts work differently per language (English: one/other; French: 0/1 singular; Russian/Arabic: several plural categories). ICU messages encode the rules declaratively: {count, plural, one {# item} other {# items}}, with the runtime picking the locale-correct branch.
ICU: "{n, plural, one {# message} other {# messages}}"
n=1 → "1 message" n=5 → "5 messages" (Russian/Arabic pick more branches)
Naive n === 1 ? singular : plural breaks beyond English — and string concatenation breaks translation generally. ICU (via Intl.PluralRules or FormatJS-style libraries) keeps whole sentences translatable with variables, plurals, selects (gender) and number formatting composed.
The classic mistakes:
- English-only ternaries.
count === 1logic shipped globally mistranslates most languages. ICU plural blocks from the start. - Split sentences.
"You have" + n + "messages"can’t reorder for languages with different word order. One message, variables inside. - Ignoring
IntlAPIs. Hand-rolled plural maps duplicateIntl.PluralRulesbadly. Use the platform’s locale data. - Zero handling. “0 items” vs “no items” — some locales (and styles) prefer explicit zero forms. Design the zero case deliberately.
- Gender and selects. Beyond plurals, gendered adjectives and formality need
selectbranches. Collect translator requirements per language, not per English assumption. - Untranslated fallback. Missing plural translations shouldn’t crash or blank. Fallback chains to base language, never to nothing.
- Testing English only. Plural bugs hide until real locales render. Test with plural-rich locales (Russian, Arabic) and pseudolocales in CI.
The rule: whole sentences as ICU messages, plurals/selects/numbers inside, platform locale data underneath. Counts are grammar — conjugate them properly.