読者です 読者をやめる 読者になる 読者になる

mmag

ハマったことメモなど

Gettextの使い方メモ

はいコンバンハ。

Gettextっていうモジュールがあるんですが、POTとかPOファイルとか、なんかよくわかんなかったので書いておきます。

できること

いわゆるi18n、アプリケーションの国際化ができます。Phoenixアプリ前提にしてしまいますが、例えばHTMLに

<h1><%= MyApp.Gettext.gettext "welcome!" %></h1>

と書いて、jaロケールのPOファイルには

msgid "welcome!"
msgstr "ようこそおいでくださいました"

enロケールのPOファイルには

msgid "welcome!"
msgstr "Welcome to My Page!"

と書いておくと、ロケールに応じていい感じに歓迎できるというわけです。ロケールの指定は、Gettext.put_locale/2で動的に設定したり、config/config.exs`などに

config :my_app, MyApp.Gettext, default_locale: "ja"

と書いて行います。

わかんなかったこと

POファイルの作り方で混乱しました。Phoenixアプリでは、privの下にgettextという謎ディレクトリが最初から作られています。

./priv
└── gettext
    ├── en
    │   └── LC_MESSAGES
    │       └── errors.po
    └── errors.pot

ここにはPOTファイルとPOファイルがあります。POTはPortable Object Templateの略なので、テンプレートです。どういうこっちゃと言うと、おもむろに

mix gettext.merge priv/gettext --locale ja

と打ってみると、このテンプレートをもとにjaロケール用のPOファイルが作られるのです。

./priv
└── gettext
    ├── en
    │   └── LC_MESSAGES
    │       └── errors.po
    ├── errors.pot
    └── ja
        └── LC_MESSAGES
            └── errors.po

まず大前提として、このテンプレートはコードから抽出された結果が吐き出される場所であるということを覚えておきましょう。ファイル名を変えたり内容でファイル分けたり、それなりに手で編集することもあるとは思いますが、日本語や英語をガリガリ書くのは別の場所です。

試しにUserモデルに

def changeset(model, params \\ %{}) do
  ...
  |> validate_length(:password, min: 8, message: MyApp.Gettext.gettext("password too short", count: 8))
  ...
end

と書いて抽出してみます。抽出タスクは

mix gettext.extract

です。これをやると、default.potが作られます。defaultという名前が気に入らない場合はdgettext("errors", "password too short")のようにdgettextドメインを指定しましょう。

./priv
└── gettext
    ├── default.pot
    ├── en
    │   └── LC_MESSAGES
    │       └── errors.po
    ├── errors.pot
    └── ja
        └── LC_MESSAGES
            └── errors.po
# default.pot

## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
msgid ""
msgstr ""
"Language: INSERT LANGUAGE HERE\n"

#: web/models/user.ex:31
msgid "password too short"
msgstr ""

Userモデルの中で使っているものが抽出されています。これでコードの変更がテンプレートファイルに反映されたので、各言語用のPOファイルへさらに反映させます。ここで使うのが先ほどのgettext.mergeタスクです。既に翻訳がいくつか書かれているところへ、新しい項目を放り込んでいくので”マージ”というわけです。

mix gettext.merge priv/gettext

enjaの下に、default.potからdefault.poが作られています。

./priv
└── gettext
    ├── default.pot
    ├── en
    │   └── LC_MESSAGES
    │       ├── default.po
    │       └── errors.po
    ├── errors.pot
    └── ja
        └── LC_MESSAGES
            ├── default.po
            └── errors.po

ようやく翻訳を書くところへきました。ja/LC_MESSAGES/default.poに、"password too short"に対応する日本語を書きます。

## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: ja\n"

#: web/models/user.ex:31
msgid "password too short"
msgstr "%{count}文字以上じゃないとダメっていう規則なんですよ。すません。"

まとめ

  • コード内でgettext "foobar"を使う
  • mix gettext.extractでテンプレートファイルを更新
  • mix gettext.merge priv/gettextで各言語ファイルに反映
  • 翻訳を書く

追記

コメントでphoenix-locale_jaを教えていただきました。Ectoのバリデーションエラーだけ翻訳を用意して頂いているようです。

テーマはFB matteをベースにしてます。作者さんに感謝を込めて。