← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Pluralisation Rules Across Languages

i18n PHP 5.3+ Intermediate
debt(d7/e5/b5/t9)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no', and while phpstan and php-intl are listed as tools, they don't automatically flag the hardcoded English ternary pattern (`$count === 1 ? 'item' : 'items'`) as a pluralisation bug. The issue only surfaces when a Russian or Arabic user encounters garbled output — it won't crash or warn, it just silently produces wrong text for non-English locales.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to switch to ICU MessageFormat, but the common_mistakes reveal this isn't a one-liner: every pluralised string across the codebase needs to be identified, all locale translation files need plural-form variants added (e.g. Russian needs 3 forms per string), and helper functions with hardcoded English rules must be replaced. This touches translation files, code call-sites, and potentially a pluralisation helper — spanning multiple files but typically within the i18n layer rather than a full cross-cutting refactor.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). The applies_to covers both web and cli contexts, meaning any developer adding a new user-facing string must understand plural form requirements. Without a correct ICU/ngettext foundation, every new translatable string with a count carries the risk of being done wrong for non-English locales, slowing down internationalisation work across the codebase. It's not quite b7 (it doesn't reshape every change) but it does persistently tax multiple work streams.

t9 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'catastrophic trap — the obvious way is always wrong' (t9). The misconception is explicitly: 'All languages have two plural forms like English.' The common_mistakes confirm the ternary `$count === 1 ? 'item' : 'items'` is the natural, obvious solution every PHP developer reaches for — and it is silently wrong for Polish (4 forms), Arabic (6 forms), Russian (3 forms), and returns no error or warning whatsoever. The 'obvious' approach works for English and fails globally, making this a textbook t9 trap.

About DEBT scoring →

Also Known As

plural forms ngettext ICU MessageFormat plural rules

TL;DR

Different languages have different plural forms — English has two (one/other), Arabic has six, Russian has three — gettext and ICU MessageFormat handle this correctly.

Explanation

English pluralisation (1 item / 2 items) is deceptively simple. Russian has three plural forms based on the last two digits. Arabic has six forms. Polish has four. Slavic languages distinguish: 1, 2-4, 5+. ICU MessageFormat (used by PHP Intl extension's MessageFormatter) handles this universally: {count, plural, one {# item} other {# items}}. gettext uses ngettext() with locale-specific plural form rules. Never write if ($count === 1) 'item' else 'items' for an i18n app — it breaks every non-English locale.

Common Misconception

All languages have two plural forms like English — Polish has four, Arabic has six, and some languages (Japanese, Chinese) have one plural form for everything; hardcoded English rules break all of them.

Why It Matters

An application that shows '5 items' in English but '5 товаров' in Russian (which should be '5 товаров' not '5 товара') fails its Russian users — ICU MessageFormat handles all 200+ locale plural rules automatically.

Common Mistakes

  • Ternary pluralisation: $count === 1 ? 'item' : 'items' — breaks for Russian, Arabic, Polish.
  • Not using ngettext() for gettext plurals — gettext() alone doesn't handle plural forms.
  • Hardcoding English plural rule in a helper function shared across locales.
  • Not including plural strings for all required forms — Russian needs 3 translations per string.

Code Examples

✗ Vulnerable
// Hardcoded English — wrong for most languages:
function formatCount(int $count, string $noun): string {
    return $count . ' ' . ($count === 1 ? $noun : $noun . 's');
}
echo formatCount(5, 'element'); // '5 elements' in English
// Russian: wrong — needs locale-specific plural form logic
✓ Fixed
// PHP Intl MessageFormatter — handles all locales:
$pattern = '{count, plural, one {# item} other {# items}}';
$fmt = new MessageFormatter('en_US', $pattern);
echo $fmt->format(['count' => 5]); // '5 items'

// gettext plural:
ngettext('%d item', '%d items', $count);

// Laravel trans_choice with locale files:
// en.json: { "items": "{1} :count item|[2,*] :count items" }
// ru.json: { "items": "{1} :count элемент|[2,4] :count элемента|[5,*] :count элементов" }

Tags


Added 16 Mar 2026
Edited 22 Mar 2026
Views 68
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 2 pings F 0 pings S 3 pings S 0 pings M 0 pings T 1 ping W 2 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F
Applebot 1
No pings yesterday
Amazonbot 9 Perplexity 7 ChatGPT 6 Scrapy 6 Ahrefs 5 Google 4 Unknown AI 3 PetalBot 3 Twitter/X 3 Meta AI 1 Majestic 1 Applebot 1
crawler 44 crawler_json 4 pre-tracking 1
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Use ICU MessageFormat for pluralisation — it handles the 6 plural categories (zero/one/two/few/many/other) that some languages require, unlike simple if/else checks
📦 Applies To
PHP 5.3+ web cli
🔗 Prerequisites
🔍 Detection Hints
if($count == 1) 'item' else 'items' — only handles English two-form plurals; no plural support for Arabic Russian Polish with 3-6 forms
Auto-detectable: ✗ No phpstan php-intl
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: File Tests: Update


✓ schema.org compliant