Translating ActiveRecord
- September 2nd, 2008
- Posted in Ruby on Rails . Tips
- Write comment
Updated October 10th, 2008 to be up to date with Rails 2.2 RC1 release.
With Rails 2.2 releasing any day now, I want to show you how to translate ActiveRecord related stuff. It is quite easy, once you know where to keep your translations. Here is a complete guide to using all built in translation methods!
Contents:
- Scenario
- Setting up
- Translating models
- Translating attributes
- Translating default validations
- Interpolation in validations
- Model specific messages
- Attribute specific messages
- Defaults
- Using error_messages_for
- Conclusion
Scenario
Suppose we’re building a forum. A forum has several types (e.g. admin) of users and suppose we want to make the most important users into separate models using Single Table Inheritance (sti). This gives us the most complete scenario in showing off all translations:
class User < ActiveRecord::Base validates_presence_of :name, :email, :encrypted_password, :salt validates_uniqueness_of :email, :message => :already_registered end class Admin < User validate :only_men_can_be_admin private def only_men_can_be_admin errors.add(:gender, :chauvinistic, :default => "This is a chauvinistic error message") unless gender == 'm' end end
Setting up
Make sure you’re running Rails 2.2 or Rails edge (rake rails:freeze:edge)
Now let’s translate all this into LOLCAT, just for fun. We need a directory to place the locale files:
mkdir app/locales
And we need to load all files as soon as the application starts. So we make an initializer:
# config/initializers/load_translations.rb %w{yml rb}.each do |type| I18n.load_path += Dir.glob("#{RAILS_ROOT}/app/locales/**/*.#{type}") end I18n.default_locale = 'LOL'
This approach is recommended, because loading files is not something you want to do during the request, when it should already be available. Setting you’re locale like this is probably not recommended, but it’s easy, if you’re just using one language.
Translating models
Next, we’re going to make some simple translation files. All ActiveRecord translations need to be in the activerecord scope. So when starting your locale file, it starts with the locale name, followed by the scope.
LOL:
activerecord:
models:
user: kitteh
admin: Ceiling catLet’s try this out in script/console
>> User.human_name => "kitteh" >> Admin.human_name => "Ceiling cat"
It’s nice to know that the method human_name is used by error messages in validations too. But we’ll come to that in just a second.
If you didn’t specify the translation of admin, it would have used the translation of user, because it inherited it.
Translating attributes
We could append to the same file, but I choose to make a new file, because it keeps this post clean and it’s a bit easier to see how the scoping works.
LOL:
activerecord:
attributes:
user:
name: naem
email: emaleAnd let’s try this again:
>> User.human_attribute_name("name") => "naem" >> Admin.human_attribute_name("email") => "emale"
Once again, you can see that single table inheritance helps us with this.
Both human_name and human_attribute cannot really fail, because if no translation has been specified, it would return the normal humanized version. So if you’re making an English site, you don’t really need to translate models and attributes.
Translating default validations
Let’s translate a few default messages:
LOL:
activerecord:
errors:
messages:
blank: "can not has nottin">> u = User.new => #<User id: nil, etc> >> u.valid? => false >> u.errors.on(:name) => "can not has nottin"
Interpolation in validations
You have more freedom in your validation messages now. With every message you can interpolate the translated name of the model, the attribute and the value. The variable ‘count’ is also available where applicable (e.g. validates_length_of)
LOL:
activerecord:
errors:
messages:
already_registered: "u already is {{model}}">> u.errors.on(:email) => "u already is kitteh"
Remember to put quotes around the translation key in yaml, because it’ll fail without it, when using the interpolation brackets.
Model specific messages
A message specified in the activerecord.errors.models scope overrides the translation of this kind of message for the entire model.
LOL:
activerecord:
errors:
messages:
blank: "can not has nottin"
models:
admin:
blank: "want!">> u.errors.on(:name) => "can has nottin" >> a = Admin.new => #<Admin id: nil, etc> >> a.valid? => false >> a.errors.on(:salt) => "want!"
Attribute specific messages
Any translation in the activerecord.errors.models.model_name.attributes scope overrides model specific attribute- and default messages.
LOL:
activerecord:
errors:
models:
admin:
blank: "want!"
attributes:
salt:
blank: "is needed for cheezburger">> a.errors.on(:name) => "want!" >> a.errors.on(:salt) => "is needed for cheezburger"
Defaults
When you specify a symbol as the default option, it will be translated like a normal error message, just like you’ve seen with :already_registered. When default hasn’t been found, it’ll try looking up the normal key you have given. With :already_registered, that key has already been set by Rails, because we’re using validates_uniqueness_of.
When you specify a string as default value, it’ll use this when no translations have otherwise been found.
>> a.gender = 'f' => "f" >> a.valid? => false >> a.errors.on(:gender) => "This is a chauvinistic error message"
Using error_messages_for
When you want to display the error messages in a model in a view, most people will user error_messages_for. These messages are also translated. The message has a header and a single line, saying how many errors there are. Here are the default English translations of these messages. I will leave it up to you to translate it to LOLCAT. Win a lifetime supply of cheezburgerz* with this mini-competition
en-US:
activerecord:
errors:
template:
header:
one: "1 error prohibited this {{model}} from being saved"
other: "{{count}} errors prohibited this {{model}} from being saved"
body: "There were problems with the following fields:"There is one slight problem with the messages it displays. error_messages_for uses the errors.full_messages in it’s list. This means that the attribute names will be put before it. Of course these will be translated with human_attribute_name, but it might not always be desirable. In other languages than English it’s sometimes hard to formulate a nice error message with the attribute name at the beginning. This will have to be fixed in later Rails versions.
Conclusion
I hope you’ll agree with me that these translation options for ActiveRecord are really nice! This is what we have been waiting for. Too bad I was a bit too late with my adjustments, so form labels don’t translate by default. I did build it, but Rails was already feature frozen by then. I will probably post a plugin that adds this functionality. Same goes for a i18n version of scaffold.
Please keep coming back to my site, or add the RSS feed to your favorite reader.
Of course, stay in touch with the i18n mailinglist. A lot of people are putting a lot of effort into the project. New plugins and gems solving problems problems rapidly. I18n is one of the more difficult things to do, so if you have a special insight in a language, please contribute!
Happy devving!
PS. Damn! I wish I was in Berlin right now!
* invisible cheezburgerz only
Such a complete post about i18n, but still lacking a “but i eated it” joke.
Can you write about how to use translated columns of database in rails? For example we have table named ‘blog’, and I want to translate it on several languages: fr, en, ru. How to do that?
BLOG:
title_en
title_ru
title_fr
text_en
text_ru
text_fr
created_at
updated_at
Now I need to use for example russian language, how to do that? In globalize it’s automatically select’s correct language.
Something like that: http://www.samlown.com/en/page/RailsTranslateColumnsPluginReadme
Dmitry: Have a look at my new post: http://iain.nl/2008/09/translating-columns/
One work around to the translation of columns in the database is to use Custom validations
Ex:
class Comment < ActiveRecord::Base
belongs_to :post
def before_validation
self.author.strip!
self.author_email.strip!
end
private
#Translates the validates_presence_of into norwegian
def validate
errors.add_to_base(“Vennligst skriv inn et brukernavn”)if self.author.blank?
errors.add_to_base(“Vennligst skriv inn en epost”) if self.author_email.blank?
end
end
@Andreas That breaks the ability to highlight the fields in the form.
Bless up young warrior. This has got to be the best translation tutorial ever to be handed out. Keep it up!
Hi Iain,
It would be nice if you placed the whole LOL.yml file somewhere just to help to understand the various scopes that the keys may have, I just spent some time trying to figure out the correct scopes ;D
Thanks for the great tutorial!
I had 2 problems with my test application:
1) if the model includes a date field (something like a birthday, rendered in the view with a multiple select) the view breaks (“can’t convert Symbol to String” error on line )
2) error template body seems to accept only one string, not “one” and “other” as in the example.
Does anyone has a cure for those problems?
I have the same problem Andrea has, I keep getting ”can’t convert Symbol to String”, which makes the whole localization thing useless… still
Andrea, jorge:
The problem is that you didn’t translate enough date and time options. You need to have them all in your locale files for date_select to work…
Have a look at this URL for common locales: http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale
Hey Iain,
I’m still new to rails and trying to get around ti and your plugin seemed very helpful and your post very good tfor my comprehension.
However, I did install the plugin on my app and I placed the translation of my labels in the activerecord… once I hit the form, I immediately get an error orf this kind:
ActionView::TemplateError (syntax error on line 33, col 11: ` user:’) on line #1 of app/views/users/edit.html.erb:
1:
2:
3:
4:
as you can see, I already have an entry in my locale:
en_US:
profile:
title: “Profile”
activerecord:
attributes:
user:
login: “Login”
email: “Email”
name: “Name”
Any idea would be greatly appreciated… the synthax was working before i installed your plugin and now it doesn’t…
Thanks in advance
Xavier
ooops, it stripped the code
it was
http://pastie.org/333707
Sorry about the 3 posts
Thanks again,
Xav
nevermind, it was a problem in the yaml file synthax, thanks
Hi,
just found your blog entry yesterday and it really helped me to get started with the brand new i18n stuff in Rails 2.2. I’m currently converting an application from gettext to i18n. Here’s my question:
Is there a way to specify general translations for some model attributes (like you can give some default translations for error messages)? For example, many of my models use a “date” column and it would be great if I only need to maintain one translation. Something like this would be great (but is not working):
activerecord => {
:attributes => {
:date => “The date” # general translation
},
:user => {
:date => “Birthdate” # model specific translation
}
}
regards,
red
@redrat
have a look at this plugin: http://github.com/dcrec1/activerecord_i18n_defaults
it allows you to do:
nl: activerecord: attributes: _all: updated_at: "Bijgewerkt op" created_at: "Gemaakt op" date: "Datum" user: date: "Verjaardag"(note the underscore before _all)
@iain: Thanks! Works as expected!
New bug: If you have in file “config/fr.yml”
activerecord: models: attributes: article: title:
“Le champ Titre”
price: “Le champ Prix errors: messages: exclusion: “est déjà pris !” notanumber: “n’est pas un nombre”
And in view: for exclusion message, you have “Le champ Titre est déja pris !”. So, you aren’t message “Le champ Prix n’est pas un nombre” but “Price n’est pas un nombre”. A bug, missing “t” function before actionview ?
Hello. And Bye.
Was the plugin merged in Rails 2.3.2? I removed it, and the localization didn’t work anymore for the labels.
@Thomas, no it wasn’t included into Rails 2.3, so you’ll still have to install the i18n_label plugin
Using 99translations.com helped us to avoid annoying mistakes in YML.
template: body:
does not take one: and other:
You can only give one string. English rails behaves the same way.
I just checked the source.
@nasmorn: You’re right. I updated the post. Thanks
If your model is composed of 2 or more words like PhysicalPerson, CustomerService, FinalRelease, etc., when showing error message on validation rails doesn’t translate it and put the default english model’s human name like instead of (in my case it shoul be in French) ‘Personne Physique’. Nevertheless, all the attributes are translated correctly. I don’t even know where to look for. With other single-word models (Service, User, etc.) everything is OK. I’m on rails 2.3.4. Any idea ? Thank you.
What is strange, when I checked it in the console like that:
Loading development environment (Rails 2.3.4)
>> PhysicalPerson.human_name
PhysicalPerson.human_name
=> “Personne physique”
>>
everything was correct. So where is the error ?