Building localization aware web application using Angular

I have come through this concept when a user of my website is from another country and doesn’t understand what my website says because of the website loads with the English fonts on their PC/Laptop. Their browser renders the website in the English language. So for making the website to load as per the Country/Region specifically, I have enabled internationalization in my website. So let’s see what it is and how can we use in our Angular applications.

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.

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

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).”

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

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.

So in today article, we will see 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. You also need at least two simple language files in JSON format to be able 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 can 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 setup HTTP client and the corresponding module for the application. For the newly generated service, add the data property to store the translation strings, and import the HttpClient 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 updating the main application module to import the HttpClientModule.

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/i18n folder and gets the corresponding file.

Testing the service

It is now time to test the service to ensure it is working as expected and it can load the translation file.

It is now time to test the service to ensure it is working as expected and it can load the translation file. You need to inject the TranslateService instance into the class constructor, invoke the use(‘en’) method, and log the resulting data to the console output.
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:

Loaded after application started

Loading locales before the application starts

Typically, we want to have at least default locale already present once the application starts. That 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 like 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:

Loaded before application start

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  pipe 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.KEYand translation object traversal.

Now it’s time for ….

Testing the pipe

It is now time to test the pipe in action and see if it is working as we expect. Update the auto-generated application template app.component.html and 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:

Auto Translated application Title

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.

For any query or improvements in the above article, please add them in the comments or email me at shubhanker.dubey@jalantechnologies.com.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s