Tutorials

Ionic Translation and Internationalization

Introduction to Ionic Translation Ionic is a popular framework for creating hybrid applications running on IOS, Android, and Windows Phone. In this...

Written by Roman Kutanov · 8 min read >

Introduction to Ionic Translation

Ionic is a popular framework for creating hybrid applications running on IOS, Android, and Windows Phone. In this article we are going to discuss how to perform Ionic translation using ngx-translate library.

One of the primary reasons for using hybrid frameworks is the speed of development. Therefore, in this article, we will also see how to employ the power of Ionic and third-party libraries to speed up the development of applications with multi-language support, and make the translation management more convenient.

Applications Translations

Why should you care? I can always do it later.

The earlier you start building your translation infrastructure, the easier the translation workflow will be. I see many times that translation becomes an issue later in the project. For example, once I encountered a funny case. A charity app of a famous world-wide brand had a slogan in the Russian language reading as “Let’s make injuries” (though the original slogan was “Let’s fight with injuries”). It took the company about two months to change it after we filed the report.

If you start without proper preparations, you are risking ending up with a complete chaos.

Ionic Translation Issues

Here are some common issues with the translations:

  • Scattered translation, that ruins your application look and feel. It’s typical that internationalization is not a systematic process, but an occasional activity.
  • Localization doesn’t take into account the application’s design and context. Words are too broad, and translation goes off the screen, or there’s an unsolicited word-wrap. Translation out of context often doesn’t make any sense.
  • Manually transfer different formats of translation files for different platforms. It takes time, and mistakes are quite probable.
  • Not reusing your translations. Often we end up having completely different translations for slightly different phrases.
  • Not paying much attention to legal binding text translations. Legal texts are essential. Don’t employ the “Google translate and forget” approach.
  • Not testing your translations. For instance, your production application might have placeholders instead of an actual text after deploying.

You can avoid most of these problems by using the translation management system like Lokalise.

Open-source Translation Libraries

Is it hard to translate Ionic?

There are several libraries you can use to translate Ionic applications:

Feature Angular internationalization Ngx-translate Transloco
Translation of built-in values (dates, numbers, currencies) x
Translation in templates x x
Translation in code –  x x
Run-time language change x x
Ionic support Hard x x

 

From a feature standpoint, the built-in I18n module looks the weakest, and it is much harder to set up, therefore we won’t cover it in this article. At the moment, I do not recommend using the built-in Angular internationalization module for the Ionic applications.

Transloco is new and fresh, and has some new cool features. ngx-translate has a proven track record, and it is also feature-rich therefore we are going to be using it in this article.

Ionic 4 Translation Modules

Installation and Initialization of ngx-translate/core

To start off, clone a sample application and install all the necessary modules:

$ git clone git@github.com:kutanov/ionic-conference-app.git
$ cd ionic-conference-app
$ npm install
$ npm install @ngx-translate/core @ngx-translate/http-loader --save

Like always, you should initialize the installed modules. Open the app.module.ts file and import them. Then configure the TranslateModule with the forRoot({}) method:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { IonicModule } from '@ionic/angular';
import { IonicStorageModule } from '@ionic/storage';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { FormsModule } from '@angular/forms';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClientModule, HttpClient } from '@angular/common/http';
export function httpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/locale/', '.json');
}
@NgModule({
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (httpLoaderFactory),
deps: [HttpClient]
}
}),
IonicModule.forRoot(),
IonicStorageModule.forRoot(),
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production
})
],
declarations: [AppComponent],
providers: [InAppBrowser, SplashScreen, StatusBar],
bootstrap: [AppComponent]
})
export class AppModule {}
view raw app.module.ts hosted with ❤ by GitHub

Translation Files

Let’s place our JSON files with Russian and English translations inside the /assets/locale/ folder. We’ll start with a very simple example to make sure everything is working fine:

{
"Menu": "Меню",
"About": "О программе"
}
view raw ru.json hosted with ❤ by GitHub

{
"Menu": "Menu",
"About": "About"
}
view raw en.json hosted with ❤ by GitHub

The translation files are in standard JSON key: value format.

With innerHTML you can use HTML code inside translation strings, so it’s perfectly normal to write something like "greetings": "<h1>Привет</h1>". To use this translation inside the markup, write <div [innerHTML]='"greetings" | translate'></div>.

Another important remark is that it’s better to structure your JSON files properly (especially if you are working with complex applications). You may use nesting for that:

{
"welcomeScreen": {
"greeting": "Hello my dear friend {{name}}"
}
}
view raw nesting.json hosted with ❤ by GitHub

Later you may refer to the nested id with a special dot notation, for example: welcomeScreen.greeting.

After the installation and setting up steps are done, we can employ a translation pipe. Pipes are a powerful instrument which resembles *nix pipes. Basically, you take the return value of one function (or a variable) and then pass this output as an input to another function.

<ion-header>
<ion-toolbar>
<ion-title>{{ 'Menu' | translate }}</ion-title>
</ion-toolbar>
</ion-header>
view raw app.component.html hosted with ❤ by GitHub

Switching the Language

So, to switch the current language of the app, you can use the translation service.

constructor(
...,
private translate: TranslateService
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
...
this.translate.use('ru');
});
}
view raw app.component.ts hosted with ❤ by GitHub

You may get access to the current language code through the currentLang variable: private currentLand: string = this.translateService.currentLang;.

Usually, Ionic applications consist of more than one component. What about lazy loading components? In this case, import the translation module into a child’s module and then use the same service. Inside lazy loaded modules, you have to use forChild():

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { AboutPage } from './about';
import { PopoverPage } from '../about-popover/about-popover';
import { AboutPageRoutingModule } from './about-routing.module';
import { TranslateModule } from '@ngx-translate/core';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
AboutPageRoutingModule,
TranslateModule.forChild()
],
declarations: [AboutPage, PopoverPage],
entryComponents: [PopoverPage],
bootstrap: [AboutPage],
})
export class AboutModule {}
view raw about.module.ts hosted with ❤ by GitHub

After you import the translation module into the child’s module, you can use the same service.

Let’s make a language selector in the parent module and see whether the values in the child module will change.

<ion-list>
<ion-item>
<ion-label>
Dark Theme
</ion-label>
<ion-toggle [(ngModel)]="dark"></ion-toggle>
</ion-item>
</ion-list>
<ion-list>
<ion-item>
<ion-label translate>Language</ion-label>
<ion-select [(ngModel)]='lang' (ionChange)='changeLanguage()'>
<ion-select-option value='ru'>Russian</ion-select-option>
<ion-select-option value='en'>English</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
view raw app.component.html hosted with ❤ by GitHub
changeLanguage() {
this.translate.use(this.lang);
}
view raw app.component.ts hosted with ❤ by GitHub

To test it in the child’s module let’s edit the about.component.html file:

<ion-title> {{ "About" | translate }}</ion-title>
view raw about.html hosted with ❤ by GitHub

Now we can switch the current language in the parent component, and this change will immediately propagate to the child’s components.

What else the ngx-translate can do? Let explore the core features.

Multi-language Support Using Pipe

{{ 'stringId' | translate }} is the simplest case of translating something. But sometimes you may want to translate a text with embedded variables. You can achieve that with parameters passed to the translate:

{{ 'stringId' | translate:{"variable": value  } }}

or

{{ 'stringId' | translate:variableDeclaredInComponent }}.

Inside the JSON file you should just provide the variable in double curly brackets {{}}.

Add a new translation to the /assets/locale/ru.json file:

"greeting": "Здравствуте {{name}}".

Define a variable in the text:

...
dark = false;
name = 'Roman';
constructor(
...
view raw app.component.ts hosted with ❤ by GitHub

And then use it inside the template:

<ion-toolbar>
<ion-title>{{ 'Menu' | translate }}</ion-title>
<p>{{ 'greeting' | translate:{name: name} }}</p>
</ion-toolbar>
view raw app.component.html hosted with ❤ by GitHub

Multi-language Support Using Service

If you require multi-language support inside your components, you may inject translation service into the necessary component inside the constructor function.

That allows you to both set and get translation values like this:

greetings: string = '';
constructor(private translateService: TranslateService) {
this.translateService.setTranslation('en', {
'about.header': 'About',
'about.greetings': 'Hello fellow translator {{name}}'
});
this.translateService.get('about.greetings', {name: 'Roman'}).subscribe((res: string) => {
this.greetings = res;
});
...
}
view raw app.component.ts hosted with ❤ by GitHub

Multi-language Support Using Directive

A directive is an HTML attribute that Angular treats as an instruction. We may use a special directive to support multi-language localization:

<div translate="Menu"></div>
<div [translate]="'Menu'"></div>
<div translate [translateParams]="{value: 'world'}">greetings</div>
view raw app.component.html hosted with ❤ by GitHub

Please note that you can use a translation id inside the tag and pass this translation id as the parameter to the directive. Directive inside the brackets tells Angular to interpret parameters as a variable name and try to resolve it.

translateParams allows you to pass parameters to the translation.

Pluralization

Human-spoken languages are very different. In most languages, there is such a thing as the declension.

By default, ngx-translate doesn’t support pluralization and declension of nouns. But there are libraries to help us here: ngx-translate-messageformat-compier and messageformat.

First of all, install these modules:

$ npm install ngx-translate-messageformat-compiler messageformat --save

To initialize the module we have to rewrite ngx-translate initialization a little bit:

import { TranslateCompiler, TranslateModule } from '@ngx-translate/core';
import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';
@NgModule({
imports: [
...
TranslateModule.forRoot({
compiler: {
provide: TranslateCompiler,
useClass: TranslateMessageFormatCompiler
},
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [HttpClient]
}
})
...
],
declarations: [AppComponent],
providers: [InAppBrowser, SplashScreen, StatusBar],
bootstrap: [AppComponent]
})
view raw app.component.ts hosted with ❤ by GitHub

As you see, we’ve changed the compiler. The new compiler allows us to use the special format to define conditional wording. For example, how to treat countable nouns, define how words are changed by the masculine and feminine gender.

Let’s take a closer look at how this format works and the most popular use-cases. What we are talking about is called ICU MessageFormat. The whole expression is provided in curly brackets {} . The important thing to do is to convert all your expressions so that they use one curly bracket instead of two. So the 'hello': 'hello {{name}}' expression turns into 'hello': 'hello {name}'.

The message starts with the variable name we pass to evaluate inside the expression. After the comma, the second parameter is a keyword defining what mode we are going to use. There are two keywords: plural and select. Plural means that we pass a countable parameter and the expression will return a value based on the given number.

The third parameter is an expression with options. If plural is passed as a second parameter, the third parameter should look like:

=0{String or expressing if the first param = 0} 1{String or expression if the first param = 1} ... someNumber{String or expression if the first param = someNumber}... other{String or expression if the first param is equal to something not specified here. Default value}

Let’s take a look at a simple example. Suppose we would like to show how many apples John has. There are three possible cases:

  • John has no apples
  • John has one apple
  • John has X apples (more than one apple, that is)

Now represent this logic in your code:

"john": "John {applesCount, plural, =0{has no apples} 1{has an apple} other{has {applesCount} apples}}"]

<div translate="john" [translateParams]="{applesCount: applesCount}">123</div>
<button [(ngModel)]='applesCount' (click)='applesCount=applesCount+1'>+</button>
view raw app.component.html hosted with ❤ by GitHub

Using Gender Information

Now what about select as the second option? Remember, we were talking about two possible options: plural and select. If we set the second option to select,  the first parameter variable will be matched against the provided options. Take a look at the following example:

person: {gender, select, male{He is} female{She is} other{It is} friend}

Here the first parameter is a variable gender, the second parameter is a keyword select, and the third parameter is the options string male{He is} female{She is} other{It is} friend}.

If gender is male, then the expression will be resolved into He is friend, if femaleShe is friend. For all other values it will be It is friend.

The logic here is very simple. The string compiler takes the value of the first parameter, in our case that’s the variable gender, checks it and looks for an exact match in the third parameter. If it hasn’t found it, then it yields the other option value. This format may seem a little bit awkward, but you’ll get used to it very fast.

Using Shared module

What if we have some shared module and we want the TranslateService to work with this module? We also want to export this module so it is available everywhere the shared module is imported.

Shared modules are a good way to make sure that you have exactly one instance of a specific service. Let’s say you have a shopping cart, and  you add goods to this shopping cart from different parts of your application. It will be very sad if you end up having multiple instances of the shopping cart and a user leaves your service because the desired goods missed the right basket.

Let’s initialize the shared module:

import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { TranslateCompiler, TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/locale/', '.json');
}
@NgModule({
declarations: [],
imports: [
CommonModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [HttpClient]
},
isolate: false
}),
],
exports: [TranslateModule],
})
export class SharedModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [ShoppingCartService, AccountService]
}
}
}
view raw shared.module.ts hosted with ❤ by GitHub

What we do here is create a module and import everything we need. We pass isolate: false to TranslateModule to make sure that it will be available outside of the module.

exports: [TranslateModule] exposes it to lazy-loading modules.

After configuring we define the method forRoot and expose providers we want to share with the rest of the app.

We then  call the SharedModule.forRoot() in the imports section of the main app module app.module.ts.

Don’t forget to initiate the TranslateModule in the app.module.ts as we did in the first section. SharedModule doesn’t substitute the initial initialization in the root module.

Translation with Lokalise

As I wrote earlier, to avoid many pitfalls it’s better to have some translation management system. It allows you to track changes, distribute one translation to many platforms, avoid repeating translations of the same text in different places, and many many more.

Setting up Lokalise for React Native is easy. Follow these steps:

  • To get started, grab your free trial.
  • Download and install Lokalise CLIv2 that will allow you to upload and download translation files.
  • Open your profile page, go to the “API tokens” section, and generate a read/write token.
  • Create a new project, give it a name
  • , and set English as a base language.
  • In the project section, press “More” and select “Settings”. There you can find the project id.
  • To upload your translations files, run lokalise2 file upload --token <token> --project-id <project_id> --lang-iso en --file assets/locale/en.json. Pay attention as on Windows it’s better to provide full path to the translation file. This command will upload translation file to Lokalise. To upload the Russian version repeat the operation with “ru” language code and the correct path to the language file.
  • If you go back to the project overview page, you will be able to see your translation keys and values. There are tons of options to further adjust the process, so please make sure to read the documentation.
  • After you’ve edited the translations, download them back by running lokalise2 file download --token <token> --project-id <project_id> --bundle_structure %LANG_ISO%.json --unzip_to ~/your_app/assets/locale/.

That’s it. Check out the official documentation for the command-line interface to learn about other commands and options.  As you see, Lokalise reduces the number of complex tasks and make the translation process a piece of cake.

Conclusion

ngx-translate is a quite simple yet powerful instrument to work with Ionic 4 translations. It’s easy to install and use and it allows you to manage translations both in templates and in components effectively. It fully supports Ionic 4 module injections and lazy loading.