YAMAGUCHI::weblog

ジャイトニオ猪場のはからいで、全財産を失いました。トランスマスターケンちゃんこと、剣持です。

golang.org/x/text/messageでI18N

はじめに

こんにちは、Stackdriver担当者です。この記事は Go Advent Calendar 2018 *1の最終日のエントリです。昨日は @yasuo-ozuさんの「Go言語は沼」 でした。

ところで今日はクリスマスですね。自分宛も含めてまだプレゼントを送っていない方はこの本を送るのがおすすめです。

Go言語による並行処理

Go言語による並行処理

年末年始休暇に読んでもらってGo言語による並行処理への理解を深めてもらいましょう!

さて、今日は準標準パッケージの "golang.org/x/text/message" の紹介です。本文に出てくる雑なサンプルのリンクを貼っておきます。

"golang.org/x/text/message" とは

godoc.org

Go準標準パッケージ内にある、国際化のためのパッケージです。履歴を見ればおわかりの通り、非常に地味に更新が続いているパッケージです。

Log - master - text - Git at Google

このパッケージは大きく分けて2つの使い方があって

  • フォーマットのローカライゼーション
  • メッセージの翻訳

の2種類があります。どちらもメッセージの出力をする際に fmt パッケージでなく、 message.Printer を使うようにするところがポイントです。

フォーマットのローカライゼーション

これはドキュメントのサンプルにあるとおりで、プリセット(CLDR, Common Locale Data Repositoryに準拠)で用意されているフォーマットを利用して、数字の3桁区切りやその区切り文字、通貨記号の取扱も可能です。*2

これらはパッケージの目的として golang.org/x/text 以下にあるデータフォーマットにはすべて対応するという目標があるようです。(まだやってない)

import (
    "golang.org/x/text/currency"
    "golang.org/x/text/language"
    "golang.org/x/text/message"
)

func localization() {
    jp := language.Japanese
    p := message.NewPrinter(jp)
    cur, _ := currency.FromTag(jp)
    // クリスマスには¥ 120000のiPadを買いました。(フォーマット引数が%dなのに通貨記号が入っているのがミソ。しかしカンマ区切りが反映されない)
    p.Printf("クリスマスには%dのiPadを買いました。\n", currency.NarrowSymbol(cur.Amount(120000.0)))
    // お年玉は10,000円あげるつもりです。(カンマ区切りが反映されている)
    p.Printf("お年玉は%d円あげるつもりです。\n", 10000)
}

メッセージの翻訳

正直ローカライゼーションのほうはまだまだ改善の余地ありという感じですが、一番良く使われるのはこちらのメッセージの翻訳機能の方でしょう。こちらは使い方が単純です。

ベースとなるフォーマット文字列をキーとして、各言語ごとに翻訳版のフォーマット文字列を指定するという形です。(message.Setmessage.SetString を使う) 指定した文字列は golang.org/x/text/message/catalog#Catalog に追加されていくだけので、アプリケーションなどで使う場合には独自のカタログを作っておくと良いでしょう。(例: エラーログメッセージなど)

import (
    "golang.org/x/text/language"
    "golang.org/x/text/message"
)

func init() {
    message.SetString(language.Japanese, "%d days to the new year day.\n",
        "新年まであと%d日\n")
    message.SetString(language.Japanese, "%s, I wish you a happy new year.\n",
        "%s、良いお年を\n")
    // フォーマット引数の順番を入れ替える場合には"[]"を使って指定する
    message.SetString(language.Japanese, "%s, %s\n", "%[2]s%[1]s\n")
}

func translation() {
    p := message.NewPrinter(language.Japanese)
    local, _ := time.LoadLocation("Local")
    nyd := time.Date(2019, 1, 1, 0, 0, 0, 0, local)
    days := nyd.Sub(time.Now()) / (time.Hour * 24)
    // 新年まであと6日
    p.Printf("%d days to the new year day.\n", days)
    // みなさん、良いお年を
    p.Printf("%s, I wish you a happy new year.\n", "みなさん")
    // 世界、こんにちは
    p.Printf("%s, %s\n", "こんにちは", "世界")
}

ただこちらもドキュメントにフォーマット引数の順番に関する記述がなかったり、plural.Selectf の動作がいまいち怪しかったりと、まだまだ改善の余地ありな状況なので、もし本番に使うなら message.SetString に限るなど限定的な使用方法が良いかもしれません。逆にContributeチャンスでもありますね。

おわりに

個人的な感想として golang.org/x/text 以下は需要の割に作りが甘い感じがしていて、今回も調べるにあたってつまずく部分がいくつかありました。このあたりのパッケージは技術的な困難さという部分よりも、テストケースをいかに作るかという部分が大きいと思いますので、これを機会にこのパッケージを使ってGo製アプリケーションの日本語化をどんどん行って、どんどんIssue登録がされると良いなと思いました。

今年も1年お疲れ様でした。Gopherのみなさん、良いお年を。

*1:ところで、オーバーフローしたGoのアドベントカレンダーが ”Go 2〜" "Go 3〜" となってるのに違和感を感じたのは僕だけでしょうか。"Go〜 その2” とかならわかるけど、最初 "Go 2〜" を見たときに、Go 2 に関するアドベントカレンダーかと思いました。

*2:ただ、このエントリを書いていてところどころ挙動が怪しいところを見つけてしまったので、これはcontributeチャンスですね。