Simple Language Translation in Angular 2 (Part 2)

How to Implement Simple Translation in Angular 2 (Part 2)

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

This is part two of the two part series on the implementation simple language translation.

Please refer to Part 1 for the first half.

Introduction

In part 2, we will enhance our translation to:

  1. Enable a placeholder for translation. For example, suppose you would like to translate Hello, [first_name] [last name]!

We want to be able to define our translation value like this:

// translation definition, %<number> as placeholder
...

'hello greet': 'Hello %0 %1'

...

And we want to be able to use the translate service like this:

this.translate.instant('hello greet', [customer.firstName, customer.lastName]);

Same goes to HTML View, we should be able to pass in a parameter.

<p>{{ 'hello greet' | translate: [customer.firstName, customer.lastName] }}</p>
  1. Publish and subscribe to a language changed event. In part 1, we mentioned that we can enhance the refreshText() function. We called the refreshText() in the selectLang() function. This is not a good practice because select language has nothing to do with refreshing text. We should publish an event whenever a language is changed and subscribe to it in order to run a function like refresh text.

  2. Enable default language and enable fallback. For example, given user sets current language to 'Chinese' and default language to 'English'. When the word 'hello' is not found in Chinese, it should fallback and find in English translation definition.

Let's start!

Add Translation Definition

Let's first add some translations in our translation definition file.

// lang-en.ts

export const LANG_EN_NAME = 'en';

export const LANG_EN_TRANS = {
    'hello world': 'hello world',
    'hello greet': 'Hello, %0 %1!', // two placeholder
    'well done': 'Well done %0', // one placeholder
    'good bye': 'bye bye', // only define in English
};
// lang-es.ts

export const LANG_ES_NAME = 'es';

export const LANG_ES_TRANS = {
    'hello world': 'hola mundo',
    'hello greet': 'Hola, %0 %1!',
    'well done': '%0 bien hecho',
};
// lang-zh.ts

export const LANG_ZH_NAME = 'zh';

export const LANG_ZH_TRANS = {
    'hello world': '你好,世界',
    'hello greet': '你好, %0 %1!',
    'well done': '干得好, %0',
};

Enable a Placeholder for Translation

This is pretty easy. We need to modify our instant function in translate.service.ts to accept an optional parameter. Note that to be more developer friendly, our parameter can accept either a string or an array of strings.

Update Translate Service

// app/translate/translate.service.ts
...

public instant(key: string, words?: string | string[]) { // add optional parameter
    const translation: string = this.translate(key);

    if (!words) return translation;
    return this.replace(translation, words); // call replace function
}

...

Now we need to implement our replace function.

// app/translate/translate.service.ts
...

private PLACEHOLDER = '%'; // our placeholder

public replace(word: string = '', words: string | string[] = '') {
    let translation: string = word;

    const values: string[] = [].concat(words);
    values.forEach((e, i) => {
        translation = translation.replace(this.PLACEHOLDER.concat(<any>i), e);
    });

    return translation;
}

...

Update Translate Pipe

We need to update our pipe too.

// app/translate/translate.pipe.ts
...

transform(value: string, args: string | string[]): any { // args can be string or string array
    if (!value) return;
    return this._translate.instant(value, args); // pass in args
}
...

Use It in HTML View

Now, let's update our HTML view to use the translation.

<!--app/app.component.html-->

<!--multiple value-->
<p>
    Translate <strong class="text-muted">Hello, %0 %1!</strong>:
<br>
    <strong>{{ 'hello greet' | translate:['Jane', 'Doe'] }}</strong>
</p>

<!--single value-->
<p>
    Translate <strong class="text-muted">Well done %0</strong>: 
    <br>
    <strong>{{ 'well done' | translate:'John' }}</strong>
</p>

2. Publish and Subscribe to the Language Change Event

Let's add an event emitter in our translate.service.ts file.

Update Translate Service

// app/translate/translate.service.ts
...

import { Injectable, Inject, EventEmitter } from '@angular/core'; // import event emitter

...

// add event
public onLangChanged: EventEmitter<string> = new EventEmitter<string>();
..

public use(lang: string): void {
    ...
    this.onLangChanged.emit(lang); // publish changes
}

...

Use It in App Component

Now we can use it in App Component. Let's refactor our code to remove the refreshText() from selectLang() that we discussed earlier.

// app/app.component.ts
...

ngOnInit() {
    // standing data
    ...

    this.subscribeToLangChanged(); // subscribe to language changes

    // set current language
    this.selectLang('es');

}

selectLang(lang: string) {
    // set default;
    this._translate.use(lang);
    // this.refreshText(); // remove this line
}

subscribeToLangChanged() {
    // refresh text
    // please unsubribe during destroy
    return this._translate.onLangChanged.subscribe(x => this.refreshText());
}

...

Now, whenever the language changes, our translation will still get updated.

3. Enable Default Language and Enable Fallback

Let's add new properties and refactor our translate function in translate.service.ts

Update Translate Service

// app/translate/translate.service.ts
...

// add these 3 new properties
private _defaultLang: string;
private _currentLang: string;
private _fallback: boolean;

public get currentLang() {
    return this._currentLang || this._defaultLang; // return default lang if no current lang
}

public setDefaultLang(lang: string) {
    this._defaultLang = lang; // set default lang
}

public enableFallback(enable: boolean) {
    this._fallback = enable; // enable or disable fallback language
}

private translate(key: string): string { // refactor our translate implementation
    let translation = key;

    // found in current language
    if (this._translations[this.currentLang] && this._translations[this.currentLang][key]) {
        return this._translations[this.currentLang][key];
    }

    // fallback disabled
    if (!this._fallback) { 
        return translation;
    }

    // found in default language
    if (this._translations[this._defaultLang] && this._translations[this._defaultLang][key]) {
        return this._translations[this._defaultLang][key];
    }

    // not found
    return translation;
}
...

Use It in App Component

You can set a default language and fallback during ngOnInit();

// app/app.component.ts
...

ngOnInit() {
    // standing data
    ...

    this.subscribeToLangChanged();

    // set language
    this._translate.setDefaultLang('en'); // set English as default
    this._translate.enableFallback(true); // enable fallback

    // set current language
    this.selectLang('es');
}

...

Use It in HTML View

Let's test it out in our HTML. Earlier in the Add Translation Definition step, we added good bye for English translation, but we didn't add that in Spanish and Chinese. If the user selected Spanish or Chinese, the value display would be bye bye because we enabled that as the fallback in our component.

<p>
    Translate <strong class="text-muted">Good bye (fallback)</strong>: 
    <br>
    <strong>{{ 'good bye' | translate }}</strong>
</p>

Summary

That's it. We have covered the basic translation setup with service and pipe and more advanced features like replace, event pub/sub, and default language.

Hope you enjoy reading. Happy Coding!

Jecelyn Yeen

Coder. Diver. Board Game Lover.

Speak English, Mandarin, JavaScript, Typescript, C# and more.

Problem solver at @iflix.