Polymerとグローバルローカライゼーションを分離


Thu 15 September 2016 作成者: Gabriel Barata

当社は、最近Polymerベースの新しいWeb UIのプレビューバージョンをリリースしましたが、初期段階ではあるものの、多くの機能をすでに搭載しています。当社の新しいWeb UIは、Nuxeo Platform向けのリッチウェブアプリの構築に使用できる汎用性のあるビルディングブロックとしてNuxeo UI要素を活用します。このブロックは、いくつかのカスタム要素と動作から構成され、その一部は、よく知られている複雑な問題を解決するものです。本日は、こうした問題の一つであるローカライゼーションを取り上げます。

##問題

今年の初め頃、1つの問題に直面しました。当社のWeb UIを多言語にローカライズする必要がありました。しかし、当社のローカライゼーションのアプローチは、Polymer用の作業ができる柔軟性が必要であり、同時に、メッセージローディングや、言語、翻訳機能などの詳細を、こうした要素を使用してアプリケーションが処理・定義できるようにしなければなりません。Polymerでは、ローカライゼーションに対応する出来合いのソリューションが提供されていなかったため、独自のソリューションを考える必要がありました。まず、「Polymer.Base」にグローバル機能を追加し、テキストをPolymer要素で翻訳する必要があるテキストコンテンツ、プロパティ、属性のすべてに連結させました。

<span>[[i18n('label.app.usersAndGroups')]]</span>

これは功を奏し、少なくとも1言語ではうまく機能しました。多言語で実験を始めると、特に、言語が外部スクリプトで変更されていたため、ラベルキーやローカライゼーション機能に変更がなくとも、その機能を使用していくつかの要素についてこの連結を再評価する必要が出てきました。さらに多くの作業が必要だと分かりました。いくつかの要素についてデータを連結して機能するが、Polymer要素以外でも設定、使用できるようなグローバル機能で、その要素の連結を更新できるものが必要でした。

##ソリューション

この問題のソリューションは、各グローバル機能に割り当てられる関数型プロパティでPolymer Behaviorを作成することでした。(そうです!文書化されていなくても、Polymerのプロパティは、関数型になり、連結することができます。)
この関数型プロパティは、動作を拡張するPolymer要素すべてによって計算済み連結で使用することができます。この動作により、他の要素か、またはその他の外部コードで起動する、グローバル機能の変更も監査し、計算済み連結の更新を起動する必要があります。このようにして、動作を実装する全ての要素について翻訳を更新させる必要があります。

このアプローチより、ローカライゼーション問題に対処することができました。始めに、翻訳機能の定義、言語の変更、新しいメッセージローディング方針の構成など、アプリケーションレベルのカスタム化を提供することを主な目的として、「nuxeo-i18n.js」というスタンドアロンスクリプトを追加しました。このスクリプトは、ラベルキーとそのデフォルト値だけでなく、結果の文字列で置換することができるいくつかのパラメータを受け入れるグローバルな翻訳機能を整列表示します。グローバル言語変数も提供され、現行の翻訳言語を保持します。

window.nuxeo.I18n.translate = function (key, defaultValue) {
  var language = window.nuxeo.I18n.language || 'en';
  var value = (window.nuxeo.I18n[language] && window.nuxeo.I18n[language][key]) || defaultValue || key;
  var params = Array.prototype.slice.call(arguments, 2);
  for (var i = 0; i < params.length; i++) {
    value = value.replace('{' + i + '}', params[i]);
  } // improve this to use both numbered and named parameters
  return value;
};

別のグローバル機能は、現行の言語用のロケールリソースをローディングする役割があり、Polymer要素内からも、その他の外部スクリプトからも起動することができます。この機能は、ドキュメントレベルのイベントを起動させ、新しいロケールがロードされたことを通知します(Polymerに厳格に依存することを避けるため、アイアンスロットを使用しないことに決めました)。

window.nuxeo.I18n.loadLocale = function() {
  return window.nuxeo.I18n.localeResolver ? window.nuxeo.I18n.localeResolver().then(function() {
    document.dispatchEvent(new Event('i18n-locale-loaded'));
  }) : new Promise(function() {});
};

この「ロードロケール」機能は、リソースをロードする方法が分かるオブジェクトであるロケールリゾルバに依存します。これにより、翻訳リソースをロードするモジュラー式構成可能なアプローチが提供され、異なるタイプ構成にカスタムリゾルバを指定することができます。
新しいWeb UIでは、非同期XHRを介してJSONオブジェクトをロードするために、ロケールリゾルバを使用しています。これは以下の通り定義されます。

window.nuxeo.I18n.localeResolver = new XHRLocaleResolver(msgFolder);

function XHRLocaleResolver(msgFolder) {
  return function() {
    return new Promise(function(resolve,reject) {
      var language = window.nuxeo.I18n.language || 'en';
      var url = msgFolder +  '/messages.' + language + '.json';
      var xhr = new XMLHttpRequest();
      xhr.open('GET', url, true);
      xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
          window.nuxeo.I18n[language] = JSON.parse(this.response); // cache this locale.
          resolve(this.response);
        }
      };
      xhr.onerror = function() {
        console.error("Failed to load " + url);
        reject(this.statusText);
      };
      xhr.send();
    });
  }
}

「nuxeo-i18n.js」に依存するi18n動作も作成しました。この動作を拡張する要素は、ドキュメントノードに添付された後、新しいロケールがロードされたかどうかを点検するためにイベントリスナーを登録します。このリスナーは、グローバル翻訳機能を「i18n」という関数型プロパティに再割り当てする機能を起動します。

Nuxeo.I18nBehavior = {
  properties: {
    i18n: {
      type: Function,
      notify: true,
      value: function() {
        return window.nuxeo.I18n.translate;
      }
    }
  },

  attached: function() {
    this.localeLoadedHandler = this.refreshI18n.bind(this);
    document.addEventListener('i18n-locale-loaded', this.localeLoadedHandler);
  },

  detached: function() {
    document.removeEventListener('i18n-locale-loaded', this.localeLoadedHandler);
  },

  refreshI18n: function() {
    this.i18n = window.nuxeo.I18n.translate;
  }
};

この「i18n」関数は、以下に示す例のように、要素のプロパティ、属性、またはtextContentに連結することができます。

<span>[[i18n('label.app.usersAndGroups', 'Users & Groups')]]</span>

Nuxeo UI要素と同様に新しいWeb UIで翻訳を必要とする要素はすべて、この動作を実装します。この動作が機能するために必要なことは、上述の通り、まずスクリプトでロケールリボルバを設定してから、言語を変更するだけでよく、ラベルをすべて更新します。

window.nuxeo.I18n.language = 'en';
window.nuxeo.I18n.loadLocale();

このアプローチにより、言語の変更やロケールリソースのローディングを支えるロジックを簡単かつプラガブルにします。また、リソースがPolymer要素、またはその他の外部JavaScriptコードのいずれかによってロードされた場合でも、データを点検に連結しておきます。

注記:5月の初めに、Polymerチームは、AppLocalizeBehaviorをプレリリースしました。これは、当社独自のローカライゼーション動作と類似していますが、カスタムグローバル機能のサポートもなく、その機能が外部で変更された場合に更新するロジックもありません。


タグ付き: Polymer, How to, Nuxeo-Platform