いろいろ探してみたり、某node.js会長に聞いてみたりしたんですが、いまいち自分のニーズにあうi18nライブラリが見つからなかったので、一番理想に近い(けど一歩足りなかった)roddeh-i18nをベースに作ってみました。MITライセンスです。
- 国際化機能をサポート
- キーで単語を置き換える(元の単語もキーとして使用できます)
- 複数形
- フォーマッティング(変数に値を設定)
- 文脈からの翻訳の選択(性別など)
- 小さくてポータブルなランタイム
- ランタイムは移植性の高いES3で書かれ、gzipで1.8キロバイト
- ランタイムは他に依存せず
- common.jsおよびAMDとも利用可能。
<script>
タグを使用したロードも可能
- ブラウザで実行可能
- 仮想DOM(Mithril, Vue.js)で使用
- スタティックなHTMLの翻訳
- node.jsで実行可能
- オフライン単位テストを簡単にする
- サーバー側のレンダリングとかCLIツールとかでも
- 翻訳メンテナンス用のCLIツールを提供
メンテナンスツールはES6で書いているので実行にはnode.js 6.0以降じゃないとダメだと思いますが、ランタイムはES3なので、IE6みたいなブラウザや、Google Apps Scriptとかからも使えると思います。
そのうちReactとかRiotにも対応したいと思いつつ今はちょっと時間が取れてません。そのうち。
インストール
$ npm install i18n4v
既存のライブラリでは何が足りなかったのか?
仮想DOMが広く使われるようになって、ウェブフロントエンドは完全にJavaScriptの世界になりました。ブラウザ専用みたいなのはたくさんありました。XMLHttpRequestで翻訳のテキストをサーバに取りに行くような・・・ですが
- ユニットテストはnode.jsでオフラインで実行することが多い(と思うし、node.jsでできたほうが速くて便利)、過度にブラウザに依存しているのは使いにくい
- サーバーサイドレンダリングをnode.jsで実行するなら、同じ辞書や同じ翻訳機能をnode.jsとブラウザで共有したい
- CLIツールのヘルプとかコメントも翻訳したい
ということで、node.jsでもブラウザでもユニバーサルな動作がするのが欲しいですよね。
ついでに、翻訳機能も、複数形対応は当然として、可能ならコンテキスト(主語が男性名詞か、女性名詞か)で訳文切り替えとかも欲しいです。このユニバーサル、複数形対応、コンテキスト対応の3つを満たすものはほとんどありませんでした。roddeh-i18nはこの点は問題なかったのですが、以下の機能も欲しかったので、これをベースに機能を追加しました
- 環境ごとのデフォルト言語の選択の補助機能の提供
- 仮想DOMとはいっても、画面すべてが仮想DOM配下におくわけではない。Mithrilでは動的変更される領域に限定して、小さくわけた複数の小パーツのみを仮想DOM化して、それ以外の部分を静的HTMLで、という運用ができる。その静的HTMLも翻訳したい。
- QtのLinguistとかgettextとかSphinxのi18n機能みたいに、辞書のメンテは自動化したい
基本の翻訳機能
辞書JSONを用意してadd関数で登録すると次のように翻訳ができます。
const i18n = require('i18n4v');
i18n.translator.add({
values:{
"Hello": "こんにちは"
}
});
i18n("Hello"); // -> こんにちは
複数形はキーの訳語を配列にして、その中に三項目の配列を入れます。それぞれ、数値の範囲(min, max)と、その時の訳語です。
nullを使うと、-∞、+∞の意味になります。%n
で、指定された数値に置き換えることができます。-%n
も使えます。ただし、細かい演算はできないので、イギリス英語では1階はground floor、2階がfirst floorというのはそれぞれ翻訳語を作らないと今は対応できません。
i18n.translator.add({
"values": {
"%n comments": [
[0, 0, "%n comments"],
[1, 1, "%n comment"],
[2, null, "%n comments"]
]
}
});
i18n("%n comments", 0); // -> 0 comments
i18n("%n comments", 1); // -> 1 comment
i18n("%n comments", 2); // -> 2 comments
フォーマッティングは次のように書きます。
i18n("Welcome %{name}", { name:"John" }); // -> Welcome John
基本的に、キーに対する訳語が見つからないと、キーそのものが結果として使われますが、見つからなかった時のフォールバック文字列を指定することもできます。
i18n("_short_key", "This is a long piece of text")); // -> This is a long piece of text
2番目のパラメータが豊富ですが、同時に使うのであれば、
i18n(キー, デフォルト文字列, 数値, フォーマットの引数)
の順番になります。
コンテキスト
男性名詞などの文脈に合わせた翻訳をするには、訳文をコンテキストごとに登録する必要があります。
matchesが条件の辞書ですが、ここでは性別だけですが、ここにいくつもの条件を追加することができます。
i18n.translator.add({
"contexts": [
{
"matches": {"gender": "male"},
"values": {
"%{name} updated their profile": "%{name} updated his profile"
}
},
{
"matches":{"gender": "female"},
"values": {
"%{name} updated their profile": "%{name} updated her profile"
}
}
]
});
i18n("%{name} updated their profile",
{ name: "John" },
{ gender: "male" }
); // -> John updated his profile
i18n("%{name} updated their profile",
{ name: "Jane" },
{ gender: "female" }
); // -> Jane updated her profile
静的HTMLの翻訳
applyToHTML()メソッドを呼ぶと、タグにdata-i18n
属性が付いた静的HTMLをまとめて翻訳します。
<article>
<h1 data-i18n>Monty Python</h1>
</article>
<script>
i18n.translator.add({
values: {
"Monty Python": "モンティ・パイソン"
}
});
i18n.translator.applyToHTML();
</script>
次の書き方をすると、翻訳のキーは「こんにちわ」ではなく、greetingになります。
<span data-i18n="greeting">こんにちわ</span>
デフォルトではサニタイズしてからHTMLを更新するのですが、data-i18n-safe
をつけると、翻訳文にタグが入っていてもそのまま挿入します。
<span data-i18n data-i18n-safe>Hello</span>
翻訳言語の選択補助
ブラウザの表示言語情報や、node.jsなら実行環境のLANG環境変数などを元に適切な言語を選択する機能も提供しています。node.jsの場合はos-localeパッケージを使って現在の言語設定を取得します。配列で渡した言語リストの中から、一番近い言語を返します。
var languages = {
en: require('./languages/en.json'),
fr: require('./languages/fr.json'),
de: require('./languages/de.json')
}
i18n.translator.selectLanguage(['en', 'de', 'fr'], function (err, lang) {
i18n.translator.add(languages[lang] ? languages[lang] : languages.en);
});
ユーザの好みの言語を保存する機能もあります。ブラウザ限定ですが、次のコードを呼ぶと、localStorageに言語を保存します。一度保存すると、次からselectLanguageはその言語を優先して返します。
i18n.translator.setLanguage('tlh');
Mithrilからの利用
MithrilはJavaScriptでJSON形式で仮想DOMを作ります。もともとテンプレートフォーマットとかなくて、「JavaScriptでできることはすべてできる」というのがMithrilの仮想DOMの特徴です。このライブラリを使うのも、簡単です。
サニタイズはMithrilがやってくれるので、特に気にする必要はありませんが、逆にタグを入れたい時はMithrilのm.trustを使ってください。
view: function (ctrl) {
return m('div', [
m('h3', i18n('Language Select')),
m('button', {onclick: ctrl.select.bind(ctrl, 'en')}, 'English'),
m('button', {onclick: ctrl.select.bind(ctrl, 'ja')}, 'Japanese')
]);
}
Mithrilのサンプルもこちらにあります。
Vue.jsからの利用
Vue.js 2.0用のプラグインも作りました
$ npm install i18n4vue
Vue.jsのサンプルはこちらにあります。
// プラグインを有効化
Vue.use(require('i18n4vue'));
// v-18nディレクティブで翻訳
<h3 v-i18n>Language Select</h3>
// .safeをつけるとサニタイズをオフにします
<h3 v-i18n.safe>Language Select</h3>
// i18nメソッド経由で使うこともできます。
<th v-for="label in dayOfWeekLabels">{{ i18n(label) }}</th>
つづき
辞書メンテツールやサーバサイドの話はNode.jsの仮想DOM用のi18nライブラリ作ったを参照してください。