In this article you will learn with examples how to get started with Angular I18n using the built-in internationalization module. We will cover the following topics:
-
Setting up the Angular application and configuring the built-in
localize
module. - Performing simple translations and providing additional translation data.
- Extracting translations to XLF files.
- Working with pluralization and gender information.
- Working with Angular pipes.
- Performing translations within components.
- Adding a language switcher.
- Building the app and deploying to production.
For the purposes of this Angular i18n internationalization/localization tutorial, we’ll be using version 11 which is the most recent version at the time this post’s writing. The concepts I’m going to show you are applicable for versions starting from 9.1 and above.
The source code can be found at GitHub .
The working demo can be found at lokalise-angular-i18n.web.app/en-US .
You may be also interested in learning how to localize Angular applications with the Transloco solution or how to add internationalization support into Ionic apps .
Preparing for Angular I18n
So, let’s start by creating a new Angular application
i18n-angular-demo
, shown below:
ng new i18n-angular-demo
Do not enable Angular routing and use CSS for stylesheets. Next, just wait for a couple of minutes and then make sure the app is starting without any issues:
cd i18n-angular-demo ng serve --open
The latter command should open the application in your browser. After you’ve made sure everything is working, stop the server and install a
localize
package:
ng add @angular/localize
This is a new package introduced in Angular 9 that will add internationalization support to the app.
Next, we’ll need to modify
angular.json
. I’ve pinpointed the relevant lines here:
{ // ... "projects": { "i18n-angular-demo": { // ... "i18n": { // <---------------------------- "sourceLocale": "en-US", // <---------------------------- 1 "locales": { // <---------------------------- "ru": "src/i18n/messages.ru.xlf" // <---------------------------- 2 } // <---------------------------- }, // <---------------------------- "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/", // <---------------------------- 3 // ... }, "configurations": { // ... "ru": { // <---------------------------- "localize": ["ru"] // <---------------------------- 4 } // <---------------------------- } }, "serve": { // ... "configurations": { "production": { "browserTarget": "i18n-angular-demo:build:production" }, "ru": { // <---------------------------- "browserTarget": "i18n-angular-demo:build:ru" // <---------------------------- 5 } // <---------------------------- } } // ... } } }, "defaultProject": "i18n-angular-demo" }
So, we are doing the following:
-
Setting the source language of the application to
en-US
. -
Adding support for the
ru
(Russian) locale. Translations for this language will live in thesrc/i18n/messages.ru.xlf
file. Note that in the previous versions of Angular you would provide the--i18n-locale
option when creating translations using command line interface, but now this option is deprecated. -
Storing the build directly under the
dist/
folder. -
Adding the Russian configuration to the
build
options. -
Providing the
ru
configuration option for theserve
command. This will set the language to Russian. Once this is in place, you’ll be able to run the application with theng serve --configuration=ru
command.
Now we can proceed to working with Angular internationalization examples!
Getting Started with Angular Internationalization
Marking the Text for Translation
In order to translate text,
use the
i18n
attribute
. Let’s see it in action by replacing
src/app/app.component.html
content with the following:
<h1 i18n>Hello world!</h1>
i18n
is a special attribute that is recognized by the localize package. During the compilation it will be removed, and the tag content will be replaced with the proper translations.
This attribute may accept a translation description such as this:
<h1 i18n="Friendly welcoming message">Hello world!</h1>
Moreover, it is possible to provide the intended meaning of the translation. Just separate the meaning and description with a pipe
|
character like so:
<h1 i18n="main header|Friendly welcoming message">Hello world!</h1>
This additional data provides context for your translators. Specifically, you may also explain on what pages the translation will be displayed, what tone should be used and so on.
Creating Translation Files
Now, where and how do we store translations for Angular I18n? Usually they live in the
src/i18n
or
src/locales
folder. As for the translation format, there are three options which are listed here:
- XLIFF 1.2 (default)
- XLIFF 2
- XML Message Bundle (XMB)
Let’s stick with the default option, but the next question is: how do we actually create translation files? Should we do that manually? No! There is a special command-line tool called
xi18n
which does the heavy lifting for us and extracts all the translations to a separate file:
ng xi18n --output-path src/i18n
Please note that for Angular 11+ it is advised to
use
ng extract-i18n
command instead
. It supports the same command line arguments.
Now in the
src/i18n
folder you will find a
messages.xlf
file. This is the base translation file with the following contents:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="8f8c1e44db621d9a326a97396e69787284671553" datatype="html"> <source>Hello world!</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">9</context> </context-group> <note priority="1" from="description">Friendly welcoming message</note> <note priority="1" from="meaning">main header</note> </trans-unit> </body> </file> </xliff>
-
trans-unit
is the tag containing a single translation.id
is a translation identifier and has a special meaning.xi18n
generates theid
for us so do not modify it here! We will discuss this attribute later in more detail. -
source
contains translation source text. -
context-group
specifies where exactly the given translation can be found. -
context-type="sourcefile"
shows the file where translation lives. -
context-type="linenumber"
shows the actual line of code. -
Also, there are two
note
tags that provide the translation description and meaning respectfully.
Next, you can copy the
messages.xlf
file and name your copy
messages.ru.xlf
. This new file is going to store Russian translations. In order to translate something, add a
target
tag immediately after the
source
in the
trans-unit
as follows:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="8f8c1e44db621d9a326a97396e69787284671553" datatype="html"> <source>Hello world!</source> <target>Всем привет!</target> <!-- <----------------- your translation --> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">9</context> </context-group> <note priority="1" from="description">Friendly welcoming message</note> <note priority="1" from="meaning">main header</note> </trans-unit> </body> </file> </xliff>
Also, it is a good idea to provide the
target-language
attribute for the
file
tag so that translation management systems can detect the locale properly, see below:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ru"> <!-- <--------- --> <!-- ... --> </file> </xliff>
After performing these changes, start the Russian version of the app and make sure the translation is showing properly:
ng serve --configuration=ru --open
Translation Identifiers
I’ve already mentioned that translation IDs (
id
attribute for the
trans-unit
tag) have special meanings. These IDs are unique and
xi18n
generates them based on the combination of the source text and its meaning. Therefore, whenever you update the translation source or its meaning, the ID will change. For instance, let’s modify our translation text:
<h1 i18n="main header|Friendly welcoming message">Hello everyone!</h1>
Now regenerate base translation file:
ng xi18n --output-path src/i18n
Now
src/i18n/messages.xlf
contains the following:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="1e70beb33c2d9c1f2f6744a3a8dc0ad5ac472767" datatype="html"> <source>Hello everyone!</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">9</context> </context-group> <note priority="1" from="description">Friendly welcoming message</note> <note priority="1" from="meaning">main header</note> </trans-unit> </body> </file> </xliff>
The ID has changed! Try starting the application again:
ng serve --configuration=ru --open
Now the welcoming message will not be translated into Russian because the translation ID has changed. To fix this, update the
id
attribute and the
source
tag in the
src/i18n/messages.ru.xlf
file like so:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ru"> <body> <trans-unit id="1e70beb33c2d9c1f2f6744a3a8dc0ad5ac472767" datatype="html"> <source>Hello everyone!</source> <target>Всем привет!</target> <!-- ... --> </trans-unit> </body> </file> </xliff>
Note that if you have multiple occurrences of the same source text and meaning, all of them will have the same translation. This is due to the fact their identifiers are similar.
Custom Translation Identifiers
So, having auto-generated translation IDs is not very convenient because they depend on the source text and the meaning. However, it is possible to provide custom identifiers using the
@@
prefix, for example:
<h1 i18n="main header|Friendly welcoming message@@welcome">Hello everyone!</h1>
Re-run the
ng xi18n
command and take a look at the base translation file:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="welcome" datatype="html"> <source>Hello everyone!</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">9</context> </context-group> <!-- ... --> </trans-unit> </body> </file> </xliff>
The ID is now set to
welcome
and it will not be changed even if you modify the source text. Don’t forget to provide a new ID in the
messages.ru.xlf
file:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ru"> <body> <trans-unit id="welcome" datatype="html"> <!-- ... --> </trans-unit> </body> </file> </xliff>
Make sure to assign unique custom identifiers for different translations! If you provide the same IDs, only the first translation will be extracted:
<h1 i18n="main header|Friendly welcoming message@@welcome">Hello everyone!</h1> <!-- "Other text" will be ignored. Translation for "Hello everyone!" will be used here instead: --> <p i18n="@@welcome">Other text</p>
Angular Internationalization Use Cases
Translating Attributes
The Angular I18n module allows us to translate both tag content and attributes. Suppose there is a link to your portfolio in the
src/app/app.component.html
:
<p> <a i18n href="/en-US/portfolio" title="My Portfolio">Portfolio</a> </p>
The link text will be translated properly, but what about the
title
and
href
? To deal with these attributes, provide the
i18n-ATTRIBUTE_NAME
attributes in the following way:
<p> <a i18n i18n-href i18n-title href="/en-US/portfolio" title="My Portfolio">Portfolio</a> </p>
Perform translation extraction as usual by running:
ng xi18n --output-path src/i18n
Copy new
trans-unit
tags from the
src/i18n/messages.xlf
file into
src/i18n/messages.ru.xlf
and provide
target
like so:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ru"> <body> <!-- ... --> <trans-unit id="323af13292ecc714ab3848b9dff48f923893be32" datatype="html"> <source>Portfolio</source> <target>Портфолио</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">12</context> </context-group> </trans-unit> <trans-unit id="18af8f7de89b3c1cebe305b0805a880a35d04eee" datatype="html"> <source>/en-US/portfolio</source> <target>/ru/portfolio</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">12</context> </context-group> </trans-unit> <trans-unit id="9b154a4a7e2e3e6c0e2432761a52b7cf8b4c8395" datatype="html"> <source>My Portfolio</source> <target>Моё портфолио</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">12</context> </context-group> </trans-unit> </body> </file> </xliff>
We have translated the link text, the URL, and the title!
Performing Pluralization
So, we’ll continue our Angular localization tutorial by discussing pluralization . Imagine we would like to show how many ongoing tasks the user has. The built-in I18n module utilizes an ICU message format which may seem a bit complex at first:
<p i18n>{tasksCount, plural, zero {no tasks} one {{{tasksCount}} task} few {{{tasksCount}} tasks} many {{{tasksCount}} tasks} other {{{tasksCount}} tasks}}</p>
-
tasksCount
is the variable that we’ll create in a moment. -
plural
is the name of an ICU expression -
zero
provides text when there are no tasks -
one
contains text when there is 1 task, or there are 101, 201, 301, etc. tasks. -
few
,many
, andother
cover all other cases. These are required for the Russian language which has some complex pluralization rules. If you require only English translations then havingzero
,one
, andmany
categories is enough.
Don’t forget to create a new variable in the
src/app/app.component.ts
file like so:
// ... export class AppComponent { tasksCount = 3 }
Extract the new translations and update the
src/i18n/messages.ru.xlf
file as follows:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ru"> <body> <!-- ... --> <trans-unit id="2aaa6a848257ea436d32c801ac6d7d25a8059edb" datatype="html"> <source>{VAR_PLURAL, plural, zero {no tasks} one {<x id="INTERPOLATION" equiv-text="{{tasksCount}}"/> task} few {<x id="INTERPOLATION" equiv-text="{{tasksCount}}"/> tasks} many {<x id="INTERPOLATION" equiv-text="{{tasksCount}}"/> tasks} other {<x id="INTERPOLATION" equiv-text="{{tasksCount}}"/> tasks} }</source> <target>{VAR_PLURAL, plural, zero {нет заданий} one {<x id="INTERPOLATION" equiv-text="{{tasksCount}}"/> задание} few {<x id="INTERPOLATION" equiv-text="{{tasksCount}}"/> задания} many {<x id="INTERPOLATION" equiv-text="{{tasksCount}}"/> заданий} other {<x id="INTERPOLATION" equiv-text="{{tasksCount}}"/> задания} }</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">15</context> </context-group> </trans-unit> </body> </file> </xliff>
Choosing Translation with Select
Another useful
ICU expression is
select
. It allows you to choose one of the translations based on a value. For example, it is very useful when working with gender information:
<span i18n>Gender: {genderCode, select, 0 {male} 1 {female} other {unknown}}</span>
Based on the value of the
genderCode
we will display either “male”, “female”, or “unknown”.
Now let’s add a
genderCode
variable and provide an ability to switch gender inside the
src/app/app.components.ts
file in the following way:
export class AppComponent { tasksCount = 201 genderCode = 0 male() { this.genderCode = 0; } female() { this.genderCode = 1; } other() { this.genderCode = 2; } }
Next, extract translations and update
src/i18n/messages.ru.xlf
:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ru"> <body> <!-- ... --> <trans-unit id="c1df5831cb8b3106de6bf8083aef223d528b2de8" datatype="html"> <source>Gender: <x id="ICU" equiv-text="{genderCode, select, 0 {...} 1 {...} other {...}}"/></source> <target>Пол: <x id="ICU" equiv-text="{genderCode, select, 0 {...} 1 {...} other {...}}"/></target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">21</context> </context-group> </trans-unit> <trans-unit id="9740425f2ef8a0757676468e5ed8e7e1b7fa473c" datatype="html"> <source>{VAR_SELECT, select, 0 {male} 1 {female} other {unknown} }</source> <target>{VAR_SELECT, select, 0 {мужчина} 1 {женщина} other {неизвестно} }</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">21</context> </context-group> </trans-unit> </body> </file> </xliff>
Note that there are two translations: one for the “Gender:” part and another for the actual
select
expression.
Lastly, display three buttons to choose gender:
<button (click)="male()">♂</button> <button (click)="female()">♀</button> <button (click)="other()">⚧</button>
Once a button is clicked, the message with the gender info will be updated instantly.
Translation without a Tag
All the examples we’ve seen before required having some sort of tag. Sometimes, you may need to translate plain text without rendering any tag at all.
It can be done with a
ng-container
, for instance:
<ng-container i18n>Copyright 2020</ng-container>
Upon page display,
ng-container
will be gone, and you’ll have simple plain text. Provide the corresponding Russian translation as usual:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ru"> <body> <!-- ... --> <trans-unit id="f986261c453f5e38fc1c390b4d903b63765aed86" datatype="html"> <source>Copyright 2020</source> <target>Права защищены 2020</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">27</context> </context-group> </trans-unit> </body> </file> </xliff>
Using Angular Pipes
The built-in
I18n module plays nicely with common pipes used for localization in Angular
:
DatePipe
,
CurrencyPipe
,
DecimalPipe
, and
PercentPipe
. All you need to do is provide the proper locale data in the
src/app/app.module.ts
:
// ... import { registerLocaleData } from '@angular/common'; import localeRu from '@angular/common/locales/ru'; registerLocaleData(localeRu, 'ru'); // ...
Now the locale data are loaded, and we can use the pipes mentioned above. For instance, let’s perform date localization:
<p>{{today | date:'fullDate'}}</p>
Create a new
today
variable in the
src/app/app.component.ts
file:
// ... export class AppComponent { today: number = Date.now() // ... }
Upon running the Russian version of the app, you will see the localized date!
Translations within Components
Sometimes you may need to perform translations within your components. This can be done by using the
$localize()
function directly. For example, add the following line to
src/app/app.component.ts
:
// ... export class AppComponent { company = "Lokalise" created_by = $localize`Created by ${this.company}` }
Use this variable in the
src/app/app.component.html
template:
<p>{{ created_by }}</p>
The problem is that this translation won’t be automatically extracted by the
xi18n
tool. Try to boot the server:
ng serve --configuration=ru --open
And you will see a warning message:
WARNING in No translation found for "3990133897753911565" ("Created by {$PH}").
This long number is a translation ID that the localize module tries to search for.
PH
means “placeholder” that we have to provide within the translation. Therefore, let’s use these data in the
src/i18n/messages.xlf
like so:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template"> <body> <!-- ... --> <trans-unit id="3990133897753911565" datatype="html"> <source>Created by <x id="PH"/></source> </trans-unit> </body> </file> </xliff>
Add Russian translation to the
src/i18n/messages.ru.xlf
. Don’t forget to provide placeholders, for example:
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ru"> <body> <!-- ... --> <trans-unit id="3990133897753911565" datatype="html"> <source>Created by <x id="PH"/></source> <target>Создано <x id="PH"/></target> </trans-unit> </body> </file> </xliff>
Now the
created_by
variable has the correct translation!
$localize
accepts a custom ID, meaning, and description as well. The format is the same:
meaning|description@@ID
. Please note that this data should be wrapped using colons:
created_by = $localize`:used on the main page|explains who created the app@@created_by:Created by ${this.company}`
Managing Translation Files with Lokalise
As you can see, Angular I18n translation files have a pretty complex format. While developers won’t have any problems working with it, translators may be confused by the weird tags and attributes. Moreover, your translator could incidentally remove an important tag and break the app. We do not want that to happen, right?
Luckily, there is a translation management system here to save the day, and this system is called Lokalise. It allows for the management of translation files with a very convenient graphical interface, enables multiple developers to collaborate on a single project, provides integrations with developer tools and services like GitHub, Amazon S3, CLI, and a robust API . Plus more integrations with project management tools like Jira and 50+ others.
To get started with Lokalise, create a free trial account — no credit card is required!
It is possible to perform all manipulations with GUI, but I propose following the “developer’s way” and utilizing the command-line interface instead. Therefore, do the following:
- Log in to your account at lokalise.com .
- Navigate to Personal Profile > API Tokens.
- Generate a new read-write token and keep this tab opened.
- Download the latest release of CLIv2 .
-
Unpack CLI somewhere on your PC and
cd
into the newly created directory. -
Create a new Lokalise project by running
lokalise2 project create
.
--name AngularI18n --token YOUR_API_TOKEN_HERE --languages
"[{\"lang_iso\":\"ru\"},{\"lang_iso\":\"en-US\"}]" --base-lang-iso "en-US"
Take note of the project ID that will be returned as a result (along with some other data).
Once the project is created, you can upload translation file as follows:
lokalise2 file upload --lang-iso ru --file "PATH_TO_PROJECT\src\i18n\messages.ru.xlf" --project-id PROJECT_ID --token TOKEN --detect-icu-plurals
Now you can invite translators to your new project!
Adding a Language Switcher
The last thing I would like to show you in this Angular internationalization tutorial is how to create a simple language switcher.
First, add a new
localesList
variable to the
src/app/app.components.ts
file in the following way:
// ... export class AppComponent { localesList = [ { code: 'en-US', label: 'English' }, { code: 'ru', label: 'Русский' } ] // ... }
Make sure to set the proper language codes.
Next, you just need to add an unordered list to the
src/app/app.component.html
:
<ul> <li *ngFor="let locale of localesList"> <a href="/{{locale.code}}/"> {{locale.label}} </a> </li> </ul>
Deploying the App
So, we have finished building our Angular I18n example app, and now it is time to share our work with the world!
Preparing for Deployment
In this article I will show you how to deploy to Firebase . Therefore, you’ll need to create a new account there to get started.
Then, install Firebase tools:
npm install -g firebase-tools
Make sure to download the latest Russian translations from Lokalise:
lokalise2 file download --unzip-to "PATH_TO_PROJECT/src/i18n" --project-id PROJECT_ID --token TOKEN --format xlf --directory-prefix=/ --filter-langs=ru
Log in to Firebase locally using the following command:
firebase login
Finally, proceed to Firebase Console and create a new application. Make sure to give it a unique name.
Configuring the App
Once everything is ready, run:
firebase init
Follow the setup wizard instructions and:
- Choose “hosting” as a required feature.
-
Choose not to re-write all requests to
index.html
. -
Provide
dist
as a public directory because our build will live inside that folder.
Once the initialization is complete, a new
firebase.json
file will be created in the project root.
Performing the Deployment
At this point we are ready to get rolling. Compile your application with an ahead-of-time compiler (which is the preferred way):
ng build --prod --localize
This will create both English and Russian versions of the app under the
dist
directory in one go. This is very convenient because in the previous Angular versions we had to build each application separately and it took significantly more time. It still possible to provide a specific version of the app to build like so:
ng build --configuration=production,ru
Once the build is finished, deploy to Firebase:
firebase deploy
Now you can browse your application. In my case there are two URLs:
Making it work with Angular Routing
At this point your application should work fine on Firebase. However, if you are going to implement a routing system, additional steps have to be taken.
Creating components and routes
Let’s create two simple routes:
/home
and
/about
that should utilize
HomeComponent
and
AboutComponent
respectively. Start by creating these two components:
ng generate component home ng generate component about
Next, open
app.module.ts
, import the router module and provide the actual routes:
import { RouterModule } from '@angular/router'; // ... // Replace imports with the following: imports: [ BrowserModule, RouterModule.forRoot([ {path: 'home', component: HomeComponent}, {path: 'about', component: AboutComponent}, {path: '', redirectTo: '/home', pathMatch: 'full'} ]), ],
So, we have added handlers for the
/home
and
/about
paths, and also we have provided a redirect to open the “home” page by default.
Now open up
app.component.html
and move everything except for the language switcher to the
home/home.component.html
file:
<!-- home.component.html --> <h1 i18n="main header|Friendly welcoming message@@welcome">Hello everyone!</h1> <p> <a i18n i18n-href i18n-title href="/en-US/portfolio" title="My Portfolio">Portfolio</a> </p> <p>{{ created_by }}</p> <p i18n>{tasksCount, plural, zero {no tasks} one {{{tasksCount}} task} few {{{tasksCount}} tasks} many {{{tasksCount}} tasks} other {{{tasksCount}} tasks}}</p> <span i18n>Gender: {genderCode, select, 0 {male} 1 {female} other {unknown}}</span> <button (click)="male()">♂</button> <button (click)="female()">♀</button> <button (click)="other()">⚧</button> <ng-container i18n>Copyright 2020</ng-container> <p>{{today | date:'fullDate'}}</p>
Tweak your
app.component.html
so that it looks like this:
<ul> <li *ngFor="let locale of localesList"> <a href="/{{locale.code}}/"> {{locale.label}} </a> </li> </ul> <nav> <a class="button" routerLink="/home" i18n>Home</a> | <a class="button" routerLink="/about" i18n>About</a> </nav> <h1 i18n>Main page</h1> <router-outlet></router-outlet>
Finally, replace the contents inside the
about/about.component.html
with a header:
<h2 i18n>About Us</h2>
Translating the new pages
We have added some new HTML tags so let’s extract the texts into our translation files:
ng extract-i18n --output-path src/i18n
If you are on Angular < 11 then use
ng xi18n
command instead (the command line arguments will be the same).
Now copy the generated XML tags from
messages.xlf
, paste them into the
messages.ru.xlf
file, and adjust your translations:
<trans-unit id="847171f94c5e932a67df0c3b4d57e4e10d844b52" datatype="html"> <source>About Us</source> <target>О нас</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/about/about.component.html</context> <context context-type="linenumber">1</context> </context-group> </trans-unit> <trans-unit id="92eee6be6de0b11c924e3ab27db30257159c0a7c" datatype="html"> <source>Home</source> <target>Домашняя</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">10</context> </context-group> </trans-unit> <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a" datatype="html"> <source>About</source> <target>О нас</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">11</context> </context-group> </trans-unit> <trans-unit id="0d175dbfa58e58e67069b734fd78e7b7a6b83fbb" datatype="html"> <source>Main page</source> <target>Главная страница</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/app.component.html</context> <context context-type="linenumber">14</context> </context-group> </trans-unit>
This is it!
Configuring Firebase
The most important step here is to configure Firebase properly. As long as we’re using the AOT compiler, Firebase will effectively serve two different applications (for Russian and English locales). Therefore, to make Firebase play nicely with our Angular routes, we should adjust the rewrite rules. To achieve that, open
firebase.json
and add the following lines:
{ "hosting": { "public": "dist", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ], "rewrites": [ // <-- add this section { "source": "/en-US/**", "destination": "/en-US/index.html" }, { "source": "/ru/**", "destination": "/ru/index.html" } ] } }
Another problem is that opening http://lokalise-angular-i18n.web.app directly (without providing locale part in the URL) results in a 404 error. To fix that issue, let’s redirect all such requests to the English version of the app:
{ "hosting": { "public": "dist", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ], "rewrites": [ { "source": "/en-US/**", "destination": "/en-US/index.html" }, { "source": "/ru/**", "destination": "/ru/index.html" } ], "redirects": [ // <-- add this section { "source" : "/", "destination" : "/en-US", "type" : 301 } ], } }
This is the final version of our
firebase.json
config file.
Deploying once again
So, we have dealt with all the Firebase issues. Now you can build your application again (as we have added new components and routes):
ng build --prod --localize
And finally deploy everything to Firebase:
firebase deploy
Great job!
Conclusion
So, in this article we have seen how to introduce internationalization in Angular apps with a built-in localize module. We have learned how to configure the Angular app, perform simple translations, work with pluralization and gender information, and how to introduce localization and translate texts within components. Finally, we have built and deployed the demo application to Firebase. Great job!
That’s all for today, folks. I hope you found this article useful. Thanks for staying with me, and I’ll see you next time!