Three ways to use HTML in your Rails translations

Rails 3 added a security protection against Cross-site scripting per default. This is a good thing because it makes your web application safer, but it also can cause troubles when you are using html in your translations. What Rails does, is to distinguish between ‘html_safe’ and not ‘html_safe’ strings. When a string is not safe, it will be always escaped. Hence, your HTML tags are shown in the text and not used as actual HTML tags.

What does that mean in your code? Let’s digg in:

Let’s say we want a formatted text like this:


lingohub (h2 tag)

can’t wait to see it (text enclosed in p tag)


Of course we use a resource file to store our text, so our application is ready to go global. And for demonstration purposes we put the HTML tags in the text (something I normally don’t recommend). Here is our resource file:

en:
  html:
    escaped: "<h2>lingohub</h2><p>can't wait to see it</p>"
    raw: "<h2>lingohub</h2><p>can't wait to see it</p>"
    html_safe: "<h2>lingohub</h2><p>can't wait to see it</p>"
    ending_html: "<h2>lingohub</h2><p>can't wait to see it</p>"
view raw gistfile1.yml This Gist brought to you by GitHub.

All these translation have the text from above and the key is named after the method we use to display them.

And here is the code with the results:

1: <%= t 'html.escaped' %> <!-- INCORRECT RESULT: &lt;h2&gt;lingohub&lt;/h2&gt;&lt;p&gt;can't wait to see it&lt;/p2&gt; -->
2: <%= raw t('html.raw') %> <!-- CORRECT RESULT: <h2>lingohub</h2><p>can't wait to see it</p> -->
3: <%= t('html.html_safe').html_safe %> <!-- CORRECT RESULT: <h2>lingohub</h2><p>can't wait to see it</p> -->
4: <%= t 'html.ending_html' %> <!-- CORRECT RESULT: <h2>lingohub</h2><p>can't wait to see it</p> -->
view raw gistfile1.erb This Gist brought to you by GitHub.

The first line translates the string and puts it right in the view. Since the string isn’t marked as html_safe, the tags are escaped using &lt; and &gt;.In doing so, Rails makes sure that when you display text from an untrusted source (e.g. user inputs), no harmful code can be embedded in your site. Line 2 uses the raw method to create a new string that is marked html_safe. Line 3 uses the html_safe method, which works like the raw method. Note, no raw! or html_safe! method exists to mark the existing string html_safe. There will be always a new string object created. Most interesting is Line 4 which shows a handy convention. When a translation title ends with “_html”, Rails assumes that the text contains HTML and you don’t want to escape it.

Like above mentioned, I am not a big fan of using HTML in your translation texts, but if you do, I would recommend to stick to the “_html” convention. It keeps the code cleaner and shows you right at the source if a translation text will be escaped or not.

Have fun translating!

Sources:

Do you speak UTF-8?

“ @ mperham : You have a problem, your data is in latin1 so you think : ” I’ll convert to UTF8 !” Now you have � problems .” cc @ kingshy_g

Everyone of us coders who dealt with encodings felt that pain, didn’t you?

During my developers career I was quite lucky. Had to deal with encodings quite seldom.
And if I had to: Ok, it wasn’t my fault. The provider of the data had chosen (by not knowing it better) that exotic encoding. But I was in charge to solve this problem!

Actually for me the whole encoding issue feels like a neverending Y2K bug.
We have the proper encodings nowadays, but we as computerists were not able to bring this topic to an end.

While reading different resource file formats Lingohub has to deal with this subject:

  • Java resource bundles are stored in ISO8859-1 with UTF-16 escapes
  • iOS strings are stored in UTF-16 (sometimes you have to guess: little/big endian)
  • XML: encoding=”UTF-8“. Good idea! But this could be a lie (by copy/paste)
  • some other formats do not have a defined encoding, nor you have any metadata that give you that information. So you have to know in your application which encoding it will be

Ok, Ok. This was just a rant and won’t give you any solutions.
I will finish it for today and will start this topic as a series of posts to give you some ideas how we solved some of our issues in the encoding domain.

Editor UI Updates

We’ve released some updates that changed the Editor design.

  • The comment area moved from the right sidebar to under the translation area.
  • The right sidebar now displays only Autotranslate (other features will follow).
  • The translation text areas are now highlighted.
  • The information panel is now above and below the text area instead of next to it.

These design changes reflect the feedback and suggestions from our first translators. The new design looks cleaner and more concise while providing more features. We hope you like it! Your feedback is welcome.

Presenting Autotranslate

With the latest release this week, we introduced Autotranslate in linguist. Autotranslate allows the translator to quickly do an initial translation of any kind of text. With this help, it’s easy to look up words or even whole phrases. Some of you probably think, “this is great, now I don’t even need a translator for my app.” While it’s true that in most cases the Autotranslate feature returns an accurate translation, you also need to consider some other vital aspects of a translation:

  • Context of the text
    Texts can have very different purposes, e.g., giving feedback for an error message, persuading a user to sign up, or simply entertaining the user.
  • Context of the application’s space
    Writing text for enterprise business software or for a social media platform are very different. Wording and sound matter very much to the target audience.
  • Accuracy and completeness
    Languages are a living thing. They constantly change and new words are created. Nobody knew a couple of years ago what a “tweet” is. Of course Autotranslate is maintained and updated as well as possible. However, there are limits like regional accents, dialects, and ambiguity.

Autotranslate is great for initial translation and as reference. Translators are great for adding the “human” touch to texts. Together they are a perfect combination for a productive translation environment. We hope you enjoy this new feature.

Screenshot of Autotranslate feature

The Importance of an API

I have worked on many software projects over the last couple of years. One aspect that became more and more important is an API (Application Programming Interface). A decade ago an API was just classes/interfaces that a developer could use to interact with a library/framework, mostly in the same language. As systems became more and more complex and diverse, SOAP quickly rose as a defacto standard in the Java/.NET community to communicate between systems. However, once again Ruby on Rails showed that things don’t need to be complicated to be useful, and it integrated parts of Roy Fielding’s dissertation of a Restful approach (REST). Since then, REST has become the standard for nearly every Web application. Many standard apps build REST APIs that allow users and developers to integrate with their services. Twitter is a prime example that creates and builds value with such an approach.

We here at linguist strongly believe that creating a vibrant community is a key factor for our success. That is why we offer an API right from the beginning. The API supports many different actions like uploading/downloading resources, creating projects, inviting new collaborators, listing collaborators…You can find a full listing in our API overview. The documentation covers all necessary information about the entry points and shows how to use the API. We planned the API with your needs and requirements in mind, and it’s now up to you to let us know what’s missing and what can be improved. If you have trouble using the API or have ideas or questions, simply create a ticket on our support page.

I’m totally excited about linguist and can’t wait to implement all those awesome features you come up with! Happy translating.

Further readings on good REST APIs:

Internationalization (I18n) API in Ruby on Rails 3 (Part III)

In Internationalization (I18n) API in Ruby on Rails 3 (Part I) and Internationalization (I18n) API in Ruby on Rails 3 (Part II) we already covered a lot of the Rails I18n functionality. In part III, we will now a look at some more advanced features of I18n.

Using Different backend with I18n

The I18n ruby gem comes with backend support for resource files (more specifically .yml files). Additionally, it also provides simple approach to exchange the backend implementation using different gem/plugin depending on your requirements. Let’s examine an example where all translations are stored within database.

Using Database backend for Ruby I18n API

You can easily use your database for storing all the translations with the help of i18n_backend_database (https://github.com/dylanz/i18n_backend_database) plugin. This plugin will store all your translations in the database instead of YAML files and provides a caching mechanism, so that the database doesn’t get hit for every translation. When you tag items using i18n.t() within the code base, all of untranslated items will be noted as well as added into the database. You can translate untranslated textual content through an administrator panel provided by plugin.

Update 2011-02-01

Please note that this plugin is not yet ready for Rails 3 (see comments). Here are the steps to use this plugin for Rails 2.x:

#install plugin in vendor/plugin folder
script/plugin install git://github.com/dylanz/i18n_backend_database.git
# add migration
script/generate i18n_backend_database
# migrate
rake db:migrate
# populate default locales
rake i18n:populate:load_default_locales
# populate the locales and translations tables from all Rails Locale YAML files.
rake i18n:populate:from_rails
# populate the translation tables from translation calls within the application.
rake i18n:populate:from_application
# create non-default locale translation records from default locale translations.
rake i18n:populate:synchronize_translations
# run all populate tasks.
rake i18n:populate:all
# translate all untranslated string values using Google Language Translation API.
rake i18n:translate:google

Add this lines in config/initializers/i18n.rb

I18n.backend = I18n::Backend::Database.new # registers the backend
I18n.backend.cache_store = :memory_store # optional: specify an alternate cache store
I18n.backend.localize_text_tag = '##' # optional: specify an alternate localize text tag, the default is ^^

In config/routes.rb register admin panel routes

map.from_plugin 'i18n_backend_database'

Now, rails application can translate all the code from the database. All non-user generated text provided by the application needs to be wrapped in a call to I18n.t().

I18n.t("Hello there!")

Interpolation is handled by passing in key/value pairs as a value to an interpolation tag    ( {{ }} ).

$ I18n.t("Hello there {{name}}!", :name => "Dylan")

$ I18n.t("Click {{here}} or {{there}}", :here => "link_to(I18n.t('midnite'), croix_path)", :there => "link_to(I18n.t('staten_island'), wu_path)")
view raw gistfile1.rb This Gist brought to you by GitHub.

Translating Routes with translate_routes Gem

The Ruby gem translate_routes helps you to translate URLs to any number of different languages, even for an already working application. This gem works with Rails 3.x. and also with other Rails versions like 2.1.x, 2.2.x and 2.3.x. The Ruby gem translate_routes is working good with all type of routing definitions, which includes RESTful as well as named routes. You don’t have to change your existing routing code, helpers, and so on; links work as expected – even in your tests.

Add translate_routes to your Gemfile:

gem 'translate_routes'
view raw Gemfile.rb This Gist brought to you by GitHub.

And let bundle do the rest: bundle install

1) Let’s say you have this in your routes.rb file:

TranslateRoutesApp::Application.routes.draw do
  match 'contact' => 'contact#index'
end

You can see the available routes with the ‘rake routes’ task:

contact  /contact(.:format) {:controller=>”contact”, :action=>”index”}

2) Now write your translations in a standard YAML file (e.g: in /config/i18n-routes.yml), including all the locales and their translations pairs:

en:
  # you can leave empty locales, for example the default one
es:
  contact: contacto

3) Append this line in your routes.rb file to activate the translations specifying the path of the translations file:

ActionDispatch::Routing::Translator.translate_from_file('config','i18n-routes.yml')

4) Execute rake routes to see the new routes:

contact_es  /es/contacto(.:format) {:controller=>”contact”, :action=>”index”}
contact_en  /contact(.:format)     {:controller=>”contact”, :action=>”index”}

5) Include this filter in your ApplicationController:

before_filter :set_locale_from_url

Now your application recognizes the different routes and sets the I18n.locale value in your controllers. But what about the routes generation? As you can see in the previous rake routes output, the contact_es_es_path and contact_en_us_path routing helpers have been generated and are available in your controllers and views. Additionally, a contact_path helper has been generated, which generates the routes according to the current request’s locale.

This means that if you use named routes, you don’t need to modify your application links because the routing helpers are automatically adapted to the current locale.

6) What about tests?

Of course, functional and integration testing involves some requests. The plugin includes some code to add a default locale parameter so they can remain untouched. Append it to your test_helper and it will be applied.

You can find additional information in the translate_routes’ wiki.

Globalize2 – Alternatives for Ruby I18n Gem

The Ruby gem Globalize2 was very popular in Rails community because it provides full-fledged internationalization solutions for Ruby on Rails application. You can use most of the features/tools independently and also you can combine them with other libraries or plugins.

The Feature/Tools for this gem are listed below.

  • Model translations – This functionality transparently translates ActiveRecord data
  • Locale LoadPath – This gem load translation data from standard locations enforcing conventions that suite your needs
  • Locale Fallbacks – This feature make sure your translation lookups fall back transparently through a path of alternative locales that make sense for any given locale in your application
  • Translation value objects – You can access useful meta data information on the translations returned from your backend and/or translated models

Conclusion

The I18n API comes with plenty of features and yet hides the complexity behind a simple to use API. With this gem, a Ruby on Rails application has a great built-in support for multiple languages. As the diversity of users is growing and more and more users speak different languages, a good localization and internationalizationsupport is necessary for every serious Web Framework. If your application has a more specific need, have a look at the various plugins and extensions available. Rails has also a great documentation, and of course there is an Internationalization section, I18n Wiki.

Internationalization (I18n) API in Ruby on Rails 3 (Part II)

In the Internationalization (I18n) API in Rails 3 (Part I) you read about

  1. Definition of Internationalization & Localization
  2. How i18n mechanism works in Rails
  3. Setup/Configuration about the i18n mechanism
  4. Using the I18n with interpolation
  5. Using the I18n with pluralization

You can read the full article on Internationalization (I18n) API in Rails 3 (Part I), if you have not yet read it.

Now, let’s start with some other features of I18n in Ruby on Rails.

Localization of Times/Dates
As I18n API provides date/time localization feature, we can easily add localized version of timestamp in Ruby on Rails application. To use this feature, first of all we need to add our localized translation for time format. Let’s change our de.yml file to add a German version.

# config/locales/de.yml
de:
  time:
    formats:
      description: "Aktuelle Uhrzeit %H Stunden und %M Minuten"

Now, we can use I18n.l or Rails #l helper to localize the Time object in our desired time format.

#script/console
>> I18n.locale = :de
=> :de
>> I18n.l Time.now, :format => :description
=> "Aktuelle Uhrzeit 20 Stunden und 07 Minuten"

You can add many more formats for date/time to make it work using the format that you want. You can also get a ready made version of different types of local files from rails-i18n github repository. Once you place those file(s) in config/locales/ folder, they are ready to use for localization.

Using views for translation
In Rails 2.3 a new feature named “localized views (templates)” was introduced. For example, your application has an ItemsController class with an index view. Your default index template page for ItemsController is rendered by app/views/items/index.html.erb. If you want a localized version of index page, you will just have to write your text in index.de.html.erb file and then will have to save in the same folder. By setting the locale to :de, you can force Ruby on Rails application to render the German version of that template. When the locale is set to the default locale, the generic index.html.erb view will be used.

Generally, this feature is used for large amount of static content pages, where putting this large text content in YAML or in Ruby dictionaries feels cumbersome.

Translating ActiveRecord errors
You can translate Active Record validation error messages easily. Active Record uses different namespaces which can be used to translate easily models, attributes, and/or validations.

For example, an Item model with a validates_presence_of validation for the name attribute like this:

class Item < ActiveRecord::Base
  validates_presence_of :name
end

In this case :blank is the key for the error message. So, Active Record will check this key in the namespaces:

activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name]
activerecord.errors.messages
errors.attributes.[attribute_name]
errors.messages

After following keys in the above mentioned order the result will be:

activerecord.errors.models.item.attributes.name.blank
activerecord.errors.models.item.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messagges.blank

If, your models are using inheritance then the messages are looked up in the inheritance chain.

For example, you might have an Book model inheriting from Item:

class Book < Item
  validates_presence_of :title
end

Then Active Record will look for messages in this order:

activerecord.errors.models.book.attributes.title.blank
activerecord.errors.models.book.blank
activerecord.errors.models.item.attributes.title.blank
activerecord.errors.models.item.blank
activerecord.errors.messages.blank
errors.attributes.title.blank
errors.messagges.blank

You can also add translations for Active Record error_messages_for helper like this.

de:
  activerecord:
    errors:
      template:
        header:
          one: "1 fehler verboten diese %{model} nicht gespeichert"
          other: "%{count} fehler verboten diese %{model} nicht gespeichert"
        body: "Es gab probleme mit den folgenden feldern:"

Organization of resource files
If you are using default i18n library settings, you may find that all files are stored in plain-text format on the disc. Placing all translations text of your Ruby on Rails application in one file per locale could be tedious to organize. You can easily store these files in a hierarchy structure which is easy to understand for you. For example, your config/locales directory could be organized like this:

|-defaults
|---de.rb
|---en.rb
|-models
|---item
|-----de.rb
|-----en.rb
|-views
|---defaults
|-----de.rb
|-----en.rb
|---items
|-----de.rb
|-----en.rb
|---navigation
|-----de.rb
|-----en.rb

By following the above way to organize resource files, you can easily differentiate model and their attribute names from text inside views, and all of this from the “defaults”. In Ruby on Rails, locale files in nested dictionaries structure are not being loaded automatically. So, to make it work we have to do this settings in config/application.rb

# config/application.rb
  config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]

Setting the Locale from the Domain Name
In the previous article you read about how to set the locale in Ruby on Rails using application.rb settings. There are also other ways to do so. You can set the locale from the domain name where your Ruby on Rails application runs. Like www.example.com will load the content in English locale, www.example.de will load the content in German locale. This way you can use top-level domain names to set the locale for Ruby on Rails application.

The benefits of using this method are:

  1. The locale setting is an obvious part of the URL.
  2. Visitors obviously know in which language the content of the website will be displayed.
  3. It is very easy to implement in Ruby on Rails.
  4. Search engines love the content in different languages served at different domains.

You can use it in your ApplicationController:

before_filter :set_locale
 
def set_locale
  I18n.locale = extract_locale_from_tld
end

def extract_locale_from_tld
  parsed_locale = request.host.split('.').last
  I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
end
 
# Get locale from top-level domain or return nil if such locale is not available
# You have to put something like:
# 127.0.0.1 example.com
# 127.0.0.1 example.it
# 127.0.0.1 example.pl
# in your /etc/hosts file to try this out locally

We can also set the locale from the subdomain in a very similar way:

# Get locale code from request subdomain (like http://it.example.local:3000)
# You have to put something like:
# 127.0.0.1 gr.example.local
# in your /etc/hosts file to try this out locally

def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
end

You can also use switching menu option in your application like this:

link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}")

assuming you would set APP_CONFIG[:deutsch_website_url] to some value like http://www.example.de.

As previously mentioned this method has benefits, but what if you do not want to or able to provide different localizations (“language versions”) on different domains. The solution to this will be to add locale code in the URL params (or request path).

Setting the Locale from the URL Params
We can also set the locale by including it in URL params like www.example.com/items?locale=de or www.example.com/de/items.

You can use it in your ApplicationController:

before_filter :set_locale
 
def set_locale
  I18n.locale = params[:locale]
end

You can get same benefits with this method as with setting the locale from the domain name, but the only little problem is it takes bit more code to implement. Here is the code to implement it by including it in our ApplicationController:

# app/controllers/application_controller.rb
def default_url_options(options={})
  logger.debug "default_url_options is passed options: #{options.inspect}\n"
  { :locale => I18n.locale }
end

This way we can easily set/override “defaults” for url_for and helper methods dependent on it. So, every url_for dependent helper method will now add the locale in the query string like, http://example.com/?locale=de.

Now, if you want URLs to look like this http://example.com/de/items (which loads the German locale), we need to set up routes with path_prefix option this way:

# config/routes.rb
scope "/:locale" do
  resources :items
end

So, when you call items_path for :de locales, an URL like http://example.com/de/items will be generated with the German locale. But, if you do not want to force the use of a locale in your routes you can set an optional path scope like:

# config/routes.rb
scope "(:locale)", :locale => /en|de/ do
  resources :items
end

This way when you access http://example.com/items without a locale, you will not get a routing error, which is very useful for setting default locale when one is not mentioned.

Setting the Locale from the Client Supplied Information
Sometimes, you have to set the locale from the information that client had supplied (not from the URL.) i.e. user’s preferred language or by choosing the locale in your application interface and saving it to their profile.

Using Accept-Language HTTP header
Client may set Accept-Language HTTP header in their browser or other client (like curl) and request the url. By supplying the information of the locale. We can implement this scenario with this code in ApplicationController.

def set_locale
  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
  I18n.locale = extract_locale_from_accept_language_header
  logger.debug "* Locale set to '#{I18n.locale}'"
end

private

def extract_locale_from_accept_language_header
  request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
end

You can also use a plugin such as Iain Hecker’s http_accept_language or even Rack middleware such as Ryan Tomayko’s locale to implement this method in production mode.

Using User Profile
In your Ruby on Rails application it is also possible that users want to set the locale for their interface by setting it from database. In user’s profile, user will have to choose the locale from a dropdown list and it will get saved in the database along with other profile data. In this case, you need to override the locale value something like this.

def set_locale
  if current_user?
    I18n.locale = current_user.locale
  else
    I18n.locale = :en
  end
end

That’s it for Part II. In Part III we will check

  • Using different backends for I18n API
  • Translating Routes

Internationalization (I18n) API in Ruby on Rails 3 (Part I)

Whenever we hear about Internationalization of web application, the first word that comes to our mind is “Translation“. There are three ways to translate software:

  • Do it Yourself
  • Pay Someone for doing Translation
  • Ask your Users

No matter what, making the software ‘Internationalization ready’ is the developer’s job. No static strings, dates in the code and so forth; every text must be put in resource files and the code decides which text to use. Luckily Ruby on Rails provides a simple ruby gem called “I18n” (internationalization) that does exactly that.

Definition of Internationalization & Localization :

First of all let’s check the definition of Internationalization & Localization. According to Wikipedia,

i18n – “Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes.” This means, the “Internationalization” functionality translates all string and other locale specific information ( like date and currency formats) from your application in desire language to end user.

l10n – “Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text.” This means, the “Localization” functionality gives translations in local-specific language and other localized formats for local specific information.

How i18n mechanism works in Rails :

All the languages are different from each other in many ways (For example pluralization rules). It is very difficult for any tools/software to provide a general solution for language translation. To solve this problem I18n API provides two features in Ruby on Rails.

  1. Give support for English and other similar languages out of the box
  2. Provide a user-friendly and adjustable functions for supporting other languages

This means in Ruby on Rails every static string has been internationalized and you can “over-ride” these defaults with “localization” of the application.

There are two parts of Ruby I18n gem which are:

  1. The public I18n API – provides a Ruby module with public methods to characterize the work of the library
  2. A default backend – which executes these public methods

The Public I18n API comes with two most important methods which are:

  1. translate # This public method do translations of the text
  2. localize # This public method localize Date and Time objects to local formats as per settings

These method have aliases #t and #l respectively, so you can use them like this:

I18n.t 'store.title'

I18n.l Time.now
view raw gistfile1.rb This Gist brought to you by GitHub.

Setup/Configuration about the i18n mechanism

You can easily setup ruby I18n gem with few simple steps and use in your application for I18n support. Ruby on Rails application set up reasonable defaults in all .rb and .yml files in config folder. If you want to change the settings, you can easily change them according to your need.

To check the defaults for I18n module you have to check config/locales directory, which is default path for translation.

You will get one file named en.yml which default locale and contains these translation strings:

en:
  hello: "Hello world"
view raw gistfile1.rb This Gist brought to you by GitHub.

English is default locale in I18n library. So, if your application is not set in different locale, :en will translate all the static text changes.

So, when you check this on console the output shows like following :

>> I18n.locale
=> :en
>> I18n.default_locale
=> :en

Some other available locales which are supported by rails 3 are:

ar, bg, bn-IN, bs, ca, cy, cz, da, de, de-AT, de-CH, dsb, el, en-GB, en-US, eo, es, es-AR, es-CO, es-MX, es-PE, et, eu, fa, fi, fr, fr-CA, fr-CH, fur, gl-ES, gsw-CH, he, hi-IN, hr, hsb, hu, id, is, it, ja, ko, lo, lt, lv, mk, mn, nb, nl, nn, pl, pt-BR, pt-PT, rm, ro, ru, sk, sl, sr, sr-Latn, sv-SE, sw, th, tr, uk, vi, zh-CN, zh-TW

The easiest method to set your own locales to override the default locale is to change config/application.rb file in Rails 3. (You can apply same settings to config/environment.rb in Rails 2). Add this line to get German language as default locale:

config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
config.i18n.default_locale = :de

The first line of code tells the location of translation file to the I18n library. The second line of code will set the default locale to :de other than :en.

Using the I18n module

Put these lines in config/locale/de.yml.

de:
  hello: "Hallo Welt"

Let’s check now whether this settings translate “hello” into “German” locale or not.

>> I18n.locale
=> :en
>> I18n.locale = 'de'
=> :de
>> I18n.t "hello"
=> "Hallo Welt"

Using the I18n with interpolation

Most of the time you want to use various variables with your translations. That’s why I18n API also provide interpolation feature.

>> I18n.backend.store_translations :en, :hello => 'Hello %{name}!'
>> I18n.translate :hello, :name => 'Newton'
=> 'Hello Newton!'


Using the I18n with pluralization
In English there are only one singular and one plural form for a given string, e.g. “1 friend” and “2 friends”. The best thing is I18n API also provides a flexible pluralization feature in Rails applications.

We must note that an interpolation variable named :count plays key role in translation and pluralization.

I18n.backend.store_translations :en, :friend => {
:one => '1 friend',
:other => '%{count} friends'
}
I18n.translate :friend, :count => 2
# => '2 friends'

The algorithm for pluralization is:

entry[count == 1 ? 0 : 1]

I.e. here 1 denotes singular and 0 denotes plural. So as specified using conditional operator, if count == 1, it will consider it as singular form otherwise its plural.

That’s it for Part I. In Part II we will check other features of I18n like

  • Localization of Times/Dates
  • Using views for translation
  • Translating ActiveModel errors
  • Organization of resource files
  • Setting the locale (from domain, parameter or client) and of course storing it in the database when the user is logged in

For more details you can check http://ruby-i18n.org/ .

Do you speak Japanese?

Reasons for running multilingual websites

As economic globalization has led to a global market, companies need to attract people from all over the world. Finding customers online may sound easy, but it’s not enough to simply offer products and services online. Internationally successful companies need to consider many other facts, too. One of them is communicating with clients in their native language.

If you communicate in your customer’s mother tongue, they will remain on your website for twice as long and are four times more likely to purchase from you!

Of course, millions of websites are in English, and more than 20% of Internet users are English speaking. But the dominance of the English language on the web is ebbing as there are billions of people on earth that speak in a different language.

Top 10 languages

The best way to win your client’s favor is to communicate in their native language. The figure below illustrates the top 10 languages used in the Internet. English (27%) is followed by Chinese (23%) and Spanish (8%) in the list of top 10 languages that most frequently appear online.

Did you know about Japanese?

What may surprise you is that Spanish is followed closely by Japanese: There are nearly one million Japanese-speaking Internet users, which represent 5.0% of all Internet users in the world and 78% of use the Internet.

Talking to Europe

The latest Eurobarometer survey provides another reason to develop multilingual websites: 9 of 10 internet users in the European Union said, that given a choice of languages, they would always visit a website in their own language. Only a third of them is using a foreign language actively, and only 20% of Internet users in the EU would buy products from a website in a foreign language.

Hello World-Hallo Welt-Hola Mundo-Привет мир-你好世界

Billions of people speak thousands of languages, most websites only one or two. Why is that? My guess is that most developers are bilingual, English and some Native Language. But wait, shouldn’t developers building websites and translators/writers deliver the content?

I am programming for about ten years now. Most of the time as a Java developer (server and desktop projects mostly), since 2008 I switched to Ruby/Rails (web projects only). In this time I worked on quite a few projects. Despite their differences, one thing had all in common: doing and maintaining translations was always dirty and chaotic matter. I am sure you experienced some of these situations:

  • The translator edited the resource file in Word with all the fancy formatting.
  • Resource files are exchanged via email. (How would it look to have to have a “pull” and “push” command?)
  • The dialog looks good in English, but in German it sucks. Simply because the same text in German in 30% longer.
  • Missing translations, unused translations.
  • Spelling mistakes, weird texts like “Am I write?” in the application. Not everyone is an English Native (if you haven’t realized it yet, I am not an English native. So please don’t judge my posts by my writing style.), but many think their English rules.
  • And a lot more.

Analyzing these problems, I came to the conclusion that two things have to change:

  1. Translation needs much more integration in the software process. So we can use Rake,CapistranoCI, and so on to get the latest resource files, to push changes to translators, …
  2. I shouldn’t never ever have to explain to a translator/writer what a .yaml file is. Translator should only see a fancy web interface where they can do their job.

So I started to work on a project called “linguist”. The goal is to make linguist the “Github” for translations. I don’t want to give away too many details at this early stage, but I will try to publish updates on this blog regularly.

One more thing, I am looking for help/support. So if you are interested on working on the project write me an email hjuskewycz (a t) hemju.com (paid or voluntary). Of course feedback, ideas, critics are welcome too.