first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
# PluralizationEngine
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```typescript
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
```typescript
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