Rails internationalization - removing validation message prefix
Current ActiveRecord implementation always prepends an error message with the attribute name. This is because of design weakness in the handling of human messages in Rails.
The data should be always interpolated into the message, string concatenation should be never used.
Good:
MY_MESSAGE = '%{attribute} is invalid'
return MY_MESSAGE % {:attribute => 'email'}
Bad:
MY_MESSAGE = 'is invalid'
return MY_MESSAGE + ' ' + 'email'
… because the correct order of the words in a sentence depends on language.
Generally it is not possible to create a message in human language by string concatenation. Only interpolation universally works!
Still bad (from validations.rb):
attr_name + I18n.t('activerecord.errors.format.separator',
:default => ' ') + message
I18n.t would not help, if you use +.
Our custom message ‘Gadget should have at least one screenshot.’ is corrupted by a prefix to ‘Screenshots Gadget should have at least one screenshot.’
I hope one day it can be fixed in Rails by substantially refactoring the ActiveRecord validations and internationalization subsystem. For now I’m fixing it with a dash of metaprogramming and monkey patching.
module ActiveRecord
class Errors
# allow a proc as a user defined message
def add(attribute, message = nil, options = {})
if options[:default].is_a?(Proc) # this is new
message = options[:default].call(attribute)
def message.full? do
true
end
end
message ||= :invalid
message = generate_message(attribute, message, options) if
message.is_a?(Symbol)
@errors[attribute.to_s] ||= []
@errors[attribute.to_s] << message
end
def full_messages(options = {})
full_messages = []
@errors.each_key do |attr|
@errors[attr].each do |message|
next unless message
if attr == "base"
full_messages << message
else
attr_name = @base.class.human_attribute_name(attr)
if message.respond_to? :full? and message.full? # new switch
full_messages << message
else
# messages for humans based on string concatenation are
# inherently broken - should not be used!
full_messages << attr_name +
I18n.t('activerecord.errors.format.separator',
:default => ' ') + message
end
end
end
end
full_messages
end
end
end
Explanation:
- copy a paste the code into a initializer (underneath config/initializers folder)
- all translated messages created by a proc get the special
full?tag which is later used in full_messages to bypass the attr_name addition - you can use attribute name in your proc to interpolate into the localized error message
This dirty hack is only needed because of design weekness of rails. Some conceptial thoughts:
Short and full error messages
Distinction between short and full error message does not make sence
- one can not be universarly infered from another for every human language
- specifing both manually hardly makes sense So there should be just error message with optional placeholder for the attribute name.
String interpolation everywhere
The data should be always interpolated into the error messages, never concatenated, because the order of the words in a sentence and gramatical rules differs from language to language.
Non-programmer capable translation files
YAML files and ruby symbol driven translation keys are unsuitable for non-programmers and people without deep knowledge of the application to be translated.
See GNU gettext for a deeper explanation of internationalization concepts that work!