Thursday, January 15, 2009

Rails : Internationalizing Views

I’m not an expert in i18n, but there seem to be two somewhat separate strategies for how to tackle the issue of translating your content into different languages.  First is the idea of externalizing your strings.  So for example say you have this content:

<h1>Welcome to the home page!</h1>

Well what you do is name it:

<h1><%= :homepage_welcome %></h1>

And then you define that resource someplace that it can be swapped out dynamically.  Rails 2.2.2 has some great support for this (I am using the karmi demo if you want to follow along), then in config/locales you can have a file en.yml (for English):

  homepage_welcome:  “Welcome to the home page!”

and fr.yml (for French):

  homepage_welcome: “Bienvenue à la page d'accueil!”

Back in your code you invoke the translator:

<h1><%= t(:homepage_welcome) %></h1>

And with a little I18N magic (again, see Karmi for what’s really going on), you have multi language support.


Well, the problem with this is that on content of a meaningful size you’re going to start running into trouble breaking it into named pieces.  You’re either going to break it into such small pieces that you lose the context in which it was used, or else you name it too generically and your translators no longer give you the optimal translation because you’re using the word in different ways, or you just plain have to translate pages full of content all at the same time.  Worse, what happens if you’ve got graphics that need to change with the translation?  How do you externalize that?  I’ve got an icon that says “New!” in a little star shape, how do I use I18N to translate *that*?

Here’s something I’m experimenting with to do the entire view pretty quick.  I haven’t gotten enough feedback yet for whether it’s a good idea, but hey.

You’ve got a controller, home, and an action, index.  As we all know, Rails will then make some assumptions that at the end of method index, it’ll render the file in app/views/home/index.rhtml (or .html.erb if you want to do it the new way).  Well, I thought, maybe that’s a good place to sneak in my locales?

So I created app/views/home/en and app/views/home/fr and copied index.rhtml to both these directories.  In the fr/ version, you make any and all changes you have to make – including changing image paths, etc…  Change it right there in the code.

Then, back in the controller, we do this:

def index
   render :action=>”#{current_locale}/index”

And you know what?  It actually worked.  I swapped out my locale to French and I get my fr/ page instead.

THIS IS VERY BASIC.  Among other things, you’d want to make sure that the content exists for the locale you’re looking at, and have some default to fall back on.  The t() method described above actually does this, allowing you to say stuff like this:   t(:homepage_welcome, :default=>”Welcome to the homepage”). 

Also, this implies that whoever is doing your translation is going to be comfortable getting raw HTML pages.  That’s not ideal.  You really do, if possible, want to separate out the content.

Lastly, you’re repeating yourself.  That’s not great.  So before tackling this you should do your best to get the logic out of the page (and into the controller), and really only use this technique on pages that are almost entirely content.

Like I said, I’m still working on it.  Maybe I’ve got enough Rails traffic on this blog that I can get some discussion going about how to improve the strategy.

1 comment:

jason said...

At SML we went the other way. We're using yml files like described in your first paragraph. To translate the "new" banner, you'd use an rb file with a lambda function choosing which banner to use.

Also, we've scoped to controller/action and models. A co-worker has written a plugin to clean up all of the scoping strings.

Changing functionality in 6 views every time would suck. Plus, how do you hand it off to the translation shop? YML files are pretty easy.