Rails I18n revs up: Globalize2 preview released!
Please note: the following explanations assume that you're familiar with the new I18n API in Rails and might leave some unanswered questions otherwise :-). Also note that this is a preview release targeted at Rails I18n developers. We'll do at least one more release and provide more complete documentation about how Globalize2 can be used by end users then.
Globalize2 preview
The first preview release of Globalize2 includes the following features and tools. Most of them can be used independent of each other so you can pick whatever tools you need and combine them with other libraries or plugins.
- Model translations – transparently translate ActiveRecord data
- Static backend – swap the Simple backend for this more powerful backend, enabling custom pluralization logic, locale fallbacks and translation value objects
- Locale LoadPath – easily load translation data from standard locations enforcing conventions that suite your needs
- Locale Fallbacks – 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 – access useful meta data information on the translations returned from your backend and/or translated models
Also, we've put together a small and simple demo application for demonstrating Globalize2's feature set. You can find globalize2-demo on GitHub. Instructions for installation are included in the readme at the bottom of that page.
The implementation of Globalize2 has been sponsored by the company BESTGroup consulting and software, Berlin.
Installation
To install Globalize2 with its default setup just use:
script/plugin install -r 'tag 0.1.0_PR1' git://github.com/joshmh/globalize2.git
This will:
- activate model translations
- set I18n.load_path to an instance of Globalize::LoadPath
- set I18n.backend to an instance of Globalize::Backend::Static
Configuration
You might want to add additional configuration to an initializer, e.g. config/initializers/globalize.rb
Model translations
Model translations (or content translations) allow you to translate your models’ attribute values. E.g.
class Post < ActiveRecord::Base
translates :title, :text
end
Allows you to translate values for the attributes :title and :text per locale:
I18n.locale = :en
post.title # Globalize2 rocks!
I18n.locale = :he
post.title # ?????????2 ????!
In order to make this work you currently need to take care of creating the appropriate database migrations manually. Globalize2 will provide a handy helper method for doing this in future.
The migration for the above Post model could look like this:
class CreatePosts < ActiveRecord::Migration
def self.up
create_table :posts do |t|
t.timestamps
end
create_table :post_translations do |t|
t.string :locale
t.references :post
t.string :title
t.text :text
t.timestamps
end
end
def self.down
drop_table :posts
drop_table :post_translations
end
end
Globalize::Backend::Static
Globalize2 ships with a Static backend that builds on the Simple backend from the I18n library (which is shipped with Rails) and adds the following features:
- It uses locale fallbacks when looking up translation data.
- It returns an instance of Globalize::Translation::Static instead of a plain Ruby String as a translation.
- It allows to hook in custom pluralization logic as lambdas.
Custom pluralization logic
The Simple backend has its pluralization algorithm baked in hardcoded. This algorithm is only suitable for English and other languages that have the same pluralization rules. It is not suitable for, e.g., Czech though.
To add custom pluralization logic to Globalize’ Static backend you can do something like this:
@backend.add_pluralizer :cz, lambda{|c|
c == 1 ? :one : (2..4).include?(c) ? :few : :other
}
Locale Fallbacks
Globalize2 ships with a Locale fallback tool which extends the I18n module to hold a fallbacks instance which is set to an instance of Globalize::Locale::Fallbacks by default but can be swapped with a different implementation.
Globalize2 fallbacks will compute a number of other locales for a given locale. For example:
I18n.fallbacks[:"es-MX"] # => [:"es-MX", :es, :"en-US", :en]
Globalize2 fallbacks always fall back to
- all parents of a given locale (e.g. :es for :"es-MX"),
- then to the fallbacks’ default locales and all of their parents and
- finally to the :root locale.
The default locales are set to [:"en-US"] by default but can be set to something else. The root locale is a concept borrowed from CLDR and makes sense for storing common locale data which works as a last default fallback (e.g. "ltr" for bidi directions).
One can additionally add any number of additional fallback locales manually. These will be added before the default locales to the fallback chain. For example:
fb = I18n.fallbacks
fb.map :ca => :"es-ES"
fb[:ca] # => [:ca, :"es-ES", :es, :"en-US", :en]
fb.map :"ar-PS" => :"he-IL"
fb[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en]
fb[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en]
fb.map :sms => [:"se-FI", :"fi-FI"]
fb[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en]
Globalize::LoadPath
Globalize2 replaces the plain Ruby array that is set to I18n.load_path by default through an instance of Globalize::LoadPath.
This object can be populated with both paths to files and directories. If a path to a directory is added to it it will look up all locale data files present in that directory enforcing the following convention:
I18n.load_path << "#{RAILS_ROOT}/lib/locales"
# will load all the following files if present:
lib/locales/all.yml
lib/locales/fr.yml
lib/locales/fr/*.yaml
lib/locales/ru.yml
lib/locales/ru/*.yaml
...
One can also specify which locales are used. By default this is set to "*" meaning that files for all locales are added. To define that only files for the locale :es are added one can specify:
I18n.load_path.locales = [:es]
One can also specify which file extensions are used. By default this is set to ["rb", "yml"] so plain Ruby and YAML files are added if found. To define that only *.sql files are added one can specify:
I18n.load_path.extensions = ['sql']
Note that Globalize::LoadPath “expands” a directory to its contained file paths immediately when you add it to the load_path. Thus, if you change the locales or extensions settings in the middle of your application the change won’t be applied to already added file paths.
Globalize::Translation classes
Globalize2’s Static backend as well as Globalize2 model translations return instances of Globalize::Translation classes (instead of plain Ruby Strings). These are simple and lightweight value objects that carry some additional meta data about the translation and how it was looked up.
Model translations return instances of Globalize::Translation::Attribute, the Static backend returns instances of Globalize::Translation::Static.
For example:
I18n.locale = :de
# Translation::Attribute
title = Post.first.title # assuming that no translation can be found:
title.locale # => :en
title.requested_locale # => :de
title.fallback? # => true
# Translation::Static
rails = I18n.t :rails # assuming that no translation can be found:
rails.locale # => :en
rails.requested_locale # => :de
rails.fallback? # => true
rails.options # returns the options passed to #t
rails.plural_key # returns the plural_key (e.g. :one, :other)
rails.original # returns the original translation with no values
# interpolated to it (e.g. "Hi !")
Other notes
Please note that the Globalize2 Static backend (just like the Simple backend) does not support reloading translation data.