Plurals — Handling Pluralization Across Languages
In this tutorial, you will learn about Plurals. We cover key concepts, practical examples, and best practices to help you master this topic.
Pluralization rules vary widely across languages, from English's simple singular/plural to Arabic's six forms and Japanese's lack of plurals.
What You'll Learn
By the end of this tutorial, you'll understand the CLDR plural categories, how to use Intl.PluralRules for locale-aware pluralization, how ICU MessageFormat handles complex plurals, and common patterns for implementing plurals in web applications.
Why It Matters
A naive implementation like "item" + (count > 1 ? "s" : "") only works for English. Russian changes the word form for 1, 2-4, and 5+. Arabic has separate forms for 0, 1, 2, 3-10, 11+. Japanese doesn't pluralize at all. Incorrect plurals are immediately noticed by native speakers and signal low-quality software.
Real-World Use
An e-learning platform shows "3 lessons completed" in English, "3 lecons terminees" in French (same pattern), but "3 уроков завершено" in Russian (uses genitive plural after 3) and "3 課程已完成" in Japanese (no plural marking). The same code handles all languages using Intl.PluralRules.
Plural Categories by Language
graph LR
A[Plural Categories] --> B[one/other
English, German,
French, Spanish]
A --> C[one/few/many/other
Russian, Ukrainian,
Serbian]
A --> D[zero/one/two/few/many/other
Arabic, Slavic
languages]
A --> E[other only
Japanese, Chinese,
Korean, Thai]
A --> F[one/two/other
Irish, Scottish
Gaelic]
style A fill:#4a90d9,color:#fff
style E fill:#e74c3c,color:#fff
style D fill:#f39c12,color:#fff
Intl.PluralRules API
// i18n/plurals-intl.js — Using Intl.PluralRules
class PluralHandler {
constructor(locale) {
this.locale = locale;
this.pluralRules = new Intl.PluralRules(locale);
this.ordinalRules = new Intl.PluralRules(locale, { type: 'ordinal' });
}
// Get cardinal plural category (for counts)
getCardinal(count) {
return this.pluralRules.select(count);
}
// Get ordinal plural category (for 1st, 2nd, 3rd)
getOrdinal(count) {
return this.ordinalRules.select(count);
}
// Get all supported categories for this locale
static getCategories(locale) {
const rules = new Intl.PluralRules(locale);
const categories = ['zero', 'one', 'two', 'few', 'many', 'other'];
return categories.filter(cat => {
// Find a number that produces this category
for (let i = 0; i <= 100; i++) {
if (rules.select(i) === cat) return true;
}
return false;
});
}
// Resolve a count-based template
resolve(templates, count) {
const category = this.getCardinal(count);
const template = templates[category] || templates.other;
if (typeof template === 'function') {
return template(count);
}
return template.replace('{count}', count);
}
// Demonstrate plural forms
demo() {
const categories = PluralHandler.getCategories(this.locale);
console.log(`Locale ${this.locale} has categories: ${categories.join(', ')}`);
const examples = [0, 1, 2, 3, 5, 10, 21, 100];
examples.forEach(count => {
console.log(` ${count} → ${this.getCardinal(count)}`);
});
}
}
// Demo English
console.log('=== English Plurals ===');
const en = new PluralHandler('en');
en.demo();
// Demo Russian
console.log('\\n=== Russian Plurals ===');
const ru = new PluralHandler('ru');
ru.demo();
// Demo Arabic
console.log('\\n=== Arabic Plurals ===');
const ar = new PluralHandler('ar');
ar.demo();
// Demo Japanese (no plurals)
console.log('\\n=== Japanese Plurals ===');
const ja = new PluralHandler('ja');
ja.demo();
Translation File Patterns
// locales/en/common.json — English: one/other
{
"items": "{count} item",
"items_plural": "{count} items",
"items_plural_0": "No items",
"notifications": "You have {count, plural, =0 {no notifications} one {# notification} other {# notifications}}."
}
// locales/ru/common.json — Russian: one/few/many/other
{
"items_plural_one": "{count} элемент",
"items_plural_few": "{count} элемента",
"items_plural_many": "{count} элементов",
"items_plural_other": "{count} элемента",
"notifications": "У вас {count, plural, one {{count} уведомление} few {{count} уведомления} many {{count} уведомлений} other {{count} уведомления}}."
}
// locales/ar/common.json — Arabic: zero/one/two/few/many/other
{
"notifications": "لديك {count, plural, zero {لا توجد إشعارات} one {إشعار واحد} two {إشعاران} few {{count} إشعارات} many {{count} إشعارًا} other {{count} إشعار}}."
}
// locales/ja/common.json — Japanese: no plural form needed
{
"items": "{count}個のアイテム",
"notifications": "通知が{count}件あります"
}
Plural-Aware Component
// components/ItemCounter.jsx — Plural-safe item display
import { useTranslation } from 'react-i18next';
function ItemCounter({ count, type }) {
const { t, i18n } = useTranslation();
// Approach 1: i18next's built-in plural suffix
const label1 = t('items', { count });
// Approach 2: ICU MessageFormat in translation
const label2 = t('items_icu', { count });
// Approach 3: Manual with Intl.PluralRules
const pluralRules = new Intl.PluralRules(i18n.language);
const category = pluralRules.select(count);
const labels = {
en: {
zero: 'No items',
one: '1 item',
other: '{count} items'
},
ru: {
one: '{count} элемент',
few: '{count} элемента',
many: '{count} элементов',
other: '{count} элемента'
}
};
const localeLabels = labels[i18n.language] || labels.en;
const label3 = (localeLabels[category] || localeLabels.other)
.replace('{count}', count);
return (
<div className="item-counter">
<p>{label1}</p>
<p>{t('selected', { count })}</p>
</div>
);
}
// Translation file:
// "items": "{{count}} item",
// "items_plural": "{{count}} items",
// "items_plural_0": "No items",
// "selected": "{{count}} selected",
// "selected_plural": "{{count}} selected"
Ordinal Plurals
// i18n/ordinals.js — Ordinal pluralization (1st, 2nd, 3rd, 4th)
function getOrdinal(count, locale = 'en') {
const rules = new Intl.PluralRules(locale, { type: 'ordinal' });
const category = rules.select(count);
const ordinalForms = {
en: {
one: count + 'st',
two: count + 'nd',
few: count + 'rd',
other: count + 'th'
},
fr: {
one: count + 'er',
other: count + 'e'
},
de: {
other: count + '.'
},
es: {
other: count + '.°'
}
};
const forms = ordinalForms[locale] || ordinalForms.en;
return forms[category] || forms.other;
}
console.log(getOrdinal(1, 'en')); // 1st
console.log(getOrdinal(2, 'en')); // 2nd
console.log(getOrdinal(3, 'en')); // 3rd
console.log(getOrdinal(4, 'en')); // 4th
console.log(getOrdinal(21, 'en')); // 21st
console.log(getOrdinal(1, 'fr')); // 1er
console.log(getOrdinal(2, 'fr')); // 2e
console.log(getOrdinal(3, 'de')); // 3.
Common Mistakes
- Using only "s" suffix for plurals. English adds "s" or "es", but many languages change the entire word. "Item" → "items" works in English. In Russian, "элемент" → "элемента" → "элементов" depending on count.
- Not handling the zero case. "0 items" sounds unnatural. Many languages have a special form for zero. ICU's =0 syntax and i18next's _0 suffix let you provide "no items" instead.
- Assuming all languages have plurals. Japanese, Chinese, Korean, and Thai don't change word forms based on count. "5 item" is correct in these languages — adding "s" would be wrong.
- Using plural rules from the wrong library version. CLDR plural data changes between versions. Ensure all environments (server, client, translation tools) use the same CLDR version for consistency.
- Forgetting about ordinal plurals. "1st", "2nd", "3rd" follow different rules than cardinal plurals (one/two/few for ordinals vs one/other for cardinals). Use Intl.PluralRules with type: 'ordinal'.
Practice Questions
- What plural categories does English use vs Russian vs Arabic?
- How does Intl.PluralRules determine the plural category for a number?
- Why do languages like Japanese not need plural forms?
- What is the difference between cardinal and ordinal plurals?
- How does i18next's _plural suffix pattern work for languages with multiple plural forms?
Challenge: Build a pluralization demo that accepts a locale and a number, displays the plural category (using Intl.PluralRules), shows the correct translation from a set of 5 test strings (items, notifications, likes, comments, shares), and includes ordinal formatting for rankings. Test with English, Russian, Arabic, and Japanese.
FAQ
Mini Project
Build a pluralization test suite: a web page that accepts a locale and displays all plural categories for that locale (with example numbers), shows the correct translation forms for 3 test strings, handles ordinal plurals, and includes a visualization of which numbers map to which categories (e.g., a number line colored by category).
What's Next
You've mastered plurals. Next, learn about Gender handling in translations for gender-aware content.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro