Active Recordエラー情報のローカライズ
11402 ワード
ActiveRecordエラーメッセージは、すでにフォーマットされている英語の文字列であり、ローカライズ処理が不便です.ローカライズするには、エラーデータを保持し、表示時にローカル言語にフォーマットする必要があります.しかしActiveRecordはエラーメッセージを文字列にフォーマットするのが早すぎて、基本的にローカライズの道を断ち切った.
ActiveRecordエラーメッセージをローカライズするために、パッチを適用します.このコードの検証を確認すると、フォーマット文字列は各検証方法に分散しており、一つ一つ書き換えるのはあまりお得ではありません.幸いにもdefaultが呼び出されましたerror_Messagesメソッドでエラー情報文字列を取得するため,ここで少し文章を書くことを考える.
これでソースを絞めて、errorsの中は文字列ではなく、ValidateErrorオブジェクトになりました.検証コードでは「%」を使用して文字列をフォーマットするため、ValidateErrorクラスでも「%」:
ValidateErrorオブジェクトを返します.
テストでfullが見つかりますMessagesでエラーが発生しました.なぜなら、「」+msgを使用しているからです.このmsgは現在ValidateErrorオブジェクトです.自然にこのように加算することはできません.ValidateErrorで実現する方法も見つかりません.pythonとDで書き換えることができるようです.rメソッドは、rubyで似たようなものがあるかどうか分かりません.実現策が見つからなかった以上、full_Messagesも書き換えました.
これだけコードを付けても書き換えます...
次に、ローカライズを追加し、一時的にformatメソッドを追加して、ローカル言語のフォーマット文字列を受け入れることを考慮することができます.
太字行は予約された拡張です.
ローカリゼーションが可能になりました.
同じエラーメッセージが同じページに2言語表示されています.
error_messages_forメソッドはまだローカライズされていませんが、実際にはhelperにすぎません.まったく使わなくてもいいです.広く使われていることを考慮して、パッチもかけましょう.この方法では文字列が2箇所ハードコーディングされており,抽出が考えられる.またfull_Messagesメソッドも早すぎてエラーメッセージを文字列化してしまい、従来の機能に影響を及ぼさないように書き換えたerror_messages_forでは別の機能に近いメソッドを呼び出します.
使用できるようになりました.
同じページに2言語のエラーメッセージが表示されています.
残りの処理はErrors#addとErrors#addです.to_base,add_to_baseはあまり推奨されていません.パラメータが1つしかありません.既存の機能を維持する場合は、拡張の余地が小さく、機能の多点を増やす方法で、しばらく管理しないほうがいいです.addメソッドを修正しerrorsを使用するつもりです.add(:title,:must_number,"must number")とerrors.add(:title,“must number”)の2つの使い方で、前者はローカライズをサポートしています.add_のためto_baseもaddを呼び出すので、これを書き直せばいいです.
テストコード:
views/posts/_form.rhtml
効果図:
ActiveRecordエラーメッセージをローカライズするために、パッチを適用します.このコードの検証を確認すると、フォーマット文字列は各検証方法に分散しており、一つ一つ書き換えるのはあまりお得ではありません.幸いにもdefaultが呼び出されましたerror_Messagesメソッドでエラー情報文字列を取得するため,ここで少し文章を書くことを考える.
class ActiveRecord::ValidateError
attr_reader :error
def initialize(error, format, *args)
@error = error
@format = format
@args = args
end
def to_s
return @format if @args.empty?
@format % @args
end
end
class ActiveRecord::Errors
def self.default_error_messages
def self.default_error_messages
@@_error_messages
end
@@_error_messages = {}
@@default_error_messages.each do |key, value|
@@_error_messages[key] = ActiveRecord::ValidateError.new(key, value)
end
@@_error_messages
end
これでソースを絞めて、errorsの中は文字列ではなく、ValidateErrorオブジェクトになりました.検証コードでは「%」を使用して文字列をフォーマットするため、ValidateErrorクラスでも「%」:
class ActiveRecord::ValidateError
def % (*args)
self.class.new(@error, @format, *args)
end
end
ValidateErrorオブジェクトを返します.
テストでfullが見つかりますMessagesでエラーが発生しました.なぜなら、「」+msgを使用しているからです.このmsgは現在ValidateErrorオブジェクトです.自然にこのように加算することはできません.ValidateErrorで実現する方法も見つかりません.pythonとDで書き換えることができるようです.rメソッドは、rubyで似たようなものがあるかどうか分かりません.実現策が見つからなかった以上、full_Messagesも書き換えました.
class ActiveRecord::Errors
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.to_s
else
full_messages << @base.class.human_attribute_name(attr) + " " + msg[b].to_s[/b]
end
end
end
full_messages
end
end
これだけコードを付けても書き換えます...
次に、ローカライズを追加し、一時的にformatメソッドを追加して、ローカル言語のフォーマット文字列を受け入れることを考慮することができます.
class ActiveRecord::ValidateError
def format(format)
format ||= @format
[b]return format if @error == :string[/b]
return format if @args.empty?
format % @args
end
end
太字行は予約された拡張です.
ローカリゼーションが可能になりました.
<%
zh_cn_error_messages = {
:inclusion => " ",
:exclusion => " ",
:invalid => " ",
:confirmation => " ",
:accepted => " ",
:empty => " ",
:blank => " ",
:too_long => " ( %d )",
:too_short => " ( %d )",
:wrong_length => " ( %d )",
:taken => " ",
:not_a_number => " " ,
:must_number => " "
}
en_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"
}
%>
<p><label for="post_title">Title</label><br/>
<%= text_field 'post', 'title' %>
<%= @post.errors.on(:title).map{|e| e.join("<br />") %></p>
<p><label for="post_title">Title</label><br/>
<%= text_field 'post', 'title' %>
<%= @post.errors.on(:title).map{|e| e.format(zh_cn_error_messages[e.error])}.join("<br />") %></p>
同じエラーメッセージが同じページに2言語表示されています.
error_messages_forメソッドはまだローカライズされていませんが、実際にはhelperにすぎません.まったく使わなくてもいいです.広く使われていることを考慮して、パッチもかけましょう.この方法では文字列が2箇所ハードコーディングされており,抽出が考えられる.またfull_Messagesメソッドも早すぎてエラーメッセージを文字列化してしまい、従来の機能に影響を及ぼさないように書き換えたerror_messages_forでは別の機能に近いメソッドを呼び出します.
class ActiveRecord::Errors
def full_messages_with_key
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
end
ActionView::Helpers::ActiveRecordHelper.module_eval do
def error_messages_for(*params)
options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {}
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
count = objects.inject(0) {|sum, object| sum + object.errors.count }
error_messages = options[:error_messages]
unless count.zero?
html = {}
[:id, :class].each do |key|
if options.include?(key)
value = options[key]
html[key] = value unless value.blank?
else
html[key] = 'errorExplanation'
end
end
header_message = options[:caption] || "#{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_with_key.map {|field, error| content_tag(:li, field + " " +
(error_messages.nil? ? error.to_s : error.format(error_messages[error.error]))) } }
content_tag(:div,
content_tag(options[:header_tag] || :h2, header_message) <<
content_tag(:p, options[:prompt] || 'There were problems with the following fields:') <<
content_tag(:ul, error_messages),
html
)
else
''
end
end
end
使用できるようになりました.
<%
<%= error_messages_for 'post' %>
<%= error_messages_for 'post', :error_messages => zh_cn_error_messages,
:prompt => " :",
:caption => " #{@post.errors.count} " %>
同じページに2言語のエラーメッセージが表示されています.
残りの処理はErrors#addとErrors#addです.to_base,add_to_baseはあまり推奨されていません.パラメータが1つしかありません.既存の機能を維持する場合は、拡張の余地が小さく、機能の多点を増やす方法で、しばらく管理しないほうがいいです.addメソッドを修正しerrorsを使用するつもりです.add(:title,:must_number,"must number")とerrors.add(:title,“must number”)の2つの使い方で、前者はローカライズをサポートしています.add_のためto_baseもaddを呼び出すので、これを書き直せばいいです.
class ActiveRecord::Errors
alias_method :add_old, :add
def add(attribute, msg = @@default_error_messages[:invalid], *args)
return add_old(attribute, ActiveRecord::ValidateError.new(msg, *args)) if msg.is_a?(Symbol)
unless msg.is_a?(ActiveRecord::ValidateError)
msg = ActiveRecord::ValidateError.new(:string, msg, *args)
end
add_old(attribute, msg)
end
end
テストコード:
class Post < ActiveRecord::Base
has_many :comments, :dependent => :destroy
validates_presence_of :title
validates_length_of :title, :in => 3 .. 5
def validate
if title.nil? || title.blank?
errors.add_to_base("You must specify a name or an email address" )
errors.add(:title, :length_must_great_than, "length must > %d", 3)
errors.add(:title, :must_number, "must number")
errors.add(:title, "Must Must")
end
end
end
views/posts/_form.rhtml
<%
zh_cn_error_messages = {
:inclusion => " ",
:exclusion => " ",
:invalid => " ",
:confirmation => " ",
:accepted => " ",
:empty => " ",
:blank => " ",
:too_long => " ( %d )",
:too_short => " ( %d )",
:wrong_length => " ( %d )",
:taken => " ",
:not_a_number => " " ,
:must_number => " ",
:length_must_great_than => " %d "
}
en_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"
}
%>
<%= error_messages_for 'post' %>
<%= error_messages_for 'post', :error_messages => zh_cn_error_messages,
:prompt => " :",
:caption => " #{@post.errors.count} " %>
<!--[form:post]-->
<p><label for="post_title">Title</label><br/>
<%= text_field 'post', 'title' %>
<%= @post.errors.on(:title).map{|e| e.format(zh_cn_error_messages[e.error])}.join("<br />") if @post.errors.on(:title) %></p>
<p><label for="post_title">Title</label><br/>
<%= text_field 'post', 'title' %>
<%= @post.errors.on(:title).map(&:to_s).join("<br />") if @post.errors.on(:title) %></p>
効果図: