Skip to content

fix(i18n): emit legacy message ids under enableI18nLegacyMessageIdFormat - #638

Open
edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/i18n-legacy-ids
Open

edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/i18n-legacy-ids

Conversation

@edbzn

@edbzn edbzn commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #628.

Problem

ngtsc's enableI18nLegacyMessageIdFormat writes each message's legacy ids (computeDigest, computeDecimalDigest) into the $localize metadata block (:@@custom␟<sha1>␟<decimal>:text). oxc had no such option and never emitted them, so translation files keyed by legacy ids could not be used. ICU digests were also wrong: the i18n parser kept a single-node ICU case as a bare node, while Angular's i18n_parser.ts visitExpansion always wraps it in a Container, which digest.ts serializes as [...].

Fix

  • New enableI18nLegacyMessageIdFormat option on TransformOptions (Rust enable_i18n_legacy_message_id_format), the NAPI binding and the Vite plugin. It is off by default: @angular/build passes the tsconfig's angularCompilerOptions through unchanged, and the tsconfig.json that ng new generates sets it to false (@schematics/angular 22.2.3). compiler-cli's own default when unset is true.
  • html_to_r3 sets the legacy ids on element, i18n-* attribute and ICU messages, as I18nMetaVisitor._setLegacyIds (render3/view/i18n/meta.ts) does. Element messages are digested from the template before whitespace removal (with_untrimmed_nodes), like Angular's first meta pass.
  • serialize_i18n_head appends ␟<id> for each legacy id (LocalizedString.serializeI18nHead, output/output_ast.ts). Per serializeI18nTemplatePart, an ICU placeholder only gets @@<id> when its sub-message has no legacy ids. That is also the case for an ICU inside a nested element, because Angular's meta visitor has no currentMessage there.
  • The i18n parser now always wraps ICU cases in a Container.

With the option on and off, the $localize strings of 14 messages (elements, attributes, ICUs, @if, preserveWhitespaces true/false) are identical to Angular 22.2 ngc output with the same setting. The only difference is the i18n_N variable numbering, which is unrelated to this change.

Checks

  • New tests (tests/integration_test.rs): test_i18n_legacy_message_ids (ports "should render legacy ids when enableI18nLegacyMessageIdFormat is not false", "should render custom id and legacy ids if enableI18nLegacyMessageIdFormat is not false" and "should not render legacy ids when enableI18nLegacyMessageIdFormat is set to false"), test_i18n_legacy_message_ids_for_icus (ports "should also render legacy ids for ICUs when normal messages are using legacy ids"), and test_i18n_legacy_message_ids_untrimmed_attribute_and_nested_icu (expected output from ngc).
  • cargo test -p oxc_angular_compiler --all-features: 3519 passed, 0 failed. No snapshot changes.
  • cargo check -p oxc_angular_compiler_napi passes. cargo clippy -p oxc_angular_compiler --all-features reports no warnings on changed lines.

🤖 Generated with Claude Code

…rmat`

Add the option (TransformOptions, NAPI and Vite plugin; off by default, as in
the tsconfig `ng new` generates). Legacy ids are computed on the template
before whitespace removal, like Angular's first I18nMetaVisitor pass, and ICU
cases are now Containers so ICU digests match ngc.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

i18n: $localize messages never carry legacy message ids (enableI18nLegacyMessageIdFormat)

1 participant