IntlPull
Technical
13 min read

Unicode CLDR Plural Rules 2026: Categories, Examples & ICU Syntax

Understand CLDR plural categories, language-specific rules, ICU MessageFormat implementation, examples, and testing strategies.

IntlPull Team
IntlPull Team
Feb 12, 2026
On this page
Summary

Understand CLDR plural categories, language-specific rules, ICU MessageFormat implementation, examples, and testing strategies.

Understanding CLDR Plural Rules

This Unicode CLDR plural rules overview explains how software decides whether a number maps to one, few, many, or another plural category before a translated message is rendered.

The Common Locale Data Repository (CLDR) is a Unicode project that provides standardized locale data for software internationalization. One of its most critical components is the plural rules system, which defines how different languages handle pluralization. Unlike English, which typically has just singular and plural forms, many languages have complex plural systems that require special handling. CLDR plural rules provide a standardized framework for handling these variations across 200+ languages, ensuring that your application displays grammatically correct messages regardless of the user's locale. The system defines six plural categories—zero, one, two, few, many, and other—which are selectively used by different languages based on their grammatical rules. Understanding these rules is essential for building truly internationalized applications that feel native to users worldwide, avoiding embarrassing grammatical errors like "1 items" or "5 fichier" that immediately signal poor localization quality.

The Six Plural Categories Explained

CLDR defines six distinct plural categories that languages can use, though no single language uses all six. The zero category is used by languages like Arabic when the count is exactly zero (e.g., "لا كتب" meaning "no books"). The one category applies to singular forms in most languages but has special rules in Slavic languages where it might apply to numbers ending in 1 but not 11. The two category is used by languages with a dual form, such as Arabic and Slovenian, when referring to exactly two items.

The few category is employed by languages like Polish, Russian, and Arabic for small quantities (typically 2-4 in Slavic languages, or 3-10 in Arabic). The many category covers larger quantities in languages like Polish (numbers ending in 2-4 except 12-14) or Arabic (11-99). Finally, the other category serves as the default fallback and is used for all numbers that don't fit the other categories—this is the only category guaranteed to exist in every language.

Practical Category Usage Examples

TypeScript
1// English uses only 'one' and 'other'
2const englishRules = {
3  one: 'n = 1',    // 1 item
4  other: '...'     // 0, 2, 3, 4, 5, ... items
5};
6
7// Arabic uses all six categories
8const arabicRules = {
9  zero: 'n = 0',           // لا عناصر (no items)
10  one: 'n = 1',            // عنصر واحد (one item)
11  two: 'n = 2',            // عنصران (two items)
12  few: 'n % 100 = 3..10',  // ٣ عناصر (3-10 items)
13  many: 'n % 100 = 11..99',// ١١ عنصرا (11-99 items)
14  other: '...'             // ١٠٠ عنصر (100+ items)
15};
16
17// Polish uses 'one', 'few', 'many', and 'other'
18const polishRules = {
19  one: 'n = 1',                              // 1 plik
20  few: 'n % 10 = 2..4 AND n % 100 != 12..14', // 2-4 pliki
21  many: 'n % 10 = 0..1 OR n % 10 = 5..9 OR n % 100 = 12..14', // 5-21 plików
22  other: '...'                               // 1.5 pliku (fractions)
23};

Language-Specific Plural Rules

Different languages have vastly different plural systems. English is one of the simplest, using only two categories: "one" for n=1 ("1 file") and "other" for everything else ("0 files", "2 files", "1.5 files"). Japanese, Chinese, Korean, and Vietnamese have even simpler rules—they use only the "other" category for all numbers, as these languages don't grammatically distinguish between singular and plural forms.

Arabic represents the most complex system, using all six categories. Numbers follow intricate rules: 0 uses "zero", 1 uses "one", 2 uses "two", 3-10 use "few", 11-99 use "many", and 100+ use "other". Additionally, the noun form changes with each category, making proper localization crucial.

Russian and Polish use four categories with rules based on the last digits of numbers. In Russian, numbers ending in 1 (except 11) use "one" (1 файл, 21 файл), numbers ending in 2-4 (except 12-14) use "few" (2 файла, 23 файла), and everything else uses "many" (5 файлов, 11 файлов). French has a unique rule where both 0 and 1 use the "one" category ("0 fichier", "1 fichier"), while all other numbers use "other" ("2 fichiers").

Comparison Table: Plural Rules by Language

LanguageCategories UsedExample RulesComplexity
Englishone, othern=1 → one★☆☆☆☆
Japaneseotherall → other☆☆☆☆☆
Frenchone, othern=0,1 → one★☆☆☆☆
Russianone, few, many, othern%10=1 AND n%100≠11 → one★★★★☆
Polishone, few, many, otherSimilar to Russian★★★★☆
Arabiczero, one, two, few, many, othern=0 → zero, n=1 → one, n=2 → two★★★★★
Welshzero, one, two, few, many, othern=0 → zero, n=1 → one★★★★☆
Slovenianone, two, few, othern%100=1 → one, n%100=2 → two★★★☆☆

ICU MessageFormat Implementation

ICU MessageFormat is the industry standard for implementing CLDR plural rules in your applications. It provides a powerful syntax for handling plurals, gender, and other locale-specific formatting. The basic plural syntax uses the {variable, plural, ...} pattern:

JavaScript
1// Basic ICU MessageFormat plural example
2const message = '{count, plural, one {# file} other {# files}}';
3
4// With Intl.MessageFormat (or similar library)
5import IntlMessageFormat from 'intl-messageformat';
6
7const formatter = new IntlMessageFormat(message, 'en');
8console.log(formatter.format({ count: 1 }));  // "1 file"
9console.log(formatter.format({ count: 5 }));  // "5 files"
10
11// Arabic example using all categories
12const arabicMessage = `{count, plural,
13  zero {لا عناصر}
14  one {عنصر واحد}
15  two {عنصران}
16  few {# عناصر}
17  many {# عنصرا}
18  other {# عنصر}
19}`;
20
21const arabicFormatter = new IntlMessageFormat(arabicMessage, 'ar');
22console.log(arabicFormatter.format({ count: 0 }));  // "لا عناصر"
23console.log(arabicFormatter.format({ count: 1 }));  // "عنصر واحد"
24console.log(arabicFormatter.format({ count: 2 }));  // "عنصران"
25console.log(arabicFormatter.format({ count: 5 }));  // "٥ عناصر"

Advanced ICU Features

ICU MessageFormat supports offset for handling patterns like "you and 2 others":

JavaScript
1const message = `{count, plural, offset:1
2  =0 {No followers}
3  =1 {Just you}
4  one {You and # other person}
5  other {You and # other people}
6}`;
7
8const formatter = new IntlMessageFormat(message, 'en');
9console.log(formatter.format({ count: 0 })); // "No followers"
10console.log(formatter.format({ count: 1 })); // "Just you"
11console.log(formatter.format({ count: 2 })); // "You and 1 other person"
12console.log(formatter.format({ count: 5 })); // "You and 4 other people"

You can also combine plurals with select for complex scenarios:

JavaScript
1const message = `{gender, select,
2  male {{count, plural, one {He has # item} other {He has # items}}}
3  female {{count, plural, one {She has # item} other {She has # items}}}
4  other {{count, plural, one {They have # item} other {They have # items}}}
5}`;

Library Support for CLDR Plurals

JavaScript/TypeScript Libraries

FormatJS (formerly React Intl) provides comprehensive ICU MessageFormat support with excellent TypeScript types and React integration:

TypeScript
1import { IntlProvider, FormattedMessage } from 'react-intl';
2
3const messages = {
4  itemCount: '{count, plural, one {# item} other {# items}}'
5};
6
7function App() {
8  return (
9    <IntlProvider locale="en" messages={messages}>
10      <FormattedMessage id="itemCount" values={{ count: 5 }} />
11    </IntlProvider>
12  );
13}

i18next supports plurals through a simpler key-based syntax:

TypeScript
1import i18next from 'i18next';
2
3i18next.init({
4  lng: 'en',
5  resources: {
6    en: {
7      translation: {
8        'item_one': '{{count}} item',
9        'item_other': '{{count}} items'
10      }
11    }
12  }
13});
14
15i18next.t('item', { count: 1 }); // "1 item"
16i18next.t('item', { count: 5 }); // "5 items"

Fluent (Mozilla's localization system) uses a different syntax but follows CLDR rules:

FLUENT
1items = { $count ->
2    [one] { $count } item
3   *[other] { $count } items
4}

Backend Library Support

Go uses the golang.org/x/text package:

GO
1import "golang.org/x/text/message"
2
3p := message.NewPrinter(language.English)
4p.Printf("%d files
5", plural.Selectf(1, "",
6    plural.One, "file",
7    plural.Other, "files",
8))

Java uses ICU4J:

JAVA
1MessageFormat mf = new MessageFormat(
2    "{count, plural, one {# file} other {# files}}",
3    Locale.ENGLISH
4);
5System.out.println(mf.format(new Object[]{5}));

Python uses the babel library:

Python
1from babel.plural import PluralRule
2
3rule = PluralRule({'one': 'n is 1'})
4print(rule(1))  # 'one'
5print(rule(5))  # 'other'

Common Mistakes and Pitfalls

Mistake 1: Hardcoding Plural Logic

Never write conditional logic for plurals in your code:

TypeScript
1// ❌ BAD: Hardcoded English-only logic
2const message = count === 1 ? `${count} item` : `${count} items`;
3
4// ❌ BAD: Incomplete logic that breaks in other languages
5const message = count === 0 ? 'no items'
6              : count === 1 ? '1 item'
7              : `${count} items`;
8
9// ✅ GOOD: Use ICU MessageFormat
10const message = formatMessage(
11  { id: 'itemCount', defaultMessage: '{count, plural, one {# item} other {# items}}' },
12  { count }
13);

Mistake 2: Forgetting Zero Handling

Some languages have specific rules for zero that differ from other plural forms:

TypeScript
1// ❌ BAD: Missing zero case for Arabic
2const message = `{count, plural,
3  one {عنصر واحد}
4  other {# عناصر}
5}`;
6
7// ✅ GOOD: Include zero for languages that need it
8const message = `{count, plural,
9  zero {لا عناصر}
10  one {عنصر واحد}
11  two {عنصران}
12  few {# عناصر}
13  many {# عنصرا}
14  other {# عنصر}
15}`;

Mistake 3: Using Plural Forms in Wrong Context

Don't use plural rules for non-countable nouns or when the number isn't displayed:

TypeScript
1// ❌ BAD: Plural for non-countable
2const message = '{count, plural, one {information} other {informations}}'; // "informations" is wrong
3
4// ❌ BAD: Plural without number
5const message = '{count, plural, one {Delete item?} other {Delete items?}}'; // Missing count
6
7// ✅ GOOD: Include the count
8const message = '{count, plural, one {Delete # item?} other {Delete # items?}}';

Mistake 4: Not Testing All Plural Forms

Many developers only test with 0, 1, and 2, missing edge cases:

TypeScript
1// Test these numbers for thorough coverage
2const testNumbers = [0, 1, 2, 3, 5, 11, 21, 22, 25, 100, 101, 1.5];
3
4testNumbers.forEach(count => {
5  console.log(count, formatMessage({ id: 'items' }, { count }));
6});

Testing Plural Rules

Unit Testing Approach

Create comprehensive test suites that cover all plural categories:

TypeScript
1import { IntlProvider } from 'react-intl';
2import { render } from '@testing-library/react';
3
4describe('Plural Rules', () => {
5  const messages = {
6    items: '{count, plural, one {# item} other {# items}}'
7  };
8
9  it('handles singular correctly', () => {
10    const { getByText } = render(
11      <IntlProvider locale="en" messages={messages}>
12        <FormattedMessage id="items" values={{ count: 1 }} />
13      </IntlProvider>
14    );
15    expect(getByText('1 item')).toBeInTheDocument();
16  });
17
18  it('handles plural correctly', () => {
19    const { getByText } = render(
20      <IntlProvider locale="en" messages={messages}>
21        <FormattedMessage id="items" values={{ count: 5 }} />
22      </IntlProvider>
23    );
24    expect(getByText('5 items')).toBeInTheDocument();
25  });
26
27  it('handles zero correctly', () => {
28    const { getByText } = render(
29      <IntlProvider locale="en" messages={messages}>
30        <FormattedMessage id="items" values={{ count: 0 }} />
31      </IntlProvider>
32    );
33    expect(getByText('0 items')).toBeInTheDocument();
34  });
35});

Testing Multiple Locales

Test plural rules across different languages to catch locale-specific issues:

TypeScript
1describe('Russian Plural Rules', () => {
2  const messages = {
3    ru: {
4      files: '{count, plural, one {# файл} few {# файла} many {# файлов} other {# файла}}'
5    }
6  };
7
8  const testCases = [
9    { count: 1, expected: '1 файл' },     // one
10    { count: 2, expected: '2 файла' },    // few
11    { count: 5, expected: '5 файлов' },   // many
12    { count: 21, expected: '21 файл' },   // one (ends in 1, not 11)
13    { count: 22, expected: '22 файла' },  // few (ends in 2, not 12)
14    { count: 25, expected: '25 файлов' }, // many
15    { count: 1.5, expected: '1.5 файла' } // other (fraction)
16  ];
17
18  testCases.forEach(({ count, expected }) => {
19    it(`formats ${count} correctly`, () => {
20      const { getByText } = render(
21        <IntlProvider locale="ru" messages={messages.ru}>
22          <FormattedMessage id="files" values={{ count }} />
23        </IntlProvider>
24      );
25      expect(getByText(expected)).toBeInTheDocument();
26    });
27  });
28});

Visual Testing with IntlPull

IntlPull provides a built-in plural preview feature that lets you visualize how your plural strings will appear across all categories and test numbers. This helps catch issues before they reach production:

TypeScript
1// In your IntlPull dashboard, the plural preview shows:
2// count=0: "0 files"
3// count=1: "1 file"
4// count=2: "2 files"
5// count=5: "5 files"
6// count=21: "21 files"
7
8// For Russian, it shows all forms:
9// count=1: "1 файл" (one)
10// count=2: "2 файла" (few)
11// count=5: "5 файлов" (many)
12// count=21: "21 файл" (one)

Ordinal Plurals

CLDR also defines ordinal plural rules for numbers used in ordering (1st, 2nd, 3rd). These follow different rules than cardinal plurals:

TypeScript
1// English ordinal rules
2const ordinalMessage = `{count, selectordinal,
3  one {#st}
4  two {#nd}
5  few {#rd}
6  other {#th}
7}`;
8
9// Examples:
10// 1 → "1st"
11// 2 → "2nd"
12// 3 → "3rd"
13// 4 → "4th"
14// 21 → "21st"
15// 22 → "22nd"
16// 23 → "23rd"
17
18// Full implementation
19import { IntlMessageFormat } from 'intl-messageformat';
20
21const formatter = new IntlMessageFormat(
22  'You finished {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}!',
23  'en'
24);
25
26console.log(formatter.format({ place: 1 }));  // "You finished 1st!"
27console.log(formatter.format({ place: 22 })); // "You finished 22nd!"

Range Pluralization

Some scenarios require pluralizing ranges of numbers:

TypeScript
1const rangeMessage = `{start}-{end} of {total, plural,
2  one {# item}
3  other {# items}
4}`;
5
6// Usage
7formatMessage(rangeMessage, { start: 1, end: 10, total: 100 });
8// "1-10 of 100 items"
9
10formatMessage(rangeMessage, { start: 1, end: 1, total: 1 });
11// "1-1 of 1 item"

Performance Considerations

CLDR plural rule evaluation is fast, but there are optimization techniques for high-volume scenarios:

TypeScript
1// ❌ SLOW: Creating formatter on every call
2function formatItems(count: number) {
3  const formatter = new IntlMessageFormat(
4    '{count, plural, one {# item} other {# items}}',
5    'en'
6  );
7  return formatter.format({ count });
8}
9
10// ✅ FAST: Cache formatters
11const formatters = new Map();
12
13function getFormatter(locale: string, message: string) {
14  const key = `${locale}:${message}`;
15  if (!formatters.has(key)) {
16    formatters.set(key, new IntlMessageFormat(message, locale));
17  }
18  return formatters.get(key);
19}
20
21function formatItems(count: number, locale: string) {
22  const formatter = getFormatter(
23    locale,
24    '{count, plural, one {# item} other {# items}}'
25  );
26  return formatter.format({ count });
27}

Integration with Translation Management

When using a TMS like IntlPull, plural strings should be stored as single keys with ICU MessageFormat syntax:

JSON
1{
2  "itemCount": "{count, plural, one {# item} other {# items}}",
3  "deleteConfirm": "{count, plural, one {Delete # file?} other {Delete # files?}}",
4  "followers": "{count, plural, offset:1 =0 {No followers} =1 {Just you} one {You and # other} other {You and # others}}"
5}

IntlPull automatically detects ICU MessageFormat syntax and provides:

  • Visual preview of all plural forms
  • Warnings if translations are missing required plural categories
  • Context screenshots for each plural variation
  • Validation that plural syntax is correct across all languages

FAQ

Q: Do I need to provide all six plural categories for every language? A: No. Each language uses only a subset of the six categories. English uses only "one" and "other", while Arabic uses all six. Your translation management system should guide translators on which categories are required for their language.

Q: What happens if I'm missing a required plural category? A: Most libraries will fall back to the "other" category, which is required for all languages. However, this will result in grammatically incorrect text. Good TMS tools like IntlPull will warn you when required categories are missing.

Q: Can I use CLDR plural rules with template literals? A: No. Template literals like ${count} item${count === 1 ? '' : 's'} only work for English. Use ICU MessageFormat or a similar library that implements CLDR rules for proper internationalization.

Q: How do I handle plurals for languages I don't speak? A: Use the CLDR plural rules specification and rely on native speakers for translation. Tools like IntlPull show translators the required plural forms automatically, so they can provide correct translations for each category.

Q: Are there any languages that don't use plurals? A: Yes. Japanese, Chinese, Korean, Vietnamese, and several other Asian languages don't grammatically distinguish between singular and plural, so they use only the "other" category for all counts.

Q: How do I test plural rules during development? A: Use test numbers that cover all categories: 0, 1, 2, 3, 5, 11, 21, 22, 100, 101, and 1.5. This ensures you catch edge cases like Russian numbers ending in 1 (but not 11) and fractions.

Q: Should I use ordinal or cardinal plurals for counts? A: Use cardinal plurals (one/few/many/other) for quantities ("5 files") and ordinal plurals (1st/2nd/3rd) for ordering ("finished 1st"). They follow different rules in many languages.

Tags
cldr
plurals
plural-rules
icu
i18n
unicode
localization
IntlPull Team
IntlPull Team
Engineering

Building tools to help teams ship products globally. Follow us for more insights on localization and i18n.