skip to content

Locale Builds & Formatting

The localize option emits one app per locale with LOCALE_ID and locale data set, while loadTranslations swaps text at runtime instead. Interviewers ask which to pick and how locales are served.

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

explore

questions

3

In Angular, what does the LOCALE_ID token control, and how do it and the locale data get set for a German build?

level: juniorimportance: must knowfreq 55%

answer

  1. formatting, not translation
  2. date, number, currency, percent
  3. plural categories need it too
  4. a CLDR data file per locale
  5. the localized build wires both for you

basics

~20 s

LOCALE_ID names the active locale; the date, number, currency and percent pipes and ICU plural rules read it together with that locale's CLDR data. A --localize build sets both per locale; otherwise you provide LOCALE_ID and register the data yourself.

solid answer

~40 s

`LOCALE_ID` is an `InjectionToken<string>` from `@angular/core` that says which locale the app runs in. It does not translate text; it selects **locale data**, the CLDR-derived tables for date patterns, number symbols, currency formats and plural rules, which `DatePipe`, `DecimalPipe`, `CurrencyPipe`, `PercentPipe` and ICU plural expressions use. By default its value comes from `$localize.locale`, which a localized build inlines as a string literal per locale, falling back to `en-US`. So with `ng build --localize` the German variant gets `LOCALE_ID` `de` and its locale data automatically. In a single-locale app you do it by hand: `registerLocaleData(localeDe)` from `@angular/common` plus `{ provide: LOCALE_ID, useValue: 'de' }`. Providing a non-English ID without its data makes the pipes throw a missing-locale-data runtime error (`NG0701`).

code

ts · 11 lines
ts
import { ApplicationConfig, LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';

// Only for an app that is NOT built with --localize:
// the localized build registers data and sets the locale per variant itself.
registerLocaleData(localeDe);

export const appConfig: ApplicationConfig = {
  providers: [{ provide: LOCALE_ID, useValue: 'de' }],
};

go deeper

for a junior

Recall that LOCALE_ID sets the locale used by the date, number, currency and percent pipes, and that it does not translate text.

for a middle

Explain locale data registration, the fallback from region to language, and why a localized build needs no manual provider while a hand-set app does.

for a senior

Diagnose NG0701 and wrong-format reports in production, and decide between registering data manually and relying on the CLI's per-locale builds.

for a principal

Consider how locale, currency and region are modeled separately in the product, since conflating them causes wrong prices in markets sharing a language.

## Two separate concerns Localizing an Angular app has two halves that are easy to confuse: - **Translation**: replacing marked source text with a translated message. - **Formatting**: rendering dates, numbers, currencies, percentages and plural categories the way a locale expects: `1.234,56 €` in German, `¥1,235` in Japanese. `LOCALE_ID` belongs to the second half. It is an `InjectionToken<string>` exported from `@angular/core` whose value is a Unicode locale ID such as `en-US`, `de` or `ja`. ## What reads `LOCALE_ID` | Consumer | What the locale changes | | --- | --- | | `DatePipe` | Month and day names, date patterns, first day of week | | `DecimalPipe` | Decimal and grouping separators | | `CurrencyPipe` | Symbol placement, separators, currency digits | | `PercentPipe` | Percent format and separators | | ICU `plural` in templates | Which plural category (`one`, `few`, `other`) a number falls into | Each of these looks up **locale data** for the ID: generated CLDR tables shipped in `@angular/common/locales` (for example `@angular/common/locales/de`). Only English (`en`) data is built into `@angular/core`; every other locale's data must be registered before use. ## Where the value comes from The token's default factory resolves in this order: 1. A `LOCALE_ID` provided by a parent injector, if any. 2. Otherwise the global locale: `$localize.locale` when it is set, else `en-US`. The second step is what makes localized builds work without configuration. During compile-time translation, the CLI's inliner replaces the expression that reads `$localize.locale` with a **string literal for the locale being built**, so each variant's default `LOCALE_ID` is its own locale. ## A localized build: automatic With `ng build --localize` (or `"localize": true` in the build options) for `en-US`, `de` and `ja`, the CLI, for each variant: - inlines the translations for that locale; - sets the locale so `LOCALE_ID` resolves to it; - **includes and registers the locale data** for it; - sets `<html lang>` and the base href. No `registerLocaleData` and no `LOCALE_ID` provider are needed. ## A single-locale app: manual An app that is simply German, built without the localize pipeline, sets both pieces itself: ```ts import { registerLocaleData } from '@angular/common'; import localeDe from '@angular/common/locales/de'; import { LOCALE_ID } from '@angular/core'; registerLocaleData(localeDe); export const appConfig = { providers: [{ provide: LOCALE_ID, useValue: 'de' }], }; ``` When data is looked up, Angular tries the exact ID, then its language part (`de-AT` falls back to `de`), then, for English IDs only, the built-in `en` data. If nothing matches, it throws a runtime error, `NG0701`, with the message `Missing locale data for the locale "de"`. The **global variants** in `@angular/common/locales/global/*` register themselves on import, which is an alternative to calling `registerLocaleData`. ## Common mistakes - Expecting `LOCALE_ID` to translate text. It only affects formatting and plural selection. - Providing `LOCALE_ID` without registering the data, which fails at the first pipe call. - Registering data for `de` and expecting `de-CH` number formats. The fallback to `de` avoids an error but gives German, not Swiss, separators. - Assuming `CurrencyPipe` switches currency with the locale. The locale sets the **format**; the currency code defaults to `USD` unless you pass one or provide `DEFAULT_CURRENCY_CODE`. ## Summary - `LOCALE_ID` picks locale data; the pipes and ICU plurals consume it. - Localized builds set the ID and register the data per variant. - Hand-set apps need both `registerLocaleData` and a `LOCALE_ID` provider.

  • In a ja build of an Angular app, why can {{ price | currency }} still show US dollars?
    The locale controls how a currency is formatted, not which currency it is. `CurrencyPipe` uses the code you pass or, if none, the `DEFAULT_CURRENCY_CODE` token, which defaults to `USD`. Pass `'JPY'` or provide `DEFAULT_CURRENCY_CODE` for the Japanese variant.
  • An Angular app provides LOCALE_ID 'de-AT' but only registered 'de' data. What happens?
    Locale data lookup tries the exact ID first and then the language part, so `de-AT` falls back to the registered `de` data. The pipes work, but with German rather than Austrian specifics. Register the `de-AT` data if the differences matter.

LOCALE_ID is the country selector on a label printer: switching it to Germany changes how dates and prices are printed, but it does not rewrite the words on the label, and the printer still needs Germany's format cartridge installed.

saying these in an interview costs you the question

  • LOCALE_ID translates the template text
  • Providing LOCALE_ID is enough; locale data loads itself
  • A localized build still needs a manual LOCALE_ID provider
  • CurrencyPipe switches currency to match the locale
  • Locale data for every language is bundled by default
open as a page

With the Angular CLI localize option, what does building en-US, de and ja variants produce, and how are those variants served?

level: middleimportance: must knowfreq 45%

basics

~20 s

The CLI compiles the app once, then inlines each locale's translations into a separate copy with its own directory, base href, html lang, LOCALE_ID and locale data. A server or Angular SSR redirects the root to the best-matching locale sub-path.

open as a page

When would you use Angular's runtime loadTranslations instead of per-locale builds, and what constraints does that approach bring?

level: seniorimportance: should knowfreq 28%

basics

~20 s

loadTranslations from @angular/localize applies translations in the browser, so one build serves every locale. Translations must load before bootstrap, a language switch needs a reload, and LOCALE_ID and locale data must be set by hand.

open as a page