エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Elixir で Gettext を使う

由緒正し I18n の仕組みということを、 Phoenix を触るようになって初めて知りました。

Phoenix ではプロジェクトを作成すると自動的にパッケージが追加されますが、通常の Elixir プロジェクトで利用する手順を調べました。

くわしくは公式ドキュメントに全部書いてあります。

プロジェクトを用意する

$ mix new my_app
$ cd my_app

パッケージを追加する

バージョンはそのときどきの適切な(たいていは最新の)バージョンを指定してください。

  defp deps do
    [
      {:gettext, "~> 0.15.0"}
    ]
  end

パッケージの取得とコンパイル

$ mix do deps.get, deps.compile

実行すると gettext 関連のタスクが追加されます。

$ mix help
...
mix gettext.extract   # Extracts translations from source code
mix gettext.merge     # Merge template files into translation files
...

プロジェクトの Gettext モジュールを作る

こんな感じで。

# lib/my_app/gettext.ex
defmodule MyApp.Gettext do
  use Gettext, opt_app: :my_app
end

Gettext を利用したコードを書く

たとえばこんな感じで。

# lib/my_app.ex
defmodule MyApp do
  import MyApp.Gettext

  def say do
    IO.puts gettext("Hello, %{name}!", name: "世界")
    IO.puts gettext("Hi.")
    IO.puts gettext("Bye.")
  end
end

実行するとこんな感じ。まだなにもしていないので gettext に与えられた文字列がそのまま出力されています。

$ mix run -e 'MyApp.say'
Hello, 世界!
Hi.
Bye.

文字列を抽出する

mix gettext.extract を実行して文字列を抽出します。 priv/gettext/default.pot というファイルが作成され、抽出した文字列が格納されます。

$ mix gettext.extract
Compiling 2 files (.ex)
Extracted priv/gettext/default.pot

ロケールに対応した .po ファイルを作成する

$ mix gettext.merge priv/gettext --locale=ja
Created directory priv/gettext/ja/LC_MESSAGES
Wrote priv/gettext/ja/LC_MESSAGES/default.po

priv/gettext の下に指定したロケールディレクトリが作成され、その中に default.po というファイルが作成されます。

msgidgettext に与えた文字列で、文字列を置き換える時の ID になります。 msgstr に置き換える文字列を記述します。このときプレイスホルダ(ここでは %{name} の部分)は置き換え後も機能するようにそのまま残します。

たとえばこんな感じ。

## `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"
"Plural-Forms: nplurals=1\n"

#, elixir-format
#: lib/my_app.ex:7
msgid "Bye."
msgstr "ぢゃ、そんな感じで。"

#, elixir-format
#: lib/my_app.ex:5
msgid "Hello, %{name}!"
msgstr "こんにちは、%{name}!"

#, elixir-format
#: lib/my_app.ex:6
msgid "Hi."
msgstr "やはぁ。"

デフォルトのロケールを指定する

config/config.exs にデフォルトロケールの指定を追加します。

# config/config.exs
config :my_app, MyApp.Gettext,
  default_locale: "ja"

コンパイルして実行

$ mix compile --force
Compiling 2 files (.ex)
Generated my_app app
$ mix run -e 'MyApp.say'
こんにちは、世界!
やはぁ。
ぢゃ、そんな感じで。

文字列を追加する

Gettext を使う文字列を追加してみます。

# lib/my_app.ex
defmodule MyApp do
  import MyApp.Gettext

  def say do
    IO.puts gettext("Hello, %{name}!", name: "世界")
    IO.puts gettext("Hi.")
    IO.puts gettext("Leave it to me!")
    IO.puts gettext("Bye.")
  end
end

抽出します。

$ mix gettext.extract
Compiling 2 files (.ex)
Extracted priv/gettext/default.pot

priv/gettext/default.pot に追加した文字列が追加されます。

ロケール.po ファイルにマージします。

$ mix gettext.merge priv/gettext --locale=ja
Wrote priv/gettext/ja/LC_MESSAGES/default.po

priv/gettext/ja/LC_MESSAGES/default.po を編集します。

#, elixir-format
#: lib/my_app.ex:7
msgid "Leave it to me!"
msgstr "むぁ〜かせて!"

コンパイルして実行します。

$ mix compile --force
Compiling 2 files (.ex)
Generated my_app app
$ run -e 'MyApp.say'e
こんにちは、世界!
やはぁ。
むぁ〜かせて!
ぢゃ、そんな感じで。

動的にロケールを変更する

Gettext.put_locale/1ロケールの文字列を指定すると動的にロケールを変更できます。

$ mix run -e 'Gettext.put_locale("en"); MyApp.say'
Hello, 世界!
Hi.
Leave it to me!
Bye.

もしくは。 MyApp.Gettextロケールのみを変更したいばあいは、Gettext.put_locale/2 を使ってモジュールを指定します。

$ mix run -e 'Gettext.put_locale(MyApp.Gettext, "en"); MyApp.say'
Hello, 世界!
Hi.
Leave it to me!
Bye.
$ mix run -e 'Gettext.put_locale(MyApp.Gettext, "en"); MyApp.say; Gettext.put_locale(MyApp.Gettext, "ja"); MyApp.say'
Hello, 世界!
Hi.
Leave it to me!
Bye.
こんにちは、世界!
やはぁ。
むぁ〜かせて!
ぢゃ、そんな感じで。

Phoenix で使う

Phoenix プロジェクトを作成すると、パッケージが追加された状態で priv/gettext も用意された状態になっています。

$ mix phx.new my_phx
my_phx/
└── priv/
     └── gettext/
          ├── en/
          │   └── LC_MESSAGES/
          │       └── errors.po
          └── errors.pot

ここで mix gettext.extract を実行すると、lib/my_phx_web/templates/page/index.html.eex"Welcome to %{name}!"priv/gettext/default.pot に抽出されます。

$ mix gettext.extract
Compiling 12 files (.ex)
Generated my_phx app
Extracted priv/gettext/errors.pot
Extracted priv/gettext/default.pot

同様に mix gettext.merge を実行します。

$ mix gettext.merge priv/gettext --locale=ja
Created directory priv/gettext/ja/LC_MESSAGES
Wrote priv/gettext/ja/LC_MESSAGES/errors.po
Wrote priv/gettext/ja/LC_MESSAGES/default.po

生成された priv/gettext/ja/LC_MESSAGES/default.po を編集します。

#, elixir-format
#: lib/my_phx_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr "%{name}へようこそ!"

config/config.exs を編集してデフォルトロケールの指定を追加します。

config :my_phx, MyPhxWeb.Gettext,
  default_locale: "ja"

サーバを起動します。

$ mix phx.server

ブラウザで http://localhost:4000 にアクセスします。

f:id:E_Mattsan:20180227213619p:plain

ぢゃ、そんな感じで。

いつか読むはずっと読まない:なぜなら、猫だから

本書の帯より。

この本に登場する猫たち

  • マシュマロを焼く天才猫
  • マスコットとして宇宙船に乗った猫
  • 銀河文明を変えた猫
  • 生き返ってから年を取らなくなった猫
  • テレパスになった猫