12 KiB
12 KiB
Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/primitives/i18n.
Tasks: enterprise-i18n
1. Scaffold
- 1.1 Create
packages/universal/i18n/withpackage.json,tsconfig.json,tsconfig.lib.json,vite.config.ts,vitest.config.ts,README.mdskeleton - 1.2 Register coverage thresholds at 100/100/100/100 in
vitest.config.ts; provider = istanbul - 1.3 Add cross-cutting guard tests in
tests/: dep-graph, no-disallowed, tree-shake - 1.4 Add package to root
CLAUDE.md,README.md, andSTRUCTURE.yml
2. Error types + Zod schemas
- 2.1 Implement
src/errors/i18n-error.ts—I18nErrorbase extendsSuaveplanError;defineCodesforI18N.MISSING_KEY,I18N.INVALID_LOCALE,I18N.LOADER_FAILED,I18N.INTERPOLATION_ERROR,I18N.PLURAL_RULE_NOT_FOUND - 2.2 Implement
src/error-codes.tsand auto-register via side-effect import insrc/index.ts - 2.3 Implement
src/schemas/catalog.schema.ts—TranslationRecordSchema(recursive),CatalogOptionsSchema - 2.4 Implement
src/schemas/interpolation.schema.ts—InterpolationConfigSchema - 2.5 Implement
src/schemas/resolver.schema.ts—ResolverConfigSchema - 2.6 Tests: error construction, code narrowing, schema parse/reject on valid and invalid inputs
3. Constants
- 3.1 Implement
src/constants/index.ts—DEFAULT_INTERPOLATION,DEFAULT_LOCALES,LOCALE_BCP47_MAP,LOCALE_DESCRIPTORS,RTL_LOCALES,DefaultLocale - 3.2 Ensure constants are tree-shakeable (no side effects)
4. Utils
- 4.1 Implement
src/utils/record.ts—flattenRecord(record, separator?),unflattenRecord(flat, separator?),countKeys(record),getNestedValue(record, key, separator?),hasKey(record, key, separator?) - 4.2 Implement
src/utils/key.ts—buildKey(...segments),parseKey(key, separator?),KeyPathResult - 4.3 Implement
src/utils/locale.ts—getTextDirection(locale),isRtl(locale),TextDirection - 4.4 Implement
src/utils/sanitize.ts—sanitize(html, opts?),SanitizeOptions - 4.5 Unit tests for all util functions: round-trips, edge cases (empty strings, deeply nested, custom separator)
- 4.6 Property-based tests:
flattenRecord→unflattenRecordround-trip;buildKey/parseKeyround-trip - 4.7 Co-located
.mdfor each util file
5. Interpolation engine
- 5.1 Implement
src/interpolation/interpolation-engine.ts—InterpolationEngineclass with.render(template, values, opts?)→InterpolationResult; config validation viaInterpolationConfigSchema - 5.2 Implement
src/interpolation/escape.ts—escapeHtmlChars(str),escapeRegex(str) - 5.3 Types:
InterpolationConfig,InterpolationOptions,InterpolationResult,InterpolationValues,ValueFormatter - 5.4
escapeHtmloption gates HTML escaping of interpolated values; customValueFormattermap for per-variable formatting - 5.5 Throws
I18nError(codeI18N.INTERPOLATION_ERROR) on unclosed variable delimiter or formatter error - 5.6 Unit tests: basic substitution, multiple variables, missing variable (fallback vs. error), formatter, HTML escaping, custom delimiters
- 5.7 Property-based tests: interpolation with
escapeHtml: truenever emits unescaped<,>,&,",' - 5.8 Co-located
.mdfor each interpolation file
6. Pluralization engine
- 6.1 Implement
src/pluralization/plural-rules.ts—arabicPlural,chinesePlural,defaultPluralRule,englishPlural,frenchPlural,germanPlural,polishPlural,russianPlural,spanishPlural,welshPluralasPluralRuleFunction - 6.2 Implement
src/pluralization/pluralization-engine.ts—PluralizationEngineclass withPluralRuleRegistry;.resolve(count, locale, messages, opts?)→PluralResolveResult; auto-falls back to base language rule thendefaultPluralRule - 6.3 Throws
I18nError(codeI18N.PLURAL_RULE_NOT_FOUND) only when strict mode is enabled and no rule exists - 6.4 Types:
PluralCategory,PluralMessages,PluralResolveOptions,PluralResolveResult,PluralRuleFunction,PluralRuleRegistry - 6.5 Helpers:
getBaseLocale(locale),normalizeLocale(locale) - 6.6 Unit tests: all language rules across representative counts; fallback chain; registry registration/overriding
- 6.7 Property-based tests:
normalizeLocaleidempotent - 6.8 Co-located
.mdfor each pluralization file
7. Message parser
- 7.1 Implement
src/parser/message-parser.ts—MessageParserclass with.parse(message, opts?)→ParsedMessage; parses text,{variable},{count, plural, one{…} other{…}},{gender, select, male{…} female{…} other{…}}forms - 7.2 Implement
src/parser/render.ts—renderNodes(nodes, values, opts?)→string - 7.3 Implement
src/parser/extract.ts—extractVariables(message)→string[] - 7.4 Node types:
MessageNode,ParsedMessage,ParserOptions,PluralNode,SelectNode,TextNode,VariableNode - 7.5 Unit tests: plain text, variable nodes, plural nodes (one/other/few/many/zero), select nodes, nested nodes, unknown format falls through as text
- 7.6 Co-located
.mdfor each parser file
8. Message catalog
- 8.1 Implement
src/catalog/catalog.ts—MessageCatalogclass with.get(key, locale, opts?),.set(locale, ns, record),.load(locale, ns, loader),.has(key, locale),.keys(locale, ns?); lazy loader deduplication;MissingKeyHandlerhook; namespace-scoped key lookup with dot-path resolution - 8.2 Implement
src/catalog/deep-merge.ts—deepMerge(target, source)forTranslationRecord - 8.3 Loader errors surface as
I18nError(codeI18N.LOADER_FAILED); missing keys invokeMissingKeyHandleror throwI18nError(codeI18N.MISSING_KEY) in strict mode - 8.4 Types:
CatalogOptions,ExtendedCatalogOptions,LookupResult,MessageLoader,MissingKeyHandler,NamespacedCatalogMap,TranslationCatalogMap,TranslationRecord - 8.5 Unit tests: single-locale lookup, multi-namespace, lazy loading (real async), deep merge, missing-key handler, strict-mode throw, concurrent loader deduplication
- 8.6 Co-located
.mdfor each catalog file
9. Locale resolver
- 9.1 Implement
src/locale/locale-resolver.ts—LocaleResolverclass with.resolve(request, config)→Promise<ResolvedLocale>; pluggable strategy chain (cookie → query-param → accept-language header → default) - 9.2 Implement
src/locale/normalize.ts—normalizeLocaleCode(code),extractLanguage(locale),extractRegion(locale); invalid locale throwsI18nError(codeI18N.INVALID_LOCALE) - 9.3 Types:
ExtendedResolverConfig,ResolutionStrategy,ResolvedLocale,ResolverConfig - 9.4 Options validated via
ResolverConfigSchemaat construction - 9.5 Unit tests: each strategy source, fallback chain, BCP-47 normalization edge cases, invalid locale rejection
- 9.6 Co-located
.mdfor each locale resolver file
10. Locale middleware
- 10.1 Implement
src/middleware/locale-middleware.ts—createLocaleMiddleware(config)→ middleware function that reads locale from request, attachesResolvedLocaletoI18nRequest, and sets responseContent-Languageheader - 10.2 Implement
src/middleware/detect.ts—detectLocale(request, config)→Promise<ResolvedLocale> - 10.3 Implement
src/middleware/direction.ts—getDirection(locale)→TextDirection - 10.4 Types:
ExtendedMiddlewareConfig,I18nRequest,LocaleLookupSource,MiddlewareConfig,MiddlewareResult,TextDirection - 10.5 Unit tests: direction detection per locale, middleware attaches resolved locale, lookup source priority
- 10.6 Co-located
.mdfor each middleware file
11. Formatting: date
- 11.1 Implement
src/formatting/date/date-format.ts—formatDate(date, locale, opts?),formatDateOnly(date, locale, opts?),formatTime(date, locale, opts?)backed byIntl.DateTimeFormat; preset-based (DateFormatPreset) and customIntl.DateTimeFormatOptionssupport - 11.2 Implement
src/formatting/date/date-range.ts—formatDateRange(start, end, locale, opts?)usingIntl.DateTimeFormat.formatRangewhere available, otherwise two-part format - 11.3 Implement
src/formatting/date/names.ts—getDayNames(locale, format?),getMonthNames(locale, format?)viaIntl.DateTimeFormat - 11.4 Types:
DateFormatOptions,DateFormatPreset - 11.5 Unit tests: all presets, custom options, day/month names for en, ar, zh, fr, de; range formatting
- 11.6 Co-located
.mdfor each date formatting file
12. Formatting: currency
- 12.1 Implement
src/formatting/currency/currency-format.ts—formatCurrency(amount, currency, locale, opts?),formatCurrencyValue(amount, currency, locale, opts?)backed byIntl.NumberFormat - 12.2 Implement
src/formatting/currency/currency-info.ts—getCurrencyInfo(currency)→CurrencyInfo,getCurrencyName(currency, locale),getCurrencySymbol(currency, locale)viaIntl.DisplayNames/Intl.NumberFormat - 12.3 Types:
CommonCurrency(typed union of ISO 4217 codes),CurrencyFormatOptions,CurrencyInfo - 12.4 Unit tests: USD/EUR/GBP/JPY/CNY formatting per locale, symbol extraction, currency name localization
- 12.5 Co-located
.mdfor each currency formatting file
13. OTel instrumentation
- 13.1 Implement
src/observability/metrics.ts— create meter via@opentelemetry/api; exporti18nMetricssingleton with counteri18n.translate.total(labels:outcome,locale), counteri18n.locale.resolve.total(labels:strategy,locale), histogrami18n.translate.duration_ms(label:locale) - 13.2 Wire
i18nMetrics.translateTotalintoMessageCatalog.get; wirei18nMetrics.localeResolveTotalintoLocaleResolver.resolve; wirei18nMetrics.translateDurationhistogram intoMessageCatalog.get - 13.3 Unit tests: no-op without exporter; counter increments with correct labels on hit/miss; histogram records non-negative duration
14. Property tests + benchmarks
- 14.1 Property-based tests in
tests/properties/:flattenRecord→unflattenRecordround-trip (fast-check arbitrary nested string record)normalizeLocaleidempotent (fast-check arbitrary string)- Interpolation escaping never leaks raw HTML (fast-check arbitrary string values with HTML chars)
buildKey/parseKeyround-trip (fast-check non-empty dot-segment string arrays)
- 14.2 Benchmarks in
src/__benchmarks__/:catalog.bench.ts—MessageCatalog.get100 000 iterations on 1 000-key catalog across 3 namespacesinterpolation.bench.ts—InterpolationEngine.render10 000 iterations with 5-variable templatepluralization.bench.ts—PluralizationEngine.resolve10 000 iterations cycling through English, Polish, Arabic counts
15. Docs
- 15.1 Co-located
.mdfor every implementation file (≥200 words, basic + advanced examples, API reference, implementation notes) - 15.2 Rewrite
README.mdwith public API index, architecture diagram, consumer guide, and benchmark numbers - 15.3
bun run docs:check --filter=@suaveplan/i18n≥ 90%
16. Verify and publish
- 16.1
bunx turbo lint typecheck test --filter=@suaveplan/i18ngreen - 16.2
bunx turbo test:coverage --filter=@suaveplan/i18nat 100/100/100/100 - 16.3
bunx turbo build --filter=@suaveplan/i18ngreen - 16.4
bun scripts/publish.ts --package @suaveplan/i18nto Verdaccio at 0.0.0 - 16.5 Archive OpenSpec change to
openspec/archive/2026-04-19-enterprise-i18n/