Adventures with Ruby

Finally! Rails goes i18n!

View Comments

Important!

I18n.store_translations isn’t available anymore and all scopes have changed. Please DON’T use this anymore as a literal guide, because a lot has changed since I wrote this. Don’t use the i18n_yaml plugin, because most of it’s functionality has been merged into Rails. Please look for one of my more recent posts here.

It has been some time, I know. It was a busy time. Since my last post I started working at Finalist IT Group, an exciting company doing exciting projects. Right now I’m doing a very demanding project for Eindhoven city council, which is quite far away from my home (well, in Holland it is anyway).

But time hasn’t stopped. Ruby on Rails is busy advancing to version 2.2. One new feature in the upcoming Rails version has caught my eye and my undivided love and attention. It’s the I18n-module built into Rails! It is already available if you’re running edge.

Rails 2.2 ships with it’s language elements already indexed and a simple backend (called SimpleBackend) for handling translations. The whole idea is to keep it as simple as possible so any developer can implement their own way of doing the i18n-dance. The SimpleBackend keeps it translations in memory. Soon a new version of Globalize will arrive for storing translation in a database and no doubt a gettext based backend will appear soon too.

Using the SimpleBackend, translating your database is dead easy. Amongst it’s features are (in no particular order):

  • Scoping, for sorting your keys and thus avoiding name clashes
  • Bulk look up of multiple translations
  • Default translations for when the translation hasn’t been found
  • Interpolation, inserting values in the middle of translations
  • Pluralization, handling multiple translations depending on a value being plural or singular
  • Having multiple default translation, for using another keys as default
  • Localization of dates and times

I can’t go into each features into one post, so I’ll be posting more in a while. First, let us take a look at the basics:

I18n.store_translations( 'en-US', { :hello => 'hello' } )
I18n.store_translations( 'nl-NL', { :hello => 'hallo' } )
I18n.translate( :hello ) # => "hello" (en-US is the default locale)
I18n.locale = 'nl-NL'
I18n.translate( :hello ) # => "hallo"

The big advantage is that I18n is now baked into Rails, so all you’re favorite “railties” will automagically be translated (en-US translations are of course default and provided for, so you don’t have to use I18n if you don’t want to). Amongst others, cool stuff like date-formats, number and currency formatting and default ActiveRecord error messages are indexed. Here is an example:

I18n.store_translations( 'nl-NL', { :support => { :array => { :sentence_connector => 'en' } } } )
%w{a b c}.to_sentence # => "a, b, en c" (look mama, no I18n.translate call!)

In date(and -time) objects you need to call the method localize to translate. You can define your own preferred formats too, so no more need for those ugly strftime method calls in your code.

I18n.localize( Time.now, :format => :long ) # "vrijdag, 8 augustus 2008, 20:51:15"

ActiveRecord column names and error messages are easily translated too! You just have to know the proper scope. What you need to do is store translations in these scopes and Rails will automatically use it for you. In my opinion it is a bit of mess. Hopefully it’ll be changed to something less scattered soon.

In this example, I have a model called Post and it has an attribute named ‘title’.

Update! The proper way to translate ActiveRecord is described here!

I18n.store_translations( 'en-US', { 
  :active_record => { 
    :error => { 
      :header_message => "default message for error_messages_for", 
      :post => "When the model name is not what you like" 
    },
    :error_messages => { 
      :blank => "your default 'cannot be blank' message", 
      :custom => { 
        :post => { 
          :title => { 
            :blank => "error message for @post.title only" 
          }
        }
      }
    }, 
    :human_attribute_names => { 
      :post => { 
        :title => "Translation of the column name" 
      } 
    } 
  } 
} )
I18n.translate :'active_record.human_attribute_names.post.title' 
# => "Translation of the column name"

Unfortunately there isn’t a good list of which translations are available. I will try to make one soon.

There are some limitations to the default I18n implementation. How you want to incorporate it in your site is completely up to you. Also, how and where you keep your translations has not been implemented. So you have to load a bunch of files yourself in which you keep translations.

But it gets easier. I made a plugin, called i18n_yaml, which handles all of this for you. It is not a different backend, but rather an extension to SimpleBackend. It stores its translation files in yaml-files found in app/locales. It also provides a before_filter to find the appropriate locale. In other words: everything you need to make SimpleBackend useful! Like Rails 2.2, it is not finished yet, but you can have a go at it of course.

Would you like to contribute to i18n? Join the mailinglist. Rails i18n also launched it’s own website: rails-i18n.org. There are a number of tutorials and articles already available, listed here.

This concludes the first part of the Rails i18n introduction. I’ll be posting some more insights into i18n soon, so stay tuned!

Update:

Locate the locale.yml files in Rails to find all possible translations. Here is a pastie with everything translated to Dutch.

Written by Iain Hecker

August 8th, 2008 at 10:37 pm

Posted in Uncategorized

  • Glenn Powell

    Hi Iain,
    Good post, a couple things though. First should the human_attribute_names be inside the active_record Hash instead of error_messages? And secondly, (at least the edge version of) Rails doesn’t currently utilize the active_record.human_attribute_names lookup for it’s form labels, error messages, etc.

    http://rails.lighthouseapp.com/projects/8994/tickets/745-form-label-should-use-i18n

    I’m hoping this will be changed in Rails core soon, but as of now, no luck.

  • Glenn Powell

    Hi Iain,
    Good post, a couple things though. First should the human_attribute_names be inside the active_record Hash instead of error_messages? And secondly, (at least the edge version of) Rails doesn’t currently utilize the active_record.human_attribute_names lookup for it’s form labels, error messages, etc.

    http://rails.lighthouseapp.com/projects/8994/tickets/745-form-label-should-use-i18n

    I’m hoping this will be changed in Rails core soon, but as of now, no luck.

  • http://infx.nl iain

    Hi Glenn,

    1: You were right, I fucked the hash up. It’s fixed now. This is why I prefer Yaml.

    2: Rails is missing much at this point. It does work in the error_messages_for() method though, although that’s the only place.

    There is some work done in cleaning stuff up at this moment, but it’s not enough at this time.

  • http://infx.nl iain

    Hi Glenn,

    1: You were right, I fucked the hash up. It’s fixed now. This is why I prefer Yaml.

    2: Rails is missing much at this point. It does work in the error_messages_for() method though, although that’s the only place.

    There is some work done in cleaning stuff up at this moment, but it’s not enough at this time.

  • http://lucaguidi.com Luca Guidi

    Hi Iain, great post!
    What about the ‘custom’ namespace in your translations hash?

  • http://lucaguidi.com Luca Guidi

    Hi Iain, great post!
    What about the ‘custom’ namespace in your translations hash?

  • http://iain.nl Iain Hecker

    Hi Luca,

    active_record.error_messages.custom is for defining your own error messages. If you want a different error message when Post.title is blank than when Product.price is blank. E.g:

    active_record:
      error_messages:
        post:
          title:
            blank: "must be filled in"
        product:
          price:
            blank: "must be specified"

    Hope this helps. I’ll be going into this shortly in a new post.

  • http://iain.nl Iain Hecker

    Hi Luca,

    active_record.error_messages.custom is for defining your own error messages. If you want a different error message when Post.title is blank than when Product.price is blank. E.g:

    active_record:
      error_messages:
        post:
          title:
            blank: "must be filled in"
        product:
          price:
            blank: "must be specified"

    Hope this helps. I’ll be going into this shortly in a new post.

  • http://juice10.com Justin Halsall

    Bedankt voor de nederlandse vertaling! Je bewijst ons nederlandse Rubyers daar een grote dienst mee!

    Kom ook eens een keer langs de amsterdam.rb of utrecht.rb meetings, dan kan ik je op een biertje trakteren.

  • http://juice10.com Justin Halsall

    Bedankt voor de nederlandse vertaling! Je bewijst ons nederlandse Rubyers daar een grote dienst mee!

    Kom ook eens een keer langs de amsterdam.rb of utrecht.rb meetings, dan kan ik je op een biertje trakteren.

  • http://github.com/yi ty

    I’ve put up a locale YMAL auto translator. It understand YAML format and can automatically translate your YMAL into othere languages. Saves a lot of time for i18n projects.
    http://github.com/yi/rails-localisation-yaml-auto-translator/tree/master

  • http://github.com/yi ty

    I’ve put up a locale YMAL auto translator. It understand YAML format and can automatically translate your YMAL into othere languages. Saves a lot of time for i18n projects.
    http://github.com/yi/rails-localisation-yaml-auto-translator/tree/master

blog comments powered by Disqus
Fork me on GitHub