Converting a Rails application from Gettext to I18n

Last week we had to convert our existing Rails application, which uses Gettext to the new I18n API in combination with the SimpleBackend. I personally never liked Gettext, there was simply not enough control over translations as PO/MO files are not native ruby or at least can be easily accessed by Ruby (like YAML files).

We therefore decided to switch to the brand new, not even released, I18n API. But now we had a serious problem, our code base isn’t small and all that code had to be converted in some way. We could do it by hand, but hey, that’s a lot of work and especially very error-prone. Some convertor had to be written. Here it is as a rails plugin: GettextToI18n. 

What does the I18n convertor do?

It scrapes your whole application and searches for gettext calls, like this:

_("to be translated")

It will convert this gettext call to the newly I18n format: 

I18n.t :message_id

Then it builds up a big hash containing all the the translations. We decided it was handy to use the scopes that are introduced in the new I18n api. So it stores the translations in the following format: 

For models:

["model"]["model_name"]={:message_1 => "to be translated"}

For controller:

["controller"]["controller_name"]={:message_1 => "to be translated"}

After this hash of translations has been built up, the convertor writes it as a YAML file to:  config/locales/template.yml.That’s all!

What’s supported?

It supports basic gettext calls. We have run it over our code base and it converts all gettext calls we use without any problem. 

A normal gettext call

_("to be translated")

converts to:

I18n.t :message_0, :scope => [:txt, :controller, :controller_name])

 

A gettext call with variables

_("My name is %{name}" % {:name => "Jaap"})

converts to:

I18n.t :message_0, :name => "Jaap", :scope => [:txt, :controller, :controller_name])

 

A gettext call with variables that contain gettext calls

_("Click %{link} to go to the homepage" % {:link => link_to(_("Here"), root_path)})

converts to:

I18n.t :message_0, :link => link_to(I18n.t(:message_1, :scope => [:txt, :controller, :controller_name]), root_path), :scope => [:txt, :controller, :controller_name])

 

Installation

./script/plugin install git://github.com/japetheape/gettext_to_i18n.git

Usage

To convert your application:
rake gettext_to_i18n:transform

Please make sure you backup your complete application as it can screw things up.

Contribution

Please contribute to this plugin and make it better, as I won’t use it anymore, cause we are not going to convert another time (I think ;-) ). Things that has to be done are:

unnamed variables:

_("I play the %s" % "saxophone")

Go to the development location at github and fork this plugin!

Tags: , ,

11 Responses to “Converting a Rails application from Gettext to I18n”

  1. Sven Says:

    Awesome. This looks like a great contribution. I love how the tools layer is already stacking up even though nothing has been released so far :)

    I’ve listed this on the rails-i18n wiki: http://rails-i18n.org/wiki

    Thanks a lot!

  2. Evgeny Says:

    But why don’t you want to use Gibberish, or some other existing i18n plugin?

  3. Fabio Says:

    This tool is very interesting. I’m following with interest Rails I18n evolutions
    but replacing the more concise

    _(“to be translated”)

    with

    I18n.t :message_0, :scope => [:txt, :controller, :controller_name])

    is not exactly what I would expect from a brand new API.
    I’m still looking concrete advantages (performance, code clarity, etc…)
    to left gettext and switch to new I18n API.

    Anyway, thanks for your post.

    Ciao.
    – fabio

  4. jaap Says:

    @Evgeny: that’s the advantage of the I18n API, we can now use this API and switch to whatever we plugin(that makes use of I18n) we want! Maybe if we find it usefull we switch to such a plugin, but for now the SimpleBackend is enough, as this is only a test for us.

  5. jaap Says:

    @Fabio The call can be more simple, you can just use t(:message_1) without the I18n before it, and if you don’t use the scopes, you even don’t have to include them. So only t(:message_1) can be enough!

  6. Gunnar Wolf Says:

    Oops… I have to agree with Fabio: So far, the Rails improvements have felt just right. However, this time… It feels I’m going to stick with gettext for all of my projects :-/
    Writing an application with just the labels as symbols, which have to be referenced from an external table, looks like the oldest and most awkward way to solve a problem. One of gettext’s biggest selling points, which has mad it a Unix-wide standard, is the ability to just write English applications – and use the English as the message identifiers themselves. Switching everything back to :ecannotparse (instead of a ‘cannot parse this shit’) just means more work to authors. And translators will also have a harder time, as they might not be technically skilled people, and will now have to open the to-be-translated files as well as the English ones to understand really what needs to be translated.
    And of course, gettext degrades quite nicely – a missing string gets sent as English. Now… even the English translation might be missing. It is much preferrable for an i18n application to give an English string every now and then than to fail towards an internal-use-onl string.

  7. Willem van Bergen Says:

    I understand the concerns you have with the new i18n API. However, I can argue against the gettext approach as well:

    We tend to adjust the English strings from time to time, to correct grammar mistakes or create more fluent sentences. This is usually NOT done by the developers. Using gettext, this means that all the translations for that string are no longer valid, because the key has changed. This was a common problem for us when we were using gettext. Using i18n and translation keys, this problem doesn’t occur, and somebody other than the developers can adjust it.

    Another problem is strings that are exactly the same in English but can mean different things, like “bank”. In other languages, these different meanings can be the cause of different translations.

    To overcome your problems, you can make the identifiers a bit nicer than your example: t(:cannot_parse_this_shit). This makes it much more clear for developers as well.

  8. Willem van Bergen Says:

    I forgot to mention that we have implemented a fallback to English if a string is not available in the current locale. So there is no fallback to the identifier, because we always have an English string available.

  9. Andre Foeken Says:

    Uglyness == i18n in rails 2.2 :(

  10. grosser Says:

    simply stay with gettext, its simpler ;)

    fast+threadsave+simple Gettext http://github.com/grosser/fast_gettext

    2.2+ rails plugin for gettext and i18n http://github.com/grosser/gettext_i18n_rails

    Example 2.3 application fast_gettext+i18n http://github.com/grosser/gettext_i18n_rails_example

  11. Hack to Learn : Converting a Rails application from Gettext to I18n Says:

    [...] by Administrator Fri, 29 May 2009 22:23:00 GMT ‘jaap’ wrote a rake task to convert your Rails application from using Gettext translation into the new I18n support, which [...]

Leave a Reply