[elixir] Elixir地域化の話

依然として開発が活発なElixirですが、日本語のサポートが欲しいと思って、地域化のパッケージを作成してみました。国際化/地域化パッケージと言えばGNU gettextですので、できるだけGNU gettextを使うようにしました。

Exgettext

地域化するためにはまず、

  1. メッセージの拾い出し(xgettext)
  2. メッセージのデータベース化(msginit)
  3. メッセージのメンテナンスフレームワーク(msgmerge)
  4. アプリケーション実行時のサポート(msgfmt, gettext)

が必要です。これらをelixirらしくl10n.というプレフィックスをつけたmixタスクにしています。

メッセージの拾い出し

これはsigil Tを使ってマークアップしてもらうことにしました。
sigil Tではソースコンパイル時にデータベースへ格納します。
また、@doc, @moduledocも収集します。こちらのほうはmix l10n.xgettextユーティリティ実行時にモジュールからCode.get_docs/2を使って収集します。これでpotファイルを作成します。

メッセージのデータベース化

mix l10n.msginitで初期メッセージデータベースを作成します。現在のLANG環境変数から言語を判断します。これは翻訳言語当たり一度だけ行います。これでpotファイルからpoファイルが生成されます。

メッセージのメンテナンスフレームワーク

poファイルは只のテキストファイルですのでエディタで編集できますが、emacsのpo-modeを使うのが楽です。何れにしてもGNU gettext互換ですのでいろいろなツールが使えます。
もとのプログラムがバージョンアップした場合には、あたらしいpotファイルに対してl10n.msgmergeを使う事でマージされたpoxファイルが生成されます。これを確認してpoファイルとして使用することができます。このあたりのワークフローもGNU gettextと同様の習慣に従っています。

実行時のサポート

翻訳が終了したら、l10n.msgfmtでメッセージオブジェクトを生成します。このフォーマットは残念ですがGNU gettextとは関係ありません。
実態はdetsデータベースになっています。poファイルやpotファイルと同様、このデータベースもpriv/配下に配置されます。というのはmix archiveでバイナリ配布パッケージを作成する際に同梱されるからです。

一方sigil Tマクロは、コンパイル時に「このデータベースを参照してメッセージをコンバートする」というgettext関数呼び出しに展開されています。
これにより、実行時にLANG環境変数に応じたメッセージが使用されます。

翻訳の単位

GNU gettextにならって、アプリケーション単位にpoファイルを作成するようにしています。Elixir的にはmixファイルのproject.apps単位となります。sigil Tはモジュール単位にimportする必要があります。

サンプルプログラム

せっかくなのでIExのヘルプをExgettextを用いて翻訳しています。

bash-3.2$ mix do deps.get, deps.compile, compile
* Getting exgettext (https://github.com/k1complete/exgettext.git)
Cloning into '/../l10n_iex/deps/exgettext'...
(...)
Generated l10n_iex.app
bash-3.2$ mix l10n.msgfmt
msgfmt for l10n_iex
priv/po/ja.po /(...)/l10n_iex/_build/dev/lib/l10n_iex/priv/lang/ja/l10n_iex.exmo
bash-3.2$ iex -S mix
Interactive Elixir (0.14.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> h(c/2)
* def c(files, path \\ ".")

Expects a list of files to compile and a path
to write their object code to. It returns the name
of the compiled modules.

When compiling one file, there is no need to wrap it in a list.

## Examples

    c ["foo.ex", "bar.ex"], "ebin"
    #=> [Foo,Bar]

    c "baz.ex"
    #=> [Baz]


iex(2)> import Exgettext.Helper
nil
iex(3)> h(c/2)
* def c(files, path \\ ".")

コンパイルするファイルのリストとオブジェクトコードを
書き出すパスを指定します。コンパイルされたモジュールの名前を
返します。

一つのファイルをコンパイルする時にリストにする必要はありません

## 例

    c ["foo.ex", "bar.ex"], "ebin"
    #=> [Foo,Bar]

    c "baz.ex"
    #=> [Baz]

iex(4)> 

これらは
GitHub - k1complete/l10n_iex: localization of IExGitHub - k1complete/exgettext: yet another gettext localization package for elixirに公開しています。まだexgettextは荒削りですので微妙な点があるかもしれません。