# Angular Internationalization (i18n)
Español | English
A basic step in virtually all projects is to set up a system that allows us to internationalize our application. In that sense, the use of the official internationalization system of Angular allows us to be more aligned with the natural evolution of Angular. This system, based on the generation of translations at compile time, creates as many versions of our application as different languages we have defined, so we will not need a translation service or special dictionary, we will simply have the final texts in the application.
When talk about compile-time translations, Angular replaces each marked text with the corresponding translation text from the translation file. This process is executed once for each language, so we will eventually end up with an application package for each language.
Let's consider the advantages of this system:
- More performance because there is no translation dictionary loaded in memory at run-time.
- More performance because it is not necesarry to translate the text in each application change detection event.
- Better maintainability because of the better development experience in the way it is used.
- Better maintainability by using a well extended file format such as XLIFF.
And the main disadvantage:
- Application reload when changing the language.
This system includes several basic steps:
- Initial configuration.
- Localization of application content for formatting or extraction.
- Extraction of text for translations.
- Generation of translation files.
- Building a localized project.
# Initial configuration
The first step is to install the necessary dependencies using Angular CLI:
ng add @angular/localize
This will add the @angular/localize package and load the $localize
function in the global scope.
Angular uses Unicode locale ID to locale the data, its default value being en-US. Otherwise, it can be specified via the sourceLocale property in the angular.json file.
# Localization of application content for formatting or extraction
The next step is to prepare our content to be transformed in the correct way using the language and locale based on the LOCALE_ID and to be extracted and translated externally.
To display our data in the correct format we have Angular pipes (opens new window) that allows us to transform them based on the language configuration we have set:
{{ myDateTime | date:'shortTime' }} // will be '5:15 PM' for 'en-US' and '17:15' for 'en-GB'
{{ myAmount | number }}``` // will be '2,138.62' for 'es' and '2 138,62' for 'fr'
To prepare our content to be extracted we have to use the i18n attribute and the $localize function to mark our text strings as translatable.
<p i18n>Text to translate.</p>
<ng-container i18n>Text to translate.</ng-container>
<img [src]="image.png" i18n-title title="Image title" i18n-alt alt="Image alt"/>
private myText: $localize 'Text to translate';
private myText: $localize `Text to translate ${variable}`;
# Extraction of text for translations
After preparing our content to be extracted, we must use Angular CLI to extract to a base translations file all the marked strings. In this case, I will create a new locale folder during the generation process inside which a new messages.xlf file will be created with all marked texts of out application.
ng extract-i18n --output-path src/locale
# Update the base translation file
- Make your changes in the app.
- Run
ng extract-i18n --output-path src/locale
to update que messages.xlf file.
# Generation of translation files
It is time to create and translate each locale file using an XLIFF file editor or create and edit it ourselves:
- We make a copy of the source language file (messages.xlf by default) in the same locale folder and rename it with each required locale (message.{locale}.xlf).
- Add a target-language attribute to the file node with the locale code, e.g. target-language="es".
- Search for the first trans-unit node.
- Duplicate the child node source and rename it target.
- Translate the content of the target node.
- Repeat the same steps above for each trans-unit node.
To help us manage the translation files we can use the [XLIFF Sync] extension (https://marketplace.visualstudio.com/items?itemName=rvanbekkum.xliff-sync) and use the Create New Target File(s) command to set up the project environment for the extension.
# Add new locales
Make a copy of the source language file and rename it with the required locale or use the XLIFF Sync extension (Create New Target File(s)) to create a new target file (a new alternate language).
- We change the angular.json file to add the new locale to the projects -> Your.Project.Name -> i18n -> locales section. I will talk about this section later.
# Managing the translation files
- We use the XLIFF Sync extension (Synchronize Translation Units command) to syncronize all translations units between the base file and all the alternate languages.
- We can edit the target files manually or if you use a external service (e.g., https://smartcat.com/) to translate them, then use the XLIFF Sync extension (Import Translations from File(s) command) to copy the translations from the selected files to the target files with the same language (remember to check the source-language and target-language file attributes from the origin translation file to make sure it works correctly).
# Building a localized project
First of all we need is define all supported locales using the i18n project property in the angular.json file:
{
// ...
"projects": {
"my-app": {
// ...
"i18n": {
"sourceLocale": "en-US",
"locales": {
"es": {
"translation": "src/locale/messages.es.xlf",
"baseHref": "/es/"
},
"pt": {
"translation": "src/locale/messages.pt.xlf",
"baseHref": "/pt/"
},
// ...
}
},
"architect": {
// ...
}
}
}
// ...
}
angular.json
We can then use our locale definition in the build configuration to decide which locales must be generate during the build process. To do this, we must use the localize option of the build section of the angular.json file:
{
// ...
"projects": {
"my-app": {
// ...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"localize": true,
"i18nMissingTranslation": "error",
// ...
},
"configurations": {
"production": {
// ...
},
"development": {
"localize": false,
// ...
},
"es": {
"localize": ["es"]
},
"pt": {
"localize": ["pt"]
}
},
"defaultConfiguration": "production"
},
// ...
}
}
}
// ...
}
angular.json
The supported values for localize are:
- true, will generate the bundles for all the defined locales.
- false, will generate only the source language bundle.
- ["locale-id-1", "locale-id-2", ...], will generate only the specified locales bundles.
When using ng server, the only allowed values for the _localize option are false and ["locale-id-1"] (single element array).
Another interesting property to configure in our angular.json file is the i18nMissingTranslation build option with the error value that throws an error on build when a translation is missing. The default value allows the build and only display warning messages about translations.
Finally we can add new build configurations for specific locales and also use it to serve the app in a specific language. Just add new small changes to our angular.json file:
{
// ...
"projects": {
"my-app": {
// ...
"architect": {
"build": {
// ...
"configurations": {
// ...
"es": {
"localize": ["es"]
},
"pt": {
"localize": ["pt"]
}
},
// ...
},
"serve": {
// ...
"configurations": {
// ...
"es": {
"browserTarget": "my-app:build:development,es"
},
"pt": {
"browserTarget": "my-app:build:development,pt"
}
},
// ...
},
// ...
}
}
}
// ...
}
angular.json
And the script to run our app in the specific language:
ng serve --configuration=es
ng serve --configuration=pt
Language not changing when debugging? When using the ASP.NET Core Angular template we could have problems when running the application locally with ng serve modifying the locale to display. After running the application for the first time and shutting it down using Ctrl+C, if you restart the application after changing the localize configuration to another locale ID, the previous process (Node.js connection listener) will still be alive and you will have to stop it manually to see your application in the new localize. To stop the above process we use the answer that fits our system and port according to this Stack Overflow question "Port 4200 is already in use" when running the ng serve command (opens new window). You can find more information on how Node.js instances continue to run after debugging ASP.NET Core in the 38897 (opens new window) GitHub topic 38897 (opens new window).
Here is a basic demo project with Angular internationalization (opens new window) in Stackblitz.
# App routing
Finally when generating our application, if my-app is the directory containing the project files, when generating multiple languages, as many subdirectories will be generated as many as locale configurations we have set. For example, the Spanish version of my-app will be in the my-app/es directory.
Once the application is generated and deployed, it will take care of routing the user to the specific directory of each language based on the routing configuration specified in the baseHref property of our angular.json file for each locale.
By default, users will be redirected to the language indicated in the Accept-Language HTTP header unless the path to a specific language is accessed. In all other cases where no language information is received, the default language of the application will be used.
# Using a default language other than en-US
By default Angular uses en-US as default locale, so we need to make some change to rewrite the default locale source used in our app.
To set the app's locale source to en (all the texts in the template are in Spanish):
- We set the LOCALE_ID token to es in the app.module.ts file to propagate the locale throughout the application.
- We use the registerLocaleData function in the app.module.ts file to register the locale that Angular will use internally.
- We set the i18n\sourceLocale property to es in the angular.json file to build only for this locale.
import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
import { LOCALE_ID, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: [{ provide: LOCALE_ID, useValue: 'es' }],
})
export class AppModule {
constructor() {
registerLocaleData(localeEs);
}
}
app.module.ts
{
// ...
"projects": {
"my-app": {
// ...
"i18n": {
"sourceLocale": "es"
},
"architect": {
// ...
}
}
}
// ...
}
angular.json