Introduction to Internationalization (i18n)

Engineering Feb 02, 2019

This blog is a step by step guide for successful implementation of multi language angular applications rendered server side.

I realized that I needed to implement internationalization when a user from another country wasn't able to understand the language of my company's website because it was loading with English fonts - a language he does not speak.

Their browser renders the website in the English language. So for making the website to load as per the Country/Region specifically, I enabled angular internationalization in my website.

So let's see what is angular internationalization and how can you use it in your angular applications.

What Is The Meaning of Internationalization (i8n)?

Internationalization is the design and development of a product, application or document content that enables easy localization for target audiences that vary in culture, region, or language.

                                                                OR

Internationalization is the designing of your website or app in such a way where it is easy to understand in various locales around the world. You can understand the term "Locale" as a region where people speak a specific language.

What Is i18n Angular?

Internationalization is often written i18n, where 18 is the number of letters between i and n in the English word.

What is Localization in Angular?

Localization refers to the adaptation of a product, application or document content to meet the language, cultural and other requirements of a specific target market (a locale).

What Does l10n Mean?

Localization is sometimes written as l10n, where 10 is the number of letters between l and n.

How Angular Internationalize Your App?

  1. Angular displays numbers ( currencies, percentages , dates ) in a local format.
  2. Marking text in component templates for translation
  3. Marking plural forms of expressions for translation
  4. Marking alternate text for translation

In other words, i18n allows applications to support and satisfy the needs of multiple locales, thus “enabling” l10n. It is because of i18n that we are able to localize all of the web projects within its pantheon of applications and open the web up to the world.

Let's start the angular internationalization tutorial on how can we add support for multiple languages on our website.

Angular i18n Support

I suggest using the Angular CLI commands to generate a new blank Angular application for the experiments.

Prerequisites

At least two simple language files in JSON format (to test that the application switches languages at runtime.)

In the src/assets folder create a new i18n subfolder and put the following en.json file inside:

{ "TITLE": "My i18n Application (en)" }

That is going to be our default “English” locale. Next, create another file in the i18n subfolder with the ua.json name.

{ "TITLE": "My i18n Application (ua)" }

As you will see, we are going to have an application title that gets translated in multiple languages on the fly. For the sake of simplicity, I am using the same string for both languages with the locale name appended to the end.

Translation Service

Now we need a separate Angular Service to handle translations for the rest of the application in a single place.

ng g service translate --module=app.

Our service needs to load the corresponding translation file from the src/assets . For this purpose, we need to

  1. Setup HTTP client and the corresponding module for the application.
  2. For the newly generated service, add the data property to store the translation strings
  3. Import the Http Client service like in the next example:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class TranslateService {
data: any = {};
constructor(private http: HttpClient) {}
}

You also need to update the main application module to import the HTTP Client  Module.

import { HttpClientModule } from '@angular/common/http';
import { TranslateService } from './translate.service';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    TranslateService
  ],
  ...
})
export class AppModule { }

At this point, our HTTP stack is ready, and we should be able to fetch translation files.

Basic translation loading

Let’s introduce a use(lang) method for the service to initiate the file download and language switch.

@Injectable()
export class TranslateService {
  data: any = {};
  constructor(private http: HttpClient) {}
  use(lang: string): Promise<{}> {
    return new Promise<{}>((resolve, reject) => {
      const langPath = `assets/i18n/${lang || 'en'}.json`;
      this.http.get<{}>(langPath).subscribe(
        translation => {
          this.data = Object.assign({}, translation || {});
          resolve(this.data);
        },
        error => {
          this.data = {};
          resolve(this.data);
        }
      );
    });
  }
}

Above is the minimalistic implementation of the translation fetching. Upon every call, the service goes to the assets/il8n folder and gets the corresponding file.

Testing The Service

It is now time to test the service to ensure it is working properly and to check if it can load the translation file.

You need to                                                                                                                              

  1. Inject the Translate Service instance into the class constructor
  2. Invoke the use(‘en’) method
  3. Log the resulting data to the console output.
  4. Update the main application component class like in the following example:
import { Component } from '@angular/core';
import { TranslateService } from './translate.service';
@Component({...})
export class AppComponent {
  constructor(private translate: TranslateService) {
    translate.use('en').then(() => {
      console.log(translate.data);
    });
  }
}

Now, if you run your application with the ng serve --open command and go to the chrome developer tools, you should see the following output:

Introduction to Internationalization (i18n)

Loading Locales Before The Application Starts

Typically, we want to have at least default locale already present once the application starts. It helps to avoid “flickering” effects when end users see resource keys while corresponding translation is still loading.

The Angular framework provides a specific APP_INITIALIZER token that allows to initialize and configure our providers before the application starts.  

Below is an example implementation that allows us to load and set en.jsonas the default application language:

import { NgModule, APP_INITIALIZER } from '@angular/core';
export function setupTranslateFactory(
  service: TranslateService): Function {
  return () => service.use('en');
}
@NgModule({
  ...
  
  providers: [
    TranslateService,
    {
      provide: APP_INITIALIZER,
      useFactory: setupTranslateFactory,
      deps: [ TranslateService ],
      multi: true
    }
  ],
  
  ...
})
export class AppModule { }

Let’s now test this in action. Go back to the app.component.ts file and remove explicit use(‘en’) call. Leave just logging to console output as shown in the next example:

@Component({...})
export class AppComponent {
  constructor(private translate: TranslateService) {
    console.log(translate.data);
  }
}

If you start the application right now (or reload the page if it is already running) the console output should still be the same as previous time:

Introduction to Internationalization

Note, however, that this time service gets initialized before application component, and we can access the language data without extra calls.

Let’s now move on to content translation.

Translation Pipe

Using pipes for translation is a common approach in the Angular world. We are going to use the following syntax to translate content for our components:

<element>{{ 'KEY' | translate }}</element>
<element title="{{ 'KEY' | translate }}"></element>
<element [title]="property | translate"></element>

Use the following Angular CLI command to create a new  TranslatePipe fast:

ng g pipe translate --module=app

Given that the translation data is already loaded and stored within the TranslateService, our pipe can access the service and get the corresponding translation based on the key.

import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from './translate.service';
@Pipe({
  name: 'translate',
  pure: false
})
export class TranslatePipe implements PipeTransform {
  constructor(private translate: TranslateService) {}
  transform(key: any): any {
    return this.translate.data[key] || key;
  }
}

The above implementation is a fundamental one to show the initial approach. For this article, we are using only one level of object properties, but you can extend the code, later on, to support property paths, like SOME.PROPERTY.KEY and translation object traversal.

Now it’s time for ….

Introduction to Internationalization (i18n)

Testing The Pipe

It is now time to test the pipe in action and see if it is working as per the expectations.

You need to                                                                                                                              

  1. Update the auto-generated application template app.component.html
  2. Replace the title element with the following block:
<h1>
  Welcome to {{ 'TITLE' | translate }}!
</h1>

Switch to the running application right now, and you should see the title value reflecting the content of the en.json file:

Introduction to internationalization

If the browser language changes, the value will reflect the content of the different JSON files as per the region specifically. We can add as many as languages through which we can display our website fonts.

What’s the biggest thing you’re struggling with right now that we as a technology consulting company can help you with? Feel free to reach out to us at info@jalantechnologies.com. We hope our assistance will help!

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.