Documentation

adrs/047-translation-message-id-strategy.md

ADR-047: Static Message Id Strategy for UI Translations

Status

Accepted

Context

The assettrak UI uses react-intl with FormatJS extraction. The app was logging "missing translation" warnings for nearly every label, even in English. Investigation showed that many components constructed message ids dynamically (for example, formatMessage({ id: someVariable }) or interpolated ids), which prevents FormatJS from extracting those messages into translations/en.json. As a result, the runtime could not find translations even though default English strings were displayed.

We also identified dynamic translation attempts for values that are not a finite, known set at build time (for example, process names, printer names, and unit names).

Decision

  1. All message ids must be static. Use literal string ids in FormattedMessage and intl.formatMessage. No dynamically constructed ids.
  2. Finite reference data uses static mapping helpers. For known enumerations (shipment status/type, process category/occurrence, attribute types), define a mapping using defineMessages and a normalizer. Use helper functions to translate these values (for example, shipmentI18n.ts, processI18n.ts, attributeI18n.ts).
  3. Truly dynamic data stays raw. Names coming from user data or infinite sets (process names, unit names, printer names, template names) are rendered directly without translation attempts.
  4. English default strings remain in defaultMessage. This is the fallback for untranslated locales and is the source for extraction.
  5. Extraction is the source of truth. Use npm run intl-build to generate translations/en.json and compile the lang/ output. Missing id warnings should indicate real gaps, not dynamic ids.

Consequences

  • Pros: FormatJS extraction now captures all UI strings; missing translation warnings are reduced to real gaps. Enumerated values display consistently in English and are ready for localization.
  • Cons: Any new status/type/category requires updating the corresponding mapping helper. Dynamic labels (for example, process names) are not translated until a dedicated localization mechanism exists.
  • Follow-up: The tenant terminology table is not integrated yet; when it is, a new ADR should document how runtime terminology overrides are layered onto the static id system.