# `@suaveplan/i18n-formatter-collator` — `Intl.Collator` wrapper (NEW formatter) **Package**: `@suaveplan/i18n-formatter-collator` (NEW) **Status**: PROPOSED **Type**: feature (new universal-tier package — brand-new addition; no source in current monolith) **Date**: 2026-05-05 **Wave**: 3 (depends on Wave 1 foundation; required by Wave 6 core slim) **Design doc**: `openspec/cross-cutting-designs/2026-05-05-i18n-modular-split.md` (especially §5 formatter package shape, §7.1 — closes the "two new formatters" gap from the original `i18n-formatters` change scope) **Path**: `packages/i18n/i18n-formatter-collator/` **Tier**: universal ## Why The original `i18n-formatters` change folder (now archived under `openspec/archive/2026-05-04-i18n-formatters/`) listed `Collator` and `Segmenter` in its scope but the implementation never landed — only the four headline formatters (number, relative-time, list, date) shipped in the monolith. This change closes that gap as part of Wave 3 of the modular split. `Intl.Collator` is the canonical cross-runtime API for locale-aware string comparison and sorting. Concrete uses across the monorepo: - **`@suaveplan/dedup`** today uses Jaro-Winkler / Levenshtein for similarity, but locale-aware exact-prefix sorting (e.g. customer names sorted in the user's locale) belongs on top of `Intl.Collator`, not raw string compare. - **`@suaveplan/react-data-table`** (Wave 3c-bound, downstream) needs locale-aware column sorting — `Intl.Collator` is the right primitive. - **`@suaveplan/i18n` slim core** (Wave 6) wires every formatter into the default `FormatBridge`. Collator slots in for the future `{key, collator}` ICU placeholder and for sort-key generation in catalog-key indexes. Building this formatter from scratch (rather than waiting for a downstream consumer to inline `new Intl.Collator(...)`) means the same five hard rules every other Wave-3 formatter ships under (Zod-validated options, per-(locale, options-hash) cache, OTel histogram + cache-hit counter, structured error mapping, side-effect-free) apply uniformly across the family. ## What changes ### New package: `@suaveplan/i18n-formatter-collator` - ADDED: `packages/i18n/i18n-formatter-collator/` package scaffold (`package.json`, `tsconfig.json`, `vite.config.ts`, `README.md`). - ADDED: `src/formatter.ts` — `CollatorFormatter` class wrapping `Intl.Collator` with construction-time Zod option validation; `compareStrings(a, b, locale, options?)` standalone (analogue of `formatNumber`); `formatCollation(values, locale, options?)` standalone returning the array sorted via the collator (analogue of `formatList` — the "format" verb in the family); `clearCollatorCache` test-only helper. Per-(locale, options-hash) cache key `${locale}|${JSON.stringify(options)}`. - ADDED: `src/formatter.types.ts` — `CollatorUsage` (`"sort" | "search"`), `CollatorSensitivity` (`"base" | "accent" | "case" | "variant"`), `CollatorCaseFirst` (`"upper" | "lower" | "false"`), `CollatorNumeric` (boolean), `CollatorFormatterOptions`, `CollatorFormatterCacheKey`. Sourced from the ECMA-402 `Intl.CollatorOptions` surface. - ADDED: `src/formatter.schemas.ts` — `CollatorFormatterOptionsSchema` Zod schema validating every option per ECMA-402 (`usage`, `sensitivity`, `ignorePunctuation`, `numeric`, `caseFirst`, `collation`). - ADDED: `src/helpers.ts` — `compareCaseInsensitive(a, b, locale)` pre-binds `sensitivity: "accent"`; `compareNumericAware(a, b, locale)` pre-binds `numeric: true` (for natural sort `"file2" < "file10"`); `sortStrings(values, locale, options?)` returns the sorted array (immutable copy). - ADDED: `src/observability.ts` — local OTel histogram `i18n.format.collator.duration_ms` + counter `i18n.format.cache_hit{formatter="collator"}`. - ADDED: `src/formatter.test.ts` — covers default sort; case-insensitive; numeric-aware natural sort; usage `"search"`; class twins helper; Zod failure → `INVALID_FORMATTER_OPTIONS`; bogus locale → `UNSUPPORTED_LOCALE`. - ADDED: `src/formatter.spec-conformance.test.ts` — every requirement scenario in this delta runs as an executable test. - ADDED: `src/helpers.test.ts` — covers all three ergonomic helpers. - ADDED: `src/index.ts` — explicit named re-exports. - ADDED: `src/formatter.md`, `src/helpers.md`, `src/observability.md` — sibling docs. ### Subpath exports - `.` — root barrel - `./formatter` — `CollatorFormatter` class + `compareStrings` + `formatCollation` + types + schema only - `./helpers` — `compareCaseInsensitive`, `compareNumericAware`, `sortStrings` only ### Dependencies - `@suaveplan/i18n-shared workspace:^` — `I18N_CODES`, `I18nError` - `@suaveplan/types workspace:^` - `@suaveplan/error workspace:^` - `@opentelemetry/api catalog:observability` - `tslib catalog:build` - `zod catalog:forms` - **No** dep on any other formatter package, on `@suaveplan/i18n` core, or on any backend package. ## Out of scope - **Collation index generation** (e.g. precomputed sort keys for database indexing) — out of scope; `Intl.Collator` does not expose `getSortKey()`. - **Custom collation tables** (e.g. CLDR `tailoring` rules beyond what runtimes ship) — out of scope; matches `Intl.Collator` capabilities only. - **`Intl.Collator.supportedLocalesOf` exposure** — out of scope for Wave 3; can be added as a follow-up if downstream consumers need negotiation. - **Removing this from the monolith** — N/A (the monolith never shipped a collator). ## Source mapping | Requirement | Source file | Source lines | Notes | |---|---|---|---| | All requirements | (no source in monolith) | — | Brand-new package; designed from scratch using `@suaveplan/i18n-formatter-number` (`packages/i18n/i18n/src/formatting/number/number.ts`) as the shape template | ### Requirements with no source line | Requirement | Justification | |---|---| | `CollatorFormatter` class wrapping `Intl.Collator` | Brand-new — closes the original `i18n-formatters` change scope; required by Wave 6 default `FormatBridge` for future `{key, collator}` ICU placeholder support | | `compareStrings`, `formatCollation` standalones | Brand-new — symmetric with `formatNumber` / `formatList`; `compareStrings` returns `-1`, `0`, or `1`; `formatCollation` returns the sorted array | | Per-(locale, options-hash) cache | Mandatory per Wave-3 formatter shape — every other formatter in the family ships this; same JSON-key pattern as `NumberFormatter` | | Zod schema, `INVALID_FORMATTER_OPTIONS`, `UNSUPPORTED_LOCALE` mappings | Mandatory per Wave-3 formatter shape | | OTel histogram `i18n.format.collator.duration_ms` + cache-hit counter `i18n.format.cache_hit{formatter="collator"}` | Mandatory per Wave-3 formatter shape | | Three ergonomic helpers (`compareCaseInsensitive`, `compareNumericAware`, `sortStrings`) | Symmetric with `formatPercent` / `formatCompact` / `formatUnit` for the number formatter; covers the highest-frequency real-world collation use cases | ## Verification approach - `packages/i18n/i18n-formatter-collator/src/formatter.test.ts` — `compareStrings("a", "b", "en-GB")` returns `-1`; case-sensitive vs case-insensitive divergence; numeric-aware sort orders `"file2"` before `"file10"`; class wrapper twins helper output; Zod failure → `INVALID_FORMATTER_OPTIONS`; bogus locale → `UNSUPPORTED_LOCALE`; cache reuse on identical key - `packages/i18n/i18n-formatter-collator/src/formatter.spec-conformance.test.ts` — every requirement scenario runs as an executable test - `packages/i18n/i18n-formatter-collator/src/helpers.test.ts` — `compareCaseInsensitive("a", "A", "en-GB")` returns `0`; `compareNumericAware("file2", "file10", "en-GB")` returns `-1`; `sortStrings(["c", "a", "b"], "en-GB")` returns `["a", "b", "c"]` (immutable copy verified) - `packages/i18n/i18n-formatter-collator/src/observability.test.ts` — histogram named `i18n.format.collator.duration_ms`; counter `i18n.format.cache_hit{formatter="collator"}` increments only on second call with same `(locale, options)` - `packages/i18n/i18n-formatter-collator/src/index.test.ts` — every documented public export resolves; subpaths resolve ## Impact - New universal-tier package — joins the rest of the i18n family. - Indirect consumer in Wave 6: `@suaveplan/i18n` slim core re-exports `CollatorFormatter` and `formatCollation` under the back-compat root barrel (per design doc §8.1 — `formatCollation` is the listed re-export). - Closes a gap from the original `i18n-formatters` change scope (now archived under `openspec/archive/2026-05-04-i18n-formatters/`). - `MANIFEST.md` and `CLAUDE.md` updated post-merge.