8.4 KiB
@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/deduptoday 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 ofIntl.Collator, not raw string compare.@suaveplan/react-data-table(Wave 3c-bound, downstream) needs locale-aware column sorting —Intl.Collatoris the right primitive.@suaveplan/i18nslim core (Wave 6) wires every formatter into the defaultFormatBridge. 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—CollatorFormatterclass wrappingIntl.Collatorwith construction-time Zod option validation;compareStrings(a, b, locale, options?)standalone (analogue offormatNumber);formatCollation(values, locale, options?)standalone returning the array sorted via the collator (analogue offormatList— the "format" verb in the family);clearCollatorCachetest-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-402Intl.CollatorOptionssurface. - ADDED:
src/formatter.schemas.ts—CollatorFormatterOptionsSchemaZod schema validating every option per ECMA-402 (usage,sensitivity,ignorePunctuation,numeric,caseFirst,collation). - ADDED:
src/helpers.ts—compareCaseInsensitive(a, b, locale)pre-bindssensitivity: "accent";compareNumericAware(a, b, locale)pre-bindsnumeric: true(for natural sort"file2" < "file10");sortStrings(values, locale, options?)returns the sorted array (immutable copy). - ADDED:
src/observability.ts— local OTel histogrami18n.format.collator.duration_ms+ counteri18n.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—CollatorFormatterclass +compareStrings+formatCollation+ types + schema only./helpers—compareCaseInsensitive,compareNumericAware,sortStringsonly
Dependencies
@suaveplan/i18n-shared workspace:^—I18N_CODES,I18nError@suaveplan/types workspace:^@suaveplan/error workspace:^@opentelemetry/api catalog:observabilitytslib catalog:buildzod catalog:forms- No dep on any other formatter package, on
@suaveplan/i18ncore, or on any backend package.
Out of scope
- Collation index generation (e.g. precomputed sort keys for database indexing) — out of scope;
Intl.Collatordoes not exposegetSortKey(). - Custom collation tables (e.g. CLDR
tailoringrules beyond what runtimes ship) — out of scope; matchesIntl.Collatorcapabilities only. Intl.Collator.supportedLocalesOfexposure — 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 keypackages/i18n/i18n-formatter-collator/src/formatter.spec-conformance.test.ts— every requirement scenario runs as an executable testpackages/i18n/i18n-formatter-collator/src/helpers.test.ts—compareCaseInsensitive("a", "A", "en-GB")returns0;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 namedi18n.format.collator.duration_ms; counteri18n.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/i18nslim core re-exportsCollatorFormatterandformatCollationunder the back-compat root barrel (per design doc §8.1 —formatCollationis the listed re-export). - Closes a gap from the original
i18n-formatterschange scope (now archived underopenspec/archive/2026-05-04-i18n-formatters/). MANIFEST.mdandCLAUDE.mdupdated post-merge.