Simple Localization in Rails 2.2

I’ve been staying on the sidelines when it comes to localization in Rails for a while now, but I couldn’t help getting excited about the upcoming native support in Rails 2.2. So, with some guidance from the Rails i18n team, I decided to give things a try.

I’ve been extremely pleased with the results so far, but I’m all ears if anyone would like to offer suggestions on how to better achieve basic localization for a Rails app. Here’s where I’m at so far in a kind of how-to format. This is all plugin-free, using only what’s available in core. I expect that plugins will be coming out to add features and functionality, but you can accomplish quite a bit without any extras.

You can try to follow along, or just get the gist be reading through the steps. As noted in the comments, this is just a proof of concept, is not secure, and shouldn’t be used in production as-is.

1. Make a new Rails app and freeze edge:

~ $ rails i18n
~ $ cd i18n
~ $ rake rails:freeze:edge

2. Make a couple of translation stores (files) in lib/locale directory:

# lib/locale/en-US.rb
{ 'en-US' => {
  :hello_world => "Hello World",
  :hello_flash => "Hello Flash"
}}

# lib/locale/pirate.rb
{ 'pirate' => {
  :hello_world => "Ahoy World",
  :hello_flash => "Ahoy Flash"
}}

3. Set I18n.locale with a before_filter:

# app/controllers/application.rb
class ApplicationController < ActionController::Base

  before_filter :set_locale

  def set_locale
    locale = params[:locale] || 'en-US'
    I18n.locale = locale
    I18n.load_path += Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ]
  end

end

4. Make a controller and route to test things out, using symbols from your translation for user messages:

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.root :controller => 'home', :action => 'index'
end

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = :hello_flash
  end
end

5. Create a view using symbols for user messages and use the “t” helper to translate:

# app/views/home/index.html.erb

'en-US') %> or 'pirate') %>

6. Fire up the old script/server and check it out:

~ $ script/server

I think that about covers it. Of course, this is a very simple example, but it should cover the basics well enough to get started. Please let me know if you have any ideas about how to simplify/improve this, and thanks again to the Rails i18n team for all of their work – everything looks great so far!

Update: You can use YAML to store translations now. Also, the I18n.populate and I18n.store_translations are no longer necessary (or available).

# lib/locale/pirate.yml
pirate:
  hello_world: Ahoy World
  hello_flash: Ahoy Flash

# app/controllers/application.rb
I18n.load_path += Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ]

Update: Rails 2.2 comes with simple i18n support fully baked in. This is great news because it makes adding internationalization support even easier. Check out the announcement on the Rails site for details, but the short version is that YAML files put into config/locales can be loaded up with a simple call to their file name. Here’s an example:

# config/locales/en.yml
en:
  hello_world: Hello World

# config/environment.rb
config.i18n.default_locale = :en

# app/views/home/index.html.erb

Rails + localization – can it get any easier? :)

Advertisement

31 Responses to “Simple Localization in Rails 2.2”


  1. 1 Ryan Bates July 21, 2008 at 11:04 am

    Thanks for posting this, step by step instructions is exactly what we need.

    One thing though, isn't it dangerous to toss params directly into a "require" statement? This gives them access to run pretty much any ruby file on your system.

    I believe it also allows them to fill up your memory if they keep requiring files at slightly different relative paths since Ruby thinks it's a different file. Yeah you could make sure ".." isn't in the string, but this still seems like a bad idea.

    I would prefer to require the translation files you need to support outside of the request entirely (maybe on startup). Not sure how that would work exactly.

  2. 2 Trevor July 21, 2008 at 2:46 pm

    Good point. I'll add a note in the post to make clear that this is just a proof of concept and shouldn't actually be used in production.

    I was thinking of using validates_inclusion_of to check that the language chosen was valid, and storing that as an value in the users table (as you would with a time zone).

    Allowing a user selected language would mean you'd need to do something like this for each request, though, I believe. I'm also not sure if the languages should just be put into a single file and loaded all at once. I'm thinking that people won't change the language much, though, so having separate files probably makes sense.

    Anyway, we're making progress at least :)

  3. 3 Arthur Carlsson July 21, 2008 at 7:48 pm

    Loading the translations could be done in an initializer. Then you could have an app/locales directory and in the initialized simply require all the files in that directory. But it would be nice to be able to lazy load the translation files.

  4. 4 Carlos Júnior July 22, 2008 at 6:38 am

    Wouldn't be better if the translations were loaded in a initializer, and then selected in the before filter? (We would need a method to know which languages were loaded too).

  5. 5 Luca Guidi July 22, 2008 at 8:46 pm

    Thanks for writing this tutorial: it focused the right points, it was what we need now!

  6. 6 zerohalo July 24, 2008 at 3:48 am

    Thanks for the writeup. I had read Sven's post but was unclear about how it was to be used. This helps a lot.

    A possible way to get around the require/params problem: Store the possible language options in an array or hash at startup. Match the user params against the array, if it exists in the array, require. Otherwise, decline.

  7. 7 John Smith July 28, 2008 at 9:42 am

    Should set_locale not take request.accept_language into account? There's no point in serving en-US by default if the user agent specifies that it does not want to see english in the first place.

  8. 8 Trevor July 28, 2008 at 10:02 am

    John, this is just a simple example meant to illustrate the basics. Of course there is plenty of room for improvement. If you're really interested in localization, you should check out the Rails i18n Google Group: http://groups.google.com/group/rails-i18n

  9. 9 Jacek Becela July 28, 2008 at 11:26 am

    There is no "t" helper and you have to make your own because the authors wanted to avoid the very fruitless flamewar discussing which one of those one-letter (or two-) method names is better: t() _() or [].

  10. 10 Karel Minarik August 12, 2008 at 12:11 pm

    _@Ryan:_ It's actually fairly easy to strip the locale loading from request cycle, see here: http://github.com/karmi/rails_i18n_demo_app/tree/… . Rails apparently loads the hash structure (available in `I18n.backend.send(:class_variable_get, :@@translations)` for inspection.)

    Then you can just set the locale in the controller filter: http://github.com/karmi/rails_i18n_demo_app/tree/… (and even perform some check like `available_locales.include? user_submitted_locale`

    Thanks Trevor for this great write-up!

    Karel

  11. 11 Juho August 14, 2008 at 8:54 pm

    Hello,

    I'm designing a web application that needs to be translated in English and Finnish. I am using the approach presented in this post to do the localization. However, when I try to display form error messages with I get a "can't convert Array to String" error if I use any other language than English (default en-US).

    I've tried to also create fi.rb -files in active_record, active_support and action_view -folders but this seems to not be enough. What should I do?

  12. 12 Trevor August 15, 2008 at 3:22 am

    Juho, I'm not sure. Check with the Rails i18n team in their Google Group:

    http://groups.google.com/group/rails-i18n

  13. 13 Louis Simoneau August 17, 2008 at 3:34 am

    Juho, I had a similar problem. It seems to be some kind of bug, and I can't even figure out what it is well enough to file a report. However, if you provide rails with translations of all the activerecord error messages for your target locale, the error goes away.

    The list of 'railties' available for translation can be found here:

    http://rails-i18n.org/wiki/pages/translations-ava…

    From there, just make a file with an I18n.store_translations call and all the messages you need to translate, and require it (either with a before_filter or in an initializer). This seems a little convoluted right now, I think the ideal would be to make a plugin that grabs a YAML translation file and makes the store_translations call based on that.

    While the possibilites of the new I18n as far as multilingual applications go are very impressive, I think the first thing we need to do is develop a simple framework for people who just want to localize rails to one non-english language, using the new API. That would allow us to get all of the "translating rails messages" bit out of the way and allow people to focus on whatever specific translation their app requires.

  14. 14 Juho August 19, 2008 at 2:19 am

    Louis, thanks! That solved my problem.

  15. 15 funfun August 22, 2008 at 5:53 am

    Thanks for writing

  16. 16 ruby developer October 11, 2008 at 4:47 am

    Thanx for news and example. Can I use i18n on my rails 2.1 ??

  17. 17 Fannar February 10, 2009 at 6:36 am

    In the update clause where it says:

    # app/controllers/application.rb

    config.i18n.default_locale = :en

    Should be

    # config/environment.rb

    config.i18n.default_locale = :en

    That is put it into the environment.rb not the application.rb

  18. 18 Trevor February 10, 2009 at 6:45 am

    Nice catch, Fannar. I've updated the post.

  19. 19 Zeba February 12, 2009 at 8:34 pm

    hi,

    I tried implementing i18n by exactly following your post…but I get the following error:

    You have a nil object when you didn't expect it!

    You might have expected an instance of Array.

    The error occurred while evaluating nil.inject

  20. 20 Trevor February 13, 2009 at 7:15 am

    I'm not sure, Zeba. Try asking around on the Rails mailing list, perhaps?

  21. 21 Josef February 21, 2009 at 7:20 am

    Hi… thanks for the tutorial – this is the degree of simplicity I need to get on board a new thing.

    At the same time I agree with John Smith. For all practical purposes, HTTP content negotiation AKA request.accept_language is essential anyways, so it could as well be covered here. Would make a great piece of work even greater ;-)


  1. 1 A Fresh Cup » Blog Archive » Double Shot #258 Trackback on July 29, 2008 at 3:12 am
  2. 2 Rails2.1.1?????Rails2.2(beta)?????? » RAILS PRESS Trackback on September 6, 2008 at 12:24 pm
  3. 3 Bookmarks about Translate Trackback on October 13, 2008 at 2:30 pm
  4. 4 Rails 2.2 Released - 27 Links and Resources To Get You Going Trackback on November 21, 2008 at 10:23 pm
  5. 5 Saiu o Rails 2.2 | Ruby Brasil Trackback on November 29, 2008 at 9:43 pm
  6. 6 Moki Systems Blog » Using Rails’ New I18n Support in Real Life: Part the First Trackback on December 11, 2008 at 11:26 am
  7. 7 Problem installing gem rails 2.2 « Fabiano Soriani’s Weblog Trackback on December 11, 2008 at 6:15 pm
  8. 8 Ward’s Words » Blog Archive » Kotoba using ActiveScaffold Trackback on January 11, 2009 at 3:58 am
  9. 9 Aladdin With A Lamp » ?Ruby on Rails?|???????? Trackback on February 12, 2009 at 7:00 am
  10. 10 Ruby on Rails Internationalization @ Ali SOGUKPINAR Trackback on July 22, 2009 at 10:14 pm
Comments are currently closed.




Follow

Get every new post delivered to your Inbox.