hanami のバリデーションメッセージを日本語化する

## はじめに

先日行われた RubyKaigi でもセッションが設けられていましたが `hanami` はドメイン駆動設計を意識した Ruby の Web アプリケーションフレームワークです。

[るびま](http://magazine.rubyist.net/?0056-hanami)で紹介されているのを見て「そういえば昔 Lotus とかあったけど、どうなったのかな？」と思って調べたところ Lotus が名前を改めたのが hanami だったと知った経緯などもあり、いい機会なので[チュートリアル](http://hanamirb.org/guides/getting-started/)を進めてみました。

殆どは先述のるびまでまとめられた内容が詳しいのですが、仮に実案件で使うとして個人的に絶対に外せないポイントがバリデーションメッセージの日本語化でした。

この記事ではそのあたりについて、自分で今理解している範囲でまとめていきます。

### ステップ1. コントローラーにおける Params に依存しないメッセージの日本語化

チュートリアルを最後まで行うと、コントローラーには `params` のバリデーションが定義されています。

```ruby
# apps/web/controllers/books/create.rb
module Web::Controllers::Books
  class Create
    # 略
    params do
      required(:book).schema do
        required(:title).filled(:str?)
        required(:author).filled(:str?)
      end
    end
    # 略
  end
end
```

実際にサーバを起動し、ブラウザからタイトルを空にした状態でリクエストを投げると Title must be filled のようなメッセージが得られると思います。

まずは後半の `must be filled` の箇所を日本語にします。

コードを書き換える前に `hanami` のバリデーションについて軽く説明すると、それ自体モデルやコントローラーには依存せず使えるライブラリになっています。

https://github.com/hanami/validations

そして、その中身で使われているのは `dry-validation` という Gem です。

https://github.com/dry-rb/dry-validation

だとすればメッセージもそこで使われているものがデフォルトになっているはず、と思い[ロケールファイル](https://github.com/dry-rb/dry-validation/blob/master/config/errors.yml)を探してきました。

これを `config` 以下に `config/messages.yml` として保存し、試しに値を上書きしてみます（ロケールとして `en` を指定したままですが、ここについては後述します）。

```yaml
en:
  errors:
    filled?: "みたしとらん！"
```

次に、先ほどのコントローラーを開いてこのロケールファイルが使われるようにします。

```ruby
# 必要な箇所だけ抜粋
params do
  configure do
    config.messages = :yaml
    config.messages_file = 'config/messages.yml'
  end

  required(:book).schema do
    required(:title).filled(:str?)
    required(:author).filled(:str?)
  end
end
```

`config.messages` についてはデフォルトで `:yaml` が指定されているので省略可能ですが、分かりやすさ重点で残しておきました。

この状態で再度リクエストを投げると、エラーメッセージが「Titleみたしとらん！」のように変更されると思います（されない場合、一度サーバを再起動してください）。

### ステップ2. Params の値を日本語化

次に `Title` や `Author` といった値も日本語化していきます。こちらはネームスペース等がさっぱり分からなかったので `i18n` Gem を使って行う事にしました。Gemfile に `gem 'i18n'` を追加して `bundle install` を実行したら作業開始です。

まず `p` などで確認してもらうと分かりますが `params.error_messages` を取っている箇所で `params.errors` を表示させると次のような値が得られます。

```ruby
{:book=>{:title=>["みたしとらん！"], :author=>["みたしとらん！"]}}
```

この構造であればロケールファイルを作って翻訳させる事ができそうです。 `config/locales` ディレクトリを作成し `config/locales/ja.yml` として次のような値を設定してみます。

```yaml
ja:
  book:
    title: "タイトル"
    author: "著者"
```

このロケールファイルを使うための設定を行わないといけないので `config/initializers/locale.rb`　を作成して起動時に `i18n` Gem がセットアップさせるよう変更します。

```
# config/initializers/locale.rb
require "i18n"

I18n.load_path = Dir[Hanami.root.join("config/locales/*.yml")]
I18n.available_locales = :ja
I18n.default_locale = :ja
I18n.backend.load_translations
```

これはサーバ起動時にしか読み込まれないと思うので、ここまで書いたらサーバを再起動してください。

次にテンプレート側に変更を加えます。本筋に関係ないので省きましたが `haml` テンプレートも問題なく使えます。

```haml
- unless params.valid?
  .errors
    %h3 There was a problem with your submission
    %ul
      - params.errors.each do |model, attrs|
        - attrs.each do |attr, messages|
          - messages.each do |message|
            %li="#{I18n.t("#{model}.#{attr}")}#{message}"
```

少し分かりにくいかもしれませんが

```ruby
{:book=>{:title=>["みたしとらん！"], :author=>["みたしとらん！"]}}
```

の内容をもとにエラーメッセージが日本語で表示されるようになっています。動作させてみた結果はこんな感じでした。

<img width="448" alt="スクリーンショット 2017-09-22 16.45.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/202869/4e93b642-509d-3c1c-3929-2480191c910a.png">

文言の是非はともかく、意図した動作をしている事はお分かり頂けたかと思います。

### 課題

実際に動作させていて気がついた問題点がいくつかありました。

#### hanami アプリケーション自体にロケーリングは存在しない？

全体、あるいは `apps` 単位でコンフィグを書けそうな気がしたのですが、ドキュメントなど見つける事ができませんでした。

実はステップ1でロケールファイルを `en` のままにしていたのは他の言語に変更する方法が分からなかったためです。

それなら、そこも `i18n` を使うように設定すればいいんじゃないか？と感じた方もいらっしゃると思いますが…

#### config.messages = :i18n が受け付けられない

コントローラーの変更で `config.messages = :yaml` と書いていましたが `hanami` のバリデーションでは `:i18n` も指定可能とされています。

しかし、少なくともコントローラーに直接記述した限りでは何故か例外が発生してしまいました。必要な設定を忘れているなどの問題かもしれませんが、今のところ動作に至っていません。

#### validation クラスの切り出し

単純に validation クラスだけを作成してしまうと `new` と `create` アクションの両方で同じテンプレートを使っているため、テンプレートの都合のためにロジック側を修正する必要が生じてしまい良さそうには思えません。

るびまで紹介されている `Hanami::Interactor` を使って切り出せば良さそうですが、何となく歯切れの悪い説明がされていて気になります（きれいに役割の分担ができているので問題なさそうに見えるのですが、どのあたりで議論があり、何がまとめきれていないのかは把握していません）。

### 最後に

実際に `hanami` を今すぐプロダクトで使いたい！と思った場合、上のような問題が発生するたびに自力で解決していくしか無さそうで、まだリスクがありそうに感じたというのが正直なところです。

まずは時間をかけてドキュメントやソースコードを追っていく事や機能面での妥協が許される会社内の案件や自分で使うツールなどで試しに採用して、考え方を理解していくのが良さそうに思いました。