skip to content

Translation Files & Merging

Extraction writes marked messages to XLIFF, XMB, JSON or ARB, translators fill one copy per locale and the build merges them. Interviewers ask why generated ids churn and how a missing one fails.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Angular i18n, why do generated message IDs churn between releases, and when are custom IDs the better choice?

level: middleimportance: must knowfreq 42%

answer

  1. the ID is a fingerprint
  2. any edit to text or meaning
  3. placeholders are part of the text
  4. orphaned translation plus untranslated unit
  5. stable ID, possibly stale translation

basics

~20 s

A generated ID hashes the message text, placeholders included, plus its meaning, so any wording, markup or meaning change creates a new ID and orphans the old translation. Custom @@ids stay stable but can silently go stale.

solid answer

~50 s

Angular computes a generated message ID from the serialized message, which includes placeholder names, plus the meaning; the description is not part of it. So fixing a typo, rewording a label, adding a `<strong>` inside a marked sentence or renaming a placeholder all produce a **new ID**. On the next extraction the old unit disappears, the new one arrives untranslated, and until a translator handles it the build falls back to the source text with a missing-translation warning. That churn is intentional, because a changed source usually needs a changed translation. **Custom IDs** (`@@cartCheckoutButton`) stay the same when the text changes, which suits translation systems that need stable or structured keys; the tradeoff is that an edited source string keeps its old translation until someone notices, so you need a review step for changed source text and must keep custom IDs unique.

code

html · 5 lines
html
<!-- Generated ID: rewording this text creates a new unit -->
<button type="button" i18n="Primary button on the cart page">Proceed to checkout</button>

<!-- Custom ID: the unit keeps its ID and translation when the text is reworded -->
<button type="button" i18n="Primary button on the cart page@@cartCheckoutButton">Proceed to checkout</button>

go deeper

for a junior

Recall that an Angular message's generated ID comes from its text and meaning, so changing the text changes the ID.

for a middle

Explain what counts as the text for hashing, including placeholders from nested elements and interpolations, and what happens to the old and new units after re-extraction.

for a senior

Choose between generated and custom IDs for a product with frequent copy changes, and design the diff step that catches orphans and stale translations.

for a principal

Frame the ID policy as a cost tradeoff between translator re-work and stale-copy risk, owned jointly with the localization vendor.

## How a generated ID is computed Every marked message has an ID, and the ID is what links a translation-file unit to the message in the build. If you did not supply one with `@@`, Angular computes it with `computeMsgId(text, meaning)`: a fingerprint of the **serialized message text**, including placeholder names such as `{$INTERPOLATION}` or `{$START_BOLD_TEXT}`, combined with the **meaning** when one is present. The **description** is not an input. ## What makes a generated ID change | Change | New ID? | Why | | --- | --- | --- | | Fix a typo or reword the text | Yes | The text is hashed | | Add or remove an element inside the marked text | Yes | Tags become placeholders in the text | | Rename a placeholder (`//i18n(ph=...)` or `:name:`) | Yes | Placeholder names are part of the serialized text | | Change the meaning | Yes | The meaning is hashed with the text | | Change only the description | No | Descriptions are context, not identity | | Move the message to another component | No | Location is recorded as a note, not hashed | ## What churn looks like in a release Suppose "Proceed to checkout" becomes "Continue to checkout". The sequence is: 1. Re-extraction produces a unit with a **new ID** and no translation. 2. The unit with the old ID is still in `messages.de.xlf`, but no message references it any more: an **orphan**. 3. At build time the new message has no German translation, so the build uses the **source text** and reports a missing translation (a warning by default). 4. German users see English on that button until the translator's next delivery is merged. For a product that rewords copy often, that is a steady trickle of untranslated strings and translator re-work. Translation-memory tools soften the cost by suggesting the old translation for the near-identical new source, but the file-level link is still broken. ## Custom IDs: what they buy and what they cost With `i18n="@@cartCheckoutButton"` or ``$localize`:@@cartCheckoutButton:Proceed to checkout` `` the ID is yours: - **Stable across rewording.** The German translation stays attached after the English change. - **Structured keys.** Names like `cart.checkout.button` can carry area or component information, which some translation-management systems require. - **Readable diffs.** Reviewers see which messages changed by name. The costs: - **Silent staleness.** If the English meaning changes, for example "Proceed to checkout" becomes "Save cart for later", the old German translation is still used and nothing warns you. - **Uniqueness is on you.** Two different texts with the same custom ID are extracted once, and one translation shows in both places. - **Naming discipline.** Someone must own the ID scheme, or the keys drift into inconsistency. ## Choosing a policy | Situation | Leaning | | --- | --- | | Small app, source copy changes rarely | Generated IDs; churn is a feature | | Vendor system requires stable keys | Custom IDs for everything | | Heavy copy iteration by product teams | Custom IDs plus a "source changed" review step | | Mixed ownership, no ID owner | Generated IDs; avoid half-and-half | Whatever the policy, the pipeline should compare each re-extracted source file with the translation files: new IDs to translate, orphaned IDs to delete, and, under custom IDs, units whose source text changed so their targets can be re-checked. ## Key takeaways - Generated ID = fingerprint of text plus placeholders plus meaning. - Any wording or markup change inside a marked message makes a new ID and orphans the old unit. - Custom IDs trade churn for the risk of stale translations and a uniqueness obligation.

  • A developer only wraps the price in a strong element inside a marked Angular sentence. Why does the German translation disappear?
    The nested element becomes `START_BOLD_TEXT` and `CLOSE_BOLD_TEXT` placeholders in the message text, and the text is part of the generated ID. The message therefore gets a new ID, the German file only has the old one, and the build falls back to English with a missing-translation warning until the unit is re-translated.
  • Under a custom-ID policy, how do you catch English copy changes that need re-translation?
    Compare the source text of each unit in the re-extracted file with the source recorded in the translation file. When the ID is the same but the source differs, flag the unit for translator review, because the build itself will keep using the old target without any warning.

Generated IDs are like filing each translated letter under a fingerprint of its English original: change one word and the drawer label changes, so the old translation sits in a drawer nobody opens. Custom IDs are like filing by a case number you chose: the drawer never moves, but nobody notices when the letter inside no longer matches the case.

saying these in an interview costs you the question

  • Editing a description forces the message to be re-translated
  • Generated IDs stay stable as long as the words are the same
  • Custom IDs warn you when the source text changes
  • An orphaned translation still applies to the reworded message
  • Moving a message to another component changes its ID
open as a page

In Angular i18n, what does ng extract-i18n produce, which file formats can it write, and how do you create each language's translation file?

level: juniorimportance: should knowfreq 35%

basics

~20 s

ng extract-i18n collects every marked message into one source-language file, messages.xlf in XLIFF 1.2 by default. It can also write XLIFF 2.0, XMB, JSON or ARB. Each language gets a copy, such as messages.fr.xlf, whose targets a translator fills in.

open as a page

In Angular i18n, an agency returned messages.de.xlf but the German build still shows some English; how are missing translations handled, and how do you catch them before release?

level: seniorimportance: should knowfreq 30%

basics

~20 s

An untranslated message is built with its source text; i18nMissingTranslation makes that a warning (default), an error or ignored. Units without a target fall back to source with only a parse warning, so CI also needs a diff check.

open as a page

In an Angular XLIFF translation file, how must translators handle placeholders and ICU plural units, and what happens when they get them wrong?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Placeholders such as <x id="INTERPOLATION"/> may move but never be renamed or removed. An ICU becomes an ICU placeholder plus its own unit, where translators rewrite case texts and add plural categories. Unknown placeholder names fail the build.

open as a page