Phoenixで多言語化(i18n)


## 環境

- Phoenix 1.2
- Elixir 1.3.3

## やること

Phoenix標準でサポートされている [**Gettext**](https://github.com/elixir-lang/gettext) でi18nします

### Gettextとは

`po`ファイルや`pot`ファイルで多言語化するライブラリです。
ソースファイル中のi18n用関数をmix taskの`mix gettext.extract`、`mix gettext.merge`から`po`ファイル, `pot`ファイルを自動的に生成してくれます。

### Installation

- `mix.exs` に依存を追加

```elixir
def deps do
  [{:gettext, "~> 0.8"}]
end
```

- application起動時にgettextも起動するよう変更

```elixir
def application do
  [applications: [:gettext, :logger]]
end
```

- 依存を更新

```bash
mix deps.get
```

### Usage

- Gettext用のモジュールを作成

`web/gettext.ex`

```elixir
defmodule MyApp.Gettext do
  use Gettext, otp_app: :my_app
end
```

- `gettext`を使う

```elixir
defmodule MyApp.FooBar do
  import MyApp.Gettext
    
  def foo do
    gettext "foo"
  end
    
  def bar do
    name = "shufo"
    message = "How are you?"
    gettext "Hello, %{name}. %{message}", name: name, message: message
  end
end
```

- `pot`ファイル(多言語化のためのテンプレート)を生成

```elixir
mix gettext.extract
```

`priv/gettext/default.pot`に以下のようなファイルが生成される

```elixir
## 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/foobar.ex:5
msgid "foo"
msgstr ""

#: web/foobar.ex:11
msgid "Hello, %{name}. %{message}"
msgstr ""
```

これを元に各`po`ファイルを生成します

- `po`ファイル(実際に多言語化するための文言を書き込むファイル)を生成

```elixir
mix gettext.merge priv/gettext --locale ja
```

`priv/gettext/ja/LC_MESSAGES/default.po`に以下のようなファイルが生成されます

```elixir
## `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/foobar.ex:5
msgid "foo"
msgstr "フー"

#: web/foobar.ex:11
msgid "Hello, %{name}. %{message}"
msgstr "こんにちは %{name} さん。%{message}"
```

Languageが`ja`となっているのが分かります。ここに実際の各言語の翻訳を書いていきます。  
以降は`gettext/1`をソース内に記述したら都度`mix gettext.extract`でテンプレートを更新して`mix gettext.merge priv/gettext`で各言語ファイルに展開するという作業をします。

#### デフォルト Locale

これだけでは`gettext`で翻訳を呼び出したとき日本語にならないのでデフォルトのlocaleとして`ja`を設定します

`config/config.exs`

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

これでデフォルトでlocaleが`ja`の翻訳ファイルが参照されるようになります。

#### Localeの切り替え

ユーザの条件やリクエスト時のパラメータによってlocaleを変更するにはPlug等で都度localeをセットします

`web/plugs/locale.ex`

```elixir
defmodule MyApp.Plug.Locale do
  import Plug.Conn

  def init(opts), do: nil

  def call(conn, _opts) do
    case conn.params["locale"] || get_session(conn, :locale) do
      nil     -> conn
      locale  ->
        Gettext.put_locale(StreamingChat.Gettext, locale)
        conn |> put_session(:locale, locale)
    end
  end
end
```

`web/router.ex`

```elixir
pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    # 追加
    plug MyApp.Plug.Locale
end
```

これでクエリパラメータに`locale`がある場合やセッションにlocaleが設定されている場合はそちらがlocaleとして設定され対応するpoファイルが読み込まれます


## まとめ

- Gettextでi18nをした
- `mix gettext.extract`でテンプレートファイルを生成した後 `mix gettext.merge`で実際の多言語化ファイルを生成した
- デフォルトのlocaleを設定した
- リクエストパラメータ等によってlocaleを変えるようにした