Mastering JavaScript PluralRules

Build multilingual applications that respect linguistic nuances with JavaScript's native Intl.PluralRules API--no external dependencies required.

Understanding Plural Rules in Internationalization

Every developer building multilingual applications faces a subtle but critical challenge: displaying the correct plural form of words based on the user's language and number value. In English, "1 item" becomes "2 items," but in Arabic, the same simple rule explodes into six distinct forms. JavaScript's native Intl.PluralRules API provides a browser-native solution that handles this complexity efficiently.

What Are Plural Rules?

Plural rules are linguistic guidelines that determine how words change based on their associated number. While English follows a simple singular/plural pattern, many languages have far more complex systems:

  • Chinese: Only one form (1 items, 2 items)
  • English: Two forms (1 item, 2 items)
  • Russian: Three forms (1 item, 2 items, 5 items)
  • Arabic: Six forms (0, 1, 2, 3-10, 11-99, 100+)

The Unicode Common Locale Data Repository (CLDR) maintains these rules for over 200 languages, and JavaScript's Intl API derives its plural rule implementations directly from this authoritative source.

Why Native Browser Support Matters

Using the native Intl.PluralRules API offers significant advantages:

  • Zero bundle size - No npm packages required
  • Consistent behavior - Supported across all modern browsers since September 2019
  • Regular updates - New languages and corrections arrive with browser updates
  • Performance - Browser-level implementation optimizations
  • No dependencies - Eliminates supply chain risk from external libraries

When applications fail to handle pluralization correctly, users perceive them as amateurish or incomplete. According to research on pluralization in multilingual apps, proper localization--including correct plural forms--significantly impacts user trust and perceived quality. Professional applications must speak to users in their native tongue, and that includes getting the grammar right. Our web development services emphasize building applications that feel native from the first interaction, while our AI automation solutions can help streamline internationalization workflows at scale.

Cardinal vs. Ordinal Plurals: The Two Types

JavaScript's Intl.PluralRules supports two fundamentally different types of pluralization, each serving distinct purposes in application development.

Cardinal Plurals (Quantity-Based)

Cardinal plurals express how many of something exists. They are the most commonly used type and handle scenarios like:

  • Notification counts: "You have 3 new messages"
  • Inventory displays: "Only 2 items left"
  • Cart quantities: "Add 5 items to cart"

The select() method returns categories like "one", "two", "few", "many", "zero", or "other" based on the number's value. For example, in English, the category "one" applies to exactly 1 item, while "other" covers everything else. In Russian, the distinction between "few" and "many" depends on specific number ranges that differ from English entirely.

Ordinal Plurals (Position-Based)

Ordinal plurals express position or rank in a sequence. English speakers know these as "st", "nd", "rd", and "th" suffixes:

  • Rankings: "She finished 1st in the race"
  • Dates: "November 21st"
  • Step indicators: "Step 3 of 5"
  • Progress: "You're on level 4"

The complexity of ordinal rules varies by language. English has four categories (one, two, few, other) with specific patterns for numbers like 11, 12, and 13 that always use "other" regardless of the suffix that would normally apply. This complexity is precisely why the native API is invaluable--it handles all these edge cases correctly without you needing to memorize linguistic rules.

Cardinal Plural Rules Example
1// Cardinal plurals (default type)2const cardinalRules = new Intl.PluralRules('en-US');3 4console.log(cardinalRules.select(1)); // "one"5console.log(cardinalRules.select(2)); // "other"6console.log(cardinalRules.select(0)); // "other"7console.log(cardinalRules.select(100)); // "other"8 9// Arabic has six forms10const arabicRules = new Intl.PluralRules('ar-EG');11console.log(arabicRules.select(0)); // "zero"12console.log(arabicRules.select(1)); // "one"13console.log(arabicRules.select(2)); // "two"14console.log(arabicRules.select(6)); // "few"15console.log(arabicRules.select(18)); // "many"
Ordinal Plural Rules Example
1// Ordinal plurals for position/rank2const ordinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });3 4console.log(ordinalRules.select(1)); // "one" → 1st5console.log(ordinalRules.select(2)); // "two" → 2nd6console.log(ordinalRules.select(3)); // "few" → 3rd7console.log(ordinalRules.select(4)); // "other" → 4th8console.log(ordinalRules.select(11)); // "other" → 11th9console.log(ordinalRules.select(21)); // "one" → 21st10console.log(ordinalRules.select(22)); // "two" → 22nd11console.log(ordinalRules.select(23)); // "few" → 23rd12 13// Helper function for ordinal formatting14function formatOrdinal(n) {15 const suffixes = { one: 'st', two: 'nd', few: 'rd', other: 'th' };16 const category = ordinalRules.select(n);17 return `${n}${suffixes[category]}`;18}19 20// Usage21formatOrdinal(1); // "1st"22formatOrdinal(2); // "2nd"23formatOrdinal(3); // "3rd"24formatOrdinal(4); // "4th"

The Intl.PluralRules Constructor

The constructor accepts two parameters: locales and options. Understanding these configuration options is essential for proper implementation.

Constructor Syntax

new Intl.PluralRules([locales[, options]])

Locales Parameter

The locales parameter can be:

  • Single string: 'en-US', 'ar-EG', 'zh-CN'
  • Array of strings: ['ar-SA', 'ar-EG', 'en-US']
  • Object with unicodeExtensionKeys: For advanced locale selection

The browser will use the first supported locale from the list, falling back to its default locale if none match. This locale fallback mechanism ensures graceful degradation when a user's preferred language isn't fully supported.

Locale Fallback Example

// Check supported locales before creating rules
const preferredLocales = ['ar-SA', 'ar-EG', 'fr-FR'];
const supported = Intl.PluralRules.supportedLocalesOf(preferredLocales);
const pr = new Intl.PluralRules(supported[0] || 'en-US');

The supportedLocalesOf() method is invaluable for applications that must know upfront whether plural rules for a specific language are available. This pattern is essential when building enterprise web applications that support diverse global audiences.

Options Object

{
 type: 'cardinal' | 'ordinal', // Default: 'cardinal'
 minimumIntegerDigits: number, // Min digits for whole number
 minimumFractionDigits: number, // Min decimal places
 maximumFractionDigits: number // Max decimal places
}

When working with currencies or scientific data, the fraction digit options become crucial. Setting minimumFractionDigits: 2 ensures monetary values always display two decimal places, which affects how plural categories are determined for values like 1.00 versus 1.50.

Constructor Options Explained

type

Specifies 'cardinal' for quantities or 'ordinal' for positions. Cardinal is the default and most commonly used.

minimumIntegerDigits

Forces leading zeros. Set to 2 for "01" instead of "1". Useful for consistent display formatting.

minimumFractionDigits

Ensures a minimum number of decimal places. Use with currency formatting for consistent display.

maximumFractionDigits

Limits decimal places. Prevents long decimals from affecting plural category determination.

Instance Methods: The Core API

Intl.PluralRules instances provide three methods for working with plural rules: select(), selectRange(), and resolvedOptions().

select(number)

Returns a string indicating which plural category applies to the given number:

const pr = new Intl.PluralRules('en-US');
pr.select(1); // "one"
pr.select(5); // "other"

Return values: "zero", "one", "two", "few", "many", or "other"

The select() method is the workhorse of the API and is used in virtually every pluralization scenario. According to the MDN documentation, the method handles edge cases like negative numbers (using absolute value) and Infinity appropriately.

selectRange(start, end)

Determines the plural category for a range of numbers:

const pr = new Intl.PluralRules('en-US');
pr.selectRange(1, 5); // "other" for English

Useful for displaying ranges like "1-5 of 10 items". The range category is determined based on linguistic conventions for that locale, which may differ from simply applying the category to the start or end value.

resolvedOptions()

Returns an object with the locale and options that were actually used:

const pr = new Intl.PluralRules('en-US', { type: 'ordinal' });
pr.resolvedOptions();
// { locale: 'en-US', type: 'ordinal', ...number options }

This method is invaluable for debugging configuration issues and for logging which locale was actually applied when providing fallback locales. In production applications, logging resolved options helps diagnose i18n issues reported by users in specific regions.

Practical Use Cases

Form Validation Messages: Display accurate error counts like "2 fields have errors" versus "1 field has an error" across all supported languages.

Progress Indicators: Show step numbers with correct ordinal suffixes: "Step 1 of 5", "Step 2 of 5", "Step 3 of 5" becomes "Step 3rd of 5".

Dashboard Analytics: Present metrics like "3 new signups today" with grammatically correct plurals for each user's preferred language.

These patterns are essential building blocks for any professional web application targeting international markets.

Practical Implementation Patterns

Building real-world applications requires patterns that combine plural rules with other i18n concerns.

Notification System

Create a reusable formatter for dynamic notifications:

function createNotificationFormatter(locale) {
 const pr = new Intl.PluralRules(locale);
 
 const messages = {
 zero: 'No new notifications',
 one: 'You have {count} new notification',
 two: 'You have {count} new notifications',
 few: 'You have {count} new notifications',
 many: 'You have {count} new notifications',
 other: 'You have {count} new notifications'
 };
 
 return function format(count) {
 const category = pr.select(count);
 return messages[category].replace('{count}', count);
 };
}

// Usage
const notify = createNotificationFormatter('en-US');
notify(1); // "You have 1 new notification"
notify(5); // "You have 5 new notifications"

Instance Caching

Avoid creating new PluralRules instances repeatedly:

const pluralCache = new Map();

function getPluralRules(locale, type = 'cardinal') {
 const key = `${locale}-${type}`;
 if (!pluralCache.has(key)) {
 pluralCache.set(key, new Intl.PluralRules(locale, { type }));
 }
 return pluralCache.get(key);
}

Form Validation Messages

Dynamic validation feedback that respects plural rules:

function validationMessage(fieldName, errorCount, locale) {
 const pr = new Intl.PluralRules(locale);
 const category = pr.select(errorCount);
 
 const templates = {
 one: `{count} ${fieldName} has an error`,
 other: `{count} ${fieldName}s have errors`
 };
 
 return templates[category].replace('{count}', errorCount);
}

// Usage
validationMessage('field', 1, 'en-US'); // "1 field has an error"
validationMessage('field', 3, 'en-US'); // "3 fields have errors"

Progress Step Indicators

Ordinal-aware step formatting for wizards and multi-step processes:

function formatStep(current, total, locale) {
 const ordinal = new Intl.PluralRules(locale, { type: 'ordinal' });
 const category = ordinal.select(current);
 
 const suffix = { one: 'st', two: 'nd', few: 'rd', other: 'th' };
 return `Step ${current}${suffix[category]} of ${total}`;
}

These implementation patterns demonstrate how the native API integrates seamlessly with existing application code while maintaining the flexibility needed for complex internationalization scenarios.

Complete Plural Formatter Class
1class PluralFormatter {2 constructor(locale = 'en-US') {3 this.cardinal = new Intl.PluralRules(locale);4 this.ordinal = new Intl.PluralRules(locale, { type: 'ordinal' });5 this.locale = locale;6 }7 8 cardinalCategory(number) {9 return this.cardinal.select(number);10 }11 12 ordinalCategory(number) {13 return this.ordinal.select(number);14 }15 16 ordinalSuffix(number) {17 const suffixes = { one: 'st', two: 'nd', few: 'rd', other: 'th' };18 const category = this.ordinalCategory(number);19 return suffixes[category] || 'th';20 }21 22 format(messageTemplate, count) {23 const category = this.cardinalCategory(count);24 const singular = messageTemplate.one;25 const plural = messageTemplate.other;26 return category === 'one' ? singular : plural;27 }28 29 formatOrdinal(messageTemplate, count) {30 const category = this.ordinalCategory(count);31 const templates = messageTemplate;32 return templates[category] || templates.other;33 }34}35 36// Usage example37const formatter = new PluralFormatter('en-US');38 39// Notification messages40const notificationMessages = {41 one: '{count} new message',42 other: '{count} new messages'43};44formatter.format(notificationMessages, 1); // "1 new message"45formatter.format(notificationMessages, 5); // "5 new messages"46 47// Ordinal messages48const rankMessages = {49 one: 'You finished {position}st',50 two: 'You finished {position}nd',51 few: 'You finished {position}rd',52 other: 'You finished {position}th'53};54formatter.ordinalCategory(3); // "few"
Native Intl.PluralRules vs External i18n Libraries
FeatureIntl.PluralRulesi18nextFormatJS
Bundle Size0 KB (native)~40+ KB~15+ KB
DependenciesNoneMany pluginsMessageFormat
Translation StorageExternalBuilt-in JSONExternal
InterpolationManualTemplate syntaxICU MessageFormat
Plural RulesNative CLDRCustom rulesNative CLDR
Browser SupportAll modern browsersAll browsersAll browsers
Learning CurveLowMediumHigh

When to Use Native API vs. Libraries

Use Intl.PluralRules directly when:

  • Your application only needs plural formatting without full translation management
  • Bundle size is a critical concern
  • You want to avoid external dependencies
  • You're already using a translation library and only need plural categories

Use i18n libraries when:

  • You need complete translation workflows (XLIFF, JSON, YAML)
  • Message interpolation is required
  • Language fallback chains are complex
  • Your team is already familiar with a specific library

The native API pairs excellently with lightweight translation solutions, allowing you to handle plural logic while keeping bundle sizes minimal.

Migration Guidance

For teams moving from libraries like i18next or FormatJS to the native API:

  1. Identify plural usage - Search your translation files for plural keys and plural-related logic
  2. Extract plural categories - Replace library-specific plural resolution with Intl.PluralRules.select() calls
  3. Build message templates - Create a mapping of category keys to message strings
  4. Test thoroughly - Verify behavior for all supported locales, especially edge cases like zero and decimals
  5. Measure bundle savings - Quantify the size reduction for stakeholder communication

The migration typically reduces bundle size significantly while simplifying the dependency graph. Our team has helped numerous clients migrate legacy i18n implementations to modern, native-first approaches as part of our web development services.

Integration with Existing Systems

The native API works alongside full-featured i18n libraries rather than replacing them entirely. If you're using FormatJS for ICU MessageFormat, you can still use PluralRules for custom category determination before passing results to the library. This hybrid approach gives you the best of both worlds: powerful translation workflows with optimized plural handling. Properly implemented internationalization also supports your SEO strategy by ensuring search engines can properly index your multilingual content.

Best Practices for PluralRules

Cache Instances

Create PluralRules instances once per locale and reuse them. Repeated instantiation is wasteful.

Use Specific Locales

Prefer 'en-US' over 'en' for consistent behavior. Locale variants have subtle differences.

Test All Categories

Write tests for zero, one, two, few, many, and other categories where applicable to your supported languages.

Handle Edge Cases

Consider negative numbers, decimals, and Infinity. Test boundary conditions for your use cases.

Check Support

Use Intl.PluralRules.supportedLocalesOf() to verify browser support before relying on the API.

Separate Concerns

Keep plural category determination separate from message formatting for cleaner code.

Frequently Asked Questions

Build Better Multilingual Web Applications

JavaScript's native internationalization APIs help you create applications that feel native to users around the world. Our team specializes in building high-performance, globally-aware web applications.