Phoenix: 多言語対応#

Published on 2020-07-15 00:00:00

Phoenixの多言語対応は Gettext モジュールを用いることで実現できます。

本記事は、Phoenixのデフォルトプロジェクトに Gettext を適用させる流れを通し、多言語対応について説明します。

概要#

Gettext を用いた多言語対応は下図の流れにそって行います。

  1. gettextマクロを*.html.eexや*.ex内の翻訳対象文字列に適用する

  2. 翻訳対象文字列をpotファイル [1] に抽出する

  3. potファイルから各言語用のpoファイル [2] を作成する

  4. poファイルに翻訳文字列を記述する

../../../_images/gettext.png

以降、流れの順に説明していきます。

事前情報として、
Phoenixプロジェクト作成時にgettextディレクトリがpriv下に作られることを知っておいてください。
$ mix phx.new my_app --no-ecto
$ tree priv/gettext
priv/gettext/
├── en
│   └── LC_MESSAGES
│       └── errors.po
└── errors.pot

gettextマクロの適用#

Phoenixサーバーの localhost:4000 で表示される "Welcome to Phoenix!" はデフォルトでgettextマクロが適用されています。

../../../_images/phoenix_default_top.png

赤枠内がgettextマクロが適用されている文字列です#

該当ファイルは以下のように記述されています。

$ head -2 lib/my_app_web/templates/page/index.html.eex
<section class="phx-hero">
  <h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>

このように多言語対応させる文字列にはgettextマクロを適用する必要があります。

翻訳対象文字列の抽出#

mix gettext.extract を使うことで翻訳対象文字列をpotファイルに抽出することができます。

$ mix gettext.extract
Compiling 13 files (.ex)
Extracted priv/gettext/default.pot
Extracted priv/gettext/errors.pot
$ cat priv/gettext/default.pot
# 中略
#, elixir-format
#: lib/my_app_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr ""
gettextマクロで囲われた文字列が msgid としてdefault.potに抽出されます。
※なぜdefault.potに抽出されるかはgettextドキュメントの Domains を参照ください。

各言語用poファイルの作成#

前節のpotファイルはテンプレートです。
このテンプレートを用い、各言語用のpoファイルを作成します。日本語用のpoファイルを作ります。

mix gettext.merge を使うことでpoファイルを作成することができます。

$ mix gettext.merge priv/gettext --locale=ja
Created directory priv/gettext/ja/LC_MESSAGES
Wrote priv/gettext/ja/LC_MESSAGES/errors.po (0 new translations, 0 removed, 0 unchanged, 0 reworded (fuzzy))
Wrote priv/gettext/ja/LC_MESSAGES/default.po (1 new translation, 0 removed, 0 unchanged, 0 reworded (fuzzy))
$ cat priv/gettext/ja/LC_MESSAGES/default.po
# 中略
#, elixir-format
#: lib/my_app_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr ""

poファイルの翻訳文字列の記述#

poファイルのmsgstrに翻訳文字列を記述します。

$ cat priv/gettext/ja/LC_MESSAGES/default.po
# 中略
#, elixir-format
#: lib/my_app_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr "%{name}にようこそ!"

デフォルトの言語設定をconfigに追記し、

$ tail -n 5 config/config.exs
config :gettext, :default_locale, "ja" # 追加

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

反映を確認します。

../../../_images/gettext_ja.png
翻訳文字列を追加した場合においても、手順は同じです。
mix gettext.extract, mix gettext.merge は繰り返し実行可能です。

警告

poファイルのmsgidの手作業による追加、変更、削除は禁止されています。

また、poファイルの変更は poedit が便利そうです。

../../../_images/poedit.png

動的な言語切替#

ユーザー操作で言語切り替えできるようにするために、
クエリーパラメータを使った切り替えの実装を説明します。

まず、localeを切り替えるPlugを作成します。

$ cat lib/my_app_web/plugs/locale.ex
defmodule MyAppWeb.Plug.Locale do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    case conn.params["hl"] || get_session(conn, :locale) do
      locale when locale in ["en", "ja"] ->
        Gettext.put_locale(locale)
        put_session(conn, :locale, locale)

      _ ->
        conn
    end
  end
end

このPlugをrouterの:browser pipelineに追加します。

head -n 11 lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

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

このPlug追加により、リクエスト毎にクエリーパラメーターまたはセッション値を確認しlocaleを切り替えるようになります。

あとはUIに以下のようなリンクをつけてやれば、

<li><%= link("English", to: Phoenix.Controller.current_path(@conn, %{hl: "en"})) %></li>
<li><%= link("日本語", to: Phoenix.Controller.current_path(@conn, %{hl: "ja"})) %></li>
../../../_images/gettext_en_ja.gif

となります。

LiveViewの場合#

セッションの値をLiveView側で受け、mountで再度put_localeする必要があります。

@impl true
def mount(params, %{"locale" => locale} = _session, socket) do
  Gettext.put_locale(locale)
  {:ok, socket}
end

ソースコード#

本記事で作成したPhoenixプロジェクトは以下に配置しています。

https://github.com/pojiro/phoenix_with_gettext_sample

参考#

以上です。