A plural category is not a number
A key named zero can mean exactly 0 in one library and a whole set of numbers in one language, so the friendly empty state you wrote for English can become the sentence shown for 10, 11 and 20 elsewhere.

The _zero key has two meanings
This catalog looks considerate. English normally says “0 files”, but a product writer preferred the empty state “No files”. i18next documents a convenient way to get it: when the count is 0 and a _zero entry exists, that entry wins over the language’s usual plural suffix 10.
01{02 "files_zero": "No files",03 "files_one": "{{count}} file",04 "files_other": "{{count}} files"05}Now translate the same keys into Latvian. In current CLDR data, the Latvian category named zero contains 0, every integer ending in 0, and the integers whose final two digits run from 11 through 19. So 10, 11 and 20 all select zero, even though none of them means “no files” 2,3.
The collision is not hypothetical. i18next asks the platform’sIntl.PluralRules implementation for a category suffix. Its translator also adds the same _zero suffix as an exact-count shortcut when count === 0, and checks that shortcut first. One key therefore means two things: “the value is exactly 0” and “the locale’s plural category is zero” 11,12.
English hides the conflict because it has no grammaticalzero category. Latvian reveals it because the name of the category sounds like the number developers put into it.
A category name is a label for a set
CLDR’s six cardinal labels are zero, one,two, few, many and the mandatory fallback other. They are mnemonic tags for grammatical sets, not definitions of the numbers inside those sets. The rules are locale-specific, and the same tag can cover very different numbers in different languages 1,2.
| Locale | Category | Some values CLDR puts in it |
|---|---|---|
| English | one | 1 |
| French | one | 0, 1, 0.0, 1.5 |
| Russian | one | 1, 21, 31, 101 |
| Russian | many | 0, 5 to 20, 25 to 30 |
| Polish | few | 2 to 4, 22 to 24 |
| Arabic | zero | 0 |
| Welsh | zero | 0 |
| Latvian | zero | 0, 10 to 20, 30, 40 |
The full current cardinal data has only two languages whosezero set contains integers other than 0: Latvian and Prussian, which share the same rule. Arabic and Welsh do have azero category, but it is the singleton value 0. Russian puts 0 in many. French puts 0 in one. A branch name tells you nothing useful until it is paired with a locale and that locale’s rule 2,3.
This is the lower-level version of a point from software localization is not a translation project: the branch structure belongs to the target language. Readingone as the integer 1 merely smuggles the English rule back into a structure meant to escape it.
An empty state is an exact-value case
“No files” is not a grammatical plural form. It is special product copy for one exact state. CLDR makes that distinction explicitly: category cases such as zero andone can each cover several formatted values, while exact cases for 0 and 1 match those values and take precedence 1.
ICU MessageFormat writes the distinction into the syntax. A keyword such as one invokes the locale rule. A selector such as=0 matches the exact number, and ICU checks explicit values before plural categories 4.
01{count, plural,02 =0 {No files}03 one {# file}04 other {# files}05}MessageFormat 2 uses a numeric literal rather than the older equal sign syntax, but preserves the same model. Numeric selectors are exact matches, keyword selectors request locale categories, and the exact match is preferred when both apply 5.
That separation matters beyond plural grammar. “No files” may be a heading with different punctuation, a call to action, or an accessible announcement that bears little resemblance to the counted sentence. Giving it its own exact branch or message key records that it is different copy. The source did not become safer because its translation was linguistically perfect; the parser still decides which perfect sentence is shown.
The same key means different things across platforms
The hard part is not that platforms disagree about Latvian. It is that they disagree about what a field named zero means.
| System | Meaning of zero | Exact 0 |
|---|---|---|
| ICU MessageFormat | Locale category 4 | =0 |
| MessageFormat 2 | Locale category keyword 5 | Numeric literal selector |
| Android quantities | Locale category, ignored for English 0 6 | Separate resource or application branch |
| Apple .stringsdict | Apple documents an English zero branch for 0; its behavior when that file moves to a broader CLDR zero locale is not documented 8 | Documented through zero in the legacy format |
| i18next | Both locale category and a count-0 shortcut 10,11 | _zero |
| Rails I18n | Legacy exact-0 shortcut; current backend says that use is incorrect and keeps it for compatibility 13,14 | "0" |
| GNU gettext | No named categories, only numeric form indexes 17 | Separate message or application branch |
Apple’s newer String Catalog documentation shows Xcode adding the forms a target language needs, such as one andother for English and four forms for Russian. It does not document an exact numeric selector or explain how the legacy Englishzero example should migrate to a language wherezero names a wider category 9. The honest answer for that collision is therefore not documented, not a guessed runtime behavior.
Rails supplies a useful history of the same design mistake. The Rails guide still describes a default English shortcut where an existing:zero key wins at count 0. The current I18n pluralization backend distinguishes exact string keys "0" and"1" from categories, and says treating:zero as exact 0 is incorrect but retained for backwards compatibility 13,14.
The expected Rails and Latvian collision does not exist in the currentrails-i18n package for a less comforting reason: its Latvian rule exposes only one and other, and sends all non-one values to other. It omits CLDR’s Latvian zero category entirely 15,16. There is no same-key collision because one side of it is missing.
The selector must see the number the reader sees
The category is not selected from the mathematical value alone. CLDR calls the input a source number, which includes the visible representation. It says that 1.0 and 1.00are different source numbers even though they are the same floating-point value, and that they can take different plural categories. Its operands include the number of visible fraction digits, the fraction with and without trailing zeroes, and the compact decimal exponent 1.
English is enough to demonstrate the consequence. Itsone rule requires an integer value of 1 with zero visible fraction digits. 1 file uses one, while1.0 files uses other 2.
01const plain = new Intl.PluralRules("en");02const fixed = new Intl.PluralRules("en", {03 minimumFractionDigits: 1,04});0506plain.select(1); // "one": 1 file07fixed.select(1); // "other": 1.0 filesECMA-402 routes those digit options through the plural-rule initializer, and MDN documents the supported minimum and maximum fraction and significant digit settings 18,19. But Intl.PluralRules does not accept number-format notation such as compact. The current specification initializes it with standard notation, so CLDR’s compact exponent operand cannot be supplied through this API.
Several older APIs cannot even express the simpler decimal case. Android’s getQuantityString takes an integer quantity, and GNU ngettext takes an unsigned integer. The gettext manual recommends a neutral string for floating-point values rather than plural selection 7,17. i18next passes a JavaScript number to Intl.PluralRuleswithout a way for the translation lookup to carry the digit options, so a separately formatted 1.0 can still select the rule for 1 12.
The selector and formatter therefore need the same locale, value and digit settings. If they are two unrelated calls, the grammar can describe a number the reader never sees.
The case for treating zero as 0
There is a strong case for the shortcut. Many interface counters are integers. An empty state often deserves friendlier wording. Exact selectors add syntax, and application branches add keys and code. In 2019, an i18next user asking how to show “No photos” for English 0 said the alternative was an if statement. In 2021, another user reported _zero not firing and expected precisely that convenience 20,21.
CLDR itself encourages plural APIs to offer explicit cases for 0 and 1 because natural empty-state wording is common 1. The mistake was not adding the capability. It was giving the exact case the same key as a grammatical category.
OpenStreetMap encountered that distinction in production. A 2023 issue reported that an English zero translation made Latvian either wrong or impossible to translate. The project merged a change that removed the English zero form and used separate no-results messages, explicitly because the key also serves numbers ending in zero in other languages 22,23.
These reports prove the bug exists. They do not establish how common it is. No published prevalence figure was found, and counting public issues would not produce one.
Give exact states and grammatical categories separate slots
The safe catalog has two axes: exact product states and grammatical categories. Keep them separate even when the framework invites you to collapse them.
- Put empty-state copy in an exact selector such as
=0, a numeric MessageFormat 2 selector, or a separate key such asfiles.empty. Do not put it in a category-shaped key. - Treat
zero,oneandmanyas opaque locale labels. Show translators the CLDR samples for the active language rather than asking them to infer values from an English word. - Format and select from the same number contract. The contract includes minimum and maximum fraction digits, significant digits and, where applicable, compact notation.
- Test one sample from every target category, every exact branch and every visible-number shape the interface can render. For Latvian, an empty-state test at 0 is not a category test at 10 or 11.
A pseudolocalized build can prove that the plural branches render and fit. It cannot prove that the target locale maps numbers to the right branches. That takes the real locale rules and representative values.
The deeper pattern is the same one behind formal or informal “you”. An English-looking label tempts the author to store an English decision in a cross-language slot. The cure is not a smarter label. It is a data model that records the two facts separately.
References
- 1.Unicode Consortium Unicode Locale Data Markup Language, Part 3: Numbers Unicode Technical Standard #35, 2026
- 2.Unicode Consortium Language Plural Rules CLDR 49 supplemental charts, 2026
- 3.Unicode Consortium Cardinal plural rules source data CLDR plurals.xml
- 4.Unicode Consortium Formatting Messages ICU User Guide
- 5.Unicode Consortium Unicode Locale Data Markup Language, Part 9: MessageFormat Unicode Technical Standard #35, 2026
- 6.Android Developers String resources: Quantity strings Android Developers documentation
- 7.Android Developers Resources Android API reference
- 8.Apple Localizing strings that contain plurals Xcode documentation
- 9.Apple Localizing and varying text with a string catalog Xcode documentation
- 10.i18next Plurals i18next documentation
- 11.i18next maintainers Translator.js i18next source code
- 12.i18next maintainers PluralResolver.js i18next source code
- 13.Rails Core Team Rails Internationalization API Ruby on Rails Guides
- 14.Ruby I18n maintainers Pluralization backend Ruby I18n source code
- 15.rails-i18n maintainers Latvian pluralization locale rails-i18n source code
- 16.rails-i18n maintainers Pluralization rules rails-i18n source code
- 17.GNU Project Additional functions for plural forms GNU gettext manual, 2026
- 18.Ecma International PluralRules Objects ECMAScript Internationalization API Specification
- 19.MDN contributors Intl.PluralRules() constructor MDN Web Docs
- 20.brightpixels How to add 0 plural option for English i18next issue #1220, 2019
- 21.bitttttten Zero suffix not working with plurals react-i18next issue #1392, 2021
- 22.OpenStreetMap contributors Separate translations for zero and zero like numbers openstreetmap-website issue #3997, 2023
- 23.Andy Allan Avoid using the zero key for pluralisation in English openstreetmap-website pull request #4077, 2023