Back to the homepage
Angular

Internationalization: How To Open an Application to the World – part 1.

Nowadays, internationalizing an application requires more things than just translation. If we want to implement i18n into our application, we need to not only support multiple languages, but also cultures and local preferences. In this article, I’ll tell you about everything you’ll encounter while working on application internationalization.

Choosing i18n library – should we trust Angular creators?

If we browse the list of internationalization libraries, we will come across a library from the Angular team themselves — @angular/localize. The more inquisitive among you will ask: “why would anyone need third party libraries when we can choose a proven solution from the Angular dev team”? Right?

Unfortunately, @angular/localize has not been with us from the start. In those distant times, the community was forced to create their own solutions to bring i18n to our favorite framework. Putting aside the past, the library from the Angular team differs drastically from other available solutions. There are currently two types of internalization libraries on the market — compile time and runtime.

Compile-time library

At the time of writing this article, the only compile time library is @angular/localize. It translates our application during compilation. Each supported language has its own bundle that is hosted at a specific URL, such as domain.com/en for English and domain.com/es for Spanish.

Advantages:

  • Accelerated application rendering process
  • No impact on the bundle size 
  • The angular-cli tool, which extract text from the application
  • Support of multiple translation file formats 
  • @angular/localize is likely to be supported as long as Angular itself
  • Implementing translations doesn’t require knowledge of good practices such as grouping and naming translation keys

Cons:

  • Changing the language involves reloading of the entire application. This is essentially the biggest drawback of the entire library, especially if user -retention is our priority.
  • It is very difficult to integrate this library with the Ionic and Electron environments. If you plan using either of them, you should expect a very small amount of information and tutorials to be available on the Internet
  • The lack of ability to dynamically modify translations once the application is built. Updating translations or fixing typos requires creating a new build
  • More difficult setup compared to runtime libraries

Runtime Libraries

Runtime — libraries of this type translate our application as it runs. The translation files are fetched on demand via HTTP requests.

Advantages:

  • Changing the language doesn’t require an application reload. This allows us to keep the user’s attention on the page, even when they have a poor Internet connection
  • Wide range of tutorials on how to implement i18n using runtime libraries in Electron and Ionic
  • The possibility of using an external service to manage the translations, relieving the developers’ workload. We can also fix and improve translations after the application is deployed to the server (more on that later)
  • They provide a great developer experience

Cons:

  • Translating our app as it runs incurs a performance cost. Although the libraries often use memoization to mitigate the impact, it’s still a factor worth considering.
  • Sending additional HTTPrequests that fetch translation files. This disadvantage can be mitigated with the use of local cache after the first download. We can also split translations into modules and load them on demand
  • Increased bundle size
  • Lack of native support for dynamic change of the LOCALE_ID injection token after application initialization 
  • A single bug preventing access to translation files is able to stop the entire process of our app.
  • A good and scalable implementation of any runtime library requires much more knowledge compared to using @angular/localize

There are currently three major third-party Angular libraries that allow us to internationalize our application. These are:

  • Angular-i18next (~10k downloads) — an Angular implementation of the i18next library. It has not gained massive popularity like it has in React or Vue.
  • @ngx-translate/core (~720k downloads) — This is the most popular runtime translation library. For a long time the future of the library was unknown because the author stopped working on it. Recently, however, the library got its first update in over a year.
  • @ngneat/transloco (~90k downloads) — the successor to ngx-translate. In my opinion, this is the best runtime library currently available for Angular. Its advantages include amazing DX, very good documentation and a wide range of available plugins.

Should we always choose @angular/localize?

As you can probably see for yourself, there is no perfect internationalization solution without tradeoffs. We can see this reflected in library downloads — both approaches enjoy equal popularity. @angular/localize has about 740,000 downloads per week, while the other three runtime libraries we listed add up to around 820,000. 

So: should we choose the i18n solution from the Angular team? In my opinion, it’s definitely worth considering. @angular/localize is a very robust, performance-driven solution that easily allows us to not only translate, but also fully internationalize the application. The difference between translation and internationalization will be explained later in the article.

However, keep in mind that when choosing a library, you should focus on the individual needs of your application and your business requirements. It is also worth keeping in mind the potential problems associated with the full implementation of i18n in the case of runtime libraries, which only appear after a long time. Non-internationalized data formats, increased loading time and LOCALE_ID injection token issues are only examples of what you may encounter. More on that later.

Create a simple application with Transloco

In my opinion, implementing apps with runtime translations requires more knowledge of general i18n patterns. I’d like to introduce you to these patterns and show you the great successor to the ngx-translate library. That is why we’ll create an application with the @ngneat/transloco library instead of going for the timeless @angular/localize library. Without further ado, let’s get to what we love the most — the coding.

After you create a new project, add Transloco using the command:

ng add @ngneat/transloco

You’ll be asked to select the language codes for which you want to generate the configuration and translation files. Refer to this list of ISO 639-1 codes.

Let’s take a look at the changes that have been made to our project. First of all, we can see the newly generated transloco-root.module.ts file in the app folder. This file contains 2 things:

  • HttpLoader — the service that deals with fetching translation files into our application. This is what we will edit if we want to change the location of the translation files or if we decide to implement an external translation management service.

  • TranslocoRootModule — this module contains the configuration of our library. Here we can configure the languages we support, our default language, and whether we want to support dynamic language changes. It is worth noting that this module is imported automatically into the main module of our application.


    Another file that was added to our project is transloco.config.js. This is where we will configure the path to the translation files, our supported languages, and scoped libraries.

The last thing added to our project is the assets/i18n folder. It contains newly created .json files that store translations.

Adding translations

Let’s add some sample translations to our application.

en.json

pl.json

As you may have noticed, we have demonstrated several of translations in the file::

  • Text Translations — Translations of this type store the text for a given key. In the above example, foodStorage is a text translation.
  • Interpolable Translations — text translations can contain values that will be interpolated on the fly, like in item.code.
  • Pluralized Translations (select) — Text in translations can be displayed conditionally, based on a string passed through as a parameter — see item.type.
  • Pluralized Translations (amount) — Similarly, text can also be displayed conditionally based on a number passed as a parameter. Take a look at item.amountInStock. 

We can group translations in objects. Use the dot notation to refer to nested translation, like so: itemSelect.grapes.

How to use translations in templates?

After creating translations, we can move on to using them in code. Transloco gives us several different ways to translate keys in templates:

  • Attribute directiveIn this approach, we specify the key in the transloco attribute and optional parameters in the translocoParams input.

  • Transloco Pipe — A solution very similar to that found in the ngx-translate library. We transform the key using a pipe and pass any parameters in its first argument.

  • Structural directiveMy favorite solution, which I find missing in other runtime libraries. With the *transloco directive, we get a t-function with which we can translate the keys in the template. This approach is recommended by the library developers themselves. It has three advantages — it follows the DRY principle, it’s very fast thanks to memoization, and it creates only one subscription in the template.

Now that we know all of the possible options for translating keys let’s move on to creating our little application.

Creating a simple application

app.component.ts

In the app.component.ts file, we inject an instance of TranslocoService and declare the getAvailableLanguages method, which will return a list of language codes for every language that we support. In the example above, we also create a onLanguageChange method that will handle the language change.

App.component.html

To implement the language change in the template, we create a select element containing the languages our application supports. When we change its value, we execute the onLanguageChange method.

After following the above steps, you should be able to change the language in your application without any problems. As you do so, the text inside the h1 tag should change.

Let’s stop for a moment to discuss some useful methods within TranslocoService. More examples can be found in the Transloco documentation.

  • getAvailableLangs() — returns a list of supported languages in our application
  • getActiveLang() — returns the currently selected language
  • setActiveLang() — sets the new language
  • translate(key, params) — translates the key synchronously
  • selectTranslate(key, params) — translates the key asynchronously. The method returns an observable that emits the translated key every time the language changes in our application.

To further familiarize you with TranslocoService, let’s use it again. We are going to set a different page title depending on the selected language. To do so, we will use the new takeUntilDestroyed() operator from Angular 16.

Naming translation keys

Over the years, many methodologies and ways of naming translation keys have been developed. In this part of the article I will introduce you to some of them and show their advantages and disadvantages.

“Direct” methodology

This technique names keys directly after the original translation. Its great advantage is its intuitiveness and simplicity, which minimizes the risk of unnecessary duplication in the future.

E.g.

While creating the keys for descriptions, paragraphs, and any other kind of long text, we should focus on their deeper context. The key we create should be relatively short, but at the same time, it should summarize the entire text. Appending a suffix like “description” may also be a good idea.

E.g.

internationalizationBenefitsDescription: “Internationalizing your application results in improved market reach, cultural inclusivity, user satisfaction, and increased opportunities for your brand.”

The biggest problem with this methodology is when you encounter a word with multiple meanings. An example would be the word “lie,” which can mean both “telling untruth” and “to be in a horizontal position.” In cases like this, it’s useful to name the key in an unambiguous manner.

E.g.

 

The use of punctuation and diacritical marks in keys can lead to errors. Therefore, it is best to omit them to avoid problems in the future.

Grouping methodology

Another way to handle keys is to group them by views. This methodology works especially great for isolated sections of our application. Its advantages certainly include non-invasiveness — since the keys are grouped by views, we can change their translation without worrying about changing anything elsewhere in the application, which is not the case in the direct methodology.

E.g.

(To get the nested keys we use dot notation: paymentDialog.proceed)

Another advantage of this solution is the ability to lazy-load translations alongside modules. After extracting the translation to a separate file and some quick configuration, we are able to reduce the initial loading time of our application.

Now let’s move on to common mistakes and popular problems we will encounter using grouped translations.

The first is using generic names for subkeys— description, paragraph, button, etc. Naming keys this way assumes that our view will always have one button, paragraph or other element which might change in the future. When more elements (and translations) are added, it can be difficult to determine which key refers to which UI element. Fortunately, this problem can be circumvented very easily by using direct methodology.

E.g:

Another common mistake for this methodology is over-nesting translations. It is a good idea to not to go beyond three nestings — any further and our keys become unreadable.

The longer we use the group methodology, the more we notice that some translations end up duplicated in other groups. To solve this problem, we can create a “common” or “generic” group in the main translation file, which will include all repeatable translations in our application.

Extra advice

  • Remember not to concatenate translations with each other under any circumstances — sooner or later this will lead to problems. Instead of concatenating translations, you can always create a new key: t(“save”) + t(“changes”) -> t(“saveChanges”)
  • By using translations in combination with innerHTML you expose yourself to potential XSS attacks.

GO TO SECOND PART

 

About the author

Dawid Kostka

Angular Developer at House of Angular. For me software development is something more than just writing code. I love exploring and playing around with finest code. I’m passionate about adrenaline and positive attitude. In spare time I skate on aggressive skates.

Don’t miss anything! Subscribe to our newsletter. Stay up-to-date with the latest trends, tips, meetups, courses and be a part of a thriving community. The job market appreciates community members.

2 comments

  1. mohammad reza

    how to add transloco to nx? pnpm i @ngneat/transloco & nx g @ngneat/transloco:ng-add –project=suplier does not working.
    NX Cannot read properties of undefined (reading ‘sourceRoot’)

    • Hi Mohammad. Thanks for reaching out and leaving a comment.
      It seems like your command is missing one hyphen in the arguments section “-project=suplier”.

      The correct command is: nx g @ngneat/transloco:ng-add --project=suplier

      Also, in case you’re using newest angular 17 you may bounce into another issue. The generator probably won’t work as the transloco haven’t got an angular 17 update yet. You can track the progress in this github issue.

Leave a Reply

Your email address will not be published. Required fields are marked *