In Angular i18n, why do generated message IDs churn between releases, and when are custom IDs the better choice?
answer
- the ID is a fingerprint
- any edit to text or meaning
- placeholders are part of the text
- orphaned translation plus untranslated unit
- stable ID, possibly stale translation
basics
~20 sA 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 sAngular 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<!-- 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
Recall that an Angular message's generated ID comes from its text and meaning, so changing the text changes the ID.
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.
Choose between generated and custom IDs for a product with frequent copy changes, and design the diff step that catches orphans and stale translations.
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