[[Angular2]] >
* Angularで国際化対応 [#r60f28e9]
#setlinebreak(on)

#contents

** 国際化の方法 [#fa3aea91]
#html(<div style="padding-left:10px">)
やり方はいくつかあって、大きく以下の2つに分けられる
- Angular標準の国際化対応を利用する
- ngx-translate 等の他のライブラリを利用する
#html(</div>)

** 2つの方法を比較した所感 [#pa06bc39]
#html(<div style="padding-left:10px">)

ngx-translate の方が全然使いやすい。

*** Angular標準 [#p254fdd4]
#html(<div style="padding-left:10px">)
Angular標準の方は、翻訳対象箇所が増える度に、翻訳用のファイルを生成し直す必要がある為、メンテナンス性は良くない。
また、翻訳用のファイルを生成し直した時に、既存の翻訳ファイルの内容を手動でマージする必要があり、かなり手間。
動的な言語切替も出来ない為、それぞれの言語用にビルドを行い、リソース自体を切り替える手法を取るしかなさそう。
#html(</div>)

*** ngx-translate [#x934a7cd]
#html(<div style="padding-left:10px">)
単純な翻訳ならテンプレートの記述がシンプル。(タグに "translate" 属性を付けるだけ)
翻訳辞書もjson記述で、ネストにも対応している為、ページやカテゴリ毎に分類して管理もできそう。
また、動的な言語切替にも対応しており、切り替え方法もシンプル。( TranslateServiceの useメソッドを呼ぶだけ )
無理やりデメリットを挙げるとしたら、言語ファイル(json)の切り替えを行う際に、HTTP通信が発生する事ぐらいだが、
通信が発生するのは最初の1回のみなので、全く気にならない。(サイズが大きくなる場合の確認は必要かもしれないが。)
#html(</div>)


#html(</div>)


** 準備 [#s11454c3]
#html(<div style="padding-left:10px">)

#myterm2(){{
ng new my-app && cd my-app
ng generate component header
ng generate component top
ng generate component page1
ng generate component page2
}}


src/app/app.module.ts
#mycode2(){{
.
+ import { RouterModule } from '@angular/router';
.
.
@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent,
    TopComponent,
    Page1Component,
    Page2Component
  ],  
  imports: [
    BrowserModule,
+   RouterModule.forRoot([
+    { path: 'top', component: TopComponent },
+    { path: 'page1', component: Page1Component },
+    { path: 'page2', component: Page2Component }
+   ], { useHash: true })
  ],  
  providers: [], 
  bootstrap: [AppComponent]
})
export class AppModule { } 
}}

src/app/app.component.html
#myhtml2(){{
<!-- headerコンポーネント -->
<app-header></app-header>

<!-- ルーターがコンポーネントを表示する場所 -->
<div id="content">
  <router-outlet></router-outlet>
</div>
}}

src/app/header/header.component.html
#myhtml2(){{
<a routerLink="/top" routerLinkActive="active">Top</a>
<a routerLink="/page1" routerLinkActive="active">page1</a>
<a routerLink="/page2" routerLinkActive="active">page2</a>
}}

#html(</div>)


** Angular標準の国際化対応を利用する場合 [#kf9b804f]
#html(<div style="padding-left:10px">)

Internationalization (i18n) として、公式ドキュメント(※)に記載されている。
※https://angular.io/guide/i18n

*** テンプレートの翻訳対象箇所を修正する [#fbaa9c16]
#html(<div style="padding-left:10px">)

翻訳対象のタグに &color(red){ i18n="@@xxx" }; を追加する

src/app/header/header.component.html
#myhtml2(){{
<a routerLink="/top" routerLinkActive="active" i18n="@@top">Top</a>
<a routerLink="/page1" routerLinkActive="active" i18n="@@page1">page1</a>
<a routerLink="/page2" routerLinkActive="active" i18n="@@page2">page2</a>
}}
#html(</div>)

*** 翻訳ソースファイルを生成する [#da6dedc7]
#html(<div style="padding-left:10px">)
#myterm2(){{
ng xi18n --outputPath src/locale
ls -1 src/locale/
messages.xlf
}}
#html(</div>)

*** 翻訳ソースファイルをコピーして日本語用のファイルを作成 [#x1ef28c0]
#html(<div style="padding-left:10px">)
#myterm2(){{
cp src/locale/messages.xlf src/locale/messages.ja.xlf
}}
#html(</div>)

*** 翻訳内容を記載する [#j70e93f1]
#html(<div style="padding-left:10px">)

src/locale/messages.ja.xlf
#myhtml2(){{
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="top" datatype="html">
        <source>Top</source>
+       <target>トップ</target>
        <context-group purpose="location">
          <context context-type="sourcefile">app/header/header.component.ts</context>
          <context context-type="linenumber">1</context>
        </context-group>
      </trans-unit>
      <trans-unit id="page1" datatype="html">
        <source>page1</source>
+       <target>ページ1</target>
        <context-group purpose="location">
          <context context-type="sourcefile">app/header/header.component.ts</context>
          <context context-type="linenumber">2</context>
        </context-group>
      </trans-unit>
      <trans-unit id="page2" datatype="html">
        <source>page2</source>
+       <target>ページ2</target>
        <context-group purpose="location">
          <context context-type="sourcefile">app/header/header.component.ts</context>
          <context context-type="linenumber">3</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>
}}
#html(</div>)

*** ローカルサーバ起動 [#l0a9e828]
#html(<div style="padding-left:10px">)

#myterm2(){{
ng serve --aot --i18nFile=src/locale/messages.ja.xlf --i18nFormat=xlf --locale=ja
}}
#html(</div>)


*** ビルド [#y088233a]
#html(<div style="padding-left:10px">)
#myterm2(){{
ng build --aot --i18nFile=src/locale/messages.ja.xlf --i18nFormat=xlf --locale=ja --output-path=/path_to_www/angular2-ja --bh /angular2-ja/
}}
#html(</div>)


***動的に切り替える場合 [#efc6a2d7]
#html(<div style="padding-left:10px">)

動的に言語を切り替える方法で見つかったのは、それぞれの言語用にビルドして別のディレクトリに配意するやり方だけだった。
https://medium.com/@feloy/deploying-an-i18n-angular-app-with-angular-cli-fc788f17e358

#myterm2(){{
ng build --aot --i18nFile=src/locale/messages.en.xlf --i18nFormat=xlf --locale=en --output-path=/path_to_www/angular2-en --bh /angular2-en/
ng build --aot --i18nFile=src/locale/messages.ja.xlf --i18nFormat=xlf --locale=ja --output-path=/path_to_www/angular2-ja --bh /angular2-ja/
ng build --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr --output-path=/path_to_www/angular2-fr --bh /angular2-ja/
}}



#html(</div>)


#html(</div>)

&br;

** ngx-translateを利用する場合 [#p3bc7288]
#html(<div style="padding-left:10px">)

https://github.com/ngx-translate/core

*** インストール [#w5d07c03]
#html(<div style="padding-left:10px">)
#myterm2(){{
npm install @ngx-translate/core --save
npm install @ngx-translate/http-loader --save
}}
#html(</div>)

*** ローダーを使用して翻訳できるようにソース変更 [#h53b0476]
#html(<div style="padding-left:10px">)

src/app/app.module.ts
#mycode2(){{
 .
 .
+ import {HttpClientModule, HttpClient} from '@angular/common/http';
+ import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
+ import {TranslateHttpLoader} from '@ngx-translate/http-loader';
 .
 .

+ export function createTranslateLoader(http: HttpClient) {
+     return new TranslateHttpLoader(http, './assets/i18n/', '.json');
+ }

@NgModule({
    .
    .
  imports: [
      .
      .
+    HttpClientModule,
+    TranslateModule.forRoot({
+        loader: {
+            provide: TranslateLoader,
+            useFactory: (createTranslateLoader),
+            deps: [HttpClient]
+        }
+    }), 
      .
      .
}}

src/environments/environment.ts
#mycode2(){{
export const environment = { 
  production: false,
+  defaultLang: 'en'
};
}}

src/app/app.component.ts
#mycode2(){{
+import {TranslateService} from '@ngx-translate/core';
+import { environment } from '../environments/environment';

export class AppComponent {
  title = 'app';
+  constructor(private translate: TranslateService) {
+    translate.setDefaultLang(environment.defaultLang);
+    translate.use(environment.defaultLang);
+  }
}
}}
#html(</div>)

*** 辞書ファイルの作成 [#n3c6ce23]
#html(<div style="padding-left:10px">)

#myterm2(){{
mkdir -p src/assets/i18n
}}

src/assets/i18n/en.json
#mycode2(){{
{
    "top":"top",
    "page1":"page1",
    "page2":"page2"
}
}}

src/assets/i18n/ja.json
#mycode2(){{
{
    "top":"トップ",
    "page1":"ページ1",
    "page2":"ページ2"
}
}}

#html(</div>)

*** HTMLテンプレートを修正 [#xf108032]
#html(<div style="padding-left:10px">)

タグの内容をそのまま翻訳する場合は translate 属性をつけるだけでOK。
spanなどのインライン要素をうまく利用すれば、殆どこれだけでいけそう。

src/app/header/header.component.html
#myhtml2(){{
<a routerLink="/top" routerLinkActive="active" translate>top</a>
<a routerLink="/page1" routerLinkActive="active" translate>page1</a>
<a routerLink="/page2" routerLinkActive="active" translate>page2</a>
}}
※その他のケースは https://github.com/ngx-translate/core を参照。


#html(</div>)

*** サーバ起動 [#te806a9b]
#html(<div style="padding-left:10px">)

普通にサーバを起動するだけ
#myterm2(){{
ng serve
}}
#html(</div>)


*** 動的に切り替える場合 [#u596cd56]
#html(<div style="padding-left:10px">)

TranslateService.use で普通に切り替えられる
ここでは、ヘッダに言語切替用のプルダウンを用意して動的に切り替えられるようにする。

src/environments/environment.ts
#mycode2(){{
export const environment = { 
  production: false,
  defaultLang: 'en',
+ languages: [ {'lang': 'en', 'label': 'English'}, {'lang': 'ja', 'label': 'Japanese'} ]
};
}}

src/app/header/header.component.html[
#myhtml2(){{
<select (change)="changeLang($event.target.value)">
<option *ngFor="let lang of languages; let i = index;" [value]="lang.lang" [selected]="isCurrentLang(lang.lang)">
<span translate>&#123;&#123;lang.label&#125;&#125;</span>
</option>
</select>
}}

src/assets/i18n/en.json
#mycode2(){{
{
    "English": "English",
    "Japanese": "Japanese",
    "top":"Top",
    "page1":"Page1",
    "page2":"Page2"
}
}}

src/assets/i18n/ja.json
#mycode2(){{
{
    "English": "英語",
    "Japanese": "日本語",
    "top":"トップ",
    "page1":"ページ1",
    "page2":"ページ2"
}
}}

src/app/header/header.component.ts
#mycode2(){{
import {TranslateService} from '@ngx-translate/core';
import { environment } from '../../environments/environment';

export class HeaderComponent implements OnInit {
  .
  .

+  languages = environment.languages;
+  currentLang: string = environment.defaultLang;
+  constructor(private translate: TranslateService) {}
+
+  changeLang(lang){
+    if (lang !== this.currentLang) {
+      this.translate.use(lang);
+      this.currentLang = lang;
+    }   
+  }
+
+  isCurrentLang(lang){
+    return lang === this.currentLang;
+  }

}}

#html(</div>)


#html(</div>)

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS