FormatJS

A polyfill for Intl.PluralRules tested by the official ECMAScript Conformance test suite

npm Version size

ECMA-402 Spec Compliance

This package implements Intl.PluralRules. See the conformance report for tested coverage and known limitations.

✅ Implemented Features

Core Methods

  • select(number) - Returns the plural category for a number ('zero', 'one', 'two', 'few', 'many', 'other')
  • selectRange(start, end) - Returns the plural category for a number range
  • resolvedOptions() - Returns resolved options
  • supportedLocalesOf(locales) - Returns supported locales

Rule Types

  • 'cardinal' (default) - For cardinal numbers (e.g., "1 item", "2 items")
  • 'ordinal' - For ordinal numbers (e.g., "1st", "2nd", "3rd")

Locale Data

  • Supports 700+ locales with accurate LDML plural rules
  • Includes all 6 plural categories per LDML specification
  • Handles complex plural rules (e.g., Arabic has 6 forms, Chinese has 1)

Locale data modules are generated from CLDR supplemental plural-rule data. If a locale is available in other CLDR datasets but absent from the supplemental plural rules, this package does not publish a locale-data module for it.

Plural selection uses the rounded value and visible fraction digits, including zeros required by minimumFractionDigits.

Example Usage

Global import

import '@formatjs/intl-pluralrules/polyfill.js'

const pr = new Intl.PluralRules('en')
pr.select(0) // "other"
pr.select(1) // "one"
pr.select(2) // "other"

const prOrdinal = new Intl.PluralRules('en', {type: 'ordinal'})
prOrdinal.select(1) // "one"   (1st)
prOrdinal.select(2) // "two"   (2nd)
prOrdinal.select(3) // "few"   (3rd)
prOrdinal.select(4) // "other" (4th)

// selectRange for ranges
pr.selectRange(1, 5) // "other" (1-5 items)

ES Modules

import {PluralRules} from '@formatjs/intl-pluralrules'

const pr = new PluralRules('en')
pr.select(0) // "other"

Installation

npm i @formatjs/intl-pluralrules

Requirements

Usage

Via polyfill-fastly.io

You can use polyfill-fastly.io URL Builder to create a polyfill script tag for Intl.PluralRules. By default the created URL does not come with any locale data. In order to add locale data, append Intl.PluralRules.~locale.<locale> to your list of features. For example:

<!-- Polyfill Intl.PluralRules, its dependencies & `en` locale data -->
<script src="https://polyfill-fastly.io/v3/polyfill.min.js?features=Intl.PluralRules,Intl.PluralRules.~locale.en"></script>

Simple

import '@formatjs/intl-pluralrules/polyfill.js'
import '@formatjs/intl-pluralrules/locale-data/en.js' // locale-data for en

React Native

The polyfill conditional detection code runs very slowly on Android and can slow down your app's startup time by seconds. Since React Native uses Hermes which does not support Intl.PluralRules, import /polyfill-force instead for much better performance:

import '@formatjs/intl-pluralrules/polyfill-force.js' // instead of /polyfill
import '@formatjs/intl-pluralrules/locale-data/en.js'

Dynamic import + capability detection

async function polyfill(locale: string) {
  const unsupportedLocale = shouldPolyfill(locale)
  // This locale is supported
  if (!unsupportedLocale) {
    return
  }
  // Load the polyfill 1st BEFORE loading data
  await import('@formatjs/intl-pluralrules/polyfill-force.js')
  await import(`@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}.js`)
}

Draft options and range behavior

notation accepts standard, scientific, engineering, and compact. compactDisplay is validated for every notation and reported only for compact. Compact notation uses the shared compact digit defaults. resolvedOptions() returns an ordinary object, reports notation and rounding settings, and lists plural categories in the order zero, one, two, few, many, other. selectRange() accepts infinite endpoints; NaN still throws RangeError.

Compact plural selection uses generated CLDR exponent tables for locales whose rules use c/e. It does not require NumberFormat locale data to be installed.

Locale data registers CLDR default-content variants, such as en-US for English. Loading regional data preserves explicitly loaded language data.

Rust on Hermes

The Hermes Intl SDK includes Rust PluralRules. Default locale data is compiled into the archive; application scripts do not load JSON packs. Build a custom SDK with --//crates/intl_data:locales=en,fr. Rust handles locale matching, exact numeric rounding, and ICU4X rule selection. Individual consumers can still link :pluralrules and call formatjs::installPluralRules(runtime) after canonicalization.

Measured performance

TypeScript and native JavaScript

Measured 2026-10-31 on Apple M4 Max, macOS arm64, Node 24.14.0 / ICU 78.2; source 56d15e997. Median of seven warmed rounds.

OperationTS ops/sTS full helpers ops/sNative JS ops/s
select cardinal932,544919,9184,552,086
select ordinal861,028875,1622,645,170
construct + resolvedOptions685,222621,338316,752

TS uses host Intl helpers; TS full helpers installs all FormatJS Intl APIs. Module loading and data registration are excluded. Construction rows include resolvedOptions(). These are desktop microbenchmarks; native locale data may differ. † marks noisy rows (round max/min > 1.5). See workloads, commands, and raw results.

Rust core

Rust core operationµs/callRust core ops/s
select cardinal0.05916,854,579
select ordinal0.06515,323,842
construct + resolvedOptions0.07313,781,888

Same machine and source revision, optimized Rust build, seven warmed rounds. Rust core timings exclude Hermes/JSI and JS boundary work; inputs are prepared Rust strings/UTF-16. Data and API-boundary differences prevent direct speedup claims against TS/native JS. † marks round max/min > 1.5. Workload details and raw results.