Ionic 2: مرحبا العالم, Hello World, Bonjour le monde

Every day more and more apps are becoming universal, being used by millions of people all over the world. This means it's becoming much more important to localize.

Hello i18n

i18n is the process of developing apps in a way such that they can be localized for language and culture, easily.

Today I'll be showing you how to take your Ionic 2 app from uni-lingual to multi-lingual.

TLDR; Repo: https://github.com/NorthMcCormick/Ionic2-ng2-translate

NgTranslate

With Ionic and AngularJS 1.x we were stuck choosing between different localization frameworks and patterns. With Ionic 2 we can utilize the new Angular Translate framework right within our app.

Pre-requisites

We'll be starting from scratch for this tutorial using the latest Ionic 2.0.1 release.

Go ahead and navigate to your project root and install these dependencies:

npm install ng2-translate --save
npm install @biesbjerg/ng2-translate-extract --save-dev

ng2-translate is the actual module our app will use to do the translations.

ng2-translate-extract is a magic CLI that will let us extract the strings in our app so that we can send them to a translator.

Prepping our app

Now that we've got the modules we need we've got to import and bootstrap them. Open up your app.module.ts then import ng2-translate and http.

import { TranslateModule, TranslateStaticLoader, TranslateLoader } from 'ng2-translate/ng2-translate';
import { Http } from '@angular/http';

@NgModule({
  imports: [
    TranslateModule.forRoot()
  ]
})

The ng2-translate module by default looks for the i18n directory but we need to use a different directory because of where our source actually is. Lets create a loader to do this. We've already imported the necessary modules so we don't have to modify that. If you'd like to know more about creating loaders and how that works I recommend visiting the ng2-translate GitHub for way more details.

Add this function above your module definition but below your imports.

Note: You will already have exports, imports, and modules here. Don't copy and paste these code samples but add to what you have

export function createTranslateLoader(http: Http) {
    return new TranslateStaticLoader(http, 'assets/i18n', '.json');
}

And now we can update our module definition:

@NgModule({
  imports: [
    TranslateModule.forRoot({
      provide: TranslateLoader,
      useFactory: (createTranslateLoader),
      deps: [Http]
    })
  ]
})

Our First Words

We're getting so close!

Our app now knows where to look for these files, but they don't exist yet!

In src/assets create a new directory named i18n (you'll notice that this matches our code from earlier) and create 3 files named en.json (English), ar.json (Arabic), and fr.json (French).

If you want to use different text you can, but for this article we're going to go with Hello World. Boring, but easy to test.

In our ar.json:

{
  "Hellur wurld!": "مرحبا العالم"
}

In our en.json:

{
  "Hellur wurld!": "Hello World"
}

In our fr.json:

{
  "Hellur wurld!": "Bonjour Le Monde"
}

Okay... We're ready.

Pipes pipes pipes

ng2-translate has two great ways to translate strings. The first is using pipes and the second is the translate service.

Lets start with the easy one: the pipe.

We're going to open our home.html in src/pages/home and change the ion-title tag to this:

<ion-title>{{ 'Hellur wurld!' | translate }}</ion-title>

Go ahead and run your app. You should see 'Hello World' in the title.

If you don't double check you are injecting everything correctly and have your translation files (the .json files we made earlier) created.

Using the service

Lets say you have a status code coming back in your app and you want to display an error message with it. You really shouldn't clutter your DOM. Good thing there's a way to avoid doing that!

Lets add two methods to our src/pages/home.ts:


updateVariables () {
	this.translate.get('Hellur wurld!').subscribe(
		value => {
			// value is our translated string
			this.helloWorldString = value;
		}
	);
}

changeLanguage (newLanguage: string) {
	this.translate.use(newLanguage);
}

And update your constructor to use the translator service:

public helloWorldString: string = '';

constructor(public navCtrl: NavController, public translate: TranslateService) {
	translate.setDefaultLang('en');
	this.updateVariables();
}

Our updateVariables method grabs our hello world string in an async fashion and sets our variable. This is to mimic the real world usage of updating dynamic strings throughout your app.

The changeLanguage method will be called from our new buttons that we are going to write up. This will update the language for us.

And last but not least, our buttons:

<ion-content padding>
  <h2>Welcome to Ionic: {{ helloWorldString }}</h2>
  
  <div text-center>
    <button ion-button round full (click)='changeLanguage("ar")'>
      Arabic
    </button>
    <button ion-button round full (click)='changeLanguage("en")'>
      English
    </button>
    <button ion-button round full (click)='changeLanguage("fr")'>
      French
    </button>
  </div>
</ion-content>

Go ahead and run this.

Waiting...

Ah! You notice something...

Okay I'll stop playing text-adventure-blogger now. As you select different languages our text in the title is updating but the text bound to our variable is not. This is because when our language changed, we didn't get the new text.

The use function on the translate service does return an observable that you can use to do this, but I find it slightly restricting. Lets go ahead and listen to the event instead.

All ears

We need to update our import in our home.ts to include the language change event.

import { TranslateService, LangChangeEvent } from 'ng2-translate';

Bam! We're ready. Now in our constructor lets add this right below our initial call to updateVariables:

translate.onLangChange.subscribe((event: LangChangeEvent) => {
	console.log('Our translations changed!');
	this.updateVariables();
});

Now when we select our buttons both texts should update.

Now how do I get more of these... translations...

We installed a nifty tool earlier that helps us extract these strings from our application. It will find things we need to translate with pipes and the service.

I've wrapped this in a shell script so that it is easier to maintain and use. (Note: Only tested on OS X)

Create a new file at the root of your project called extractTranslations.sh and add these contents:

#!/bin/sh

echo "Starting extraction"

FILES=./src/assets/i18n/*
for f in $FILES
do
	echo "Extracting for $f "
	# take action on each file. $f store current file name
	ng2-translate-extract --dir ./src --output $f
done

What this script does is grab a list of the translation files we already have, run the extract command to search our source, and then re-populate the translation files. It will add any new translations we don't have yet and keep any that aren't being used.

We will need to give that executable permissions otherwise it won't run. Do that with chmod +x extractTranslations.sh

Now we want to be able to easily run it. Open your package.json and add it to the list of scripts. With our new clean project, that now looks like:

"scripts": {
    "clean": "ionic-app-scripts clean",
    "build": "ionic-app-scripts build",
    "ionic:build": "ionic-app-scripts build",
    "ionic:serve": "ionic-app-scripts serve",
    "extract": "./extractTranslations.sh"
}

Run the command with npm run extract and watch the magic happen. Now it won't actually do much because we already have our strings in the translation files.

Add a new translation, for example:

<p>{{ 'HELLO_WORLD_2' | translate }}</p>

And run the script again. Our files should now look like this:

{
	"Hellur wurld!": "Bonjour Le Monde",
	"HELLO_WORLD_2": ""
}

Other

You might be wondering why I chose Hellur wurld! as the variable. It is just to show you that you can pretty much put whatever you want in there as a the key of the translation. This becomes super handy when translations become more complex. Some people organize them with constant-like names such as HELLO_WORLD but I find that unintuitive because even as developers not all of us read that way.

Have other questions?

Let me know in the comments! I will keep exploring this and writing about localization if there are more questions or needs such as automatic RTL, complex translation cases, defaults, etc. This just scratches the surface.

Also never hesitate to leave me other tutorials you would find helpful. I'm always up for a challenge.

Resources

GitHub Repo with completed project: https://github.com/NorthMcCormick/Ionic2-ng2-translate

Extracting CLI: https://www.npmjs.com/package/@biesbjerg/ng2-translate-extract

Ng2-Translate: https://github.com/ngx-translate/core

Ionic Framework: https://ionicframework.com