node.js
Web プロジェクトで i18n を実装するには、よくある問題があります。次のような場合、問題はさらに悪化します。
- Web コンポーネントを使用する ( Polymerなど)
- サーバー側とクライアント側のファイルに単一の翻訳ファイルを使用する
- 一部のアイテムをプログラムで翻訳する (動的に作成された文字列など)
Mozilla チームによって開発された真新しい L20n ライブラリのおかげで、この問題は比較的簡単に解決できます。
node.js
Web プロジェクトで i18n を実装するには、よくある問題があります。次のような場合、問題はさらに悪化します。
Mozilla チームによって開発された真新しい L20n ライブラリのおかげで、この問題は比較的簡単に解決できます。
最初に、目的別にグループ化されたファイルを個別に保持するプロジェクト構造を作成しました。
.
+-- app.js
+-- piblic
| +-- locales
| +-- app.ru.l20n
| +-- app.en.l20n
|
+-- node_models
| +-- l20n
|
+-- bower_components
| +-- Polymer libraries
|
+-- app_modules
| +-- app-l20n-node
| +-- index.js
|
+-- app_components
+-- app-l20n
+-- app-l20n.html
+-- app-custom-component
+-- app-custom-component.html
アイデアは単純です。app-l20n-node
すべてのサーバー側ジョブをローカライズするモジュールとして使用されapp-l20n
、ユーザー インターフェイス l10n の Polymer コンポーネントです。
Runnpm install l20n --save
現在のバージョンは 3.5.1 で、小さなバグがあります。l20n のメイン ファイルに./dist/compat/node/l20n.js
は 2 つの必須変数があり、コードのどこにも使用されていませんが、ライブラリの Devdependencies でのみ言及されているため、起動時にアプリをクラッシュさせる可能性があります。それを避けるために、私はそれらをライブラリコードに直接コメントしました:
//var string_prototype_startswith = require('string.prototype.startswith');
//var string_prototype_endswith = require('string.prototype.endswith');
とのような名前の翻訳ファイルを/public/locales/
フォルダーに作成しました。L20n ルールによると、ファイルの内容は次のようになります。app.ru.l20n
app.en.l20n
<foo "Foo translation">
<bar "Bar translation">
<register[$variant] {
infinitive: "Register now!"
}>
L20n 用のノード モジュールを作成します。私の場合、コードはapp_modules\app-l20n-node\index.js
次のようになります。
'use strict';
const L20n = require('l20n');
var path = require('path');
module.exports = function(keys, lang){
const env = new L20n.Env(L20n.fetchResource);
// Don't forget nice debug feature of L20n library
env.addEventListener('*', e => console.log(e));
// I suppose that I'll always provide locale code for translation,
// but if it would not happen, module should use preset
var langs = [];
if(!lang) {
// you should define locales here
langs = [{code: 'ru'}, {code: 'en'}]
} else {
langs = [{code: lang}]
}
// set context, using path to locale files
const ctx = env.createContext(langs,
[path.join(__dirname, '../../public/locales/app.{locale}.l20n')]);
const fv = ctx.formatValues;
return fv.apply(ctx, keys);
};
これで、このモジュールを node.js コードで使用して、キーとロケールによって要求された翻訳を取得できます。ハードコーディングされたロケールの代わりに、express-sessions
ユーザー定義のロケールをセッション属性として使用および保存しますreq.session.locale
。しかし、それはすべてプロジェクトに依存します。
var l20n = require('../../app_modules/app-l20n-node');
l20n(['foo','bar'], 'en')
.then((t)=>{
console.log(t); // ["Foo translation", "Bar translation"]
});
ここで、L20n 用の Polymer コンポーネントを作成する必要があります。
まず、ライブラリと翻訳ファイルへのリンクを html に追加します<head>
。
<script src="/node_modules/l20n/dist/compat/web/l20n.js"></script>
<link rel="localization" href="/locales/app.{locale}.l20n">
今度app-l20n.html
は、新しい動作を持つ Polymer コンポーネントを作成します。
<script>
/**
* Create namespace for custom behavior or use existing one.
*/
window.MB = window.MB || {};
MB.i18n = {
/**
* Use l20n.js to translate certain strings
* @param component A Polymer component, usually "this.translate(this, props);"
* @param props An array of keys to translate: strings or arrays.
*/
translate: function(component, props) {
var view = document.l10n;
var promise = view.formatValues.apply(view, props);
promise.then(function(args){
for (var i in args){
var prop = props[i];
// strings with parameters represented by arrays:
// ["string", {param: value}]
if (Array.isArray(prop)) {
// get property name - usually the same, as translation key
// so the object would have properties obj.Foo and obj.Bar
var propName = prop[0];
// if it is needed to create multiple translations of the same
// string in one component, but with different parameters,
// the best way is to use suffix:
// ["string", {param: value}, "_suffix"]
if (prop.length == 3) propName = propName + prop[2];
component.set(propName, args[i]);
}
// common strings
else component.set(prop, args[i]);
}
});
}
};
</script>
いいえ、新しい動作の準備が整ったので、カスタム Polymer コンポーネントに実装できます。Polymer Behavior または L20n カスタム タグ属性機能を使用して、プログラムで翻訳を取得できます。
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="/bower_components/gold-email-input/gold-email-input.html">
<link rel="import" href="/bower_components/paper-button/paper-button.html">
<link rel="import" href="/app_components/app-l20n/app-l20n.html">
<dom-module id="app-custom-component">
<template>
<gold-email-input
auto-validate
required
name="email"
value="{{Foo}}"
label="{{Bar}}">
</gold-email-input>
<paper-button onclick="regFormSubmit(event)">
<iron-icon icon="perm-identity"></iron-icon>
<span data-l10n-id="Register" data-l10n-args='{"variant": "infinitive"}'></span>
</paper-button>
</template>
<script>
function regFormSubmit(event){}
Polymer({
is: 'app-custom-component',
behaviors: [
MB.i18n
],
ready: function(){
this.$.passwordValidator.validate = this._validatePasswords.bind(this);
// add your component properties to array. They can be simple like in this example, or
// more complex, with parameters: ["Some_key", {param: "xxx"}].
// you can even translate the same string in different properties, using custom suffix:
// ["Some_key", {param: "yyy"}, "_suffix"] and place it in template with shortcut: {{Some_key_suffix}}
var translateProps = ["Foo", "Bar"];
// now translate, using behavior
this.translate(this, translateProps);
}
});
</script>
</dom-module>
この小さなチュートリアルがお役に立てば幸いです。