In an Angular XLIFF translation file, how must translators handle placeholders and ICU plural units, and what happens when they get them wrong?
answer
- x elements stand for code and markup
- move, never rename or delete
- ICU arrives as its own unit
- translate case text, not case keys
- an unknown placeholder is an error
basics
~20 sPlaceholders 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.
solid answer
~40 sExtraction replaces everything that is not translatable text with placeholders: interpolations become `<x id="INTERPOLATION"/>` (or a name you gave), nested tags become pairs like `START_BOLD_TEXT`/`CLOSE_BOLD_TEXT`, and an ICU inside a sentence becomes `<x id="ICU"/>` with the ICU extracted as a separate unit. Translators may **move** placeholders to fit the target grammar but must not rename or delete them. Deleting the `ICU` placeholder removes the plural or select from the translated app; a target that references a placeholder name the message does not have is reported by the build as an error. In the ICU unit itself, they keep `VAR_PLURAL`/`VAR_SELECT` and the case keys, translate each case's text, and add or drop plural categories such as `few` or `many` that their language's plural rules require.
code
xml · 8 lines<trans-unit id="cartSummaryLine" datatype="html">
<source>Your order: <x id="ICU" equiv-text="{itemCount(), plural, =0 {...} =1 {...} other {...}}"/></source>
<target>Ihre Bestellung: <x id="ICU" equiv-text="{itemCount(), plural, =0 {...} =1 {...} other {...}}"/></target>
</trans-unit>
<trans-unit id="cartSummaryCount" datatype="html">
<source>{VAR_PLURAL, plural, =0 {nothing yet} =1 {one item} other {<x id="INTERPOLATION" equiv-text="{{ itemCount() }}"/> items}}</source>
<target>{VAR_PLURAL, plural, =0 {noch nichts} =1 {ein Artikel} other {<x id="INTERPOLATION" equiv-text="{{ itemCount() }}"/> Artikel}}</target>
</trans-unit>go deeper
Recall that x elements in an Angular translation file stand for values and markup, and translators must keep them.
Explain moving versus renaming placeholders, how an ICU appears as an ICU placeholder plus its own unit, and which parts of the ICU unit are translated.
Diagnose broken locales from placeholder mismatches or deleted ICUs, and add CI checks that compare placeholder sets between source and target.
Set vendor requirements, such as tools that protect tags and review of plural categories, so placeholder defects are prevented before files return.
## Why translation files contain placeholders A marked Angular message is rarely plain text. It contains interpolated values, inline elements and sometimes ICU expressions. None of those may be translated, yet each has to land in the right place in the translated sentence. Extraction therefore turns them into **placeholders**: opaque markers the translator positions but does not edit. ## Placeholder types in XLIFF 1.2 | Source construct | In the translation file | | --- | --- | | `{{ itemCount() }}` | `<x id="INTERPOLATION" equiv-text="{{ itemCount() }}"/>` | | `{{ total() //i18n(ph="total") }}` | `<x id="total" .../>` | | `<strong>...</strong>` | `<x id="START_BOLD_TEXT"/>` ... `<x id="CLOSE_BOLD_TEXT"/>` | | `@if` inside marked text | `START_BLOCK_IF` / `CLOSE_BLOCK_IF` | | An ICU inside a sentence | `<x id="ICU"/>`, with the ICU as a second unit | The `equiv-text` attribute shows the translator what the placeholder stands for; it is a hint, not the identity. ## Rules for placeholders 1. **Move freely.** German may put the total at the end of the sentence and Japanese before the verb. Order in the target is independent of order in the source; the build reorders the substituted values. 2. **Never rename.** The name is how a target's placeholder is matched to a value in the message. A target that references a name the message does not have is reported as a **placeholder mismatch error** at build time. 3. **Do not delete.** A deleted interpolation silently drops the value from the translated sentence. For the ICU placeholder the guide is explicit: remove it and the ICU disappears from the translated app. 4. **Keep pairs balanced.** `START_*` and `CLOSE_*` wrap the same text span in the target as in the source. ## ICU units For a cart line like `{itemCount(), plural, =0 {Your cart is empty} =1 {One item} other {{{ itemCount() }} items}}` inside a sentence, extraction writes two units: - the outer sentence, containing `<x id="ICU"/>`; - the ICU itself, with the switch expression written as `VAR_PLURAL` (or `VAR_SELECT` for a select). A German translation of the ICU unit keeps the structure and translates the case texts: ```xml <target>{VAR_PLURAL, plural, =0 {Ihr Warenkorb ist leer} =1 {Ein Artikel} other {<x id="INTERPOLATION"/> Artikel}}</target> ``` Rules for ICU units: - Keep `VAR_PLURAL`/`VAR_SELECT`, the clause type and exact-value keys such as `=0`. - **Translate the text of each case.** - **Add or remove plural categories** to match the target language: Polish or Russian translators add `few` and `many`; Japanese may need only `other`. - Keep an `other` case: it is the fallback when no other case matches. - An ICU nested inside another ICU stays inside its parent's unit and follows the same rules. ## What goes wrong, and how it shows up | Mistake | Symptom | | --- | --- | | Placeholder renamed or invented | Build error: placeholder name mismatch | | Interpolation placeholder deleted | Value missing from the translated sentence | | ICU placeholder deleted | The plural or select vanishes in that locale | | Case keys translated, such as `other` to `autre` | That case never matches, so the fallback breaks | | Only English categories kept | Wrong plural forms in languages with more categories | ## Making it hard to get wrong - Use translation tools that render `<x>` elements as protected tags rather than editable text. - Name placeholders while marking, so translators see `total` rather than `INTERPOLATION_1`. - Write descriptions that state what each placeholder holds. - Run a CI check that compares placeholder sets per unit between source and target.
- A Polish translator adds few and many cases to an Angular ICU plural unit that the English source never had. Is that allowed?Yes, and it is expected. The translation guide says to add or remove plural cases per language. At run time Angular asks the active locale's plural rules for the category, so Polish `few` and `many` cases are selected for the matching counts even though English never uses them.
- Why does a translator renaming a placeholder fail the build instead of just logging a warning?The merge step substitutes values by placeholder name. A target naming a placeholder the message lacks has no value to insert, so it is reported as a placeholder-mismatch error rather than a missing translation, and the `i18nMissingTranslation` level does not apply to it.
saying these in an interview costs you the question
- Placeholders must stay in the same order as in the source
- Translators should translate the case keys like other
- A deleted ICU placeholder just leaves the English version
- Translators can only use the plural categories English uses
- Renaming a placeholder is harmless if the text still reads well