Golang用のi18nパッケージを作ってみた

Goアドベントカレンダーその3です。前日は[zaneli@githubさんのGo言語で「この猫なに猫？」](http://www.zaneli.com/blog/20161220)でした。JSとオーバーラップした内容なので他のアドベントカレンダーとクロスポストしちゃえ、と思っていたんですが、Qiitaの仕様上ダメだったので個別にエントリーを書く羽目になりました。みなさん真似しないように。

以前[golangでi18n](http://qiita.com/shibukawa/items/f0e4df597e62372fe7d5)というエントリーを書きましたが、新しいパッケージ[github.com/shibukawa/i18n4v](https://github.com/shibukawa/i18n4v)を作りました。[godocはこちら](https://godoc.org/github.com/shibukawa/i18n4v)。

なぜ作ったかというと、以下のような機能を十分に満たす国際化機能が欲しかったからです。

* キーで単語を置き換える（元の単語もキーとして使用できます）
* 複数形
* フォーマッティング(変数に値を設定)
* 文脈からの翻訳の選択（性別など）
* 翻訳の言語を複数同時に扱える
* JavaScriptと共通の辞書ファイルを共有 **!!new**

ということで、JavaScript用のパッケージを作ったついでにGo用のも作ってみました。JSコードを元にしたため、Goのjsonパッケージで簡単にパースできるような構造になってなかったので、そこはいろいろ見えない所でがんばっています。

Goの国際化というと、以前mattnさんに教えてもらった[Go本家での取り組み](https://github.com/golang/proposal/blob/master/design/12750-localization.md)もあります。機能性としては今回作ったものの上位互換っぽい感じなので、将来的にはこのライブラリは廃止して、今回作ったファイルフォーマットとの相互変換ツールにでもすればいいかな、と思っています。

基本の翻訳
---------

Add(data []byte), AddFromString(data string)という初期化関数があり、これで単語を登録しますソースはJSONです。正規表現の初期化と同じようにMustが前についたバージョン(MustAdd, MustAddFromString)もあります。

```go
    i18n4v.MustAddFromString(`{
        "values": {
            "Cancel": "キャンセル"
        }
    }`)

    i18n := i18n4v.Translate

    i18n("Cancel")  // -> キャンセル
```

複数形の翻訳
------------

日本語だとあまり細かく気にする必要はありませんが、複数形も対応しています。さきほどと違って、訳語に配列をわたします。数値の最小値と最大値、マッチしたときの訳語の配列が要素としてはいっています。nullの場合は開放端ということで処理します。``%n``と``-%n``は第二引数で渡された数値(とその負の値)に置換されます。

```go
    i18n4v.MustAddFromString(`{
        "values": {
            "%n comments": [
                [0, 0, "%n comments"],
                [1, 1, "%n comment"],
                [2, null, "%n comments"]
            ]
        }
    }`)
    i18n := i18n4v.Translate
    i18n("%n comments", 1)  // -> 1 comment
```

フォーマット
---------

``%{キー}``で、後で与えた置換用の辞書と置き換えた翻訳もできます。

```go
    i18n := i18n4v.Translate
    i18n("Welcome %{name}", i18n4v.Replace{"name":"John"})  // -> Welcome John
```

翻訳キーを別の用語に置き換え
-------------------------

翻訳語が見つからなかった時は、検索のキーをそのまま返すのですが、第二引数に文字列を渡すと代わりにそれを返します。

```go
    i18n := i18n4v.Translate
    i18n("_short_key", "This is a long piece of text")  // -> This is a long piece of text
```

コンテキストで訳語を切り替え
------------------------

valuesではなく、contextsというキーの中に配列で翻訳セットを入れることができます。それぞれにmatchesという名前の辞書があり、一致したコンテキストの翻訳セットが使われます。今までの機能とも組み合わせたサンプルです。

今までの説明ではみんな第二引数に渡していましたが、複数の項目を渡す時は以下の順序を守る必要があります(があとで順不同にしたい)

1. 翻訳キー
2. 見つからなかった時の代替テキスト
3. 複数形の数値
4. フォーマットの引数
5. コンテキスト

```go
    MustAddFromString(`{
        "contexts": [
            {
                "matches": {"gender": "male"},
                "values": {
                    "%{name} uploaded %n photos to their %{album} album": [
                          [0, 0, "%{name} uploaded %n photos to his %{album} album"],
                          [1, 1, "%{name} uploaded %n photo to his %{album} album"],
                          [2, null, "%{name} uploaded %n photos to his %{album} album"]
                    ]
                }
            },
            {
                "matches": {"gender":"female"},
                "values": {
                    "%{name} uploaded %n photos to their %{album} album": [
                          [0, 0, "%{name} uploaded %n photos to her %{album} album"],
                          [1, 1, "%{name} uploaded %n photo to her %{album} album"],
                          [2, null, "%{name} uploaded %n photos to her %{album} album"]
                    ]
                }
            }
        ]
    }`)

    i18n := i18n4v.Translate
    i18n("%{name} uploaded %n photos to their %{album} album", 4,
        Replace{"name": "Jane", "album": "Hen's Night" },
        Context{"gender": "female" })
    // -> Jane uploaded 4 photos to her Hen's Night album
```

サーバ用
---------

これ以外にも、サーバ用に、翻訳のインスタンスを複数作成する機能があります。Create/CreateFromString/MustCreate/MustCreateFromStringあたりの関数で作ります。翻訳機能自体は一緒です。

サーバだと全翻訳ファイルをオンメモリで持っていて、ユーザの言語(Accept-Languageヘッダー)によって切り替える必要とかありますよね。それ用の機能です。

Todo
----

基本機能はできましたが、まだまだ未完成です。

* デフォルトコンテキストの設定
* 現在の環境(OSの言語設定など)、Accept-Languageヘッダーから、言語の候補を選択する機能
  * node.jsのこれを移植する: https://github.com/sindresorhus/os-locale
  * これも移植する: https://github.com/sindresorhus/lcid
* Gopher.js用のコードはだいたいできたが未テスト
* JSの方のツールと同じく、ASTをごにょごにょして、辞書ファイルを生成する機能
* JSのIntlの機能のラップ(カレンダーの表示とかいろいろ)

データのJSONもGUIの入力補助ツールを作りたいところ

JS側の使い方とか機能の紹介
-----------------------

こちらの今日書いたエントリー([Mithril、Vue.jsの仮想DOM用のi18nライブラリ作った](http://qiita.com/shibukawa/items/c2298b21b4629941b230))を参照してください。

明日はHatajoeさんが何かをひねり出されるそうです。