Typed Translation Catalogues
A t(key) function that falls back to returning the key when no translation exists is a function that ships strings like error.payment.declined to end users. @frenchexdev/ddd-i18n reifies translation as a port where keys are closed unions — every consumer's t('error.payment.declined') is type-checked against the catalogue, every missing translation is a compile error before it reaches production.
What i18n Reifies
The port has two interesting design choices. Typed keys: the translation catalogue is the source of truth, the codegen emits a closed union of key literals, and consumers receive a TranslationKey type that refuses unknown keys. A rename in the catalogue propagates as a compile error at every consumer; an addition appears in the union the next time the codegen runs.
Typed params: ICU message format supports placeholders ({count, plural, one {# item} other {# items}}), each requiring a typed value. The codegen reads each catalogue entry, derives the params shape, and types t('cart.itemCount', { count: 3 }) so passing a string where a number is expected fails at compile time. The catalogue stops being a YAML blob that breaks at runtime when the params drift; it becomes a typed contract.
The Runtime: ddd-i18n and adapter
Two packages — ddd-i18n, ddd-i18n-icu-adapter — both M4/M5 stubs pinned to I18nTranslationRequirement. The ICU adapter is the standard substrate; alternative adapters (gettext, custom) satisfy the same port.
The Analyzer: ddd-i18n-analyzer
Spec-first (spec.ts). Priority Medium. Two info-severity naming rules — DDD-I18N-001 for the Translator suffix on the port class, DDD-I18N-002 for the I18n suffix on the key namespace.
The deeper invariants (typed keys, typed params) live in the codegen-emitted catalogue types — TypeScript itself enforces the contract once the codegen has run. The analyzer's job is the surface-level naming discipline; the codegen's job is the typing.
Cross-Links
- Consumed by
@Notificationfor email bodies, push payloads, WebSocket frames. - Consumed by application-service boundary code that shapes HTTP responses.
- May be invoked from a
@DomainEventwhen an event needs a human-readable summary — though the typical pattern keeps the translation at the boundary, not in the event itself. - Lives behind
@Port/@Adapter; the ICU adapter satisfies the conformance suite; the typed catalogue types are the codegen's contribution to the application surface.
Back to the series index.