RORK LABJP
PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updatesPLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
Articles/Dev Tools
Dev Tools/2026-07-13Advanced

When "{n} items" Breaks Across Languages — Designing Quantity Strings with CLDR Plural Categories and Intl.PluralRules

The assumption that one is singular and everything else is plural falls apart in Russian and Arabic. Here is how to hold the CLDR plural categories as a map and render quantities correctly with Intl.PluralRules and i18next, drawn from localizing an indie wallpaper app into sixteen languages.

Rork547Localization8PluralsIntl.PluralRulesi18next

Premium Article

A few days after shipping a wallpaper app in sixteen languages, I got a short note from a Russian-speaking user: the favorites count "looked off somehow." On my own iPhone, in Japanese and in English, nothing was wrong.

It took me a while to trace it. I had written the count as n === 1 ? "1 item" : n + " items". If you only ever look at English and Japanese, that branch looks permanently correct. But in Russian the word form changes between two and five, and then twenty-one snaps back to the same form as one. The very premise of the ternary simply did not exist in that language.

This article reframes how quantity strings branch per language through the map of CLDR plural categories, then walks through selecting the right form with Intl.PluralRules and i18next. When you send a Rork or Expo app out into the world, this is the quiet trap I want to close before release.

Where "one is singular, everything else is plural" stops holding

The singular-versus-plural split we carry around unconsciously is really just a convenience of one language: English. Look across the world's languages and quantity forms are far more varied.

Japanese has no grammatical plural at all. "1 item" and "3 items" share the same form; you just drop n in. English is special only at one, then flat. If that were the whole world, a ternary would be enough.

The trouble starts beyond that. Russian and Ukrainian pick among three forms based on the trailing digits. Polish is similarly intricate, and Arabic carries six forms: zero, one, two, few, many, and other. A single "everything else is plural" branch cannot render any of those languages correctly.

So quantity bugs surface only in the languages a developer can't read. They never reproduce on your own device, so review never catches them. That is exactly why the per-language rules should live in a standardized map, not in your head.

Hold the CLDR plural categories as a map

That map is the CLDR (Unicode Common Locale Data Repository) plural categories. CLDR defines each language's quantity rules as a mapping onto one of six categories.

CategoryMeaningRepresentative languages
oneSingular-likeEnglish, German, Spanish (at 1)
otherDefault / everything elseJapanese, Chinese, Korean (only this)
fewSmall countsRussian, Polish, Czech
manyLarge countsRussian, Polish, Arabic
twoDualArabic, Slovenian, Welsh
zeroZeroArabic, Latvian

The point is that these are not numbers but a language's own partitioning of word forms. Russian classifying 21 as one is counterintuitive, yet you never have to memorize the rules. Let CLDR hold the map; you only supply one string per category name.

In a language like Japanese that has only other, the translator writes a single string. Whether more categories exist is the language's business, and the calling code should absorb that difference behind one identical call.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Why a ternary plural breaks in Russian, Arabic, and Polish, explained through CLDR categories
A reusable helper that selects the right form with Intl.PluralRules, plus a Hermes fallback
How to keep cardinals and ordinals apart, and how to test the languages that actually break
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

Dev Tools2026-08-22
Every bulk replace exited zero. The damage was in the lines I did not delete
Run a bulk replace over generated code and the breakage lands on the neighbouring lines, not the matched ones. Here is what broke in a live project, and a dependency-free guard that checks the invariants a replace must preserve.
Dev Tools2026-08-21
The four acceptance checks I still run after the build turns green
A successful build does not mean a shippable build. Here are the four failure classes an AI build loop cannot see, and a dependency-free script that inspects the artifact itself before you submit.
Dev Tools2026-08-20
A beta-SDK build can reach TestFlight, but it can't reach review
Builds made with a beta Xcode can be distributed through TestFlight, but they cannot be submitted for App Store review. Here is how to check which SDK produced your build, and how to protect your release profile in eas.json.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →