July 21, 20267 min readLoxily Team

Why Plurals Break in Game Localization — and How ICU MessageFormat Plural Fixes Them

English adds -s, Russian has four plural forms, Arabic six. Why '{count} messages' breaks across languages — and how ICU MessageFormat Plural fixes it.

Share

The most overlooked string in your game is usually "You have {count} new messages": an English developer types {count} message(s) or the sloppier {count} messages, and it looks fine — in English. Ship it to other languages and it falls apart: Russian picks one of four word forms by the last digit, Arabic has six plural categories, and Chinese needs no change yet gets an English -s bolted on. This isn't a translation-quality problem — plurals simply cannot be solved by concatenating strings, which is exactly what ICU MessageFormat Plural is for. Here's what it is, where the friction lives, and how we made it actually work in game localization.

Why "{count} items" breaks in other languages

An English developer's mental model of plurals has two slots: 1 is singular, everything else gets an -s. But that's just English. CLDR (Unicode's localization data) defines exactly which plural categories each language must distinguish, and languages differ wildly:

LanguagePlural categoriesCount
Chinese / Japanese / Koreanother1
English / Germanone other2
French / Spanish / Portugueseone many other3
Russian / Polishone few many other4
Arabiczero one two few many other6

Concatenation — gluing {count} onto one fixed chunk of text — assumes the whole world runs English's two-slot rule. So in Russian 21 needs сообщение and 22 needs сообщения (last digits 1 and 2 fall into different plural categories), yet concatenation locks in a single form and flattens both; Arabic loses its dedicated dual and zero wording, and Chinese carries an -s it never wanted. Players may not name the bug, but they immediately feel "this game wasn't built for me." We cover placeholder and variable failures broadly in the automated QA checklist for game localization; plurals are the hardest of those to catch by eye and the most corrosive to immersion.

What ICU MessageFormat Plural is

ICU MessageFormat is the de facto standard in localization. It hands "pick a word form by number" to the runtime instead of to string concatenation. An English source looks like this:

{count, plural, one {You have # new message} other {You have # new messages}}

# is the number placeholder; one / other are CLDR categories. At render time the runtime — on our frontend that's next-intl, which sits on the browser's native Intl.PluralRulesselects the correct branch for the current language and the actual value of count. Translated to Russian, the message expands to four branches; to Chinese, it collapses to one; to Arabic, it expands to six — each language getting exactly the categories its grammar requires, no more and no fewer.

The payoff: plural correctness shifts from "translators and QA eyeball it" to "data and the runtime guarantee it." But there's a real catch — nobody wants to hand-write those braces.

The cost of hand-writing ICU, and how we skip it

Ask a translator, or even a developer, to hand-type {count, plural, one {…} few {…} many {…} other {…}} and they'll get it wrong: a missing closing brace, # written as {count}, a required category dropped — any of these makes next-intl throw or degrade at runtime. So instead of handing ICU syntax to a human, we built a structured plural editor:

  • It decides how many boxes to show from the source language. The editor reads Intl.PluralRules — the same engine the runtime selects branches with — so a Chinese source gets a single other box, Russian gets four, Arabic gets six. Editor and runtime can never disagree about how many forms a language has.
  • Every box carries real example numbers. The one box is labelled "1, 21, 31…", the few box "2, 3, 4…", so the author sees at a glance which numbers each form governs.
  • Plain text promotes to a plural in one step. An existing {count} languages selected can be lifted straight into a plural skeleton — no rewrite from scratch.
  • Live preview plus save-time validation. The full ICU is assembled live below the boxes; on save it's checked with the same @formatjs parser next-intl compiles with, so malformed syntax can't be persisted — a broken plural never reaches the pipeline or the runtime.

Authors just fill labelled boxes with natural language; the editor assembles the ICU on save. Get the source structure right, and every target language has a correct basis to expand from. Here's what it looks like in the product — a labelled box per form with example numbers, the full ICU assembled live below:

Loxily's structured plural editor: an English source ("You have {count} new messages") is recognised as a plural and split into its two CLDR forms — ONE and OTHER — each a labelled box with example numbers, with the full ICU MessageFormat assembled live below, so authors fill natural language and never hand-type braces

One source plural, expanded to each language's plural

Once the source is structured, the AI translation pipeline takes over. It does not throw the whole ICU string at a model as ordinary text — that's the classic way a plural gets "flattened" (the whole sentence stripped down to a bare {count}). Instead it expands to each target language's own CLDR category set:

  • A Chinese source (1 form) translated to Russian: the pipeline knows Russian needs one / few / many / other and has the model produce all four, rather than copying one.
  • A source that's already multi-form (say a Russian source) collapses correctly to 1 form for Chinese and re-expands to 6 for Arabic — multi-category source to multi-category target is expansion we've verified end to end.
  • Romance languages like French, Spanish, and Portuguese recently gained a many category in CLDR (for large and compact numbers); the pipeline backfills it so no form is missed.

Here's that in the product: a single-form Chinese source, translated to Russian, has its Result column expand automatically into one / few / many / other — one source string, target-side forms aligned to each language's grammar, scored 100/100:

Loxily string list: a single-other Chinese source translated to Russian, whose Result column expands automatically into the four ICU forms one / few / many / other, with a green # plural badge by the source and a 100/100 AI score

So the author maintains one source plural, and grammatically correct plurals for dozens of languages are generated by the pipeline. This "feed the model context and structure first, translate second" approach is the same throughline we describe in where AI breaks in game context — plurals are just its most structured, most machine-guaranteeable piece.

Two gates that keep broken plurals from shipping

Even a correctly expanded plural can be damaged in transit — most often a lost apostrophe escape ('{0}' turned into a quoted live argument) or a branch left untranslated. So we run automatic gates in the translation pipeline:

  1. Plural-parity checks. Every target translation is checked against the CLDR category set its language requires; anything with a missing category, a swapped form, or a plural flattened to a bare placeholder is sent back for re-translation rather than passed silently.
  2. Escape and placeholder guards. Dedicated interception for ICU apostrophe-escape loss and translated placeholders, so live arguments don't throw at runtime or render as garbage.

Plural bugs, unlike typos, usually only surface under a real language and a real number, which makes them a great fit for in-game real-time string fixing as a backstop: if a player does hit one, you can locate the exact string and fix it in minutes without waiting for the next build. But catching it at the source is cheaper still — which is exactly what the editor plus these automatic checks are for.

Conclusion

Plurals are the textbook case of "English intuition betraying you": the add-an--s model holds for English and for nothing else. The right move isn't asking translators to hand-write braces — it's handing "pick the form by language" back to the standard (ICU MessageFormat Plural) and the runtime (Intl.PluralRules), then lowering the authoring cost with a structured editor, expanding per language in the pipeline, and blocking broken branches with validation gates. One actionable takeaway: from now on, write any number-bearing UI string as a plural at the source — stop concatenating {count} items. What you save is the cost of reworking dozens of languages one by one.

Related articles