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 !'
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 => '8 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.