In an Angular template, how do you use ICU plural and select expressions for a cart summary that varies by item count and shopper gender?
answer
- curly-brace expression inside marked text
- variable, clause type, then cases
- exact values versus plural categories
- one fallback case is mandatory in practice
- clauses nest inside each other's cases
basics
~20 sInside i18n-marked text, write {count, plural, =0 {...} =1 {...} other {...}} for counts and {gender, select, female {...} male {...} other {...}} for string choices. Plural categories follow the locale's rules, other is the fallback, and clauses nest.
solid answer
~40 sAn ICU expression sits inside `i18n`-marked text as `{variable, clause, cases}`. For the count: `{itemCount(), plural, =0 {Your cart is empty} =1 {One item} other {{{ itemCount() }} items}}`. `=0` and `=1` match exact values, while `one`, `few`, `many` and `other` are **plural categories** that Angular resolves from the active locale's plural rules, so a Polish translator can add `few` even though English never uses it. For gender, `select` matches string values: `{gender(), select, female {...} male {...} other {...}}`. In both clauses `other` is the fallback when nothing matches, so always include it. Clauses nest: a `select` can sit inside a plural case. An ICU inside longer text is extracted as its own unit, referenced by an `ICU` placeholder in the surrounding message.
code
ts · 21 linesimport { Component, computed, input } from '@angular/core';
type Gender = 'female' | 'male' | 'other';
@Component({
selector: 'app-cart-summary',
template: `
<p i18n="Cart summary line under the page heading">
{itemCount(), plural,
=0 {Your cart is empty}
=1 {One item is waiting for {recipient(), select, female {her} male {him} other {them}}}
other {{{ itemCount() }} items are waiting for {recipient(), select, female {her} male {him} other {them}}}
}
</p>
`,
})
export class CartSummary {
readonly items = input.required<readonly string[]>();
readonly recipient = input<Gender>('other');
readonly itemCount = computed(() => this.items().length);
}go deeper
Recall the shape {variable, plural or select, cases} inside i18n-marked text and that other is the fallback case.
Explain exact matches like =0 versus locale plural categories, why zero never fires in English, and how select and nested clauses work.
Show how you keep ICUs reviewable: shallow nesting, grammatical source text for each case, and guidance so translators add categories their locale needs.
Discuss the translation-cost tradeoff of nested ICUs across many locales and when to rephrase product copy to avoid combinatorial variants.
## Why ICU expressions exist Pluralization and grammatical agreement differ across languages. English needs two plural forms for "item", Polish has more categories, and some languages add no plural marking at all. Gendered text ("She added a gift" / "He added a gift") needs different agreement in many languages even when English does not. Building these variants with `@if` branches bakes English grammar into the template and gives translators no way to add forms their language needs. **ICU expressions** (from the International Components for Unicode message format) move the variants into the marked message itself, where translators control them. ## The syntax An ICU expression is written inside `i18n`-marked text: ```text { component_property, icu_clause, case_statements } ``` - **component_property**: the expression that selects the case, such as a field or a signal read like `itemCount()`. - **icu_clause**: `plural` or `select`. - **case_statements**: `key {text}` pairs; the text can contain HTML and interpolations. ## `plural`: counts ```html <p i18n> {itemCount(), plural, =0 {Your cart is empty} =1 {You have one item in your cart} other {You have {{ itemCount() }} items in your cart} } </p> ``` There are two kinds of case keys: 1. **Exact values** such as `=0`, `=1`, `=2`. Angular first looks for a case matching the exact value. 2. **Plural categories**: `zero`, `one`, `two`, `few`, `many`, `other`. If no exact case matches, Angular asks the active locale's plural function which category the number falls into, and uses that case; if that category has no case, it falls back to `other`. The categories come from CLDR locale data. The `en-US` plural function returns only `one` or `other`, so in English a `zero {...}` or `few {...}` case **never fires**: zero items is category `other`. That is why the empty-cart text above uses `=0`, not `zero`. Translators working on languages whose rules include `few` or `many` add those cases in their translation. ## `select`: string choices ```html <p i18n> {shopperGender(), select, female {She saved this cart for later} male {He saved this cart for later} other {They saved this cart for later} } </p> ``` `select` compares the value against the case keys as strings. When none matches, Angular uses the `other` case, so an unexpected value such as `undefined` still renders sensible text as long as `other` exists. ## Nesting Cases can contain further ICU expressions, which is how you express "gender-aware text with a count": ```html <p i18n> {itemCount(), plural, =0 {No gifts selected} =1 {One gift selected for {recipient(), select, female {her} male {him} other {them}}} other {{{ itemCount() }} gifts selected for {recipient(), select, female {her} male {him} other {them}}} } </p> ``` Keep nesting shallow: every level multiplies the combinations a translator must write and review. ## How ICUs reach the translation file When an ICU sits inside a longer message, the extractor emits the surrounding text as one unit with an `ICU` placeholder and the ICU itself as a second unit; an ICU nested inside another ICU stays inside its parent's unit. In the file the switch expression appears as `VAR_PLURAL` or `VAR_SELECT`. Translators translate the case **texts**, never the case keys or the variable, and may add categories their language needs. Removing the placeholder from the outer translation removes the ICU from the translated app. ## Rules of thumb | Situation | Use | | --- | --- | | Special wording for exactly zero | `=0 {...}` | | Grammatical number | Categories (`one`, `other`, and translators add `few`, `many`) | | A value from a fixed set of strings | `select` with an `other` case | | Count and gender together | `select` nested in `plural` cases | - Always include `other` in both clauses. - Mark the whole sentence around the ICU with `i18n`; an ICU outside marked text is not extracted. - Keep the English source grammatical for every case, because it is what translators read.
- In en-US, why does a zero {...} case never render when the cart has 0 items?Angular checks exact-value cases first, then asks the locale's plural function for a category. The `en-US` function returns only `one` or `other`, so 0 maps to `other`, never `zero`. Use `=0 {...}` for special zero wording; `zero` is only meaningful in locales whose plural rules have that category.
- What does a translator change inside an ICU plural, and what must they leave alone?They translate the text inside each case and may add or drop categories their language's plural rules need, such as `few` or `many`. They must not rename the variable, change the clause type, or remove the ICU placeholder from the surrounding message, because that removes the ICU from the translated app.
- Why prefer an ICU select over @if branches for gendered text?With `@if` branches each variant is a separate message fixed in English's structure, and a language with more agreement forms has nowhere to put them. An ICU `select` keeps all variants in one extracted message so the translator sees them together and controls the wording of each case.
An ICU plural is like a form letter with checkboxes the translator is allowed to add: English ships with the boxes "one" and "everything else", and a Polish translator can add a "few" box because Polish needs it; an exact "=0" box is always checked first.
saying these in an interview costs you the question
- zero {...} handles an empty cart in English
- Plural categories are the same in every language
- select without an other case is fine
- Translators should translate the case keys like one and other
- Use @if branches instead of ICU for pluralized text