When it comes to Angular, we’ve already covered some popular internationalization solutions for Angular and seen how to localize Angular apps with the help of the I18next framework. Today, I’d like to go a step further and talk about Angular translation in its latest (eighth) version by using the built-in I18n module.
The release of Angular 8 was announced at the beginning of 2019 and should go live quite soon. As you probably know, there are multiple solutions supporting Angular i18n. Nonetheless, I’d now recommend sticking to the built-in internationalization module. I18next is a great framework, but it’s quite “heavy” and can often be perceived as overkill. Ngx-translate is quite a solid tool as well, but it was considered to be a temporary solution from the very beginning. Now that Angular has its very own I18n module, I wouldn’t start a new project with ngx-translate as it seems to have a whole lot of bugs. Some users even report it didn’t play nicely with the latest versions of the framework.
Considering all of this, what we’ll focus on in this tutorial will be:
- Upgrading your app to Angular 8
- Performing translations with the help of the built-in I18n module
- Translating attributes
- Performing pluralization
- Setting up an AOT compiler and introducing multilingual support
- Deploying the app to Firebase
- Integrating with Phrase to simplify translation files management
The working demo can be found at https://ngdemo8.firebaseapp.com/en/.
Getting Ready to Start with Angular Translation
We’ll start off by creating a sample Angular 8 application. Please note that at the time of writing this article, Angular 8 has only a release candidate version (a stable version should go live at the end of May or beginning of June 2019). Therefore, to get started with Angular 8, you first need to take a couple of simple steps.
To begin with, make sure you have Node.js 10 installed.
Next, install the latest stable version Angular by running:
Subsequently, create a new demo application:
Navigate to the application’s directory and update to Angular 8:
This operation may take a couple of minutes, and then you are good to go!
In order to mark translatable content, you should use an
i18n attribute. In the simplest case, this attribute doesn’t accept any value at all. It just says: “This node should be translated properly”. However, you can provide the meaning of and description for the translation. Open the
src/app/app.component.html file and replace its content with the following markup:
“Main header” stands here for the meaning. “Friendly welcoming message” is the description; note that it’s separated by a pipe (
|). This information is very helpful for translators – having context at their disposal enables them to deliver more accurate translations.
Where Translations Dwell…
The next question is how do we actually translate our header?. Translation messages live in separate files that may have one of the following formats:
- XML Localization Interchange File Format (XLIFF, version 1.2), this is the default format we are going to stick with
- XLIFF version 2
- XML Message Bundle (XMB)
If you got used to working with JSON or YAML formats, XLIFF may seem a bit complex at first. But, fear not: There’s nothing we can’t handle!
You may create translation files manually, but it’s much simpler to use the built-in
We are creating a
messages.ru.xlf file inside the
src/locale folder which will contain translations for the Russian locale.
xi18n won’t just create this file but rather search for all translatable content and extract it properly. Therefore, open the
messages.ru.xlf file and provide the translation for our welcome message (I’ve pinpointed it with a comment):
Every translation is wrapped with a
trans-unit tag. The
source tag contains the translation key, whereas the
target hosts translation value. You can also see where this translation resides and what are the description and its meaning.
Now take a look at the
id attribute of the
trans-unit tag. You should not alter this ID directly because it’s used to provide translation for the proper node. The ID has a unique value which is generated using the text inside the node and its meaning (not a description). Suppose we have two different nodes with different a description but similar meaning and text:
They’ll have the same ID and, effectively, the same translation, even though the description differs.
However, in many cases, this is not very convenient, especially if you want multiple nodes to have the same translation. To overcome this problem, you may assign custom IDs inside the
i18n attribute. Custom IDs should be prefixed with
Now re-run the
xi18n tool and take a look at the
Now the ID is much more user-friendly. Also note that this ID was found on lines 9 and 11, but the translation will be the same in both cases.
One thing to remember when assigning custom IDs is that they should be unique. If two nodes have the same IDs, they will always have the same translation!
When Attributes Are Translated
Interestingly, Angular I18n can translate any attribute of the given tag. Take a look at the following example:
This abbreviation explains what CERN is. However, I would like to translate both the abbreviation and the title. To do that, I’ve added an
i18n-title attribute saying that the title should have its translation too. Run the
xi18n tool once again and open the
We’ve got two separate
trans-unit tags now, and therefore, we can translate both the abbreviation and the title! Note that translatable attributes may also have meaning, description, and ID. To add any of these, simply assign a proper value to the
i18n-title attribute as shown in the previous section.
To Pluralize Or Not To Pluralize
One typical task every developer faces sooner or later is the need to pluralize a given string. Let’s suppose I’d like to display how many unread messages a user has got. First of all, let’s simply hard-code this number in our app component:
Now, add a paragraph with the following content:
This syntax is written according to the ICU message format. Yes, it does look quite strange. In reality, things are much simpler.
newMessages is the variable we have defined in the component.
plural means the translation should be properly pluralized based on the
newMessages value. Then, we provide translations for different cases, according to the CLDR plural rules that Angular I18n relies on. There are four cases (in Russian, pluralization rules are more complex than in English).
Note » Be aware of the potential problem that I’ve encountered! If you provide
p on one line and its pluralized contents on another line,
xi18n creates two separate translation units with different values but the same key. This seems like a bug which was also present in Angular 6.
Now, as always, run the
xi18n tool and open the
We provide translations for cases when there is one message or there are a few, many, or no messages. In case there are only a few or many messages, we also need to interpolate the actual number, therefore we use an
x tag with a special
INTERPOLATION ID and
equiv-text with the
If you are unsure of how many cases should be provided for some language, use this table as a reference.
We Need No Element!
Sometimes, you might want to translate some text without wrapping it in any tag. To do that, use a special
ng-container tag with a
The compiler will perform translation and then remove the
ng-container. As a result, you will see a plain text on your page without any wrapping element. Sweet!
Creating a Multilingual App
It can often be the case that you want to create a multilingual app with the ability to switch between the languages. In this section, we will see how to achieve that and configure the app properly.
First things first, we need to add a language switcher to the page. I’d like to have support for two languages, English and Russian, therefore tweak the component in the following way:
This is the list of supported languages that we will render on the page. Next, tweak the template:
Effectively, this will display an unordered list with links leading to
Before proceeding to the next section, generate a translation file for the English locale:
Here is the full contents for the
Lastly, make sure you have translated everything properly inside the
A Tale of Two Compilers
Next, we need to decide which compiler we’d like to use for production. Angular has two types of compilers available:
- JIT (Just in Time) compiler – converts Angular code into a code understandable by the browser during runtime. This is the default type of compilation and it’s the recommended option for the development environment. Nevertheless, in terms of production, JIT is strongly discouraged in favor of AOT.
While Angular I18n does support the JIT compiler, we’ll stick to AOT which is recommended for the production environment. The idea is to generate two separate packages of the app translated into English and Russian, respectively. When the user changes the language of the app, they’ll effectively switch between the packages of our application.
Setting Up the AOT Compiler
To set up the AOT compiler, open the
angular.json file and find the
configurations section under the
build key. Add
en keys inside
configurations (I skipped other options for brevity):
- We enable the AOT compiler for both configurations in that we set
- The packages should reside inside the
dist/enfolders, respectively (the
i18nFileinstructs where the translation file resides
i18nLocalesets the locale for the package
i18nMissingTranslationinstructs what to do if some key does not have a translation; the default action is to signal a warning, while other supported values are
baseHrefsets the base URL portion for the given package
Feel free to further tweak these configurations to adapt them to your own needs.
Now, we also need to tweak the
serve section inside the
angular.json file (I’ve omitted other options for brevity):
Note that the
NGDemo8:build:ru effectively means that the build configuration named
ru should be executed. Therefore, if you have named your build configuration differently in the previous step, you should provide the proper name here as well. Also, don’t forget to replace
NgDemo8 with the name of your own app.
Having this configuration in place, you may run the following commands:
ng serve --configuration=ruand
ng serve --configuration=ento serve the application locally with the given language. Note that the URLs are
http://localhost:4200/enas we have set
ng build --configuration=ruand
ng build --configuration=ento build Russian and English packages of the app. Run these commands now and make sure they are working properly
It is also possible to provide options directly to the
build commands, for example:
Our application is now ready for production, and we can deploy it!
Deploying to Firebase
In this section, I will show you how to deploy your multilingual app to Firebase. The deployment process is as straightforward as it can be:
- Create a Firebase account if you don’t have one
- Navigate to the Firebase console and create a new project (I’ve named mine
- Install Firebase tools globally by running
npm install -g firebase-tools
- Login via your account using the
- Initialize Firebase in the root directory of your project by issuing
- Follow the wizard to configure the app. Note that the requests should not be re-routed to
index.html. It’s because of this that the answer is “no” when the wizard asks you this question. Make also sure you provide the proper path to the application packages (in our case, they reside in the
- Last but not least, run
firebase deployto publish your application!
Here is my
firebase.json config file that you can use as a reference:
When you need to update your application, run the build task again and then perform
You may find the working demo by visiting these links:
Use Phrase To Manage Translation Files
As you can see, XLIFF files are quite complex and it is pretty tedious to edit them by hand. Things get even worse when translators need to work with these files, as translators aren’t tech-savvy. Luckily, Phrase can greatly simplify things for you. It provides a convenient online editor that allows multiple translators to collaborate with ease and get translations done without worrying about the underlying file format. Phrase also has other great features like webhook integrations, assignable jobs, activity tracking, and many others.
Configuring the Phrase CLI Tool
Let me guide you through the process of integrating Phrase into your translation workflow.
- First of all, grab your free trial if you don’t have an account yet
- Create a new project and choose the XLIFF translation file format
- Download the CLI tool for your OS; make sure that the
phraseappexecutable is available in your
- Open the Access Tokens page and generate a new API token with a read-and-write scope (we’ll use it to communicate with Phrase)
phraseapp initin the root of your project; this command will boot a setup wizard
- Paste the token you’ve generated in step 4
- Choose a project you’ve already created
- If you’ve selected the XLIFF file format in step 2, simply choose the default format; otherwise, explicitly set XLIFF
- Next, the wizard will ask for the upload and download paths; answer
./src/locale/messages.<locale_name>.xlfto both questions (note that the
<locale_name>should be typed as it is)
- Lastly, answer
yto upload your files to Phrase
If you’ve done everything right, you should see two fully translated locales in your Phrase project.
Here is the sample
.phraseapp.yml config file:
Now you may run
phraseapp pull to download your translation files and
phraseapp push to upload all changes from your app to Phrase.
In this article, we discussed how to translate Angular applications with the help of the built-in I18n module. We took a closer look at its usage and how to create a multilingual app with an AOT compiler. On top of that, we deployed our application to Firebase and integrated it with Phrase to simplify translation file management (if you’re still looking for a solid translation management solution, give Phrase a try). Quite good for one single article, isn’t it?