最近因為工作需要使用到 Angular i18n 的功能,所以就決定在看 document 的過程中整理一下 Angular i18n 的一些用法和觀念。
Table of Contents
1. 建立 Angular 專案
首先在全域安裝 Angular cli:
npm install -g @angular/cli
接著使用 Angular cli 快速建立新的專案:
ng new i18n-tutorial
2. 安裝相關套件
使用 Angular cli 來安裝相關的套件:localize 為 Angular i18n 會使用到的套件,material 則是 Angular 的 UI framework。
本篇文章就不多贅述 Angular material 的用法,想了解詳細用法的話可以參考官方文件:Angular Material UI component library。
ng add @angular/localize
ng add @angular/material
3. 新增 component 以及 import module
3.1 Import module
接著 import 會用到的 module,這時候我會習慣將 material 會用到的 module 獨立出來,然後在 shared-module 裡 import material-module ,會讓檔案結構看起來比較乾淨一點,等專案變大時也會比較好維護:
// src/app/shared/material.module.ts
import { NgModule } from '@angular/core';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatMenuModule } from '@angular/material/menu';
import { MatButtonModule } from '@angular/material/button';
@NgModule({
imports: [
MatIconModule,
MatSelectModule,
MatFormFieldModule,
MatInputModule,
MatToolbarModule,
MatMenuModule,
MatButtonModule
],
exports: [
MatIconModule,
MatSelectModule,
MatFormFieldModule,
MatInputModule,
MatToolbarModule,
MatMenuModule,
MatButtonModule
],
providers: []
})
export class MaterialModule { }
// src/app/shared/shared.module.ts
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { MaterialModule } from './material.module';
@NgModule({
imports: [
FormsModule,
ReactiveFormsModule,
MaterialModule
],
exports: [
FormsModule,
ReactiveFormsModule,
MaterialModule
],
providers: []
})
export class SharedModule { }
3.2 新增 component
再來使用 Angular cli 來快速建立 component:
ng g c nav-bar
ng g c i18n-form
使用 Angular cli 來產生 component 最大的優點是會自動幫你在 module 的 decalrations 裡新增該 component,因此我們接著只要在 app.module.ts 裡 import 剛剛寫的 shared-module.ts 就好:
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { MaterialModule } from './material.module';
@NgModule({
imports: [
FormsModule,
ReactiveFormsModule,
MaterialModule
],
exports: [
FormsModule,
ReactiveFormsModule,
MaterialModule
],
providers: []
})
export class SharedModule { }
目前為止,專案的架構如下:
├── src
│ ├── app
│ │ ├── app-routing.module.ts
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── i18n-form
│ │ │ ├── i18n-form.component.css
│ │ │ ├── i18n-form.component.html
│ │ │ ├── i18n-form.component.spec.ts
│ │ │ └── i18n-form.component.ts
│ │ ├── nav-bar
│ │ │ ├── nav-bar.component.css
│ │ │ ├── nav-bar.component.html
│ │ │ ├── nav-bar.component.spec.ts
│ │ │ └── nav-bar.component.ts
│ │ └── shared
│ │ ├── material.module.ts
│ │ └── shared.module.ts
4. 切版以及指定要使用 Angular i18n 的 html 標籤
在這個小專案我們會有 3 個 component:
1. nav-bar: 包含一個 select 用來選擇語言
2. i18n-form: 模擬使用到多國語言的 form
3. app: 用來放以上 2 個 component
4.1 nav-bar.component
nav-bar component 主要的功能是使用 select 標籤來切換各種語言,因此在切換標籤的時候會需要用到瀏覽器提供的 api ,來利用 url 中的字串判斷目前的語言,以及切換語言後要把 url 中的語言替換成目標語言,再 redirect 到替換後的 url。
<!-- src/app/nav-bar/nav-bar.component.html -->
<mat-toolbar color="primary" class="nav-bar mat-elevation-z2">
<ng-container>
<mat-select
(selectionChange)="onSelectionChange($event.value)"
[value]="displayLanguage"
class="selectContainer"
>
<mat-select-trigger>
<div class="select-trigger-container">
<mat-icon>language</mat-icon>
<div>{{ i18nLang }}</div>
</div>
</mat-select-trigger>
<mat-option *ngFor="let language of languageList" [value]="language.code">{{ language.name }}</mat-option>
</mat-select>
</ng-container>
</mat-toolbar>
/* src/app/nav-bar/nav-bar.component.css */
.nav-bar {
position: fixed;
top: 0;
z-index: 99;
}
.selectContainer {
width: 50px;
height: 100%;
}
// src/app/nav-bar/nav-bar.component.ts
import { Component, OnInit } from '@angular/core';
interface LanguageMapping {
[key: string]: string
}
@Component({
selector: 'app-nav-bar',
templateUrl: './nav-bar.component.html',
styleUrls: ['./nav-bar.component.css']
})
export class NavBarComponent implements OnInit {
displayLanguage: string = 'en';
languageList = [
{ code: 'en', name: 'English' },
{ code: 'tw', name: 'Traditional-Chinese' },
{ code: 'jp', name: 'Japan' }
];
get i18nLang() {
const mapping: LanguageMapping = {
en: 'US',
tw: 'ZH',
jp: 'JP'
}
return mapping[this.displayLanguage];
}
constructor() { }
ngOnInit() {
this.displayLanguage = this.getCurrentLanguage();
}
onSelectionChange($event: string) {
this.redirectTo($event);
}
private redirectTo(redirectLang: string) {
const redirectPathName = window.location.pathname.replace(`/${this.displayLanguage}/`, `/${redirectLang}/`);
window.location.pathname = redirectPathName;
}
private getCurrentLanguage = () => {
const lang = ['en', 'tw', 'jp'];
const currentLang = lang.find(l => new RegExp(`/${l}/`).test(window.location.pathname));
if (!currentLang) {
return 'en';
}
return currentLang;
};
}
4.2 i18n-form.component
i18n-form component 就是個一般的 component,然而要使用 angular i18n 的功能則必須在 html 的標籤加上 i18n attribute,加上 i18n attribute 的 html element 會被等等下的指令(後面會提到)給轉換為語言檔。
<!-- src/app/i18n-form/i18n-form.component.html -->
<form [formGroup]="i18nForm" (ngSubmit)="onSubmit()">
<mat-form-field appearance="outline">
<mat-label i18n>Email</mat-label>
<input
matInput
type="email"
placeholder="Email"
formControlName="email"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label i18n>Password</mat-label>
<input
matInput
type="password"
placeholder="密碼"
formControlName="password"
/>
</mat-form-field>
<div class="buttonContainer">
<button
mat-raised-button
type="reset"
(click)="onClear()"
i18n
>
Reset
</button>
<button
mat-raised-button
color="primary"
type="submit"
[disabled]="!i18nForm.valid"
i18n
>
Sign Up
</button>
</div>
</form>
/* src/app/i18n-form/i18n-form.component.css */
form {
display: flex;
flex-direction: column;
padding: 2em;
margin: 5rem auto;
width: 20%;
}
.buttonContainer {
display: flex;
justify-content: space-between;
}
// src/app/i18n-form/i18n-form.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators, AbstractControl, ValidatorFn } from '@angular/forms';
@Component({
selector: 'app-i18n-form',
templateUrl: './i18n-form.component.html',
styleUrls: ['./i18n-form.component.css']
})
export class I18nFormComponent implements OnInit {
i18nForm = this.fb.group(
{
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(4)]],
}
);
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
}
onClear(): void {
this.i18nForm.reset();
}
onSubmit(): void {
console.log(this.i18nForm.getRawValue());
}
}
4.3 app.component
<!-- src/app/app.component.html -->
<app-nav-bar></app-nav-bar>
<app-i18n-form></app-i18n-form>
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'i18n-tutorial';
}
5. 產生語言檔
5.1 新增翻譯原始檔
使用 Angular cli 來產生語言檔:
# --out-path 用來指定編譯完的檔案要放在哪裡
ng extract-i18n --output-path src/locale
下完這指令後 Angular 會自動幫你把有套用到 i18n attribute 的 html element 給抓出來,並產生翻譯的原始檔如下:
<!-- src/locale/messages.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en-US" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="4768749765465246664" datatype="html">
<source>Email</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
</trans-unit>
<trans-unit id="1431416938026210429" datatype="html">
<source>Password</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="5478121364779850827" datatype="html">
<source> Reset </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">27,28</context>
</context-group>
</trans-unit>
<trans-unit id="744750008248382275" datatype="html">
<source> Sign Up </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">36,37</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
5.2 新增英文檔
接著複製上面那份檔案,並修正副檔名,因為原始語言是英文,所以不用做任何更動,修正副檔名即可:
<!-- src/locale/messages.en.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en-US" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="4768749765465246664" datatype="html">
<source>Email</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
</trans-unit>
<trans-unit id="1431416938026210429" datatype="html">
<source>Password</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="5478121364779850827" datatype="html">
<source> Reset </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">27,28</context>
</context-group>
</trans-unit>
<trans-unit id="744750008248382275" datatype="html">
<source> Sign Up </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">36,37</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
5.3 新增中文翻譯檔
複製翻譯原始檔,並將副檔名改為 zh.hant.xlf ,再將要翻譯後的文字用 target 標籤包起來:
<!-- src/locale/messages.zh.hant.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en-US" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="4768749765465246664" datatype="html">
<source>Email</source>
<target>電子郵件</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
</trans-unit>
<trans-unit id="1431416938026210429" datatype="html">
<source>Password</source>
<target>密碼</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="5478121364779850827" datatype="html">
<source> Reset </source>
<target> 重設 </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">27,28</context>
</context-group>
</trans-unit>
<trans-unit id="744750008248382275" datatype="html">
<source> Sign Up </source>
<target> 註冊 </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">36,37</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
5.4 新增日文翻譯檔
同中文翻譯檔做法,只是 target 標籤裡變成翻譯過後的日文:
<!-- src/locale/messages.jp.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en-US" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="4768749765465246664" datatype="html">
<source>Email</source>
<target>Eメール</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
</trans-unit>
<trans-unit id="1431416938026210429" datatype="html">
<source>Password</source>
<target>パスワード</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="5478121364779850827" datatype="html">
<source> Reset </source>
<target> リセット </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">27,28</context>
</context-group>
</trans-unit>
<trans-unit id="744750008248382275" datatype="html">
<source> Sign Up </source>
<target> 登録する </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">36,37</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
5.5 翻譯檔架構
目前的翻譯檔架構如下:
src/locale
├── messages.en.xlf
├── messages.jp.xlf
├── messages.xlf
└── messages.zh.hant.xlf
6. 修改 angular.json
接著要修改 angular.json 讓 angular 知道以 development 或是在 build 專案時該用什麼語言檔
6.1 修改專案的 i18n 設定
在這個步驟可以指定要使用的翻譯檔以及該語言要使用什麼 url:
// angular.json
{
...
"projects": {
"i18n-tutorial": {
"i18n": {
"locales": {
"en": {
"translation": "src/locale/messages.en.xlf",
"baseHref": "/en/"
},
"tw": {
"translation": "src/locale/messages.zh.Hant.xlf",
"baseHref": "/tw/"
},
"jp": {
"translation": "src/locale/messages.jp.xlf",
"baseHref": "/jp/"
}
}
},
...
},
},
}
6.2 修改 build 的設定
// angular.json
"build": {
...
"configurations": {
"en": {
"localize": ["en"]
},
"tw": {
"localize": ["tw"]
},
"jp": {
"localize": ["jp"]
}
}
},
6.3 修改 serve 的設定
// angular.json
"serve": {
...
"configurations": {
"production": {
"browserTarget": "i18n-tutorial:build:production"
},
"en": {
"browserTarget": "i18n-tutorial:build:en"
},
"tw": {
"browserTarget": "i18n-tutorial:build:tw"
},
"jp": {
"browserTarget": "i18n-tutorial:build:jp"
}
}
}
7. 用 development mode 驗證 i18n 功能
7.1 使用預設的 development
ng serve
這時跑起來的畫面就是我們最原始的 html 檔,也就是英文的版本:
7.2 使用 configuration=tw
ng serve --configuration=tw
跑起來後會發現 Angular 把翻譯檔套用到 html 裡面了,現在有使用 i18n 的標籤都會被 Angular 翻譯:
8. build app
最後把 app build 起來,來驗證 Angular i18n 的功能吧!
8.1 build –localize
ng build --localize
8.2 安裝 http-server
我們需要安裝 http-server 來跑 build 後的檔案
npm install -g http-server
8.3 用 http-server 來跑 build 後的檔案
需要先進到 build 後的資料夾裡,接著直接執行 http-server ,就大功告成了:
cd dist/i18n-tutorial
http-server
可以看到使用 select 標籤切換語言時,url 會跟著變化, form 裡面的語言也會跟著翻譯,成功完成了一個 Angular i18n 的簡單 app!
9. Angular i18n 進階用法
9.1 使用 i18n id
我們可以觀察一下剛剛加了 i18n attribute 的 html element 在被 Angular 編譯為翻譯檔時會有 id:
這個時候的 id 是由 Angular 產生的一串數字,然而很多時候當我們的專案成長到一定的程度時,會有許多地方會用到相同的 i18n 功能,例如:很多個 component 可能都有 email 的欄位,要是我每次在 email 加一個 i18n 的 attribute,那麼在每個用到 email i18n 的地方都會產生獨立的 id。
i18n 提供了指定 id 的功能,只要加上 @@ ,就可以告訴 Angular :我的這個 html element 要用的是 id = … 的 i18n 。
舉例來說,我在剛剛的 i18n-form.component 下增加了一個 input 和 button element ,模擬其他 component 用到 email i18n 的狀況,這時候只要加上 i18n=”@@Email” ,Angular 就知道這三個 element 用的都是 id = Email 的 i18n:
<!-- src/app/i18n-form/i18n-form.component.html -->
<form [formGroup]="i18nForm" (ngSubmit)="onSubmit()">
<mat-form-field appearance="outline">
<mat-label i18n="@@Email">Email</mat-label>
<input
matInput
type="email"
placeholder="Email"
formControlName="email"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label i18n>Password</mat-label>
<input
matInput
type="password"
placeholder="密碼"
formControlName="password"
/>
</mat-form-field>
<div class="buttonContainer">
<button
mat-raised-button
type="reset"
(click)="onClear()"
i18n
>
Reset
</button>
<button
mat-raised-button
color="primary"
type="submit"
[disabled]="!i18nForm.valid"
i18n
>
Sign Up
</button>
</div>
<mat-form-field appearance="outline" style="margin-top: 2em;">
<mat-label i18n="@@Email">Email</mat-label>
<input
matInput
type="email"
placeholder="Email"
/>
</mat-form-field>
<div>
<button
mat-raised-button
color="primary"
i18n="@@Email"
>
Email
</button>
</div>
</form>
編譯過後的翻譯原始檔如下:
<!-- src/locale/messages.xlf -->
...
<trans-unit id="Email" datatype="html">
<source>Email</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">54,55</context>
</context-group>
</trans-unit>
...
如此一來我在修正其他翻譯檔時就只要改一個地方就好。
9.2 在 TypeScript 中使用 i18n
很多時候我們的資料並不是寫死在 html 檔裡,而是寫在 TypeScript 檔裡再讓 html 去讀取 TypeScript 裡的 data,我們也可以直接在 TypeScript 中使用 Angular i18n。
舉例來說:我在剛剛的 i18n-form.component 中增加了一個 select 欄位,裡面的資料是由 TypeScript 產生:
<!-- src/app/i18n-form/i18n-form.component.html -->
<form [formGroup]="i18nForm" (ngSubmit)="onSubmit()">
<mat-form-field appearance="outline">
<mat-label i18n="@@Email">Email</mat-label>
<input
matInput
type="email"
placeholder="Email"
formControlName="email"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label i18n>Password</mat-label>
<input
matInput
type="password"
placeholder="密碼"
formControlName="password"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-select formControlName="address">
<mat-option
*ngFor="let city of cityList"
[value]="city.id"
>{{ city.name }}</mat-option>
</mat-select>
</mat-form-field>
<div class="buttonContainer">
<button
mat-raised-button
type="reset"
(click)="onClear()"
i18n
>
Reset
</button>
<button
mat-raised-button
color="primary"
type="submit"
[disabled]="!i18nForm.valid"
i18n
>
Sign Up
</button>
</div>
<mat-form-field appearance="outline" style="margin-top: 2em;">
<mat-label i18n="@@Email">Email</mat-label>
<input
matInput
type="email"
placeholder="Email"
/>
</mat-form-field>
<div>
<button
mat-raised-button
color="primary"
i18n="@@Email"
>
Email
</button>
</div>
</form>
這時候只要在 TypeScript 裡加上 $localize`:@@[i18n id]:[預設HTML顯示的字樣]`就有如同 i18n 的效果:
// src/app/i18n-form/i18n-form.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators, AbstractControl, ValidatorFn } from '@angular/forms';
const cityList = [
{
id: 1,
name: $localize`:@@TaipeiCity:Taipei City`
},
{
id: 2,
name: $localize`:@@NewTaipeiCity:New Taipei City`
},
{
id: 3,
name: $localize`:@@KeelungCity:Keelung City`
}
]
@Component({
selector: 'app-i18n-form',
templateUrl: './i18n-form.component.html',
styleUrls: ['./i18n-form.component.css']
})
export class I18nFormComponent implements OnInit {
cityList = cityList;
i18nForm = this.fb.group(
{
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(4)]],
address: [1, [Validators.required]]
}
);
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
}
onClear(): void {
this.i18nForm.reset();
}
onSubmit(): void {
console.log(this.i18nForm.getRawValue());
}
}
編譯過後的翻譯原始檔:
...
<trans-unit id="TaipeiCity" datatype="html">
<source>Taipei City</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.ts</context>
<context context-type="linenumber">8</context>
</context-group>
</trans-unit>
<trans-unit id="NewTaipeiCity" datatype="html">
<source>New Taipei City</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.ts</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="KeelungCity" datatype="html">
<source>Keelung City</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.ts</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
...
最後面直接多了剛剛的 select list。
接著只要跟剛剛一樣為每個翻譯檔加上 target 標籤,標籤內容為翻譯過後的語言, i18n 就能發揮作用囉!
<!-- src/locale/messages.zh.hant.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en-US" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="Email" datatype="html">
<source>Email</source>
<target>電子郵件</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">49</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">62,63</context>
</context-group>
</trans-unit>
<trans-unit id="1431416938026210429" datatype="html">
<source>Password</source>
<target>密碼</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="5478121364779850827" datatype="html">
<source> Reset </source>
<target> 重設 </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">27,28</context>
</context-group>
</trans-unit>
<trans-unit id="744750008248382275" datatype="html">
<source> Sign Up </source>
<target> 註冊 </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.html</context>
<context context-type="linenumber">36,37</context>
</context-group>
</trans-unit>
<trans-unit id="TaipeiCity" datatype="html">
<source>Taipei City</source>
<target> 台北市 </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.ts</context>
<context context-type="linenumber">8</context>
</context-group>
</trans-unit>
<trans-unit id="NewTaipeiCity" datatype="html">
<source>New Taipei City</source>
<target> 新北市 </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.ts</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="KeelungCity" datatype="html">
<source>Keelung City</source>
<target> 基隆市 </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/i18n-form/i18n-form.component.ts</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
用 development 驗證 i18n 是否作用:
ng serve --configuration=tw
畫面如下:
10. 參考資料
Localizing your app – Angular
Angular 9 Tutorial on Internationalization (i18n)
Angular Material UI component library