validationのエラーメッセージ(error_messages_for)の日本語化

何が問題か

RubyOnRalsには、ライブラリとして便利な機能がデフォルトで提供されている。「error_messages_for」もその1つ。これは、ユーザーがフォームからデータを送信してきた時、内容にもれがあったり、値が不正であることをメッセージとして出力するもの。
しかし、デフォルトは英語表記。しかも、カラム名を使用してしまうのであまりよくない。そこで、これを日本語化する。

解決策

主に3つあると思う。

ActiveHeartは、作成者が非推奨していた。何らかの脆弱性なのかな。ググれば出ると思う。このライブラリの中に日本語化をサポートするものがある。
ruby-gettextも日本語化をサポートするが、こちらは日本語化だけでなく、様々な言語にアプリケーションを対応させることができる。世界向けサービスして、多くの言語に対応させたい時に使える。
上のようなライブラリを使っても良いが、今回は日本語化するだけなので、「3」の自分でやることにする(笑)

例として、次のようなものを用意する。(簡略化のため、HTMLのタグは省略)

  • user.rb
class User < ActiveRecord::Base
  SEX_TYPES = [["","man"],["","woman"]]
  
  validates_presence_of :name
  validates_inclusion_of :sex, :in => SEX_TYPES.map{|humanized_data,data| data}
end
  • entry.rhtml
<%= error_messages_for :user %> 

<% form_for :user, :url => {:action => :save_user} do |form| -%>
  <%= form.text_field :name, :size => 40 %>
  <%= form.select :sex,
                   User::SEX_TYPES,
                  :prompt => "性別"
  %>
  <%= submit_tag("登録") %>
<% end -%>

この状態で、nameのフィールドを空にして、また、なんらかの方法でsexを「man,woman」以外の値をフォームで送信すると、デフォルトでは、以下のようなメッセージが出力される。




2 errors prohibited this order from being saved

There were problems with the following fields:

  • Name can't be blank
  • Sex is not included in the list

  • ソース
<div class="errorExplanation" id="errorExplanation">
  <h2>2 errors prohibited this order from being saved</h2>
  <p>There were problems with the following fields:</p>
  <ul>
    <li>Name can't be blank</li>
    <li>Sex is not included in the list</li>
  </ul>
</div>

これを日本語化する。

以下の手順で解決する
  • 1.「Name」等のカラム名を日本語かす(最終的な解決策はこちら
  • 2.「can't be blank」等の個々のエラーメッセージを日本語化する(最終的な解決策はこちら
  • 3.「2 errors〜fields:」までの全体のエラーメッセージを日本語化する(最終的な解決策はこちら
1.「Name」等のカラム名を日本語かする

まず、どうやてってメッセージが表示されるのかを理解する

header_message = 
 "#{pluralize(count, 'error')} prohibited this #{(options[:object_name] ||params.first).to_s.gsub('_', ' ')} from being saved"

error_messages = objects.map {|object| object.errors.full_messages.map {|msg|content_tag(:li, msg) } }

content_tag(
              :div,
              content_tag(options[:header_tag] || :h2, header_message) <<
              content_tag(:p, 'There were problems with the following fields:') <<
              content_tag(:ul, error_messages),
              html
           )

まず、「"#{pluralize(count, 'error')} prohibited this〜」ってとこが、手順3で日本語化する文章の部分だ。後の話になるが、これがメソッド内に直接かかれているってことは、上書きして〜ってことはできない。
注目すべきは以下の部分

error_messages = objects.map {|object| object.errors.full_messages.map {|msg|content_tag(:li, msg) } }

「contend_tag(:li, msg)」は、「<li>msg</li>」を生成する。つまり、このコードが、「<li>Name can't be blank</li>」を生成している。そして、「object.errors.full_messages」の部分が、「Name can't be blank」を生成している。よって、この部分を調べてみる。

def full_messages
  full_messages = []

  @errors.each_key do |attr|
    @errors[attr].each do |msg|
      next if msg.nil?

      if attr == "base"
        full_messages << msg
      else
        full_messages << @base.class.human_attribute_name(attr) + " " + msg
      end
    end
  end
  
  full_messages
end

ここだ。

full_messages << @base.class.human_attribute_name(attr) + " " + msg

このmsgが「can't be blank」だ。
@errors.each_keyはエラーが発生したフィールドの対応するキー、つまり、["name","sex"]という配列。
そして、@errors[:attr]には、対応するメッセージが入っているということだ。今回の場合、@errors["name"] #=> "can't be blank"ということ。
しかし、メッセージは「Name can't〜」となっている。この「Name」はどっからでてきたのか。それが、「@base.class.human_attribute_name(attr) 」の部分だ。つまり、ここのメソッドをいじれば良さそう。(実際は直接いじるのではなく、オーバーライドする)さっそく調べる。

  • AtiveRecord::Base.human_attribute_name
def human_attribute_name(attribute_key_name) 
  attribute_key_name.humanize
end

このhumanizeによって"name"を"Name"にしている。しかし、humanizeメソッドには引数は渡せないので、オーバーライドするならこの「human_attribute_name」メソッドだろう。これは、ActiceRecord::Baseのクラスメソッドなので、Userモデル(これはActiveRecord::Baseを継承している)でオーバーライドすればよい。その方法は以下のようになる。

1.「Name」等のカラム名を日本語かするの解決策
class User < ActiveRecord::Base
  HUMANIZED_KEY_NAMES = {
    "name" => "名前",
    "sex" => "性別"
  }
  
  SEX_TYPES = [["","man"],["","woman"]]
  
  validates_presence_of :name
  validates_inclusion_of :sex, :in => SEX_TYPES.map{|humanized_data,data| data}

  class << self
    def human_attribute_name(attribute_key_name)
      HUMANIZED_KEY_NAMES[attribute_key_name] || super
    end
  end
end

ハッシュを使って変換を行う。ハッシュに登録されていない場合は、元の「human_attribute_name」に処理を渡す。

2.「can't be blank」等の個々のエラーメッセージを日本語化する

さっき、ActiceRecord::Errors#full_messagesを見た際に@errors[:attr]で、そのカラムに対するエラーメッセージを参照できることが分かった。なので、@errors[:attr]に代入しているところを探す。

def add(attribute, msg = @@default_error_messages[:invalid])
  @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
  @errors[attribute.to_s] << msg
end
def add_on_blank(attributes, msg = @@default_error_messages[:blank])
  for attr in [attributes].flatten
    value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
    add(attr, msg) if value.blank?
  end
end

この2つで、「Name」フィールドが空の場合、@errorsにその内容を記録している。add_on_blankのfor文の最後のadd(attr, msg)でaddを呼び、そこで@errors[:name]にmsg(エラーメッセージ)が登録される。
よく見ると、msgが@@default_error_messagesハッシュのエントリーで初期化されている。これが、「can't be blank」メッセージを出しているっぽい。探す。

 @@default_error_messages = {
      :inclusion => "is not included in the list",
      :exclusion => "is reserved",
      :invalid => "is invalid",
      :confirmation => "doesn't match confirmation",
      :accepted  => "must be accepted",
      :empty => "can't be empty",
      :blank => "can't be blank",
      :too_long => "is too long (maximum is %d characters)",
      :too_short => "is too short (minimum is %d characters)",
      :wrong_length => "is the wrong length (should be %d characters)",
      :taken => "has already been taken",
      :not_a_number => "is not a number"
    }

 cattr_accessor :default_error_messages

これをアプリケーション側でオーバーライドする。しかし、ApplicationRecordのようなモデルから共通参照できるものはないので、「environment.rb」にてオーバーライドする。

2.「can't be blank」等の個々のエラーメッセージを日本語化するの解決策
  • environment.rb
# Include your application configuration below

ActiveRecord::Errors.default_error_messages.merge!(
  :blank => "が入力されていません",
  :inclusion => "が適切な値ではありません"
)

今回の場合は、「blank」と[inclusion]が関係あるので、merge!メソッドでそこのみ上書き。後は、元のメッセージを使う。


3.「2 errors〜fields:」までの全体のエラーメッセージを日本語化する

今まではデフォルト設定をオーバーライドしてきたが、ここに関しては少し違う。ActionView::Helpers::ActiveRecordHelper#error_messages_forの一部で見たように、メソッド内にメッセージが直接書かれているので、これは、一度error_messages_forを使って生成されたメッセージに対して、置換を行う方法でいけると思う。具体的には次のようになる。このコードは、ApplicationHelperに記述する。

3.「2 errors〜fields:」までの全体のエラーメッセージを日本語化するの解決策
def ja_error_messages_for(*params)
  result = error_messages_for(*params)
  result.sub!(/<h2>(\d+).*<\/h2>/) do 
    "<h2>入力項目に#{$1}つのエラーがあります</h2>"
  end
  result.sub!(/<p>.*<\/p>/,"")
end

英語のメッセージの部分を抽出し、日本語の文で置き換える。


合わせてentry.rhtmlのerror_messages_forの部分を次のように変更

  • entry.rhtml(修正版)
<%= ja_error_messages_for :user %> 

実行結果



入力項目に2つのエラーがあります

  • 名前 が入力されていません
  • 性別 が適切な値ではありません

追記

本当は、3については次のようにやりたい

alias :old_error_messages_for :error_messages_for
def error_messages_for(*params)
  result = old_error_messages_for(*params)
  result.sub!(/<h2>(\d+).*<\/h2>/) do 
    "<h2>入力項目に#{$1}つのエラーがあります</h2>"
  end
  result.sub!(/<p>.*<\/p>/,"")
end

しかし上手く行かない。なぜかスーパークラスのerror_messages_forが呼ばれてしまう><;