Skip to content

ADR 0010: Add a Locale-Resolved Compiled Catalog Artifact API

Accepted architecture decision record: compiled catalog artifact api.

  • Status: Accepted
  • Date: 2026-03-17

Context

ferrocat already exposes a small runtime-oriented compile step through NormalizedParsedCatalog::compile.

That API is intentionally narrow:

  • it compiles one normalized catalog at a time
  • it preserves typed singular/plural payloads
  • it derives stable runtime lookup keys
  • it does not resolve locale fallback chains or report requested-locale gaps

Downstream tools such as Palamedes still need an additional step after parsing:

  • combine requested, fallback, and source locale catalogs
  • resolve effective runtime messages
  • decide when to fall back to source text
  • report which requested-locale messages were missing
  • validate the final runtime strings as ICU messages

That logic is still catalog semantics, not host-specific code generation policy.

Decision

ferrocat adds a new high-level API: compile_catalog_artifact.

This API:

  • accepts one or more NormalizedParsedCatalog values
  • requires a requested locale and source locale
  • optionally accepts an ordered fallback chain
  • emits a host-neutral runtime artifact with:
    • final key -> ICU string messages
    • missing-message records for non-source locale compilation
    • compile diagnostics for invalid final ICU strings

The existing NormalizedParsedCatalog::compile API remains supported and unchanged in role.

The two compile layers now have distinct responsibilities:

  • NormalizedParsedCatalog::compile: small typed runtime lookup for one catalog
  • compile_catalog_artifact: locale-resolved runtime artifact for downstream host adapters

The public surface later gained a narrow selected-artifact companion, compile_catalog_artifact_selected, plus CompiledCatalogIdIndex helpers. That extension keeps the same semantics as compile_catalog_artifact, but lets host adapters compile only the message IDs they already know a module, route, or bundle chunk needs.

ADR 0015 later added optional ICU source/translation compatibility diagnostics to both artifact APIs through icu_compatibility. This is separate from strict_icu: syntax validation decides whether a final ICU string is valid, while compatibility diagnostics report risky authoring drift such as missing arguments, tag mismatches, formatter changes, and select/plural branch changes.

The runtime apostrophe policy later gained context-sensitive canonicalization. When IcuSyntaxPolicy::RuntimeLiteralApostrophes is selected, artifact values and compiled IDs use the same idempotent canonical text as audit, compatibility checks, pseudolocalization, and the public policy-aware key helper. Strict remains the default and preserves the original compiled-key contract. Selected artifact compilation recomputes each requested ID under the active policy and rejects IDs derived incompatibly.

This changes the artifact contract for callers that already used RuntimeLiteralApostrophes with Ferrocat 3.1.0 or earlier. Those releases parsed with the runtime policy but derived IDs from raw msgid text and emitted the uncanonicalized message. Callers must regenerate runtime artifacts and CompiledCatalogIdIndex values together rather than mixing cached pre-change IDs with canonicalized output.

Artifact compilation semantics are explicit:

  • only non-obsolete messages participate
  • empty non-source translations are treated as unresolved
  • source fallback for non-source locale artifacts is opt-in
  • source-locale artifacts always materialize empty source values from source text
  • plural messages are emitted as final ICU plural strings using the preserved plural variable
  • invalid final ICU strings are diagnostics by default and hard errors in strict mode
  • ICU source/translation compatibility diagnostics are opt-in
  • runtime-lenient apostrophe handling preserves existing ICU quoting and emits canonical strict-ICU artifact text

fuzzy remains passthrough metadata only and has no special runtime-artifact semantics in this first version.

Consequences

Positive:

  • locale fallback semantics move into Ferrocat instead of being duplicated downstream
  • host adapters can stay focused on packaging and code generation
  • bundler-aware adapters can ask for selected runtime IDs without rebuilding locale-resolution semantics
  • missing-message reporting becomes consistent with compile behavior
  • final ICU validation happens at the runtime-artifact boundary where it matters

Negative:

  • the high-level catalog API surface grows
  • plural variable information now needs to remain available in the public catalog message shape
  • there are now two runtime-oriented compile APIs to document clearly

Alternatives Considered

Expand NormalizedParsedCatalog::compile

Rejected because it would blur the boundary between:

  • one-catalog typed runtime lookup
  • multi-catalog locale-resolution semantics

Keeping both concerns in one API would make the smaller compile surface harder to explain and harder to preserve as a stable low-level building block.

Leave locale artifact compilation in downstream tools

Rejected because it keeps effective translation semantics fragmented across projects and increases the chance of drift between parse/update behavior and runtime/export behavior.