Simple Localization in Rails 2.2

July 21st, 2008 by Trevor

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_translations "#{RAILS_ROOT}/lib/locale/#{locale}.rb"
  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
<h1><%=t :hello_world %></h1>
 
<%=t flash[:notice] %>
 
<%= link_to 'en-US', root_path(:locale => 'en-US') %> or
<%= link_to 'pirate', root_path(:locale => '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_translations "#{RAILS_ROOT}/lib/locale/pirate.yml"
 

Comments

Posting code? Please use Pastie.

Have a question? Please visit the Forum.

17 Comments

  1. 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.

    Comment by Ryan Bates on July 21, 2008

  2. 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 :)

    Comment by Trevor on July 21, 2008

  3. 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.

    Comment by Arthur Carlsson on July 22, 2008

  4. 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. Thanks for writing this tutorial: it focused the right points, it was what we need now!

    Comment by Luca Guidi on July 23, 2008

  6. 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.

    Comment by zerohalo on July 24, 2008

  7. 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.

    Comment by John Smith on July 28, 2008

  8. 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

    Comment by Trevor on July 28, 2008

  9. 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 [].

    Comment by Jacek Becela on July 28, 2008

  10. [...] Simple Localization in Rails 2.2 - A look at the coming feature. (via RubyFlow) [...]


  11. _@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/master/config/initializers/locales.rb . 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/master/app/controllers/application.rb#L13 (and even perform some check like `available_locales.include? user_submitted_locale`

    Thanks Trevor for this great write-up!

    Karel


  12. 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?

    Comment by Juho on August 15, 2008

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

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

    Comment by Trevor on August 15, 2008

  14. 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-available-in-rails

    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.


  15. Louis, thanks! That solved my problem.

    Comment by Juho on August 19, 2008

  16. Thanks for writing

    Comment by funfun on August 22, 2008

  17. [...] ??? Rails-2.1.1??????????? - Hello, world! - s21g Riding Rails: Rails 2.1.1: Lots of bug fixes ???????Ruby on Rails?????????Rails 2.2???????? | ???????? | ????????? Simple Localization in Rails 2.2 - almost effortless [...]