21 May 2013

Localize Rails App ( & RTL the design )

In this post I will share how I localize my Rails apps to Arabic. What we'll do is set up the application to accept locales, internationalize the bits of our application that we want to be localized and add the translations. Oh and since Arabic is language written and read from right to left, we have to change the HTML structure or CSS files, so I will talk about how I handle this too.
Before we start, you should be a bit familiar with Rails i18n API, the guides is a great resource.

Setting the Locale

We need to set the value of the local (the I18n.locale variable) before we can use it in our application. This is a simple part, we add it in a before_filter in the application controller:

# app/controllers/application_controller.rb
before_filter :set_locale
def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
end

So the locale part must be passed as a parameter in every request, this is how we can do it:

Passing the Locale

There are many ways to pass the locale around in users' requests. We can pass it like this: mysite.com/clients?locale=ar, or we can put the locale part as an extra bit before the the actual routes like this: mysite.com/ar/clients which looks cleaner, so this is what we'll do:

# config/routes.rb
scope "(:locale)", :locale => /en|ar/ do
  resources :books
  root :to => "welcome#index"
end

Here we wrapped our routes with an optional scope that accepts either ar or en, using any other locale will redirect to a 404. The root :to => part redirects mysite.com/, mysite.com/en and mysite.com/ar to the welcome page.

What happens when a user doesn't specify the locale? We use the default one. You can set it in application.rb:

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

Now we need to make sure that every request contains this locale part, so we override default_url_options method in the application controller:

# app/controllers/application_controller.rb
def default_url_options(options={})
  { :locale => I18n.locale }
end

This method passes the locale value in all URLs, by default. For example, if we have this route (/:locale)/clients and we want to get its URL, we use the helper method: clients_path, which will return the URL for that resource automatically including the locale, so it will return this: mysite.com/ar/clients
If you dont wanna override default_url_options then you will have to pass the locale manually in your requests, so you wouldn't just say clients_path, you will have to say clients_path(:locale=>I18n.locale).

Adding Translations

We can translate our app in many ways, we can use the default method (simple backend), or we could use something more flexible like Redis as a key:value backend to manage our translations. Here, I will use the simple default way, in which we store our translations in YAML files!

The locale files are located in config/locales. Now, since I am translating Arabic and English, I need 2 files en.yml and ar.yml.

# config/locales/en.yml
en:
  greeting: Hello
# config/locales/ar.yml
ar:
  greeting: مرحبا

You can create nested structures in the YAML files, or create more YAML files, of course. (but don't forget to restart the server when you add new locale files)

I18n the application

Here we edit every part of our application that we want to be localized. In the controllers and views, we can wrap our text with the I18n.t method, or t for short. For example, in the views, you will do something like: t('greeting'), and depending on the current locale, the right translation will appear.

This is almost it! One bit remaining, the design. Arabic is RTL language, so the page direction should be changed, along with the CSS. I wrote a ruby gem that does that, it will convert a CSS file and flip the design, and thats all we really need.
Assuming you installed the gem, we can use it in the terminal with any of your CSS files:

css_convertor app/assets/stylesheets/styles.css

It will create a new file styles-ar.css in the same directoy which has the design flipped. Now all we're left to do is tell our application when to load which styles, of course according to the current locale. So it's a simple if statement in any of your layouts:

  <% if I18n.locale.to_s == "ar" %>
    <%= stylesheet_link_tag    "styles-ar" %>
  <% else %>
    <%= stylesheet_link_tag    "styles" %>
  <% end %>

That's it! If you have any feedback, you can send me an email, or a pull request =)