[Angular] Day29. Internationalization (i18n)

Angular 提供了 i18n 功能让我们开发专案时可以让我们的专案应在不同的国家中被使用,Localization 是为不同语言环境构建应用程序版本的过程,包过提取文本已翻译成不同语言以及为特定语言环境设置数据格式。

使用 Angular i18n 为应用程序进行国际化:

  • 使用 pipes 显示本地化的日期、数字、百分比和货币
  • 在 template 中标记文本以进行翻译
  • 标复数形式的表达方式以提供翻译
  • 标记替代文本进行翻译

要使用 Angular i18n 的前提下需要准备好 Angular CLI,因为几乎全部的任务都需要使用 Angular CLI

  • 使用 Angular CLI 将标记的文本提取到 source language file
  • 为每种语言制作此文本的副本,将这些翻译文件发送给相关的翻译人员,对文本内的语言进行翻译
  • 在为一种会多种语言环境构建应用程序时,使用 Angular CLI 合并完成的翻译文件

https://ithelp.ithome.com.tw/upload/images/20210828/20124767LCHVMC00vy.jpg


Add the localize package

要使用 Angular CLI 的功能需要先使用 Angular CLI 将 @angular/localize 添加到专案中

ng add @angular/localize

使用这个 CLI Command 後会在 package.jsonpolyfill.ts 中导入 @angular/localize,要注意如果没有加入这个功能的话,使用 i18n 功能将会失败。


Refer to locales by ID

Angular i18n 使用 Unicode 语言环境标示符 (ID) 引用语言环境,他可以用於指定 语言国家/地区,这个 ID 由语言标示符所组成,例如 en 代表英语或 fr 代表法语,後面可以加上一个 破折号 ( - )区域扩展名,例如 US 表示美国 CA 表示加拿大,所以可以变成 en-US 他代表在美国的英语,fr-CA 代表在加拿大的法语以此类推, Angular 会依照这个 ID 来查找正确的对应区域设置数据。

在默认情况下 Angular 是使用 en-US 作为应用程序的初始语言环境,可以在 angular.json 中的 sourceLocale 中更改原始语言环境。


Prepare templates for translations

讲了这麽多,接着直接来看看如何在 Angular 终使用 i18n 吧,要翻译应用程序的 template 需要通过使用 Angular i18n attribute 和其他 attribute 来为翻译器准备文本,通常会使用以下的步骤:

  1. 标记要翻译的文本
  2. 添加有用的描述和含义,这用於帮助翻译人员知道这个要翻译的文本是什麽东西
  3. 翻译不用於显示的文本
  4. 标记翻译的元素 attribute
  5. 标记复数和代替翻译,以符合不同语言的复数规则和语法结构

Mark text for translations

使用 i18n 这个 attribute 标记要翻译的 template 的内容,将它放在需要翻译的元素标签上并带有要翻译的固定文本,举个例子

<h1>Hello i18n!</h1>

如果要将上面这个 <h1> 元素进行翻译的话,将 i18n attribute 添加到上面

<h1 i18n>Hello i18n!</h1>

Add helpful descriptions and meanings

当翻译人员与程序开发人员是不同人时,程序开发人员需要将这个要翻译的内容做一些解释好让翻译人员知道这个是什麽,才能进行准确的翻译,所以需要在 i18n attribute 中添加额外的说明用於描述这个翻译是什麽

<h1 i18n="An introduction header for this sample">Hello i18n!</h1>

除了加上这个 i18n attribute 的意图之外,还需要添加这个对於这个元素的描述,这样可大幅提高翻译的精准度,使用 | 将意图与描述分开 <maining> | <description>

<h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1>

Translate text not for display

如果需要翻译一个还没有显示出来的元素时,请将文本加入到 <ng-container>

<ng-container i18n>I don't output any element</ng-container>

Mark element attributes for translations

如果要翻译 HTML 的 attribute,例如 <img> 中的 title attribute 应该要将 i18n 使用 - 符号连接要翻译的 attribute

<img [src]="logo" title="Angular logo">

若要翻译 HTML 元素的 attribute 请添加 i18n-attribute 其中的 attribute 是需要翻译的 attribute 名称

<img [src]="logo" i18n-title title="Angular logo" />

也可以使用 i18n-attribute="<meaning> | <description>@@<id>" 语法分配含义、描述以及自定义的 ID


Mark plurals and alternates for translation

不同语言有不同的复数规则和语法结构,这让翻译的难度大大增加,所以为了简化翻译请使用带有正规表达式UnicodeICU 子句。

Mark plurals

使用 plural 来标记如果逐字翻译可能没有意义表达,例如如果想用英文显示 updated x minutes ago 可能希望显示 jest nowone minute agox minutes ago,可以使用下面的例子

<span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
  • 第一个参数 minutes 绑定了 component 的 property,她决定了显示的分钟数
  • 第二个参数将这个标记为 plural 翻译类型
  • 第三个参数定义了复数类别及其匹配值的模式
    • 当 property minutes 为 0 时会使用 =0 { just now }
    • 当 property minutes 为 1 时会使用 =1 { one minute ago }
    • 如果不匹配前面两个则会显示 property 的值

Mark alternates and nested expressions

如果要根据变量的值来决定替代的文本时则需要翻译所又替代文本,select 子句类似 plural 子句,根据定义的字串值标记替代文本的选择,举例来说 template 绑定了 component 的 property,透过 property 的值决定要翻译 malefemaleother

<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span> 

也可以将两种不的子句嵌套再一起

<span i18n>Updated: {minutes, plural,
  =0 {just now}
  =1 {one minute ago}
  other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}}
</span>

Learn by example

上面介绍了一堆 i18n 的概念与用法後,接这直接来建立一个例子吧

Create component

ng generate component form

Complete the form component

  1. 在 form.component.ts 中新增 Form Model

    import { Component } from '@angular/core';
    import { FormBuilder, Validators } from '@angular/forms';
    
    @Component({
      selector: 'app-form',
      templateUrl: './form.component.html',
      styleUrls: ['./form.component.css']
    })
    export class FormComponent {
      form = this.fb.group({
        username: ['', Validators.required],
        password: ['', Validators.required],
      })
      constructor(private fb: FormBuilder) { }
    }
    
  2. 在 form.component.html 中绑定 FormControl 并在要翻译的元素上加上 i18n

    <form [formGroup]="form">
        <div class="content">
            <label for="username" i18n>Username</label>
            <input type="text" id="name" class="form-control" formControlName="username" />
            <label for="password" i18n>Password</label>
            <input type="text" id="name" class="form-control" formControlName="password" />
        </div>
        <div class="optionBtn">
            <button type="button" class="btn btn-success" i18n>Login</button>
        </div>
    </form>
    

https://ithelp.ithome.com.tw/upload/images/20210828/20124767IvMMcvJsHE.png

Generate language files

准备好要翻译的 template 後请使用 Angular CLI extract-i18n command 将 tempalte 中标记的文本提取到 source language file

ng extract-i18n

extract-i18n 使用 XML 本地化交换文件格式,在项目的根目录中创建一个名为 messages.xlf 的 source language file,可以使用一些 option 选项

  • -output-path: 更改 soruce language file 位置
  • —format:更改 soruce language file 格式
  • —outFile:更改 soruce language file 名称

可以使用 --format 将 soruce language file 的格式更改为以下几种格式

ng extract-i18n  --format=xlf
ng extract-i18n  --format=xlf2
ng extract-i18n  --format=xmb
ng extract-i18n  --format=json
ng extract-i18n  --format=arb

Translate various languages

产生了 source language file 後接着来将他们翻译为各国的语言,由於 Angular 预设语言就是英文,所以不需要对英文进行翻译

Translate chinese

将 source language file 复制一份後更改他的档名,更改为 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="5248717555542428023" datatype="html">
        <source>Username</source>
        <target>使用者名称</target>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/form/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/form/form.component.html</context>
          <context context-type="linenumber">5</context>
        </context-group>
      </trans-unit>
      <trans-unit id="2454050363478003966" datatype="html">
        <source>Login</source>
        <target>登陆</target>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/form/form.component.html</context>
          <context context-type="linenumber">9</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>

在要翻译的名称 <source> 下方加上 <target> 并把要翻译的内容填入

Translate Japanese

<?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="5248717555542428023" datatype="html">
        <source>Username</source>
        <target>ユーザー名</target>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/form/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/form/form.component.html</context>
          <context context-type="linenumber">5</context>
        </context-group>
      </trans-unit>
      <trans-unit id="2454050363478003966" datatype="html">
        <source>Login</source>
        <target>ログイン</target>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/form/form.component.html</context>
          <context context-type="linenumber">9</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>

Merge translations into the app

将需要翻译的文本设定好後,接着更改 angular.json 的内容让 Angular 知道该使用哪一个语言

{
	...
	"projects": {
		"project-name": {
		"i18n": {
        "locales": {
          "tw": {
            "translation": "messages.zh.hant.xlf",
            "baseHref": "/tw/"
          },
          "jp": {
            "translation": "messages.jp.xlf",
            "baseHref": "/jp/"
          }
        }
      }
		}
	}
}

修改 build 的设定

"architect": {
	"build": {
	   ...
	   "options": {
       "localize": true,
       "aot": true,
			...
		},
		"configurations": {
		  "tw": {
            "localize": ["tw"]
          },
          "jp": {
            "localize": ["jp"]
          },
	    }
	}
}

修改 serve 的设定

"serve": {
	"builder": "@angular-devkit/build-angular:dev-server",
  "configurations": {
		"production": {
       "browserTarget": "Angular-blog:build:production"
     },
     "tw": {
       "browserTarget": "Angular-blog:build:tw"
     },
     "jp": {
       "browserTarget": "Angular-blog:build:jp"
            },
	   "development": {
       "browserTarget": "Angular-blog:build:development"
     }	
	}
}

Test i18n

使用 Angular CLI 将专案跑起来,来看看是否完成翻译

ng serve --configuration=tw --open

https://ithelp.ithome.com.tw/upload/images/20210828/20124767LkrRPBsZbj.png

ng serve --configuration=jp --open

https://ithelp.ithome.com.tw/upload/images/20210828/20124767V3j0Qi41tg.png

当完成翻译後可以在表单上面新增一个 select 用於选择要显示什麽语言,可以做到这种效果

img


结论

本章中介绍了如何使用 Angular 的 i18n 功能做出国际化的应用程序,先在 template 中将要翻译的内容加上 i18n attribute,之後使用 Angular CLI Command ng extract-i18n 产生 source langange file,获得 source langange file 之後就可以对不同的语言进行翻译,在 <soruce> 的下方加上 <target> 并在其中填入翻译的内容,最後在 angular.json 中进行 i18n 的设定就完成了,是不是很简单呢。

下一篇会介绍 Angular 中一个重要的观念那就是 Module,在前面很多篇中都会看到要引入某某 module 到 app.module.ts 中,这个 Module 可以看成是将各个功能进行模组化分割,这样比较方便管理与测试,详细的内容就留到明天讲解吧,明天见


Reference


<<:  [面试][後端]请简述 Node.js 的 Event Loop

>>:  D-1, length、size & count

Day 28:合并排序法 Merge Sort

Merge Sort采用Divide and Conquer的方式,其实他的概念本身就是递回(rec...

[Day05] Github page

Github page 的特色 劈头就上官方文件和 Landing page。 Github pag...

Day12 - 如何查询委托单状态

中秋过,老一辈常讲甚麽中秋变盘,认不认同看个人。但中秋之後的几天大涨大跌的,是不是好刺激? 想作多或...

[Day30] -- 完赛

这次的铁人赛进入了最後一天,感谢夥伴们彼此的扶持,也感谢没有放弃的自己。这次的DRF系列文章希望能帮...

入门魔法 - 常用阵列方法(一) forEach、filter、 map

前情提要 艾草:「你过来帮我摆一下魔法阵!」 「哇喔,看起来好厉害唷!这里有三个空位是要摆什麽吗?」...