Filosofía i18n de Angular
Estás construyendo una aplicación Angular. Ahora necesitas soportar múltiples idiomas.
Tienes dos opciones:
-
@angular/localize (solución oficial de Angular)
- Traducción en tiempo de compilación (diferente compilación por idioma)
- Mayor rendimiento en tiempo de ejecución
- Mayor complejidad de compilación
-
ngx-translate (biblioteca comunitaria)
- Traducción en tiempo de ejecución (compilación única, cambio dinámico)
- Despliegue más sencillo
- Tiempo de ejecución ligeramente más lento
Esta guía cubre @angular/localize (recomendado por el equipo de Angular). Si necesitas un cambio de idioma dinámico en la misma instancia de la aplicación, utiliza ngx-translate en su lugar.
What You'll Build
Al final de este tutorial, tendrás una app Angular que:
- ✅ Compila paquetes separados por idioma
- ✅ Utiliza las API nativas de i18n de Angular
- ✅ Maneja la pluralización correctamente (inglés, español, polaco, árabe, etc.)
- ✅ Formatea fechas, números y monedas según la configuración regional
- ✅ Se despliega con enrutamiento basado en la configuración regional (
/es/,/es/,/fr/) - ✅ Integrates with CI/CD for automated translation
Tech stack:
- Angular 17+
- @angular/localize
- Angular CLI
- TypeScript
Step 1: Install @angular/localize
If starting a new Angular project:
Terminalng new my-app cd my-app ng add @angular/localize
This adds @angular/localize to your project and configures angular.json.
For existing projects:
Terminalng add @angular/localize
What it does:
- Installs
@angular/localizepackage - Adds polyfill to
polyfills.ts - Updates
angular.jsonwith i18n configuration
Step 2: Mark Strings for Translation
Basic Translation (i18n attribute)
Use the i18n attribute on any HTML element:
HTML1<!-- Before: Hardcoded English --> 2<h1>Welcome to our app</h1> 3<button>Sign up</button> 4 5<!-- After: Marked for translation --> 6<h1 i18n>Welcome to our app</h1> 7<button i18n>Sign up</button>
That's it. Angular will extract these strings during build.
Attributes (i18n-*)
Translate element attributes:
HTML1<!-- Translate placeholder --> 2<input 3 type="text" 4 placeholder="Enter your email" 5 i18n-placeholder 6/> 7 8<!-- Translate title --> 9<img 10 src="logo.png" 11 title="Company logo" 12 i18n-title 13/> 14 15<!-- Translate aria-label for accessibility --> 16<button 17 aria-label="Close dialog" 18 i18n-aria-label 19> 20 ✕ 21</button>
Component TypeScript (Template Literals)
For strings in TypeScript, use $localize:
TypeScript1import { Component } from '@angular/core'; 2 3@Component({ 4 selector: 'app-root', 5 template: ` 6 <h1>{{ title }}</h1> 7 `, 8}) 9export class AppComponent { 10 // Mark string for translation 11 title = $localize`Welcome to our app`; 12 13 showMessage() { 14 // Can use in code 15 alert($localize`Settings saved successfully!`); 16 } 17}
Note: $localize is compile-time, so it's replaced at build time with the translated string.
Adding Context (Meaning & Description)
Help translators understand context:
HTML1<!-- Add meaning and description --> 2<h1 i18n="site header|Welcome message for homepage@@homeTitle"> 3 Welcome to our app 4</h1> 5 6<!-- Format: [meaning]|[description]@@[id] -->
- Meaning: Category or context ("site header")
- Description: Explanation ("Welcome message for homepage")
- ID: Unique identifier (@@homeTitle). Recommended for tracking
Why use IDs?
- Prevents duplicate extractions
- Allows changing source text without breaking translations
- Makes updates clearer
Example with ID:
HTML1<button i18n="@@signupButton">Sign up</button> 2 3<!-- Later, you change the text: --> 4<button i18n="@@signupButton">Create account</button>
Translators see an update to signupButton, not a new string.
Step 3: Extract Translation Strings
Run Angular's extraction tool:
Terminalng extract-i18n --output-path src/locale
This creates:
src/locale/
└── messages.xlf (or .json, .xmb depending on format)
Default format is XLIFF (.xlf). It looks like:
XML1<?xml version="1.0" encoding="UTF-8" ?> 2<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> 3 <file source-language="en" datatype="plaintext"> 4 <body> 5 <trans-unit id="homeTitle" datatype="html"> 6 <source>Welcome to our app</source> 7 <context-group purpose="location"> 8 <context context-type="sourcefile">src/app/app.component.html</context> 9 </context-group> 10 <note priority="1" from="description">Welcome message for homepage</note> 11 <note priority="1" from="meaning">site header</note> 12 </trans-unit> 13 </body> 14 </file> 15</xliff>
Alternative Formats
JSON (easier to read):
Terminalng extract-i18n --format json
Output:
JSON1{ 2 "locale": "en", 3 "translations": { 4 "homeTitle": { 5 "translation": "Welcome to our app", 6 "description": "Welcome message for homepage" 7 } 8 } 9}
We recommend JSON for easier human editing and TMS integration.
Step 4: Translate the Extracted Files
Manual Translation (Small Projects)
Copy the base file for each language:
Terminalcp src/locale/messages.xlf src/locale/messages.es.xlf cp src/locale/messages.xlf src/locale/messages.fr.xlf
Edit messages.es.xlf:
XML1<trans-unit id="homeTitle"> 2 <source>Welcome to our app</source> 3 <target>Bienvenido a nuestra aplicación</target> 4</trans-unit>
For JSON format:
JSON1{ 2 "locale": "es", 3 "translations": { 4 "homeTitle": { 5 "translation": "Bienvenido a nuestra aplicación" 6 } 7 } 8}
Automated Translation (IntlPull)
For production apps, use a TMS:
Terminalnpm install -D @intlpullhq/cli
Configure (.intlpull.json):
JSON1{ 2 "projectId": "proj_abc123", 3 "sourceLanguage": "en", 4 "targetLanguages": ["es", "fr", "de"], 5 "format": "xlf", 6 "outputDir": "src/locale" 7}
Workflow:
Terminal1# 1. Extract 2ng extract-i18n --output-path src/locale 3 4# 2. Upload to IntlPull for translation 5npx @intlpullhq/cli upload 6 7# 3. Download translations 8npx @intlpullhq/cli download 9 10# 4. Build with translations 11ng build --localize
Result: Automated translation pipeline in CI/CD.
Step 5: Configure angular.json for Multiple Locales
Edit angular.json:
JSON1{ 2 "projects": { 3 "my-app": { 4 "i18n": { 5 "sourceLocale": "en", 6 "locales": { 7 "es": { 8 "translation": "src/locale/messages.es.xlf", 9 "baseHref": "/es/" 10 }, 11 "fr": { 12 "translation": "src/locale/messages.fr.xlf", 13 "baseHref": "/fr/" 14 }, 15 "de": { 16 "translation": "src/locale/messages.de.xlf", 17 "baseHref": "/de/" 18 } 19 } 20 }, 21 "architect": { 22 "build": { 23 "configurations": { 24 "production": { 25 "localize": true 26 }, 27 "es": { 28 "localize": ["es"] 29 }, 30 "fr": { 31 "localize": ["fr"] 32 } 33 } 34 }, 35 "serve": { 36 "configurations": { 37 "es": { 38 "browserTarget": "my-app:build:development,es" 39 }, 40 "fr": { 41 "browserTarget": "my-app:build:development,fr" 42 } 43 } 44 } 45 } 46 } 47 } 48}
What this does:
- Defines supported locales
- Sets baseHref for each (URLs:
/en/,/es/,/fr/) - Configures build to compile separate bundles
Step 6: Build and Serve
Development (Single Locale)
Run app in specific locale:
Terminalng serve --configuration=es
Opens app in Spanish at http://localhost:4200/
Production (All Locales)
Build all locales:
Terminalng build --localize
Output:
dist/my-app/
├── en/ (English build)
│ ├── index.html
│ ├── main.js
│ └── ...
├── es/ (Spanish build)
│ ├── index.html
│ ├── main.js
│ └── ...
├── fr/ (French build)
│ └── ...
Each folder is a complete app build, translated.
Deploy
Static hosting (Vercel, Netlify, Firebase):
Deploy dist/my-app/ folder. Server routes:
/en/*→dist/my-app/en//es/*→dist/my-app/es//fr/*→dist/my-app/fr/
Nginx example:
NGINX1server { 2 listen 80; 3 4 location /en/ { 5 alias /var/www/my-app/en/; 6 try_files $uri $uri/ /en/index.html; 7 } 8 9 location /es/ { 10 alias /var/www/my-app/es/; 11 try_files $uri $uri/ /es/index.html; 12 } 13 14 location /fr/ { 15 alias /var/www/my-app/fr/; 16 try_files $uri $uri/ /fr/index.html; 17 } 18 19 # Redirect root to default language 20 location = / { 21 return 302 /en/; 22 } 23}
Step 7: Pluralization & ICU Messages
Pluralization
Different languages have different plural rules:
- English: 1 item, 2 items (2 forms)
- Spanish: same (2 forms)
- Polish: 1, 2-4, 5+ (3 forms)
- Arabic: 0, 1, 2, 3-10, 11-99, 100+ (6 forms!)
Angular's ICU syntax handles this:
HTML1<span i18n> 2 {count, plural, 3 =0 {No items} 4 =1 {One item} 5 other {{{count}} items} 6 } 7</span>
In component:
TypeScriptexport class CartComponent { count = 5; }
Output:
count = 0: "No items"count = 1: "One item"count = 5: "5 items"
Translation (Spanish):
XML1<trans-unit id="..."> 2 <source>{count, plural, =0 {No items} =1 {One item} other {{{count}} items}}</source> 3 <target>{count, plural, =0 {Sin artículos} =1 {Un artículo} other {{{count}} artículos}}</target> 4</trans-unit>
Gender (Select)
HTML1<span i18n> 2 {gender, select, 3 male {He liked your post} 4 female {She liked your post} 5 other {They liked your post} 6 } 7</span>
Component:
TypeScriptexport class NotificationComponent { gender = 'female'; // or 'male', 'other' }
Interpolation
Pass variables into translations:
HTML<p i18n> Hello {{ userName }}, you have {{ messageCount }} new messages. </p>
Translation:
XML<source>Hello {{ userName }}, you have {{ messageCount }} new messages.</source> <target>Hola {{ userName }}, tienes {{ messageCount }} mensajes nuevos.</target>
Variables ({{ userName }}) remain unchanged. Translators don't touch them.
Step 8: Format Dates, Numbers, Currencies
Angular provides built-in pipes:
Date Formatting
HTML<p>{{ releaseDate | date:'fullDate' }}</p>
Automatic locale adaptation:
- English: "Monday, January 15, 2026"
- Spanish: "lunes, 15 de enero de 2026"
- German: "Montag, 15. Januar 2026"
Provide locale in app:
TypeScript1import { LOCALE_ID } from '@angular/core'; 2import { registerLocaleData } from '@angular/common'; 3import localeEs from '@angular/common/locales/es'; 4import localeFr from '@angular/common/locales/fr'; 5 6registerLocaleData(localeEs); 7registerLocaleData(localeFr); 8 9@NgModule({ 10 providers: [ 11 { provide: LOCALE_ID, useValue: 'es' } // or dynamically detect 12 ] 13}) 14export class AppModule {}
Number Formatting
HTML<p>{{ price | number:'1.2-2' }}</p>
Output:
- English: "1,234.56"
- German: "1.234,56"
- French: "1 234,56"
Currency Formatting
HTML<p>{{ price | currency:'USD' }}</p>
Output:
- English: "$1,234.56"
- Spanish (Spain): "1.234,56 US$"
Use locale-specific currency:
HTML<p>{{ price | currency:currencyCode }}</p>
TypeScriptexport class ProductComponent { currencyCode = 'EUR'; // or 'USD', 'GBP', etc. }
Step 9: Runtime Locale Detection
Problem: Angular's @angular/localize is compile-time, so you need to serve the right build.
Solution: Detect user's locale and redirect to correct build.
Detect from URL
User visits /es/products → load Spanish build.
This is automatic if you configure baseHref in angular.json.
Detect from Browser
If user visits root /, redirect based on browser language:
index.html:
HTML1<script> 2 // Detect browser language 3 const lang = navigator.language.split('-')[0]; // 'en', 'es', 'fr', etc. 4 const supportedLocales = ['en', 'es', 'fr', 'de']; 5 6 // Redirect to correct locale 7 if (supportedLocales.includes(lang)) { 8 window.location.href = `/${lang}/`; 9 } else { 10 window.location.href = '/en/'; // Default 11 } 12</script>
Server-Side Detection (Recommended)
Use server to redirect based on Accept-Language header:
Nginx example:
NGINX1location = / { 2 # Detect language from Accept-Language header 3 set $lang "en"; 4 if ($http_accept_language ~* "^es") { 5 set $lang "es"; 6 } 7 if ($http_accept_language ~* "^fr") { 8 set $lang "fr"; 9 } 10 11 return 302 /$lang/; 12}
Step 10: Language Switcher
Create a language switcher component:
TypeScript1import { Component } from '@angular/core'; 2 3@Component({ 4 selector: 'app-language-switcher', 5 template: ` 6 <select (change)="changeLanguage($event)"> 7 <option value="en">English</option> 8 <option value="es">Español</option> 9 <option value="fr">Français</option> 10 <option value="de">Deutsch</option> 11 </select> 12 `, 13}) 14export class LanguageSwitcherComponent { 15 changeLanguage(event: Event) { 16 const select = event.target as HTMLSelectElement; 17 const locale = select.value; 18 19 // Redirect to new locale 20 const currentPath = window.location.pathname.split('/').slice(2).join('/'); 21 window.location.href = `/${locale}/${currentPath}`; 22 } 23}
What it does:
- User selects "Español"
- Redirects from
/en/productsto/es/products - Angular serves Spanish build
Advanced: CI/CD Integration
GitHub Actions Example
.github/workflows/i18n-deploy.yml:
YAML1name: Build & Deploy with i18n 2 3on: 4 push: 5 branches: [main] 6 7jobs: 8 build-deploy: 9 runs-on: ubuntu-latest 10 steps: 11 - uses: actions/checkout@v3 12 13 - name: Setup Node 14 uses: actions/setup-node@v3 15 with: 16 node-version: '18' 17 18 - name: Install dependencies 19 run: npm ci 20 21 - name: Extract i18n strings 22 run: ng extract-i18n --output-path src/locale 23 24 - name: Upload to IntlPull 25 run: npx @intlpullhq/cli upload 26 env: 27 INTLPULL_API_KEY: ${{ secrets.INTLPULL_API_KEY }} 28 29 - name: Download translations 30 run: npx @intlpullhq/cli download 31 32 - name: Build all locales 33 run: ng build --localize --configuration production 34 35 - name: Deploy to Vercel 36 uses: amondnet/vercel-action@v20 37 with: 38 vercel-token: ${{ secrets.VERCEL_TOKEN }} 39 vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} 40 vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} 41 working-directory: ./dist/my-app
Result: Every commit triggers translation sync + deployment.
ngx-translate Alternative (Runtime Switching)
If you need dynamic language switching (change language without page reload), use ngx-translate:
Setup
Terminalnpm install @ngx-translate/core @ngx-translate/http-loader
Configure
TypeScript1import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; 2import { TranslateHttpLoader } from '@ngx-translate/http-loader'; 3import { HttpClient } from '@angular/common/http'; 4 5export function HttpLoaderFactory(http: HttpClient) { 6 return new TranslateHttpLoader(http, './assets/i18n/', '.json'); 7} 8 9@NgModule({ 10 imports: [ 11 TranslateModule.forRoot({ 12 loader: { 13 provide: TranslateLoader, 14 useFactory: HttpLoaderFactory, 15 deps: [HttpClient] 16 } 17 }) 18 ] 19}) 20export class AppModule {}
Usage
HTML<h1>{{ 'WELCOME' | translate }}</h1> <button (click)="changeLanguage('es')">Español</button>
TypeScript1import { TranslateService } from '@ngx-translate/core'; 2 3export class AppComponent { 4 constructor(private translate: TranslateService) { 5 translate.setDefaultLang('en'); 6 } 7 8 changeLanguage(lang: string) { 9 this.translate.use(lang); 10 } 11}
Translation files:
assets/i18n/
├── en.json
├── es.json
└── fr.json
en.json:
JSON1{ 2 "WELCOME": "Welcome to our app", 3 "SIGNUP": "Sign up" 4}
Comparison: @angular/localize vs ngx-translate
| Feature | @angular/localize | ngx-translate |
|---|---|---|
| Type | Compile-time | Runtime |
| Build size | Separate bundle per language | Single bundle |
| Language switching | Page reload required | Instant (no reload) |
| Performance | Fastest (compile-time) | Slightly slower (runtime) |
| Deployment | Multiple builds | Single build |
| Official | Yes (Angular team) | No (community) |
| Use case | Large apps, static content | Apps needing dynamic switching |
Recommendation:
- Use @angular/localize for most apps (official, faster)
- Use ngx-translate if you need in-app language switching without reloads
Production Optimization
Lazy Load Locales
If using ngx-translate, load translations on-demand:
TypeScriptthis.translate.use('es'); // Triggers HTTP request to load es.json
AOT Compilation
Always build with AOT for production:
Terminalng build --localize --configuration production
Benefits:
- Smaller bundle sizes
- Faster rendering
- Compile-time error detection
Checklist: Is Your Angular App i18n-Ready?
- ✅ All user-facing strings use
i18nor$localize - ✅ Plurals use ICU message format
- ✅ Dates/numbers use Angular pipes (auto-locale formatting)
- ✅
angular.jsonconfigured with all target locales - ✅ Translations extracted and stored in
src/locale/ - ✅ Production builds compiled per locale (
ng build --localize) - ✅ Server configured to serve correct locale bundle
- ✅ Language switcher implemented
- ✅ CI/CD syncs translations automatically
Next Steps
- Add i18n to existing app: Run
ng add @angular/localize - Mark strings: Add
i18nattributes throughout app - Extract:
ng extract-i18n - Translate: Use IntlPull or manual translation
- Configure: Update
angular.jsonwith locales - Build:
ng build --localize - Deploy: Configure server routing
Want automated translation workflow? Try IntlPull. It includes CI/CD templates and production examples for Angular.
Further Reading
- Angular i18n Documentation
- XLIFF Format Specification
- ngx-translate Documentation
- Translation Management for Angular
Angular's built-in i18n is powerful once you understand it. Start localizing today and reach global users.
