App Internationalization & Localization For Angular Apps

By Arik A.

Mar 4, 2019

i18n and you

As developers, we’d like our applications to be accessible and user-friendly to a world-wide audience. Obviously, this includes people who do not know the language the app was originally designed in.

That is why translating our applications is vital for a much broader user experience.

However, this process may not be as straightforward as it seems. Some challenges you might run into are:

  1. To fully translate our applications, we will need to change every string and title in them and remain consistent. For example, imagine you need to change the title “Avocados are awesome!” which appears on several pages, to “Learn more about avocados!” while at the same time translating it into Hebrew and French.
  2. Some languages are right-to-left (RTL) writing systems, and would render in the browser on the opposite side of your intended titles/strings. This will result in an awkward looking page. And we have no intention of building the same pages twice for an application.

Today, I will show you how to use the power of i18n to generate the titles and texts that your application will use for all languages from a single source of truth, focusing on challenge #1.

So what are we to do? Enter i18n.

What is i18n?

i18n is an abbreviation for Internationalization and Localization.
It is the means of adapting computer software to different languages.

From the angular docs:

Internationalization is the process of designing and preparing your app so it can be usable in different languages.

Localization is the process of translating your internationalized app into specific languages for particular locales.

Pre-requisites:

To make the most out of this example, some knowledge of angular 2, javascript, and html/css is required.

Node package dependencies

We will start by installing the following node packages:

  1. angular and angular-cli angular-cli (to get angular cli run npm install -g @angular/cli)
  2. The internationalization (i18n) library for Angular (npm i @ngx-translate/core --save)
  3. The i18n json file generator (npm i i18n-generator --save)

Let’s begin

First, we will create a new angular-cli project. (ng new)

Creating our translations map

Let’s open our package.json file and add/modify the following scripts:

// package.json
"scripts": {  
   "start": "npm run i18n:generate && ng serve",
   "build": "npm run i18n:generate && ng build --prod",
   "i18n:generate": "i18n src/i18n/i18n.pipe src/i18n"
 }

The script for i18n:generate (which comes with the i18n-generator plugin) will create the translation files from the specified 18n.pipe input file and output them in a location of our choice (in this example, the src/i18n folder).

We will use this file as the map for our entire application’s translations.

i18.pipe file example:

i18n=> | en | iw



=> SIDEBAR
AVOCADO | Avocado | אבוקדו
PIZZA | Pizza | פיצה
RECIPES | Cooking Recipes  | מתכוני בישול
MENU | Main Menu | תפריט ראשי
<=

Let’s go over the file structure:

i18n=> | en | iw

This line defines the language configurations for i18n. Ie, the locales that we will use (by their initials. For a complete list see “References/further reading” at the bottom of this page) and their order.

In this example, we define every first string to be placed for the English (en) translation and every second one for Hebrew (iw) translations.

When compiled, the pipe file will output two files (made visible after running npm start for the first time): en.json, and iw.json – containing their respective language strings.

=> SIDEBAR
AVOCADO | Avocado | אבוקדו
<=

Let’s break this down:

=> 
<=

This alone creates a group of strings.
Grouping our titles/strings into groups makes them easier to work with.

Next, we define a ‘SIDEBAR’ group, with several titles in it and add an AVOCADO meaning before the | of every string series. This helps us have a better understanding of what this title represents.

Whenever our application calls SIDERBAR.AVOCADO, the title would be translated into either Avocado or אבוקדו, accordingly.

ngx-translate translation service and other preps

After setting the titles and strings for each language like we did above, we wrote “instructions” on which titles and strings should be translated, and into what.

But our application doesn’t know about them yet. We have to load them in our app.modules like so.

// app.module.ts
import { RTLDivDirective } from './directives/rtl-div.directive';
import {TranslateModule} from '@ngx-translate/core';
import { HttpClientModule, HttpClient } from '@angular/common/http';



export function createTranslateLoader(http: HttpClient) {
    return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
@NgModule({
    imports: [
        BrowserModule,
        TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: (createTranslateLoader),
        deps: [HttpClient]
      }
    })
    ]    
})

In the loader, we tell the translationModule to use the json files inside ./assets/i18n that we created above for translation in our app (use en.json for “en” locale, etc.).

Now we can start getting needed translations done!

Using it in our application:

All we have to do now is call i18n with ngx-translate, like so:

<h1>My favourite fruit is {{ 'SIDEBAR.AVOCADO' | translate }}</h1>

Translation service and rtlDiv

There will be cases, being an angular app, where we will get our strings from another place, would like to manipulate the string somehow, or would like to save its value.

In any case, you can directly access them with the translate service from ngx-translate.

(For a complete list of available functions, see “References” below).

// translation service example
import { TranslateService } from '@ngx-translate/core';


@Injectable()
export class TranslatorService {
  constructor(public translate: TranslateService) {
  }


  async init(lang) {
    this.translate.setDefaultLang(lang);
    this.translate.use(lang);
    this.translate.getDefaultLang();
  }


  changeLanguage(lang) {
    this.translate.setDefaultLang(lang);
    this.translate.use(lang);   
  }


  isRTL = () => {
    return this.translate.currentLang === 'iw';
  }


  selectedLang = () => {
    return this.translate.currentLang === 'iw' ? 'עב' : 'EN';
  }


  getTranslationLanguage = () => {
    return this.translate.currentLang === 'iw' ? 'iw' : 'en';
  }


  getTranslation(lang) {
    return this.translate.getTranslation(lang);
  } 


}

A directive that subscribes to the RTL direction:

@Directive({selector: '[rtlDiv]'})
export class RTLDivDirective {


  isRTL: boolean;


  setDirection(element, isRTL): void {
    if (isRTL) {
      element.nativeElement.classList.add('rtl');
    }
  }


  constructor(
    private translator: TranslatorService,
    el: ElementRef
  ) {
      this.isRTL = translator.isRTL();
      this.setDirection(el, this.isRTL);
      translator.translate.onLangChange.subscribe((event: LangChangeEvent) => {
        this.setDirection(el, translator.isRTL());
      });
    }
}

This directive will add a class called rtlDiv onto the element whenever the translation service’s direction is changed to “iw” (see translation service above). In this way, we can be sure our component changes when the language does, and style it accordingly!

Usage example:

<div class="regular-style" rtlDiv>I'll become green when the direction is right to left</div>
.regular-style {
  color:red;
}
.rtlDiv {
  color:green;
}

Conclusion

Thanks for reading and making it this far!

In this post, I presented the rationale behind translation technology in our software, the challenges it poses, as well as a basic way for you to generate a translation file for your angular app.

I hope that the knowledge here will help you in building your current or next application.

Until next time!

References/ further reading

The angular docs on i18n

List of i18n locales

Translate service functions

Wikipedia article

Leave a Reply

Your email address will not be published.