Adventures with Ruby

Translating ActiveRecord

View Comments

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:

  1. Scenario
  2. Setting up
  3. Translating models
  4. Translating attributes
  5. Translating default validations
  6. Interpolation in validations
  7. Model specific messages
  8. Attribute specific messages
  9. Defaults
  10. Using error_messages_for
  11. 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 cat

Let’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: emale

And 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
=> #
>> 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
=> #
>> 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

Update

This post is old. Still, after 2 years, it still accounts for 10% of my daily traffic. How cool is that?
Seriously though, read the official Rails guide on this subject, for up to date information!

Written by Iain Hecker

September 2nd, 2008 at 12:45 am

Posted in iain.nl

  • http://ariekanarie.nl Arie

    Such a complete post about i18n, but still lacking a “but i eated it” joke.

  • http://ariekanarie.nl Arie

    Such a complete post about i18n, but still lacking a “but i eated it” joke.

  • http://dmitry.eu/ Dmitry

    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.

  • http://dmitry.eu/ Dmitry

    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.

  • http://dmitry.eu/ Dmitry
  • http://dmitry.eu/ Dmitry
  • http://iain.nl/2008/09/translating-columns/ Translating Columns • iain.nl

    [...] September 3rd • English, Howto’s and tips, Ruby on Rails Category Dmitry asked in the comments of my last post about translating ActiveRecord: Can you write about how to use translated columns [...]

  • http://iain.nl Iain Hecker

    Dmitry: Have a look at my new post: http://iain.nl/2008/09/translating-columns/

  • http://iain.nl Iain Hecker

    Dmitry: Have a look at my new post: http://iain.nl/2008/09/translating-columns/

  • http://lizz.no Andreas Lyngstad

    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

  • http://lizz.no Andreas Lyngstad

    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

  • http://iain.nl Iain Hecker

    @Andreas That breaks the ability to highlight the fields in the form.

  • http://iain.nl Iain Hecker

    @Andreas That breaks the ability to highlight the fields in the form.

  • http://iain.nl/2008/09/form-labels-in-rails-22/ Form Labels in Rails 2.2 • iain.nl

    [...] For more information about where to put your translations, read my post about translating ActiveRecord. [...]

  • http://iain.nl/2008/09/acts_as_translatable_model-plugin/ acts_as_translatable_model plugin • iain.nl

    [...] you can do familiar things with the class, just as you would do with normal ActiveRecord models. Supply a translation: nl-NL: [...]

  • http://iain.nl/2008/10/localizing-dates-and-times-in-rails-22/ Localizing Dates and Times in Rails 2.2 • iain.nl

    [...] I’m writing for internationalizing a Rails 2.2 application. Please read the first part, Translating ActiveRecord, if you haven’t already. This time I’m going to talk about how to localize dates and [...]

  • http://www.home.no/reddvinylene Redd Vinylene

    Bless up young warrior. This has got to be the best translation tutorial ever to be handed out. Keep it up!

  • http://www.home.no/reddvinylene Redd Vinylene

    Bless up young warrior. This has got to be the best translation tutorial ever to be handed out. Keep it up!

  • http://blog.codevader.com/ Maurício Linhares

    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!

  • http://blog.codevader.com/ Maurício Linhares

    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!

  • Andrea

    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?

  • Andrea

    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?

  • jorge

    I have the same problem Andrea has, I keep getting ”can’t convert Symbol to String”, which makes the whole localization thing useless… still :(

  • jorge

    I have the same problem Andrea has, I keep getting ”can’t convert Symbol to String”, which makes the whole localization thing useless… still :(

  • http://iain.nl/ iain

    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

  • http://iain.nl/ iain

    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

  • http://stormmybrain.com ZaAaV

    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

  • http://stormmybrain.com ZaAaV

    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

  • http://stormmybrain.com ZaAaV

    ooops, it stripped the code
    it was

  • http://stormmybrain.com ZaAaV

    ooops, it stripped the code
    it was

  • http://stormmybrain.com ZaAaV

    http://pastie.org/333707

    Sorry about the 3 posts :)

    Thanks again,

    Xav

  • http://stormmybrain.com ZaAaV

    http://pastie.org/333707

    Sorry about the 3 posts :)

    Thanks again,

    Xav

  • http://stormmybrain.com ZaAaV

    nevermind, it was a problem in the yaml file synthax, thanks :)

  • http://stormmybrain.com ZaAaV

    nevermind, it was a problem in the yaml file synthax, thanks :)

  • redrat

    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

    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

  • http://iain.nl/ iain

    @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)

  • http://iain.nl/ iain

    @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)

  • redrat

    @iain: Thanks! Works as expected!

  • redrat

    @iain: Thanks! Works as expected!

  • http://www.mokisystems.com/blog/using-rails-new-i18n-support-in-real-life-part-the-second/ Moki Systems Blog » Using Rails’ New I18n Support in Real Life: Part the Second

    [...] and another thing that really helped was using the human_name stuff built in to activerecord. So, for example, everywhere I found an “Agent” in the text instead of [...]

  • http://www.remmrit.com/translate Bookmarks about Translate

    [...] http://mamitamala.com/?p=780 – bookmarked by 4 members originally found by gerard0 on 2008-11-12 Translating ActiveRecord http://iain.nl/2008/09/translating-activerecord/ – bookmarked by 4 members originally found by [...]

  • Sam

    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 ?

  • Sam

    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 ?

  • http://patrickespake.wordpress.com/2009/01/31/ruby-on-rails-22-como-usar-i18n-traduzir-as-mensagens-de-validacao-do-model-e-atributos-do-model/ Ruby on Rails 2.2 como usar I18n, traduzir as mensagens de validação do model e atributos do model « Patrick Espake

    [...] tradução do nome do model eu não consigui fazer funcionar, segui isso aqui: http://iain.nl/2008/09/translating-activerecord/ mas não funcionou. « Ruby on Rails validação [...]

  • kookimebux

    Hello. And Bye. :)

  • kookimebux

    Hello. And Bye. :)

  • Thomas

    Was the plugin merged in Rails 2.3.2? I removed it, and the localization didn’t work anymore for the labels.

  • Thomas

    Was the plugin merged in Rails 2.3.2? I removed it, and the localization didn’t work anymore for the labels.

  • http://iain.nl iain

    @Thomas, no it wasn’t included into Rails 2.3, so you’ll still have to install the i18n_label plugin

  • http://iain.nl iain

    @Thomas, no it wasn’t included into Rails 2.3, so you’ll still have to install the i18n_label plugin

  • Simon

    Using 99translations.com helped us to avoid annoying mistakes in YML.

  • Simon

    Using 99translations.com helped us to avoid annoying mistakes in YML.

  • nasmorn

    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

    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.

  • http://iain.nl Iain Hecker

    @nasmorn: You’re right. I updated the post. Thanks

  • http://iain.nl Iain Hecker

    @nasmorn: You’re right. I updated the post. Thanks

  • Javix

    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.

  • Javix

    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.

  • Javix

    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 ?

  • Javix

    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 ?

  • loullylag

    Click here to get VPN service!

    Anonymous surfing
    Using our service you’ll be fully anonymous in the Internet. Hide your IP address, and nobody will know that strange visitor from Germany ( Great Britain, Estonia and so ), is you.

    Full access to network
    You can use any services, access any sites and use any software with us. BitTorrent, Skype, Facebook, MySpace, Twitter, Pocker .. we have no restrictions.

    Traffic protection
    Don’t worry, from this moment all you data will be protected using 256 bit Blowfish encryption algorithm. Nobody can access your internet data.

    Wide variety of countries
    You can choose one of over twenty high speed servers located in different parts of the world, from South America coast to islands in Indian Ocean.

    Related keywords:
    anonymous surfing review
    proxy server vpn
    anonymous secure surfing
    proxy vpn
    anonymous vpn free
    internet explorer vpn
    vpn dial up
    ssl vpn
    Traffic protection
    anonymous surfing freeware
    anonymous surfing software
    vtunnel
    anonymous surfing vpn
    best anonymous browser
    surf the web anonymous
    best anonymous surfing
    anonymizer anonymous surfing review
    firefox anonymous surfing
    Virtual Private Networks
    Free Vpn Client Software
    anonymous surfing software
    anonymous surfing software
    anonymous proxy
    Buy Cheap Zoloft

blog comments powered by Disqus
Fork me on GitHub