As your customers start selling his application to more and more markets, a necessity of application internationalization (i18n) becomes an important aspect of application development. Accessing more markets is very substantial benefit, however most of the time your customer is not aware of how many costs it does involve and how complicated the process of internationalization may be. This usually goes well beyond hiring translators and just re-engineering a product so its individual parts are replaced on language change. It might be important to take into considerations things like writing direction, data sorting, charsets, images, screen layout changes, validations, currency conversions, automated testing issues etc. A well written article that outlines an example of process that may be applied to internationalization of large application can be found MSDN library artice.

This article in turn attempts to highlight the main issues of the part of internationalization that on one hand has the greatest impact on end-user and on the other hand has a great impact on everyday coding – interface translations. Despite the fact that in this article we’re going analyze how internationalization can be approached in Ruby on Rails application, most of the issues and possible solutions provided can be applied in different development environments.

Keys naming

One of the first dilemmas faced when developing multi-language application is choosing the most appropriate naming convention for keys, that will represent text to be translated.

There is a temptation to either keep the keys short, therefore reducing the amount of typing when one needs to refer to translated information or to keep them long and deep nested, therefore having well defined structure. However going short is not usually a good idea. Despite the fact that one does not have to remember long paths to certain translations, it usually results in awkwardly looking and long key names, that have to support differentiation required. Consider following, slightly exaggerated example:

en-GB:
 dashboard_main_table_headers_first_name: First name

Can easily be fragmented into

en-GB:
 dashboard:
 main_table_headers:
 first_name: First name

On the other hand, too nested keys may become tortuous and difficult to remember, where in fact many levels can be easily omitted without introducing ambiguity, e.g.

en-GB:
 dashboard
 show:
 view:
 main_section:
 table:
 headers:
 first_name: First name

By removing some unnecessary levels and merging some of them into one, we can easily achieve reasonable solution.

en-GB:
 dashboard:
 show:
 table_headers:
 first_name: First name

Second question to be asked in regards of keys creation, is what to actually include within a key. Answer probably is: “Whatever would be intuitive, unambiguous, and let you find and write your translations efficiently”. An example of such a structure may look like this:

:
E.g.
en-GB:
 customer:
 products:
 show:
 label_price: Price

Naming of last level should be consistent not to think too much how to name specific key every time we have to do so. Obviously we may need to add some common branch to translations that will store keys shared between namespaces etc. Still having well defined naming convention will definitely boost efficiency of heavy translation dependant applications.

Translation files structure

In small applications there is rather no necessity to prepare more locale files than one per language. Still as the application grows, translation files are becoming hard to maintain and manage – therefore well thought tree of locale files may be a way to avoid this king of problems. As an additional bonus, it will be much easier to distribute translation pack in example for specific module of the same application.

Some examples of translation files structures

Module name based
 ..yml
 ..yml #Shared
E.g.
invoicing
 admin.en-GB.yml
 admin.da-DK.yml
 admin.en-GB.yml
 admin.da-DK.yml
Namespace / Role name based
 ..yml
 ..yml #Shared

or

admin
 invoicing.en-GB.yml
 invoicing.da-DK.yml
 invoicing.en-GB.yml
 invoicing.da-DK.yml
l
 ..yml
 ..yml #Shared

And so on… You can even put language packs in different directories. Still in most cases one level nesting feels to be enough for most situations. It is up to you which approach you choose and will be the most appropriate for given app.

Getting rid of unused translations

Continuous development, upgrades, refactorings etc. often result in orphaned translation branches within translation files unless there is great attention to keeping everything tide. Anyway sometimes we would like to check if our translation files are not a little bit too large. First idea is to scan source code for keys and compare it with whatever lies in locale files – after that, just remove all keys that were not mentioned within the code. This actually has two drawbacks.

First one is if keys inheritance is used or some keys are not called from main application code but from some libraries etc. This would obviously result in removing keys that are being used. The second scenario results in the same effect and is caused by using string interpolation when calling keys.

To approach this problem we may try to enable keys logging and let the application run (preferably on production environment). Enabling this mechanism and running a full test suite will help a lot as well (depending on tests coverage). This approach augmented with scanning the source code should give us almost (if not totally) complete of keys used within application that we can compare against our bulky translation files.

A simple code for logging translation keys used

module I18n
 module Registry
 protected
 def lookup(locale, key, scope = [], options = {})
 @log ||= Logger.new(File.join(Rails.root, 'log', 'i18n_registry.log'))
 @log.info key
 super
 end
 end
 end
I18n::Backend::Simple.send :include, I18n::Registry

Finding missing translations

Just as finding unused translations we may augment source code scanning with some kind of registry. This time we can base our solution on exception_handler hook, that is delivered by Rails I18n

I18n.exception_handler = lambda do |exception, locale, key, options|
    @log ||= Logger.new(File.join(Rails.root, 'log', 'missing_translations.log'))
case exception
    when I18n::MissingTranslationData
       @log.info key
          options[:rescue_format] == :html ? exception.html_message : exception.message
       end
    else
       raise exception
    end
 end

Adding new translations

Adding translations to translation files during development is one of the most unproductive tasks. This is especially the case in application with two or more languages. However, using the power of exception_handler, we can automate this process of keys creation.

When missing translation key is called for the first time, this may spawn the key in translation files in each language, create translation automatically based on key name etc. We can even call some service, that will roughly translate the generic translation into different languages automatically. There is actually a gem provided that allows that, so instead of pasting a code here, have a look at its github repository.

Delegating work on translations

Unless the team has some dedicated translators it is rather common to delegate translating to some external resources, let it be customer, customer’s employee, outsourced translators etc. In all cases some kind of approach to the process of translating has to be developed.

Directly editing files can be a bit of awkward and not easy to cope with, especially for translators unexperienced in translation files syntax. Small indentation change or some special sign removal can even result in application not being able to start. It is the cheapest way to start though.

Developing dedicated translators interface can be tempting, however needs to be well thought through. If external services do not deliver for example required access control, it may be worth developing something in-house. In this case 37signals’ Tolk might be used as a foundation. Browse github for other shared solutions not to reinvent the wheel from scratch.

SaaS based solutions seem to be the best solution in most cases though – designed to work with teams of translators, can heavily reduce number of problems, time spent and facilitate process of localizing your app. For translating rails applications, there are two major players at the moment: rails-only LocaleApp and more general WebTranslateIt. Please review their features and decide which one suits you the most.

Next step

There are many more aspects of internationalization of an application that are not even tackled in this article – it has rather covered aspects fo dealing with translations during application development – this however is a basis for all future i18n related tasks. So choose whatever approach fits you the best and make your app speak another language.