The Ruby I18n gem allows you to translate your application from default English to a custom language (yes, even custom-English one) and provides multi-language support - this is the process of "internationalization" and “localization”. Don’t be afraid, it only seems complicated and hard! I18n provides you a great support and after you grasp the basics, everything else is a piece of cake.

Why do we need I18n gem and how it works?

Natural human languages differ in so many ways. Grammar, sentence construction and other syntax rules are unique for each one of them. If you want to improve the recognition of your app you should think of adapting your application written in one particular language to the different receivers, regions and it's languages. This process is called internationalization and localization.

Providing tools to solve all problems with internationalization is impossible (or at least very, very hard). This is a reason why Rails I18n API provides support for English and similar languages and makes it easy to customize everything in sake of usage of any other language.That’s why each and every static string in the Rails framework (e.g. ActiveRecord messages, date formats, etc.) is internationalized. So basically, to localize your Rails app is to define translated values for these strings in desired language.

In the i18n gem the most important methods are translate (lookup text translations) and localize (localize Date and Time objects to local formats). Their aliases are respectively .t and .l - you use them like this: I18n.t ‘product.name’ and I18n.l Time.now (or use Rails’ #t and #l helpers (which is a preferred way to do it)).

In order to properly internationalize your application you might hit some bumps in the road. For instance, you have to take grammar rules under consideration, as rules correct in one language might be incorrect in another. Let’s review a few issues that you might have to deal with.

scoping translations

When you build your locale files, you often group your keys under the same parent as it makes more sense. It might look like this:

Of course, you could reference them by their full key:

but it’s long, repetitive and not very readable. Thankfully t() takes a scope:

and if you name your views and partials properly (better do it, it saves a lot of time later on), there’s no need to specify the scope because .name_label references products.index.name_label as you are in products/index.html.haml view:

pluralization

In English we have only one singular and one plural form of a given string (“one product”, “two products”). However, in many other languages there are more than one plural form (Polish, for example). That’s where Rails’ simple way to deal with pluralization in translations might help you. If you want to show your users how many products they have, you can do it in that way:

But you have to remeber that to make it fully work you need to include pluralization module to the Simple Backend (or any other backend you use) and store pluralization rules - as written here and here. There’s also one special key - zero - which might be used if defined (but doesn’t have to). If it’s not, the other key is used.

date/time

If you want to localize timestamps, it is also possible and not complicated - you just pass the Time object to I18n.l or use Rails’ #l helper. You pick a format by passing the :format option:

= l Time.now, format: :short

And add a time format in your translations file (for English is already there in Rails' defaults). Let’s assume that your application has the “pirate” version:

You might need to add some more date/time formats to your locale to make I18n backend work properly. But first take a look at rails-i18n repository at GitHub - there’s a big chance that someone already did that for you! Put such file (or files) in your config/locales/ directory, and they are ready to use.

html escaping

If you have lines in your views that look like:

= raw(t('products.description')) or

= raw(t('announcements.new_account'))

you can get rid of that raw part. Add _html at the end of your translation key to escape html and the job is done. Simple, looks nicer and is consistent with the rest of your t() calls:

= t(‘products.description_html)

= t('announcements.new_account_html')

currencies

If the product’s price is “100”, proper translation in English is “$100”. But in German or other language of the EU (Eurozone, to be specific) country it should be “100 €”, not “€100” (however, this is just about proper display, not exchange - $100 is not equal to 100 €! If you want to convert 100 dollars to 100 euros, you have to do it yourself, or use gem like money-rails).

Thankfully, I18n gem has a feature called “variable interpolation”. It allows you to use variables in translation definitions and pass them to the translation method in your views:

= t('product_price', price: @product.price)

custom locale hierarchy

If your application uses locales other than default English locale, you may consider storing them in a proper hierarchy. If you use the default SimpleStore (that comes with the i18n library), all your dictionaries are stored in plain-text files on the disk, and putting translations for all parts of your application in one file per locale is hard to manage.

For example, your config/locales directory could look like this - you can separate model and model attribute names from text inside views, and all of this from the "defaults" (e.g. date and time formats). Neat and clean, isn’t it?

Bear in mind that the default locale loading mechanism in Rails doesn’t load locale files in nested dictionaries! You have to tell Rails to do it in your config/application.rb :

unused and missing translations

When your application grows and grows, your translation files grow as well. When you change the content of your views you change its translations too. That might create some freely hanging unused or missing translations. You obviously don’t want them in your app - to get rid of (or fix) them, use i18n-tasks. Take a better look at two (of many) commands: i18n-tasks unused and i18n-tasks missing .

Just add it to your Gemfile (don’t forget to bundle ):

And run:

All found unused or missing translations will appear in a form of a table in your console:

Lost in (Rails) translation

When your application is big enough, your locales contain thousands (if not even more) of phrases. Your YAML files are endless and making any changes in them brings only more and more confusion, not to mention possible errors and typos - trust us, we have been there. That’s why we have created Lit: simple i18n web interface, build on top of twitter bootstrap, that you may find helpful in translating app (highly inspired by Copycopter by thoughtbot).

If we have your curiosity, read a blog post where we describe it deeply - feel free to use the tool. It would really simplify your life.

Summary

To wind it up, methods and solutions described in this post might help you if you want to internationalize and localize your application. They are useful and not hard to implement - feel encouraged to try them. However, Rails I18n is far more powerful than presented here. We highly recommend you to dig deeper!

Photo from: freeimages.com