← Home

The Ruby on Rails I18n core api

The pivotal point of the new I18n api in Rails is the I18n module which is provided as a gem and shipped with Rails in ActiveSupport's vendor directory. This module comes with the following features:

  • The main translation method #translate which is used to lookup translations.
  • The #localize method which is used to localize Date, DateTime and Time objects.
  • It stores the current locale in Thread.current.
  • It stores a default locale which is used when no locale has been passed or set.
  • It stores a backend which carries the actual implementation for the translate and localize methods.
  • It comes with a default exception handler which catches exceptions that are raised in the backend.

Both the backend and the exception handler can be swapped with different implementations. Also, #translate is aliased to #t and #localize is aliased to #l for convenience.

#translate supports the following common I18n features which are implemented in the provided Simple backend and should be implemented in all future backends:

  • Lookup of translations by a locale and nested (scoped, namespaced) keys.
  • Defaults that will be used if the lookup does not yield a translation.
  • Interpolation of values to a translation string.
  • Pluralization of translations depending on a :count option.

Lookup, scope and nested keys

Translations are looked up by keys which can be both Symbols or Strings, so these calls are equivalent:


I18n.t :message
I18n.t 'message'

#translate also takes a :scope option which can contain one or many additional keys that will be used to specify a "namespace" or scope for a translation key:


I18n.t :invalid, :scope => [:active_record, :error_messages]

This looks up the :invalid message in the ActiveRecord error messages.

Additionally, both the key and scopes can be specified as dot separated keys as in:


I18n.translate :"active_record.error_messages.invalid"

Thus the following calls are equivalent:


I18n.t 'active_record.error_messages.invalid'
I18n.t 'error_messages.invalid', :scope => :active_record
I18n.t :invalid, :scope => 'active_record.error_messages'
I18n.t :invalid, :scope => [:active_record :error_messages]

Defaults

When a default option is given its value will be returned if the translation is missing:


I18n.t :missing, :default => 'Not here'
# => 'Not here'

If the default value is a Symbol it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned.

E.g. the following first tries to translate the key :missing and then the key :also_missing. As both do not yield a result the string 'Not here' will be returned:


I18n.t :missing, :default => [:also_missing, 'Not here']
# => 'Not here'

Interpolation

All options besides :default and :scope that are passed to #translate will be interpolated to the translation:


I18n.backend.store_translations 'en-US', :thanks => 'Thanks {{name}}!'
I18n.translate :thanks, :name => 'Jeremy'
# => 'Thanks Jeremy!'

If a translation uses :default or :scope as a interpolation variable an I18n::ReservedInterpolationKey exception is raised. If a translation expects an interpolation variable but it has not been passed to #translate an I18n::MissingInterpolationArgument exception is raised.

Pluralization

The :count interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR:


I18n.backend.store_translations 'en-US', :inbox => {
  :one => '1 message', 
  :other => '{{count}} messages'
}
I18n.translate :inbox, :count => 2
# => '2 messages'

The algorithm for pluralizations in en-US is as simple as:


entry[count == 1 ? 0 : 1]  

I.e. the translation denoted as :one is regarded as singular, the other is used as plural (including the count being zero).

If the lookup for the key does not return an Hash suitable for pluralization an I18n::InvalidPluralizationData exception is raised.

Bulk and namespace lookup

To lookup multiple translations at once an array of keys can be passed:


I18n.t [:odd, :even], :scope => 'active_record.error_messages'
# => ["must be odd", "must be even"]

Also, a key can translate to a (potentially nested) hash as grouped translations. E.g. one can receive all ActiveRecord error messages as a Hash with:


I18n.t 'active_record.error_messages'
# => { :inclusion => "is not included in the list", :exclusion => ... }

Setting and passing a locale

The locale can be either set pseudo-globally to I18n.locale (which uses Thread.current like, e.g., Time.zone) or can be passed as an option to #translate and #localize.

If no locale is passed I18n.locale is used:


I18n.locale = 'de-DE'
I18n.t :foo 
I18n.l Time.now

Explicitely passing a locale:


I18n.t :foo, :locale => 'de-DE'
I18n.l Time.now, :locale => 'de-DE'

I18n.locale defaults to I18n.default_locale which defaults to 'en-US'. The default locale can be set:


I18n.default_locale = 'de-DE'

Using a different backend

A different backend can be set on the I18n module:


I18n.backend = I18n::Backend::Gettext

The I18n gem (and thus Rails) only ships with the Simple backend which is tailored to Rails' needs. Other backends can be provided as external solutions.

Exceptions and exception handlers

The #translate method catches exceptions that are thrown in the backend and passes them to the exception handler that is defined as I18n.exception_handler and defaults to I18n#default_exception_handler.

In #default_exception_handler all exceptions are re-raised except for MissingTranslationData exceptions. When a MissingTranslationData exception has been caught the #default_exception_handler will return the exception's error message.

This behaviour is particulary useful during, e.g., view developement when the developer does not want to switch contexts (add a translated string to the view, define the key and translation somewhere else, go back to the view, ...) because otherwise the application would break because of a missing translation.

On the other hand in different contexts a different exception handling might be useful. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module:


module I18n
  def just_raise_that_exception(*args)
    raise args.first
  end
end

I18n.exception_handler = :just_raise_that_exception

This would re-raise all caught exceptions including MissingTranslationData.

Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides a method #translate. When a MissingTranslationData exception occurs in this context the helper wraps the message into a span with the css class translation_missing.

To do so the helper forces I18n#translate to raise exceptions no matter what exception handler is defined by setting the :raise option:


I18n.t :foo, :raise => true # always re-raises exceptions from the backend

Populating the translations storage

Libraries can use I18n.load_translations to populate the translations storage:


# in active_support.rb
I18n.load_translations 'active_support/locale/en-US.yml'

# in active_support/locale/en-US.yml
en-US:
  date:
    formats:
      default: "%Y-%m-%d"
      # ...

The Simple backend can load YAML files as well as plain Ruby files. If you want to store translations in Ruby make sure the file evaluates to a plain Ruby Hash like so:


# translations in Ruby
{ :'en-US' => {
  :date => { 
    :formats => {
      :default => "%Y-%m-%d"
      # ...
    }
  }
}}

Other backends might add capabilities to load from different sources like, e.g., SQL for a database backend or PO/MO files for a gettext backend.

Get involved!

If you'd like to join us working on Ruby on Rails's future I18n support, provide feedback or ask questions please do so! You can find our Google Group over at http://groups.google.com/group/rails-i18n.

blog comments powered by Disqus