the ruby on rails i18n core api
DESCRIPTION
My presentation on I18n during Ruby India Conference 2010, Bangalore, IndiaTRANSCRIPT
The Ruby on Rails The Ruby on Rails I18n Core APII18n Core API
PRESENTED BYPRESENTED BY
-Neeraj Kumar-Neeraj Kumar
IntroductionIntroduction
• English – Default Language.English – Default Language.• GettextGettext• Tough Task – transform your ROR App Tough Task – transform your ROR App
into its regional language & providing a into its regional language & providing a tool to solve all problems at once.tool to solve all problems at once.
• Provides easy-to-use and extensible framework.Provides easy-to-use and extensible framework.• Translating Translating
- to a single custom language other than English.- to a single custom language other than English.
- for providing multi-language support.- for providing multi-language support.
• To abstract all strings and other locale To abstract all strings and other locale specific bits (such as date or currency specific bits (such as date or currency formats) out of your application. The formats) out of your application. The process of “localization” means to provide process of “localization” means to provide translations and localized formats for translations and localized formats for these bits.these bits.
• Sven FuchsSven Fuchs• Shipped with rails (started with rails-2.2)Shipped with rails (started with rails-2.2)
• In the process of internationalizing: In the process of internationalizing: – Ensure you have support for i18nEnsure you have support for i18n– Tell Rails where to find locale Tell Rails where to find locale
dictionariesdictionaries– Tell Rails how to set, preserve and Tell Rails how to set, preserve and
switch localeswitch locale
SetupSetup for RoR App for RoR App
• Configure the I18n ModuleConfigure the I18n Module– .rb and .yml + .rb and .yml + translations load pathtranslations load path, ,
automatically.automatically.– translations load path (translations load path (I18n.load_pathI18n.load_path) )
- will be loaded automatically and - will be loaded automatically and available in your application.available in your application.
– environment.rbenvironment.rb - instructions to - instructions to customize the locale directory and customize the locale directory and default locale.e.g.default locale.e.g.
# config.i18n.load_path << # config.i18n.load_path << Dir[File.join(RAILS_ROOT, 'my', Dir[File.join(RAILS_ROOT, 'my', 'locales', '*.{rb,yml}')]'locales', '*.{rb,yml}')]
# config.i18n.default_locale = :de # config.i18n.default_locale = :de
en: en:
hello: "Hello hello: "Hello world"world"
• Setting and Passing the LocaleSetting and Passing the Locale
- For more locales - to set and pass the - For more locales - to set and pass the locale between requests.locale between requests.
- Don't use session or cookies to store the - Don't use session or cookies to store the chosen locale.chosen locale.
before_filter :set_localedef set_locale
# if params[:locale] is nil then I18n.default_locale will be used
I18n.locale = params[:locale]end
- URL : - URL : http://example.com/books?locale=pthttp://example.com/books?locale=pt - will load Portuguese localization.- will load Portuguese localization.
• Locale setting from the URL ParamsLocale setting from the URL Params
- link_to( books_url(:locale => - link_to( books_url(:locale => I18n.locale)) - tedious and probably I18n.locale)) - tedious and probably impossible.impossible.
- For 'centralizing dynamic decisions - For 'centralizing dynamic decisions about the URLs' in its about the URLs' in its ApplicationController#default_url_optionApplicationController#default_url_options.s.
# app/controllers/application_controller.rbdef default_url_options(options = {})
logger.debug “default_url_options is passed options:
#{options.inspect}\n”{:locale => I18n.locale}
end
- Every helper method dependent on url_for - Every helper method dependent on url_for automatically include the locale in the query automatically include the locale in the query stringstring..
# config/routes.rbmap.resources :books, :path_prefix => '/:locale'
# => www.example.com/nl/booksmap.root '/:locale', :controller => “dashboard”
- Drawback of default_url_options - Drawback of default_url_options implementation pass the :id implementation pass the :id
option, e.g. link_to option, e.g. link_to 'show', book_url(:id 'show', book_url(:id => book)=> book)
• Locale setting from the Domain NameLocale setting from the Domain Name
- advantages- advantages–Locale, an obvious part of URLLocale, an obvious part of URL–Trivial to implementTrivial to implement– Intuitively grasp the language of Intuitively grasp the language of
content before loadingcontent before loading–Search engines like content in Search engines like content in
different languages at different different languages at different domains.domains.
before_filter :set_locale
def set_localeI18n.locale = extract_locale_from_uri
end
def extract_locale_from_tldparsed_locale = request.host.split('.').lastI18n.available_locales.include?
(parsed_locale.to_sym) ? parsed_locale : nilend
- parsed_locale = request.host.split('.').first - parsed_locale = request.host.split('.').first to set the locale from subdomain.to set the locale from subdomain.
• Locale setting from the Client Supplied Locale setting from the Client Supplied InformationInformation
- information can come from - information can come from – Users preferred language (set in Users preferred language (set in
their browser)their browser)– Users geographical location Users geographical location
inferred from IPinferred from IP– By choosing locale in your By choosing locale in your
application interface and saving to application interface and saving to the profile.the profile.
def set_localelogger.debug = “* Accept-Language:
#{request.env['HTTP_ACCEPT_LANGUAGE']}”I18n.locale =
extract_locale_from_accept_language_headerend
def extract_locale_from_accept_language_headerrequest.env['HTTP_ACCEPT_LANGUAGE'].scan(/
^[a-z]{2}/).firstend
- Using GeoIP (or similar) Database – - Using GeoIP (or similar) Database – GeoIP Lite CountryGeoIP Lite Country
- User Profile- User Profile
en:activerecord:
models:user: fooadmin: bar
attributes:user:
login: “Handle” # => will translate User attribute “login” as “Handle”
Active Record Model TranslationsActive Record Model Translations
en:activerecord:
errors:messages:
blank: “can not has nothing”# => #<User id:nil, etc># => u.valid?# => false# => u.errors.on(:name)# => “can not has nothing”
– Error Messages ScopeError Messages Scope - Active - Active Record Validation Error Messages Record Validation Error Messages TranslationTranslation
– Active Record will look up this key in Active Record will look up this key in the namespacesthe namespaces– activerecord.errors.models.activerecord.errors.models.
[model_name].attributes.[model_name].attributes.[attribute_name] [attribute_name]
– activerecord.errors.models.activerecord.errors.models.[model_name] [model_name]
– activerecord.errors.messages activerecord.errors.messages
en:activerecord:
errors:messages:
already_registered: “u already is {{model}}”# => u.errors.on(:email)# => “u already is foo”
– Error Message InterpolationError Message Interpolation– Count can be used for PluralizationCount can be used for Pluralization
– Translation for the Active Record Translation for the Active Record error_messages_for Helpererror_messages_for Helper
en: activerecord: errors: template: header: one: “1 error prohibted this {{model}} from being saved” other: “{{count}} errors prohibted this {{model}} from being saved” body: “There were problems with the following fields:”
Anatomy of GemAnatomy of Gemi18n
i18n
i18ni18n
backendbackend
core_extcore_ext helpers
helpers localeslocales
backendbackend
exceptionsexceptions
gettextgettext
helpershelpers
localelocale
i18ni18n
versionversion
localelocale
gettextgettext
fallbacksfallbacks
tagtag
active_recordactive_recordInterpolation
compiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
Interpolationcompiler
fastfast
gettextgettext
helpershelpers
Interpolationcompiler
Interpolationcompiler
linkslinks
metadatametadata
simplesimple
active_recordactive_record
basebase
cachecache
cascadercascader
chainchain
cldrcldr
fallbacksfallbacks pluralization
pluralization
missingmissing
translationtranslation
store_procsstore_procs
backend
i18n.rbi18n.rb
• get and set methods for default_locale.get and set methods for default_locale.
def default_locale(locale)@@default_locale = locale.to_sym rescue
nilend
• Get method for locale - either Get method for locale - either default_locale or locale in default_locale or locale in Thread.currentThread.current hash.hash.
i18n.ri18n.rbb
• Set method for locale - set the locale in Set method for locale - set the locale in Thread.currentThread.current
• Returns the current backend.Returns the current backend.
def backend@@backend ||= Backend::Simple.new
end
• Set method for current backend.Set method for current backend.
simple.simple.rbrb • Makes easier to extend the Simple Makes easier to extend the Simple
backend's behaviour by including backend's behaviour by including modules e.g. modules e.g. I18n::Backend::Simple.send(:include, I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization).I18n::Backend::Pluralization).
module I18nmodule Backend
class Simpleinclude Base
endend
end
i18n.rbi18n.rb
def available_locales@@available_locales ||=
backend.available_localesend
I18n.rbI18n.rb
• Default scope separator method (i.e Default scope separator method (i.e default_separator) default_separator) – set your separator set your separator – return the separator.return the separator.
• Default is '.'Default is '.'
I18n.rbI18n.rb
• Exception handler method (i.e. Exception handler method (i.e. exception_handler)exception_handler)– set your exception handlerset your exception handler– return the current exception handler.return the current exception handler.
• Default is :default_exception_handler Default is :default_exception_handler private method.private method.
I18n.rbI18n.rb
def default_exception_handler(exception, locale, key, options)
return exception.message if MissingTranslationData === exception
raise exceptionend
I18n.rbI18n.rb
• load_path=(load_path) method - set load load_path=(load_path) method - set load path instance.path instance.
• load_path methodload_path method– *.rb*.rb and contain plain and contain plain RubyRuby Hashes.Hashes.– *.yml*.yml and contain and contain YAMLYAML data. data.
I18n.rbI18n.rb
• Class method config returns I18n Class method config returns I18n configuration objectconfiguration object
def configThread.current[:i18n_config] ||= I18n::Config.new
end
• method config=(value) sets I18n method config=(value) sets I18n configuration object.configuration object.
I18n.rbI18n.rb
• Method to reload the translations.Method to reload the translations.• Main method Main method translatetranslate and and localizelocalize• I18n.t :message I18n.t :message I18n.t 'message' I18n.t 'message'
I18n.rbI18n.rb
• Translation method – scope options, Translation method – scope options, interpolation, pluralization, defaults interpolation, pluralization, defaults
• :scope option – one or many keys - scope :scope option – one or many keys - scope for a translation keyfor a translation key
I18n.t :invalid, :scope => [:active_record, :error_messages] # => I18n.translation :invalid :active_record.error_messages.invalid
I18n.rbI18n.rb
• InterpolationInterpolationI18n.t :foo, :bar => 'baz' #=> 'foo baz'
I18n.t :foo, :count => 1 #=> 'foo'I18n.t :foo, :count => 0 #=> 'foos'I18n.t :foo, :count => 2 #=> 'foos'
• PluralizationPluralization
• DefaultsDefaults
I18n.t :foo, :default => 'bar'
I18n.rbI18n.rb
def translate(&args)options = args.pop if args.last.is_a?(Hash)key = args.shiftlocale = options && options.delete(:locale) || config.localeraises = options && options.delete(:raise)config.backend.translate(locale, key, options || {})rescue I18n::ArgumentError => exceptionraise exception if raiseshandle_exception(exception, locale, key, options)
endalias :t :translate
I18n.rbI18n.rb
def localize(object, options = {})locale = options.delete(:locale) || config.localeformat = options.delete(:format) || :defaultconfig.backend.localize(locale, object, format, options)
endalias :l :localize
base.rbbase.rb
• load_translations method - accepts list of load_translations method - accepts list of paths of translation files.paths of translation files.
def load_translations(*filenames)filenames.each { |filename|
load_file(filename) }end
base.rbbase.rb
def load_file(filename)type = File.extname(filename).tr('.', '').downcaseraise UnknownFileType.new(type, filename) unless respond_to?(:”load_#(type)”)data = send(:”load_#(type)”, filename)data.each { |locale, d| merge_translation(locale, d) }
end
base.rbbase.rb
def store_translations(locale, data, options = {})merge_translations(locale, data, options)
end
def merge_translations(locale, data, options = {})locale = locale.to_symtranslations[locale] ||= {}separator = options[:separator] || I18n.default_separatordata = unwind_keys(data, separator)data = deep_symbolized_keys(data)
merger = proc do |key, v1, v2|Hash === v1 && Hash === v2 ?
v1.merge(v2, &merger) : (v2 || v1)endtranslations[locale].merge!(data, &merger)
end
base.rbbase.rb
base.rbbase.rb
def translate(locale, key, options = {}) raise InvalidLocale.new(locale) unless locale return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
if options.empty? entry = resolve(locale, key, lookup(locale, key), options) raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
else count, scope, default = options.values_at(:count, :scope, :default) values = options.reject { |name, value| RESERVED_KEYS.include?(name) }
entry = lookup(locale, key, scope, options) entry = entry.nil? && default ? default(locale, key, default, options) : resolve(locale, key, entry, options)
base.rbbase.rb
raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
entry = pluralize(locale, entry, count) if count
entry = interpolate(locale, entry, values) if values
end entryend
base.rbbase.rb
base.rbbase.rb
• look_up method look_up method – looks up the translation from the looks up the translation from the
translations hash. translations hash. – Splits keys or scopes containing dots Splits keys or scopes containing dots
into multiple keys e.g. currency.format into multiple keys e.g. currency.format - %w(currency format).- %w(currency format).
base.rbase.rbb
• localize methodlocalize method
case match when '%a' then I18n.t(:"date.abbr_day_names”, :locale => locale, :format => format)[object.wday] when '%A' then I18n.t(:"date.day_names”, :locale => locale, :format => format)[object.wday] when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon] when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour end
base.rbbase.rb
CustomizationCustomization
• Different BackendsDifferent Backends– I18n::Backend::SimpleI18n::Backend::Simple
– shipped with Active Support of shipped with Active Support of vendor directory, work for english vendor directory, work for english or similar languages.or similar languages.– capable of reading translations capable of reading translations
but cannot dynamically store but cannot dynamically store them to any format.them to any format.
MissingTranslationData # no translation was found for the requested keyInvalidLocale # the locale set to I18n.locale is invalid (e.g. nil)
• Different Exception HandlersDifferent Exception Handlers
- customization – e.g. the default exception - customization – e.g. the default exception handling does not allow to catch missing handling does not allow to catch missing translation during automated test easilytranslation during automated test easily..
– I18nI18n– own backendown backend–makes easy to exchange the makes easy to exchange the
Simple backend implementation Simple backend implementation with that to fits better with your with that to fits better with your need.need.
I18n.backend = Globalize::Backend::Static.new
module I18ndef just_raise_that_exception(*args)
raise args.firstend
end
I18n.exception_handler = :just_raise_that_exception
ReferencesReferences• http://guides.rails.info/i18n.htmlhttp://guides.rails.info/i18n.html
• http://rails-i18n.org/wikihttp://rails-i18n.org/wiki
• http://iain.nl/2008/09/translating-activerecord/http://iain.nl/2008/09/translating-activerecord/
• http://github.com/svenfuchs/rails-i18nhttp://github.com/svenfuchs/rails-i18n
• http://rails-i18n.org/wiki/wikipages/i18n-rails-guidehttp://rails-i18n.org/wiki/wikipages/i18n-rails-guide
• http://www.artweb-design.de/2008/7/18/the-ruby-on-http://www.artweb-design.de/2008/7/18/the-ruby-on-rails-i18n-core-apirails-i18n-core-api
Questions?Questions?
Thank You!Thank You!