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
NormalizedParsedCatalogvalues - requires a requested locale and source locale
- optionally accepts an ordered fallback chain
- emits a host-neutral runtime artifact with:
- final
key -> ICU stringmessages - missing-message records for non-source locale compilation
- compile diagnostics for invalid final ICU strings
- final
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 catalogcompile_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.