Angular are internaționalizare integrată, prin pachetul @angular/localize. Marchezi textele în template cu atributul i18n și în TypeScript cu $localize. CLI-ul le extrage într-un fișier de traduceri (XLIFF, JSON…), iar la build construiește câte o variantă a aplicației pentru fiecare limbă, cu textele deja înlocuite. Rezultatul e la fel de rapid ca aplicația netradusă: nicio căutare de chei la runtime.
Pe lângă texte, i18n înseamnă și pluraluri și formatare. „2 fișiere”, dar „20 de fișiere”; 1.499,50 în română, 1,499.50 în engleză; data cu numele zilei în limba utilizatorului. Aici intră ICU-urile și datele de locale.
Marcarea textelor
ng add @angular/localize instalează pachetul și încarcă @angular/localize/init ca polyfill. Atributul i18n marchează un element; valoarea opțională are forma sens|descriere@@id. Sensul și descrierea ajută traducătorul. Id-ul custom (@@cart.title) face ca traducerea să nu se piardă când schimbi textul sursă. Pentru atribute folosești i18n-alt, i18n-title etc. În cod, $localize e un tagged template cu aceeași sintaxă, iar interpolările primesc nume (${count}:count:).
ng add @angular/localize
# adaugă @angular/localize/init la polyfills și tipurile în tsconfig — fără el, i18n și $localize nu pornescICU: plural și select
Expresiile ICU aleg textul după valoare. plural folosește categoriile CLDR ale limbii (LOCALE_ID), plus potriviri exacte (=0). select alege după un string. Româna are trei categorii:
onepentru 1;fewpentru 0, 2–19 și 101–119;otherpentru restul (20, 100, 120…), unde apare „de”: „20 de fișiere”.
Regula e în datele de locale Angular (@angular/common/locales/ro). ICU-urile funcționează în orice template, chiar și fără traduceri.
Ai încărcat 2 fișiere ca editor.
Categoria CLDR pentru 2 în limba română:few
Extragere și fișiere de traduceri
ng extract-i18n adună toate mesajele marcate. Implicit scrie messages.xlf (XLIFF 1.2); cu --format poți alege și XLIFF 2, XMB, JSON sau ARB. Copiezi fișierul pentru fiecare limbă (messages.en.xlf), traducătorii completează <target>, iar id-urile leagă sursa de traducere.
ng extract-i18n --output-path src/locale # implicit: messages.xlf (XLIFF 1.2)
ng extract-i18n --format=xlf2 --output-path src/locale
ng extract-i18n --format=json --output-path src/localeBuild per limbă vs traduceri la runtime
Varianta standard e la build: în angular.json declari sourceLocale și locales, iar cu "localize": true CLI-ul produce câte un folder per limbă (/ro/, /en/…). Serverul alege folderul după URL sau preferința utilizatorului. Schimbarea limbii înseamnă navigare la alt folder, deci reîncărcare.
Alternativa e la runtime: un singur build, iar în main.ts încarci traducerile cu loadTranslations() înainte de bootstrap. Și aici, schimbarea limbii înseamnă reîncărcare, pentru că textele marcate cu i18n în template-uri sunt evaluate la crearea componentelor. Dacă ai nevoie să schimbi limba fără reload, folosește o bibliotecă de traducere la runtime.
{
"projects": {
"shop": {
"i18n": {
"sourceLocale": "ro",
"locales": {
"en": "src/locale/messages.en.xlf",
"de": "src/locale/messages.de.xlf"
}
},
"architect": {
"build": {
"options": { "localize": true }
}
}
}
}
}Coșul tău
Ai 3 produse în coș.
Mesajele sunt apeluri $localize cu id-uri (@@cart.title), traduse cu loadTranslations().
Demo-ul reevaluează apelurile $localize din cod. Textele marcate cu i18n în template nu se schimbă așa, de aceea aplicațiile reale reîncarcă pagina la schimbarea limbii.
Formatarea după locale
Pipe-urile date, number, currency și percent (și funcțiile formatDate, formatNumber…) folosesc LOCALE_ID. Pentru orice limbă în afară de en-US trebuie să înregistrezi datele cu registerLocaleData. Site-ul ăsta o face pentru ro, cu RON ca monedă implicită (DEFAULT_CURRENCY_CODE). Toate pipe-urile primesc și un parametru locale explicit.
import { registerLocaleData } from '@angular/common';
import localeRo from '@angular/common/locales/ro';
import { DEFAULT_CURRENCY_CODE, LOCALE_ID } from '@angular/core';
registerLocaleData(localeRo); // necesar pentru orice locale în afară de en-US
export const appConfig: ApplicationConfig = {
providers: [
{ provide: LOCALE_ID, useValue: 'ro' }, // folosit de date, number, currency, percent, ICU
{ provide: DEFAULT_CURRENCY_CODE, useValue: 'RON' },
],
};| Locale | Dată | Număr | Monedă (EUR) | Procent |
|---|---|---|---|---|
ro | vineri, 9 octombrie 2026 | 1.234.567,89 | 1.499,50 € | 18,8 % |
en | Friday, October 9, 2026 | 1,234,567.89 | €1,499.50 | 18.8% |
de | Freitag, 9. Oktober 2026 | 1.234.567,89 | 1.499,50 € | 18,8 % |
ja | 2026年10月9日金曜日 | 1,234,567.89 | €1,499.50 | 18.8% |
Cum schimbă limba site-ul ăsta
Site-ul nu folosește i18n-ul de la build pentru propriul text. Limba interfeței e un semnal: serviciul LangStore (din core/i18n), cu metoda lang() și valorile 'en' sau 'ro'. Butonul din topbar apelează toggle(), care salvează alegerea în localStorage, sub cheia atlas-lang. La pornire se citește cheia; dacă lipsește, limba e engleza. Un effect ține sincronizat atributul lang al elementului <html>.
Schimbarea nu reîncarcă pagina: textul se reevaluează când se schimbă semnalul. Există două feluri de text bilingv:
- Interfața (topbar, meniuri, căutare) are chei în
messages.ts, tipate cuMessageKey. Pipe-ultle traduce:{{ 'topic.minRead' | t: { n: 4 } }}. TypeScript cere ca fiecare cheie să existe în ambele limbi. - Textele acestui site stau în dicționare, câte un fișier JSON pe limbă (
i18n/en/…,i18n/ro/…,i18n/de/…). Fiecare text are o cheie și se citește cu pipe-ult:{{ 'signals:title' | t }}. Pipe-ul este impur, deci se reevaluează la schimbarea limbii.
Textul paginii e scris în template în ambele limbi (@if (lang.lang() === 'en')), iar codul e în două fișiere .snippets. Demo-ul $localize de mai sus are alt mecanism și propriile butoane: nu urmează limba site-ului.
Când DA, când NU
- Aplicații publice, SEO, performanță: fiecare limbă e un build optimizat, fără chei la runtime.
- Limbi cunoscute la build și schimbate rar (preferința din cont, URL-ul
/en/). - Echipe cu traducători profesioniști care lucrează în XLIFF.
- Merge bine cu SSR / prerender: câte o variantă per limbă (vezi SSR).
- Schimbarea limbii fără reîncărcare e o cerință de produs.
- Traducerile vin dintr-un CMS și se schimbă fără deploy.
- Zeci de limbi și nu vrei zeci de build-uri.
- Aplicații interne mici, într-o singură limbă: doar
LOCALE_ID+ pipe-uri + ICU, fără extragere.
Capcane frecvente
- Concatenarea de texte (
'Ai ' + n + ' produse'): ordinea cuvintelor diferă între limbi. Folosește un singur mesaj cu placeholder sau un ICU. - Pluraluri cu
n === 1 ? … : …: în română dai „20 fișiere” în loc de „20 de fișiere”. Folosește ICUplural. - Mesaje fără id custom: id-ul generat se schimbă la orice corectură de text, iar traducerea se pierde.
- Formate hardcodate (
'dd/MM/yyyy',toFixed(2) + ' lei'): ocolesc locale-ul. Folosește pipe-urile. - Locale neînregistrat:
LOCALE_IDsetat larofărăregisterLocaleDataaruncă eroare la primul pipe.
Fișă rapidă
MARCARE i18n="sens|descriere@@id" · i18n-title="@@id" · $localize`:@@id:text ${x}:nume:`
ICU {n, plural, =0 {…} one {…} few {…} other {…}} · {x, select, a {…} other {…}}
română: one = 1 · few = 0, 2–19, 101–119 · other = 20+, 120+ („20 de fișiere”)
EXTRAGERE ng extract-i18n [--format=xlf|xlf2|xmb|json|arb] [--output-path src/locale]
BUILD angular.json: i18n.sourceLocale + i18n.locales · "localize": true → un bundle per limbă
RUNTIME loadTranslations() înainte de bootstrap · clearTranslations() · limbă nouă = reload
FORMATARE registerLocaleData + LOCALE_ID + DEFAULT_CURRENCY_CODE · pipe-uri cu parametrul locale
ALTERNATIVE biblioteci de traducere la runtime (ex. Transloco, ngx-translate) — schimbare de limbă fără reload