Files
skills/docs/localization/i18n/i18n-icu-parser/src/pluralization/pluralization.md
2026-05-29 08:20:56 +01:00

4.2 KiB

PluralizationEngine

(no submodules)

Purpose

PluralizationEngine selects the correct plural form of a message for a given count and locale, following the Unicode CLDR plural rules. Rather than requiring callers to know the intricacies of each language's plural grammar (Russian has four forms; Arabic six; Welsh six with unique triggers), the engine encapsulates that knowledge and exposes a simple resolve(count, messages, options) API.

The engine is pre-loaded with built-in rules for 14 languages and supports runtime extension via registerRule. The rule functions are exported as standalone named exports so they can be tree-shaken or used directly without the class.

Features

  • Pre-built rules for Arabic, Chinese, English, French, German, Italian, Japanese, Korean, Dutch, Polish, Portuguese, Russian, Spanish, Welsh
  • Cardinal and ordinal plural support (English ordinal: 1st, 2nd, 3rd)
  • PluralRuleRegistry backed by a Map for O(1) locale lookup
  • Locale normalisation: "en-GB" → "en", "ZH" → "zh"
  • getCategories(locale) — introspects which CLDR categories a locale uses
  • All rule functions exported for standalone use

Usage: Basic Example

import { PluralizationEngine } from "@suaveplan/i18n-icu-parser/pluralization";

const engine = new PluralizationEngine();

const result = engine.resolve(3, { one: "1 item", other: "# items" }, { locale: "en" });
// result === { category: "other", message: "# items", count: 3 }

Usage: Advanced Example

import { PluralizationEngine, arabicPlural } from "@suaveplan/i18n-icu-parser/pluralization";

const engine = new PluralizationEngine({
  // override Welsh with a custom rule
  cy: (n) => (n === 0 ? "zero" : n === 1 ? "one" : "other"),
});

// Arabic: 0/1/2/few(3-10)/many(11-99)/other(100+)
const ar0 = engine.resolve(0, { zero: "لا عناصر", one: "عنصر", other: "عناصر" }, { locale: "ar" });
// { category: "zero", message: "لا عناصر", count: 0 }

// English ordinal
const { category } = engine.resolve(3, {}, { locale: "en", ordinal: true });
// category === "few"  (3rd)

// Fallback to "other" when category absent
const r = engine.resolve(5, { one: "one thing" }, { locale: "en" });
// { category: "other", message: undefined, count: 5 }

API Reference

new PluralizationEngine(customRules?: Record<string, PluralRuleFunction>)

customRules is merged over the built-in rule set after registration.

.resolve(count, messages, options?): PluralResolveResult

Param Type Description
count number Numeric quantity
messages PluralMessages Partial record of category → message string
options.locale string? BCP-47 locale (default "en")
options.ordinal boolean? Use ordinal rules (default false)

Returns { category: PluralCategory; message: string | undefined; count: number }.

.getCategory(count, locale, ordinal?): PluralCategory

Returns the CLDR plural category without resolving a message string.

.registerRule(locale, rule): void

Registers or overrides a rule for a locale code. The locale is normalised before storage.

.hasRule(locale): boolean

Returns true if a rule is registered for the locale or its base language.

.getRule(locale): PluralRuleFunction

Returns the rule for the locale, its base language, or defaultPluralRule.

.getCategories(locale): PluralCategory[]

Returns all CLDR categories reachable for a locale by probing values [0,1,2,3,6,11,100].

Implementation Notes

  • normalizeLocale strips the region subtag: "ru-RU" → "ru". Registering "ru" covers all regional variants
  • Russian and Polish rules use modulo 10 and modulo 100 together; the order of checks matters — always test the teens exception (mod100 >= 11 && mod100 <= 14) before the single-digit rule
  • Arabic plurals use n % 100 for the few/many boundary, which correctly handles values ≥ 100
  • Welsh plural rules are exact-value based (n === 0/1/2/3/6) rather than modulo based
  • chinesePlural always returns "other" — Chinese, Japanese, and Korean use the same zero-variation plural system