iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 13
0

前言

前一篇已經說過為甚麼多語言的網站很重要,也示範了如何達到多語言的效果。但是,我們對於語言翻譯的需求還有很多,例如我如果想要讓一串句子每次搭配不同的變數怎麼辦,像是「你好,Tiger Liu」或是「你好,Tiger」這樣的可變性。或是如果有些語詞在某些語言中翻譯不出來、不想翻譯或是不小心忘記增加字典時,例如「掰掰囉!」在英文翻成「Bye Bye!」可是在日文的字典檔忘記加上這個字,要讓網站還是能夠顯示「Bye Bye!」怎麼做?

目標

  • 讓我們的翻譯可以有占位符號 ( placeholder),如此以來可以讓句子的可變性變大,如前言所述一般。
  • 當在語言包找不到符合的字句的時候,自動用備用的語言當替補。

我們將繼續用上一篇的程式來修改
完成的 Plunker

開工

語言包

先來看看我們的語言包最後長怎樣

// 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', // 只有在英文中定義
}

// lang-zh.ts

export const LANG_ZH_NAME = 'zh';

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

更新 translate 服務

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

public instant(key: string, words?: string | string[]) { // 增加可能的變數
    const translation: string = this.translate(key);

    if (!words) return translation;
    return this.replace(translation, words); // 呼叫 replace 
}

...

接著來定義replace

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

private PLACEHOLDER = '%'; // 我們的佔位符

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

    const values: string[] = [].concat(words);// 導入輸入的參數字串
    values.forEach((e, i) => {
        //會用 e 替換掉 %i: %0, %1, ...
        translation = translation.replace(this.PLACEHOLDER.concat(<any>i), e);
    });

    return translation;
}

...

更新 Pipe

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

transform(value: string, args: string | string[]): any { // args 可以是單一字串或是字串列
    if (!value) return;
    return this._translate.instant(value, args); // 加入 args
}
...

更新模板

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

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

<!-- 單一值通道 -->
<p>
    Translate <strong class="text-muted">Well done %0</strong>: 
    <br>
    <strong>{{ 'well done' | translate:'John' }}</strong>
</p>

建立事件

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

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

...

// 增加事件
public onLangChanged: EventEmitter<string> = new EventEmitter<string>();
..

// 選取用甚麼語言
public use(lang: string): void {
    ...
    this.onLangChanged.emit(lang); // 發布變化
}

...

主程式

先移除 selectLang() 中的 refreshText(),我們要重新架構。

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

ngOnInit() {
    // 載入一些東西
    ...

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

    // 設定目前語言
    this.selectLang('zh');

}

selectLang(lang: string) {
    // 設為預設
    this._translate.use(lang);
    // this.refreshText(); // 刪掉這行
}

subscribeToLangChanged() {
    // 刷新文字
    return this._translate.onLangChanged.subscribe(x => this.refreshText());
}

...

這樣設定之後,只要語言改變,就會自動更新

預設 & 備用語言

備用語言的概念是,假設預設是英文,當日文沒有字串時,就回去用預設的英文字串。

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

// 增加 3 個 properties
private _defaultLang: string;
private _currentLang: string;
private _fallback: boolean;

public get currentLang() {
    return this._currentLang || this._defaultLang; // 當沒有當前選取語言時用預設語言
}

public setDefaultLang(lang: string) {
    this._defaultLang = lang; // 設定預設
}

public enableFallback(enable: boolean) {
    this._fallback = enable; // 是否啟用備用
}

private translate(key: string): string { // 翻譯傳入的字串
    let translation = key;

    // 發現目前選的語言有這個字串
    if (this._translations[this.currentLang] && this._translations[this.currentLang][key]) {
        return this._translations[this.currentLang][key];
    }

    // 不用備用
    if (!this._fallback) { 
        return translation;
    }

    // 發現預設語言有這字串
    if (this._translations[this._defaultLang] && this._translations[this._defaultLang][key]) {
        return this._translations[this._defaultLang][key];
    }

    // 都沒有
    return translation;
}
..

當然我們也可以

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

ngOnInit() {
    // 可以做一些事情..
    ...

    this.subscribeToLangChanged();

    // 設定預設語言
    this._translate.setDefaultLang('en'); // set English as default
    this._translate.enableFallback(true); // enable fallback

    // 設定目前要用的語言
    this.selectLang('zh');
}

...

現在可以來測試語言包沒有字串時,是否會回去找預設的語言字串

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

上面這一段,不管換西班牙語或是中文,都會回去找英文的字串來用


上一篇
[Day 12] Angular 2 多語言 ( 1 )
下一篇
[Day 14] Angular 2 + <ng-content> 嵌入式設計靈活組織 HTML
系列文
Angular 2 之 30 天邁向神乎其技之路31

尚未有邦友留言

立即登入留言