Briebug Blog

Sharing our thoughts with the community

Internationalizing your Angular app

Internationalizing your Angular app


Supporting a user’s locale and language is an often overlooked but important way to make your app user-friendly. When you internationalize your app, you’ve taken an important step in making your app accessible and usable for more users around the world.


This article will give you an overview of options to consider when approaching internationalization in an Angular app, and how to implement them. I’ve included formatting dates and numbers, Angular’s i18n tooling, the popular Ngx-translate, the newest Angular translation library, Transloco, how to make a language switcher, and how to debug i18n. I’ve also created an example app that shows one approach to i18n in Angular:

i18n?

First, where did we get the “i18n” name? It’s a numeronym, which is a type of abbreviation:

The first letter, followed by the number of letters in between, followed by the last letter.

Before we jump into the translation of text, let’s pick some low-hanging fruit — formatting dates and numbers. To do this you need to use locales.

Dates and numbers

A user’s browser tells you not only the preferred language of the user, but also their locale, or region. For instance, are you an english speaker in the US (en-US), or an english speaker in the UK? (en-GB). The proper formatting of dates and numbers depends on this.


To get the user’s locale and language, do not use the LOCALE_ID injection token, unless you are serving a separate app for each locale. It is static and does not detect anything on the user’s end. Here is a safe way to get the user’s locale (and language):

Here we are getting the user’s preferred locale and language client-side. This is a little different than the traditional method of looking at request headers server-side. Call this method, providing a default value, and assign a value to a property in your component:

Then you can pass the locale to pipes in your template:

You must import locale data for the locales you want to support (besides en-US) in app.module.ts:

You’ll need to check the angular repo to see the locales available to import. The pipes can then format dates and numbers appropriately.


A date with the locale en-US looks like this:

    Aug 20, 2019


For en-GB, it looks like this:

    20 Aug 2019


And fr:

    20 août 2019


Numbers and currency will occasionally appear differently too:

en-US and en-GB:

    23.82


fr:

    23,82


That was easy! Let’s move on to the hard part: Translation.

Angular’s solution: Angular i18n

The internationalization that comes with Angular is robust, but complex to implement. The major caveat is that it requires you to have different builds and serve different apps for each language. That’s fine if you’re ok with routing like this, with the language in the route:

The process looks like this:


  1. Add the localize package
  2. Use the i18n directive on elements with text in your template that need to be translated. (You can’t translate text in code).
  3. Extract the text with the CLI to build the translation files in one of 3 translation file formats (XLIFF 1.2, XLIFF 2, or XMB), which are designed to be edited with translator software.
  4. Then send them off to a translator who owns this software to get translated.
  5. Finally, merge them into your build, and configure your server to serve essentially completely separate apps (each with a different base url) for each language.


You’ll need to add build and serve configurations for each language in angular.json:

Serving different apps for each route will require you to set up your webserver to inspect the request headers, determine the language and redirect to the appropriate route.


This solution may be ideal for enterprise, but is probably not ideal for small teams, or projects with time/budget constraints. If you’d like something simpler, read on. But if Angular i18n is right for you, they provide a nice guide.

Ngx-translate

Ngx-translate is a very popular tool maintained by Olivier Combe, a former member of the Angular team, and member of the Briebug team. The npm stats he quotes suggest this package is used in 1 of every 5 Angular apps. The community has gotten involved and built lots of additional tools for Ngx-translate around extracting text, finding unused translation keys, migration tools and more.

It’s very easy to setup and super simple to use. Provide a JSON file with key value pairs, and then you can use the service, pipe, or directive to translate the text.

Although you’ll have to extract the translations by hand, (or try your luck with a third-party tool), you can change the language at runtime, translate with a pipe or directive, or use the service in code. It’s modularized into the different parts you may need under scoped packages on npm.

Implement Ngx-translate

Install the necessary packages:

Then include the TranslateModule in your app module:

Then you implement it in your app component:

From there you just provide json files, and store them in src/app/assets/i18n: en.json, fr.json, etc. You can use nested JSON to organize your translations like this:

It also supports params inside the string enclosed in curly braces. Then in your template:

Importing and Exporting TranslateModule in a shared module quickly makes the Ngx-translate pipes/directives/services available in other modules.

Transloco

Transloco is a newer i18n library (introduced in August 2019) for Angular with all the bells and whistles from Netanel Basal and friends. It includes schematics, supports Ivy, testing, supports multiple fallbacks, lazy loading translations, and even Universal.

Implement Transloco

Setting up Transloco is easy; the schematic does most of the work for you. All the things explained here are also used in my demo repo.

You’ll be prompted with the following:

As you can see from the CREATE logs, the schematic creates the loader for you, and stubs out your json files. It even sets up Transloco in the app module.


Now comes the manual part: extract all the strings your app needs into the JSON files. Just like Ngx-translate, you can use nested JSON and params. In fact, you could use the exact same files with either library.


Now you can use the transloco pipe:

Or, as they recommend, the structural directive:

If you need to add html tags in the text like <strong> or <em>:

Lazy-loading translation files

Translation files can be big! Transloco allows us to lazy-load them. For a module called “Lazy”, your module would look like this:

Your i18n folder will have a folder with the same name as the scope, containing the same language files:

It’s worth pointing out that scope names need to be lower-cased. Now you can use them in a component in your lazy-loaded module:

Translating in code

Whenever possible, the easiest way to translate in code is to just set a property value to a translation key. The logic in your code can determine which key to use:

Then pipe it in your template:

If that’s not possible, you can use the service to translate in code, but in order to react to runtime language changes you’ll need to pipe off the service’s langChanges property:

Make sure listenToLangeChange is set to true in the config (see below). For lazy-loaded translations in code, you’ll need to do this:

But wait, that’s not all…

Transloco has more tools for you, too many to list here. They have recently added support for MessageFormat in a separate package, to help manage gender and pluralization. Another package available from Transloco allows you to persist translation files locally for the user, so they don’t have to be downloaded every time they access a page. Conveniently it offers a TTL option so those files don’t remain cached indefinitely. You can read about these plugins and more here.


Let’s see what a Transloco language switcher looks like.




Making a language/locale switcher

You can follow along below and also check out the demo repo. With Transloco, you’ll need to make sure it is configured to listen for language changes. Here is the config provided in app.module. Notice listenToLangChange:

In your service or component, declare an array of locales you support, and several related properties:

Don’t forget, you must import locale data for the locales you want to support (besides en-US) in app.module.ts, for proper formatting of dates and numbers:

Then we’ll detect the user’s preferred locale, and if we support it, switch to it. We are using the getUsersLocale() method embedded at the beginning of this article. Remember, locales hold the language, and sometimes the region when necessary.

The regular expression supportedRegex is generated from your locales array: /^en|^fr/. It checks if there’s a match for the language in the detected locale. If so, it calls updateLocale().

This method checks if we support the locale, and if so, sets it. Then it extracts the language and calls the Transloco service to set the language, since we already determined it was supported with the supportedRegex.


Finally, here’s the html markup for the switcher. This is a very simple way to do it without any form controls:

Now you have a complete language switcher, which automatically detects the user’s locale/language and pre-selects it. Check out the repo for the full code, or stackblitz for a demo.

Debugging internationalization

You’ll want to test to make sure that the app automatically switches to the preferred language and locale, if you support it. To do this in Chrome, follow these instructions:


  1. Visit chrome://settings/languages and then expand “Languages”.
  2. Add the languages you want to test.
  3. Move the one you want to test first to the top by clicking the ellipsis and then “move to the top”.
  4. Refresh your app and check if it properly detected the language.

That’s it! I hope this article has helped you find the best option for internationalizing your app, as well as give you the first steps for supporting locales and languages.


Thank you, Je vous remercie, Gracias, 谢谢

ありがとうございました

Thanks for reading, and thanks for internationalizing your app! Your users will thank you too. 


Thanks to Phil Feinstein and Olivier Combe.

View Details
- +
Sold Out