28
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Mithril.jsAdvent Calendar 2016

Day 21

Mithril、Vue.jsの仮想DOM用のi18nライブラリ作った

Last updated at Posted at 2016-12-20

いろいろ探してみたり、某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ライブラリ作ったを参照してください。

28
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?