前言

一個小網站,客群可能很單一,在台灣也許只要網站只要有中文就好了。可是隨著服務擴大,客群也跟著變大,可能產品進軍亞洲,甚至拓展到全球,這時候至少會提供英文介面,甚至日文、韓文都有能。當我們點進大公司的網站時,也都可以看到他們提供好幾種語言,可見多語言的介面非常重要。

目標

模板可以這樣使用

<!-- 當我們翻譯成西班牙語,應該變成 'hola mundo'  -->
<p>{{ 'hello world' | translate }}</p>

模組的部分大概會長這樣

this.translate.use('es'); // 用西班牙文

關於翻譯技術層面的新知識並沒有太多,主要是將一些基礎概念作應用,因此接下來比較不會有太多文字解釋,主要還是以程式碼來展現如何實現我們的目標。

Plunker

藍圖

|- app/
    |- app.component.html
    |- app.component.ts
    |- app.module.ts
    |- main.ts
    |- translate/
        |- index.ts
        |- lang-en.ts
        |- lang-es.ts
        |- lang-zh.ts
        |- translate.pipe.ts
        |- translate.service.ts
        |- translation.ts
|- index.html
|- systemjs.config.js
|- tsconfig.json

動工

首先要連結我們的字典檔

// app/translate/translation.ts

import { OpaqueToken } from '@angular/core';

// 引進我們的語言檔案包
import { LANG_EN_NAME, LANG_EN_TRANS } from './lang-en';
import { LANG_ES_NAME, LANG_ES_TRANS } from './lang-es';
import { LANG_ZH_NAME, LANG_ZH_TRANS } from './lang-zh';

// translation token
export const TRANSLATIONS = new OpaqueToken('translations');

// 翻譯辭典
const dictionary = {
[LANG_EN_NAME]: LANG_EN_TRANS,
[LANG_ES_NAME]: LANG_ES_TRANS,
[LANG_ZH_NAME]: LANG_ZH_TRANS,
};

// providers
export const TRANSLATION_PROVIDERS = [
{ provide: TRANSLATIONS, useValue: dictionary },
];

TRANSLATION_PROVIDERS 會註冊在一開始的 bootstrap。

Translate Service

接著要建立服務,用來做切換語言用

// app/translate/translate.service.ts

import {Injectable, Inject} from '@angular/core';
import { TRANSLATIONS } from './translations'; // import our opaque token

@Injectable()
export class TranslateService {
private _currentLang: string;

public get currentLang() {
    return this._currentLang;
}

// inject our translations
constructor(@Inject(TRANSLATIONS) private _translations: any) {
}

public use(lang: string): void {
    // set current language
    this._currentLang = lang;
}

private translate(key: string): string {
    // private perform translation
    let translation = key;

    if (this._translations[this.currentLang] &amp;&amp; this._translations[this.currentLang][key]) {
        return this._translations[this.currentLang][key];
    }

    return translation;
}

public instant(key: string) {
    // call translation
    return this.translate(key); 
}

}

Translation Pipe

之前介紹過 Pipe,目的是讓我們在模板中的文字,能透過 Pipe 來做切換。

// app/translate/translate.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '…/translate'; // translate service

@Pipe({
name: 'translate',
pure: false // 使成為 impure
})

export class TranslatePipe implements PipeTransform {

constructor(private _translate: TranslateService) { }

transform(value: string, args: any[]): any {
    if (!value) return;
    return this._translate.instant(value);
}

}

@Pipe 裡面的 pure 要選 false。這樣才會變成 impure pipe。 Angular 在每次偵測變化的週期都會執行 impure pipe。如果是 pure pipe,只有在 Input 有變化才會執行。 所以這邊我們要加上這句,才能順利讓程式翻譯。

字典檔

簡單來說就是放一堆語詞的模組。

// lang-zh.ts

export const LANG_ZH_NAME = 'zh';

export const LANG_ZH_TRANS = {
'hello world': '你好,世界',
};

App Module

基礎設置,將用到的東西引入,並將 TranslatePipe 注入。

// app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { TRANSLATION_PROVIDERS, TranslatePipe, TranslateService } from './translate';

@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, TranslatePipe ], // Inject Translate Pipe here
bootstrap: [ AppComponent ],
providers: [ TRANSLATION_PROVIDERS, TranslateService ]
})

export class AppModule { }

App Component

// app/app.component.ts

import { Component, OnInit } from '@angular/core';
import { TranslateService } from './translate';

@Component({
moduleId: module.id,
selector: 'app-root',
templateUrl: 'app.component.html',
})

export class AppComponent implements OnInit {

public translatedText: string;
public supportedLanguages: any[];

constructor(private _translate: TranslateService) { }

ngOnInit() {
    // standing data
    this.supportedLangs = [
    { display: &apos;English&apos;, value: &apos;en&apos; },
    { display: &apos;Espa&#xF1;ol&apos;, value: &apos;es&apos; },
    { display: &apos;&#x4E2D;&#x6587;&apos;, value: &apos;zh&apos; },
    ];

    // &#x8A2D;&#x5B9A;&#x76EE;&#x524D;&#x8A9E;&#x8A00;
    this.selectLang(&apos;zh&apos;);
}

isCurrentLang(lang: string) {
    //&#x78BA;&#x5B9A;&#x9078;&#x7684;&#x8A9E;&#x8A00;&#x662F;&#x5426;&#x70BA;&#x986F;&#x793A;&#x8A9E;&#x8A00;
    return lang === this._translate.currentLang;
}

selectLang(lang: string) {
    // &#x8A2D;&#x5B9A;&#x76EE;&#x524D;&#x8A9E;&#x8A00;
    this._translate.use(lang);
    this.refreshText();
}

refreshText() {
    // &#x8A9E;&#x8A00;&#x6539;&#x8B8A;&#x5C31;&#x5237;&#x65B0;
    this.translatedText = this._translate.instant(&apos;hello world&apos;);
}

}

App Template

這邊我們用兩種方式翻譯,一個是使用 Pipe,一個使用服務。

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

<div class="container">
<h4>Translate: Hello World</h4>
<div class="btn-group">
<button *ngFor="let lang of supportedLangs"
(click)="selectLang(lang.value)"
class="btn btn-default" [class.btn-primary]="isCurrentLang(lang.value)">
{{ lang.display }}
</button>
</div>
<div style="margin-top: 20px">
<p>
使用 Pipe 翻譯: <strong>{{ 'hello world' | translate }}</strong>
</p>
<p>
使用 Service 翻譯: <strong>{{ translatedText }}</strong>
</p>
</div>
</div>