App Internationalization and localization For Angular Apps

Mar 5 2019

App internationalization and localization (I18n and you)

As developers, we’d like our applications to be accessible and user-friendly to a worldwide audience -
that includes people who do not speak the language the app was originally designed in.
That is why translating our application is vital for a great 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 application, we will need to change every string and title in it and remain consistent. For example imagine you need to change the title ‘Avocados are awesome!’, that appears on several pages, to ‘learn more about Avocados!’ while at the same time translating it to 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 our 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 tackling 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 to 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)

Lets begin

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

Creating our translations map

Lets 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 | תפריט ראשי
<=

Lets 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 further reading) 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 | אבוקדו
<=

Lets 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, To 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 Transltation 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 doesnt 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 some translation 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 direcly access them with the translateService service from ngx-translate.
(For a complete list of available functions, see further reading)

// 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);
  } 


}


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 translation service's direction is changed to 'iw' (see translation service above). 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/article I have presented the need behind translation technology in our software, the challengs it presents, and 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

Arik A.
Software Developer
Back to Blog