AngularJSアプリを国際化する方法

公開: 2022-03-11

アプリを国際化することは、ソフトウェア開発を苦痛な経験にする可能性があります。特に、最初から始めない場合や、アプリに対して意欲的なアプローチをとる場合はなおさらです。

フロントエンドとバックエンドが明確に分離されている最新のアプリは、国際化に関してはさらに扱いにくい場合があります。 突然、従来のサーバーサイドページで生成されたWebアプリの国際化に役立った、実績のあるツールにアクセスできなくなります。

したがって、AngularJSアプリでは、適切なロケールでレンダリングするために、国際化(i18n)およびローカリゼーション(l10n)データをクライアントにオンデマンドで配信する必要があります。 従来のサーバー側でレンダリングされたアプリとは異なり、すでにローカライズされているページを配信するためにサーバーに依存することはできなくなりました。 ここで多言語PHPアプリケーションの構築について学ぶことができます

この記事では、AngularJSアプリを国際化する方法と、プロセスを容易にするために使用できるツールについて学習します。 AngularJSアプリを多言語化すると、いくつかの興味深い課題が発生する可能性がありますが、特定のアプローチにより、これらの課題のほとんどを簡単に回避できるようになります。

シンプルなi18n対応のAngularJSアプリ

クライアントがユーザーの好みに基づいてその場で言語とロケールを変更できるようにするには、いくつかの重要な設計上の決定を行う必要があります。

  • 最初から言語やロケールに依存しないようにアプリをどのように設計しますか?
  • i18nおよびl10nデータをどのように構成しますか?
  • このデータをクライアントに効率的に配信するにはどうすればよいですか?
  • 開発者のワークフローを簡素化するために、低レベルの実装の詳細をどのように抽象化しますか?

これらの質問にできるだけ早く答えることで、開発プロセスの障害を回避できます。 これらの課題のそれぞれは、この記事で扱われます。 堅牢なAngularJSライブラリを介したものもあれば、特定の戦略やアプローチを介したものもあります。

AngularJSの国際化ライブラリ

AngularJSアプリを国際化するために特別に構築されたJavaScriptライブラリがいくつかあります。

angular-translateはAngularJSモジュールであり、フィルターとディレクティブを提供し、i18nデータを非同期でロードする機能を備えています。 MessageFormatによる複数化をサポートし、高度に拡張可能で構成可能になるように設計されています。

プロジェクトでangular-translateを使用している場合は、次のパッケージのいくつかが非常に役立つことがあります。

  • angular-sanitize :翻訳でのXSS攻撃を防ぐために使用できます。
  • angular-translate-interpolation-messageformat :性別に依存するテキストフォーマットをサポートする複数化。
  • angular-translate-loader-partial :翻訳された文字列をクライアントに配信するために使用されます。

真にダイナミックな体験のために、 angular-dynamic-localeを束に追加することができます。 このライブラリを使用すると、ロケールを動的に変更できます。これには、日付、数値、通貨などがすべてフォーマットされる方法が含まれます。

はじめに:関連パッケージのインストール

AngularJSボイラープレートの準備ができていると仮定すると、NPMを使用して国際化パッケージをインストールできます。

 npm i -S angular-translate angular-translate-interpolation-messageformat angular-translate-loader-partial angular-sanitize messageformat

パッケージがインストールされたら、アプリの依存関係としてモジュールを追加することを忘れないでください。

 // /src/app/core/core.module.js app.module('app.core', ['pascalprecht.translate', ...]);

モジュールの名前はパッケージの名前とは異なることに注意してください。

最初の文字列を翻訳する

アプリにテキスト付きのツールバーとプレースホルダーテキスト付きのフィールドがあるとします。

 <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">Hello</a> </div> <div class="collapse navbar-collapse"> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" ng-model="vm.query" placeholder="Search"> </div> ... </div> </div> </nav>

上記のビューには、国際化できる2ビットのテキストがあります。「Hello」と「Search」です。 HTMLに関しては、1つはアンカータグの内部テキストとして表示され、もう1つは属性の値として表示されます。

それらを国際化するには、両方の文字列リテラルをトークンに置き換える必要があります。トークンは、ページのレンダリング中に、AngularJSがユーザーの好みに基づいて実際に翻訳された文字列に置き換えることができます。

AngularJSは、トークンを使用して、指定した変換テーブルでルックアップを実行することでこれを行うことができます。 モジュールangular-translateは、これらの変換テーブルがプレーンなJavaScriptオブジェクトまたはJSONオブジェクト(リモートでロードする場合)として提供されることを想定しています。

これらの変換テーブルが一般的にどのように見えるかの例を次に示します。

 // /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello", "SEARCH": "Search" } } // /src/app/toolbar/i18n/tr.json { "TOOLBAR": { "HELLO": "Merhaba", "SEARCH": "Ara" } }

上からツールバービューを国際化するには、文字列リテラルを、AngularJSが変換テーブルで検索するために使用できるトークンに置き換える必要があります。

 <!-- /src/app/toolbar/toolbar.html --> <a class="navbar-brand" href="#" translate="TOOLBAR.HELLO"></a> <!-- or --> <a class="navbar-brand" href="#">{{'TOOLBAR.HELLO' | translate}}</a>

内部テキストの場合、 translateディレクティブまたはtranslateのいずれかを使用できることに注意してください。 (ここでtranslateディレクティブについて、ここでtranslateフィルターについて詳しく知ることができます。)

これらの変更により、ビューがレンダリングされるときに、 angular-translateは、現在の言語に基づいて、 TOOLBAR.HELLOに対応する適切な翻訳をDOMに自動的に挿入します。

属性値として表示される文字列リテラルをトークン化するには、次の方法を使用できます。

 <!-- /src/app/toolbar/toolbar.html --> <input type="text" class="form-control" ng-model="vm.query" translate translate-attr-placeholder="TOOLBAR.SEARCH">

では、トークン化された文字列に変数が含まれている場合はどうなるでしょうか。

「Hello、{{name}}。」のようなケースを処理するために、AngularJSがすでにサポートしているのと同じ補間構文を使用して変数置換を実行できます。

翻訳表:

 // /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello, {{name}}." } }

その後、さまざまな方法で変数を定義できます。 ここにいくつかあります:

 <!-- /src/app/toolbar/toolbar.html --> <a ... translate="TOOLBAR.HELLO" translate-values='{ name: vm.user.name }'></a> <!-- or --> <a ... translate="TOOLBAR.HELLO" translate-value-name='{{vm.user.name}}'></a> <!-- or --> <a ...>{{'TOOLBAR.HELLO | translate:'{ name: vm.user.name }'}}</a>

複数化とジェンダーへの取り組み

複数化は、i18nとl10nに関してはかなり難しいトピックです。 言語や文化が異なれば、言語がさまざまな状況で複数形を処理する方法について異なる規則があります。

これらの課題のために、ソフトウェア開発者は単に問題に対処しない(または少なくとも適切に対処しない)ことがあり、その結果、次のようなばかげた文を生成するソフトウェアになります。

 He saw 1 person(s) on floor 1. She saw 1 person(s) on floor 3. Number of people seen on floor 2: 2.

幸い、これを処理する方法には標準があり、標準のJavaScript実装はMessageFormatとして利用できます。

MessageFormatを使用すると、上記の構造が不十分な文を次のように置き換えることができます。

 He saw 1 person on the 2nd floor. She saw 1 person on the 3rd floor. They saw 2 people on the 5th floor.

MessageFormatは、次のような式を受け入れます。

 var message = [ '{GENDER, select, male{He} female{She} other{They}}', 'saw', '{COUNT, plural, =0{no one} one{1 person} other{# people}}', 'on the', '{FLOOR, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}', 'floor.' ].join(' ');

上記の配列を使用してフォーマッターを作成し、それを使用して文字列を生成できます。

 var messageFormatter = new MessageFormat('en').compile(message); messageFormatter({ GENDER: 'male', COUNT: 1, FLOOR: 2 }) // 'He saw 1 person on the 2nd floor.' messageFormatter({ GENDER: 'female', COUNT: 1, FLOOR: 3 }) // 'She saw 1 person on the 3rd floor.' messageFormatter({ COUNT: 2, FLOOR: 5 }) // 'They saw 2 people on the 5th floor.'

MessageFormatangular-translateで使用して、アプリ内でその全機能を利用するにはどうすればよいですか?

アプリの設定では、メッセージ形式の補間が次のように利用可能であることをangular-translateに指示するだけです。

 /src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); });

次に、変換テーブルのエントリがどのように表示されるかを示します。

 // /src/app/main/social/i18n/en.json { "SHARED": "{GENDER, select, male{He} female{She} other{They}} shared this." }

そして、ビューで:

 <!-- /src/app/main/social/social.html --> <div translate="SHARED" translate-values="{ GENDER: 'male' }" translate-interpolation="messageformat"></div> <div> {{ 'SHARED' | translate:"{ GENDER: 'male' }":'messageformat' }} </div>

ここでは、AngularJSのデフォルトの補間器の代わりにメッセージ形式の補間器を使用する必要があることを明示的に指定する必要があります。 これは、2つの補間器の構文がわずかに異なるためです。 これについて詳しくは、こちらをご覧ください。

アプリに翻訳テーブルを提供する

AngularJSが翻訳テーブルからトークンの翻訳を検索する方法がわかったので、アプリは最初に翻訳テーブルをどのように認識しますか? どのロケール/言語を使用する必要があるかをアプリにどのように伝えますか?

ここで$translateProviderについて学びます。

次のように、アプリのcore.config.jsファイルで直接サポートする各ロケールの変換テーブルを提供できます。

 // /src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); $translateProvider.translations('en', { TOOLBAR: { HELLO: 'Hello, {{name}}.' } }); $translateProvider.translations('tr', { TOOLBAR: { HELLO: 'Merhaba, {{name}}.' } }); $translateProvider.preferredLanguage('en'); });

ここでは、現在の言語を英語(en)として宣言しながら、英語(en)およびトルコ語(tr)のJavaScriptオブジェクトとして翻訳テーブルを提供しています。 ユーザーが言語を変更したい場合は、$translateサービスを使用して変更できます。

 // /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, $translate) { $scope.changeLanguage = function (languageKey) { $translate.use(languageKey); // Persist selection in cookie/local-storage/database/etc... }; });

デフォルトでどの言語を使用すべきかという問題はまだあります。 アプリの初期言語をハードコーディングすると、常に受け入れられるとは限りません。 このような場合、別の方法は、$translateProviderを使用して言語を自動的に決定しようとすることです。

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.determinePreferredLanguage(); });

definePreferredLanguageは、 window.navigatordeterminePreferredLanguageを検索し、ユーザーから明確なシグナルが提供されるまでインテリジェントなデフォルトを選択します。

遅延読み込み変換テーブル

前のセクションでは、JavaScriptオブジェクトとしてソースコードに直接変換テーブルを提供する方法を示しました。 これは小規模なアプリケーションでは許容できる場合がありますが、このアプローチはスケーラブルではありません。そのため、変換テーブルはリモートサーバーからJSONファイルとしてダウンロードされることがよくあります。

この方法で変換テーブルを維持すると、クライアントに配信される初期ペイロードサイズが減少しますが、さらに複雑になります。 ここで、i18nデータをクライアントに配信するという設計上の課題に直面しています。 これを注意深く処理しないと、アプリケーションのパフォーマンスが不必要に低下する可能性があります。

なぜそんなに複雑なのですか? AngularJSアプリケーションはモジュールに編成されています。 複雑なアプリケーションでは、多くのモジュールがあり、それぞれが独自のi18nデータを持っています。 したがって、i18nデータを一度にロードして提供するなどの単純なアプローチは避ける必要があります。

必要なのは、i18nデータをモジュールごとに整理する方法です。 これにより、必要なときに必要なものだけをロードし、以前にロードしたものをキャッシュして、同じデータのリロードを回避できます(少なくともキャッシュが無効になるまで)。

ここでpartialLoaderが役立ちます。

アプリケーションの変換テーブルが次のように構成されているとします。

 /src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json

この構造に一致するURLパターンでpartialLoaderを使用するように$translateProviderを構成できます。

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoader('$translatePartialLoader', { urlTemplate: '/src/app/{part}/i18n/{lang}.json' }); });

当然のことながら、「lang」は実行時に言語コードに置き換えられます(例:「en」または「tr」)。 「パーツ」はどうですか? $ translateProviderは、ロードする「パーツ」をどのように認識しますか?

$translatePartialLoaderを使用して、コントローラー内でこの情報を提供できます。

 // /src/app/main/main.controller.js app.controller('MainCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('main'); }); // /src/app/toolbar/toolbar.config.js app.controller('ToolbarCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('toolbar'); });

これでパターンが完成し、特定のビューのi18nデータが、そのコントローラーが最初に実行されたときに読み込まれます。これはまさに必要なものです。

キャッシング:ロード時間の短縮

キャッシングはどうですか?

$translateProviderを使用して、アプリ構成で標準キャッシュを有効にできます。

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoaderCache(true); // default is false });

特定の言語のキャッシュを無効にする必要がある場合は、 $translateを使用できます。

 $translate.refresh(languageKey); // omit languageKey to refresh all

これらの要素を適切に配置すると、アプリケーションは完全に国際化され、複数の言語をサポートします。

番号、通貨、日付のローカライズ

このセクションでは、 angular-dynamic-localeを使用して、AngularJSアプリケーションで数値、通貨、日付などのUI要素のフォーマットをサポートする方法を学習します。

このためには、さらに2つのパッケージをインストールする必要があります。

 npm i -S angular-dynamic-locale angular-i18n

パッケージがインストールされたら、モジュールをアプリの依存関係に追加できます。

 // /src/app/core/core.module.js app.module('app.core', ['tmh.dynamicLocale', ...]);

ロケールルール

ロケールルールは、$ localeサービスに依存するコンポーネントによって日付、数値、通貨などをフォーマットする方法の仕様を提供する単純なJavaScriptファイルです。

現在サポートされているロケールのリストは、こちらから入手できます。

これは、 angular-locale_en-us.jsからのスニペットで、月と日付のフォーマットを示しています。

 ... "MONTH": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], "SHORTDAY": [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], ...

i18nデータとは異なり、ロケールルールはアプリケーションに対してグローバルであり、特定のロケールのルールを一度にロードする必要があります。

デフォルトでは、 angular-dynamic-localeは、ロケールルールファイルがangular/i18n/angular-locale_{{locale}}.jsにあることを想定しています。 それらが他の場所にある場合は、 tmhDynamicLocaleProviderを使用してデフォルトをオーバーライドする必要があります。

 // /src/app/core/core.config.js app.config(function (tmhDynamicLocaleProvider) { tmhDynamicLocaleProvider.localeLocationPattern( '/node_modules/angular-i18n/angular-locale_{{locale}}.js'); });

キャッシングは、 tmhDynamicLocaleCacheサービスによって自動的に処理されます。

ロケールルールは文字列変換よりも変更される可能性が低いため、ここではキャッシュの無効化はそれほど問題になりません。

ロケールを切り替えるために、 angular-dynamic-localetmhDynamicLocaleサービスを提供します。

 // /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, tmhDynamicLocale) { $scope.changeLocale = function (localeKey) { tmhDynamicLocale.set(localeKey); // Persist selection in cookie/local-storage/database/etc... }; });

自動翻訳による翻訳テーブルの生成

ロケールルールはangular-i18nパッケージに同梱されているため、必要に応じてパッケージの内容をアプリケーションで利用できるようにするだけです。 しかし、翻訳テーブルのJSONファイルをどのように生成しますか? ダウンロードしてアプリケーションにプラグインできるパッケージは正確にはありません。

1つのオプションは、プログラムによる翻訳APIを使用することです。特に、アプリケーションの文字列が変数や複数形のない単純なリテラルである場合はそうです。

Gulpといくつかの追加パッケージを使用すると、アプリケーションのプログラムによる翻訳をリクエストするのは簡単です。

 import gulp from 'gulp'; import map from 'map-stream'; import rename from 'gulp-rename'; import traverse from 'traverse'; import transform from 'vinyl-transform'; import jsonFormat from 'gulp-json-format'; function translateTable(to) { return transform(() => { return map((data, done) => { const table = JSON.parse(data); const strings = []; traverse(table).forEach(function (value) { if (typeof value !== 'object') { strings.push(value); } }); Promise.all(strings.map((s) => getTranslation(s, to))) .then((translations) => { let index = 0; const translated = traverse(table).forEach(function (value) { if (typeof value !== 'object') { this.update(translations[index++]); } }); done(null, JSON.stringify(translated)); }) .catch(done); }); }); } function translate(to) { return gulp.src('src/app/**/i18n/en.json') .pipe(translateTable(to)) .pipe(jsonFormat(2)) .pipe(rename({ basename: to })) .pipe(gulp.dest('src/app')); } gulp.task('translate:tr', () => translate('tr')); This task assumes the following folder structure: /src/app/main/i18n/en.json /src/app/toolbar/i18n/en.json /src/app/navigation/i18n/en.json ...

スクリプトは、最初にすべての英語の翻訳テーブルを読み取り、文字列リソースの翻訳を非同期的に要求してから、英語の文字列を翻訳された文字列に置き換えて、新しい言語の翻訳テーブルを作成します。

最後に、新しい翻訳テーブルは英語の翻訳テーブルの兄弟として書き込まれ、次のようになります。

 /src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json /src/app/navigation/i18n/en.json /src/app/navigation/i18n/tr.json ...

getTranslationの実装も簡単です。

 import bluebird from 'bluebird'; import MicrosoftTranslator from 'mstranslator'; bluebird.promisifyAll(MicrosoftTranslator.prototype); const Translator = new MicrosoftTranslator({ client_id: process.env.MICROSOFT_TRANSLATOR_CLIENT_ID, client_secret: process.env.MICROSOFT_TRANSLATOR_CLIENT_SECRET }, true); function getTranslation(string, to) { const text = string; const from = 'en'; return Translator.translateAsync({ text, from, to }); }

ここではMicrosoftTranslateを使用していますが、GoogleTranslateやYandexTranslateなどの別のプロバイダーを簡単に使用できます。

プログラムによる翻訳は便利ですが、次のようないくつかの欠点があります。

  • ロボットの翻訳は短い文字列に適していますが、それでも、さまざまなコンテキストでさまざまな意味を持つ単語に落とし穴が生じる可能性があります(たとえば、「プール」は水泳やグループ化を意味する場合があります)。
  • APIは、メッセージ形式に依存する変数または文字列を含む文字列を処理できない場合があります。

これらの場合やその他の場合、人間による翻訳が必要になる場合があります。 ただし、これは別のブログ投稿のトピックです。

フロントエンドの国際化は気が遠くなるように見えるだけです

この記事では、これらのパッケージを使用してAngularJSアプリケーションを国際化およびローカライズする方法を学習しました。

angular-translateangular-dynamic-locale 、およびgulpは、面倒な低レベルの実装の詳細をカプセル化するAngularJSアプリケーションを国際化するための強力なツールです。

この投稿で説明されているアイデアを説明するデモアプリについては、このGitHubリポジトリを確認してください。