Angular Translation: A Closer Look at Angular 8

Angular's release cycle provides for the launch of a new major version every six months. In this tutorial, we'll go through the process of translating Angular 8 apps and take a closer look at how PhraseApp integration can simplify translation file management.

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 PhraseApp 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!

Performing Translations

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 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:

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 xi18n tool:

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.

Translation ID

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 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 @@ symbols:

Now re-run the xi18n tool and take a look at the messages.ru.xlf file:

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!

Translation Use-Cases

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 messages.ru.xlf file:

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 messages.ru.xlf file:

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 newMessages value.

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 i18n attribute:

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.

Language Switcher

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 /en and /ru paths.

Before proceeding to the next section, generate a translation file for the English locale:

Here is the full contents for the messages.en.xlf file:

Lastly, make sure you have translated everything properly inside the messages.ru.xlf file:

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:

  • AOT (Ahead of Time) compiler – converts Angular HTML and TypeScript (or JavaScript) code into the optimized code during the build phase before the browser actually downloads the code. This is the recommended type of compilation for production since it provides great performance and loading time
  • 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 ru and en keys inside configurations (I skipped other options for brevity):

  • We enable the AOT compiler for both configurations in that we set aot to true
  • The packages should reside inside the dist/ru and dist/en folders, respectively (the outputPath option)
  • i18nFile instructs where the translation file resides
  • i18nLocale sets the locale for the package
  • i18nMissingTranslation instructs what to do if some key does not have a translation; the default action is to signal a warning, while other supported values are error and ignore
  • baseHref sets 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=ru and ng serve --configuration=en to serve the application locally with the given language. Note that the URLs are http://localhost:4200/ru and http://localhost:4200/en as we have set baseHref earlier
  • ng build --configuration=ru and ng build --configuration=en to 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 serve and 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:

  1. Create a Firebase account if you don’t have one
  2. Navigate to the Firebase console and create a new project (I’ve named mine ngdemo8)
  3. Install Firebase tools globally by running npm install -g firebase-tools
  4. Login via your account using the firebase login command
  5. Initialize Firebase in the root directory of your project by issuing firebase init
  6. 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 dist folder)
  7. Last but not least, run firebase deploy to 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 firebase deploy.

You may find the working demo by visiting these links:

Great job!

Use PhraseApp 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, PhraseApp 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. PhraseApp also has other great features like webhook integrations, assignable jobs, activity tracking, and many others.

Configuring the PhraseApp CLI Tool

Let me guide you through the process of integrating PhraseApp into your translation workflow.

  1. First of all, grab your free trial if you don’t have an account yet
  2. Create a new project and choose the XLIFF translation file format
  3. Download the command line interface tool for your OS; make sure that the phraseapp executable is available in your PATH
  4. Open the Access Tokens page and generate a new API token with a read-and-write scope (we’ll use it to communicate with PhraseApp)
  5. Run phraseapp init in the root of your project; this command will boot a setup wizard
  6. Paste the token you’ve generated in step 4
  7. Choose a project you’ve already created
  8. If you’ve selected the XLIFF file format in step 2, simply choose the default format; otherwise, explicitly set XLIFF
  9. Next, the wizard will ask for the upload and download paths; answer ./src/locale/messages.<locale_name>.xlf to both questions (note that the <locale_name> should be typed as it is)
  10. Lastly, answer y to upload your files to PhraseApp

If you’ve done everything right, you should see two fully translated locales in your PhraseApp 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 PhraseApp.

Conclusion

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 PhraseApp to simplify translation file management (if you’re still looking for a solid translation management solution, give PhraseApp a try). Quite good for one single article, isn’t it?

Angular Translation: A Closer Look at Angular 8
5 (100%) 5 votes
Author
Ilya PhraseApp Content Team
Comments