A product ships English. The code renders counts with a `count === 1 ? singular : plural` branch and builds sentences by concatenating the number with a translated noun. Arabic and Russian are added next quarter. What happens in each new locale, what does the user see, and what stops it?
Show the full answer Hide the answer
What happens, locale by locale
Unicode CLDR defines up to six cardinal plural categories a language may use - zero, one, two, few, many, other. English cardinals use two of them, which is why a two-branch ternary looks correct forever.
Russian uses four: one, few, many, other. A count of 1, 21 or 31 selects one; 2 selects few; 5 selects many. The branch sends every count except those ending in a lone 1 to the single "plural" string the translator supplied, so most counts are grammatically wrong - and counts ending in 1 but not 11 are only about 9% of integers, so "most" means almost all of them.
Arabic uses all six. 0 selects zero, 2 selects two, 3 to 10 select few, 11 to 99 select many, 100 selects other - and 101 selects few again, because the category depends on the final digits rather than magnitude. Four of the six categories have no string in the catalogue at all.
Where it amplifies
Concatenation freezes word order and blocks agreement. Russian requires the noun's case to agree with the numeral, so even a correct plural category produces a wrong sentence when the number is glued on from outside. Languages that place the quantity differently cannot be expressed at all.
Worse, the translation pipeline never asks for the missing forms. It sees one source string, so the translator is never prompted for zero, two, few or many. The defect is invisible in the workflow that exists to prevent it, which is why it is not caught before release.
What the user sees
Nothing that looks like a failure. HTTP 200, no exception, no log line - plausible text that reads as broken to a native speaker. It arrives as a complaint from a sales lead or a market manager, typically weeks after launch, and it cannot be reproduced by anyone on the team who does not speak the language.
What stops it
One message per sentence, with plural selection inside the message, resolved against the platform's CLDR data. ICU MessageFormat expresses it directly, and the browser already carries the rules - Intl.PluralRules, standardised in ECMAScript 2018, returns the category for a locale and a number, so nobody hand-writes the rule table.
The mechanism that makes it stick is a build check, not a review: fail the build when a message contains a plural placeholder and the target locale's required categories are missing from its catalogue. Pair it with pseudo-locale rendering so the defect is visible before any translation exists.
What would have to be true for it to self-heal
Nothing - this is missing data, not a transient fault. Retries, caches and fallbacks cannot invent a grammatical form that was never authored. The only automatic defence is the gate in the pipeline; the locale fallback chain makes it worse by serving a form from a language with different rules.
When not to reach for ICU
When the string contains no count, which is most strings, or when the product will only ever ship in languages with two cardinal categories - English, German, Dutch, Italian, Spanish and their neighbours. The honest rule is cost-based: ICU costs one dependency and a message-extraction step, and retrofitting it costs re-authoring every counted string plus a re-translation cycle across every locale. Adopt it when a second locale is on the roadmap, not when the first bug report arrives.