Skip to content

Plurals — Handling Pluralization Across Languages

DodaTech Updated 2026-06-28 7 min read

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

  1. What plural categories does English use vs Russian vs Arabic?
  2. How does Intl.PluralRules determine the plural category for a number?
  3. Why do languages like Japanese not need plural forms?
  4. What is the difference between cardinal and ordinal plurals?
  5. 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

How many plural categories does English have?

English has 2 cardinal categories: 'one' (1, 21, 31, etc.) and 'other' (0, 2, 3, 4, 5...). Arabic has 6: zero, one, two, few, many, other. Japanese has 1: other (never changes).

What determines a language's plural categories?

Historical language evolution. CLDR (Common Locale Data Repository) maintains the rules for every language based on linguistic research. The rules are algorithmic and based on the number's value.

Does 'few' mean 'a few' like 3-5 in English?

No. The category names (few, many) are just labels. In Russian, 'few' applies to numbers ending in 2-4 (2, 3, 4, 22, 23, 24...). In Arabic, 'few' applies to 3-10. Don't infer meaning from the English word.

How do I handle plural-only words like 'scissors' or 'pants'?

These are 'pluralia tantum' — words that only exist in plural form. Handle them with context-aware translations: {count, plural, one {1 pair of scissors} other {{count} pairs of scissors}}.

Can I use machine learning for pluralization?

You don't need to. Plural rules are deterministic and well-defined. Intl.PluralRules gives you the correct category for any locale and number. Your job is to provide the right translation for each category.

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