This concludes the Ruby month of my tutorial series. So before I look at the interesting case of the ruby gettext approach to internationalization (using the fast_gettext gem) here are the links to previous articles on internationalization for Ruby that I have covered so far (in case you want to catch up):
- the basic usage of the i18n gem with how-tos for Ruby, Rails, Sinatra and Padrino
- the advanced features of ihe 18n gem
- an alternative to i18n, the r18n gem
Ruby gettext internationalization: What Fast Gettext can do
The topic of our fourth and final article will be covering the fast_gettext gem. Fast Gettext features simplicity of use, clean namespace, it is thread safe, extendible and it supports multiple backends. Since translations are cached after the first use, performance is almost the same for all the backends.
When it comes to the performance, it is claimed on the fast_gettex Github page that it is much faster and much more efficient then the competition:
Hash | FastGettext | GetText | ActiveSupport I18n::Simple | |
---|---|---|---|---|
Speed\* | 0.82s | 1.36s | 4.88s | 21.77s |
RAM\* | 4K | 8K | 4480K | 10100K |
Included backends | db, yml, mo, po, logger, chain | mo | yml (db/key-value/po/chain in other I18n backends) | |
\*50.000 translations with ruby enterprise 1.8.6 through rake benchmark |
We already wrote extensively about Gettext in some of our previous articles, you can get back to them in order to read more about PHP internationalization with Gettext or setting up gettext backend for i18n Ruby gem.
Setting up for ruby gettext: Installation of fast_gettext
$ sudo gem install fast_gettext
Setting the text domain and current locale
This should be done once in every Thread (e.g. Rails -> ApplicationController):
FastGettext.text_domain = 'my_app'
# only allow these locales to be set (optional)
FastGettext.available_locales = \['de','en','fr','en_US','en_UK'\]
FastGettext.locale = 'de'
Translation lookup with fast gettext
include FastGettext::Translation
*('Car') == 'Auto'
\('not-found') == 'not-found'
s*('Namespace|no-found') == 'not-found'
n('Axis','Axis',3) == 'Achsen' #German plural of Axis
_('Hello %{name}!') % {:name => "Pete"} == 'Hello Pete!'
Managing translations - MO/PO files
PO files are plain text files and can be created in any plain text editor. There is a command line tool from the GNU gettext package that can automatize this process to a degree. After the PO file is ready, it can be converted to an MO file using a command line tool from the same package. We have covered this process in detail in one of our previous articles. The author of fast_gettext has started to work on MO/PO parser/generator, so you can check that project as well.
There are also rake tasks that can be used or customized.
When PO or MO files are ready, it is possible to load either of them:
MO files:
FastGettext.add_text_domain('my_app',:path => 'locale')
PO files:
FastGettext.add_text_domain('my_app',:path => 'locale', :type => :po) # :ignore_fuzzy => true to not use fuzzy translations # :report_warning => false to hide warnings about obsolete/fuzzy translations
Managing translations - database
Any model DataMapper/Sequel or any other (non-database) backend can be used. The only thing that is required is to implement the response to self.translation(key, locale) call. The plurals are separated by default with pipes ( | ), but it is possible to overwrite this. Database access is cached, so only the first lookup hits the database.
require "fast_gettext/translation_repository/db" FastGettext::TranslationRepository::Db.require_models #load and include default models FastGettext.add_text_domain('my_app', :type => :db, :model => TranslationKey)
Managing translations - yaml
If you already have a bunch of yaml files, or you just like the format and yet you want to exploit the performance of fast gettext, no problem, it supports yaml. The syntax and indentation rules should be used as in i18n.
FastGettext.add_text_domain('my_app', :path => 'config/locales', :type => :yaml)
Rails plugin
There is a rails plugin that simplifies the setup.
To install it as a gem, just include it in Gemfile and run bundle:
gem 'gettext_i18n_rails'
The plugin can make use of the i18n Rails localization, you just need to add the files to config/locales directory.
To initialize:
# config/initializers/fast_gettext.rb
FastGettext.add_text_domain 'app', :path => 'locale', :type => :po
FastGettext.default_available_locales = \['en','de'\] #all you want to allow
FastGettext.default_text_domain = 'app'
And in your application:
# app/controllers/application_controller.rb
class ApplicationController < ...
before_filter :set_gettext_locale
Any call to I18n that matches a gettext key will be translated through FastGettext:
I18n.locale <==> FastGettext.locale.to_sym
I18n.locale = :de <==> FastGettext.locale = 'de'
Pluralization
Plurals are selected by index, for example: ['car', 'cars'][index], and the pluralisation rule decides which form will be used. By default, only the plural form for English and other similar languages is supported: count == 1 ? 0 : 1
If a language that uses a different plural form needs to be supported, a custom pluralisation rule needs to be added either via Ruby:
FastGettext.pluralisation_rule = lamda{|count| count > 5 ? 1 : (count > 2 ? 0 : 2)}
or via mo/po file:
Plural-Forms: nplurals=2; plural=n==2?3:4;
Defaults
If only one text domain is used, setting FastGettext.default_text_domain = 'app'
is sufficient and no more text_domain=
is needed.
If the simple rule of "first availble_locale
or 'en'" is not suficcient, FastGettext.default_locale = 'de' can be set
.
If no available_locales are set, fallback can be set in default_available_locales.
Loading multiple repositories
Any number of repositories can be used to find a translation, and when the first can not translate a given key, the next one is searched:
repos = \[
FastGettext::TranslationRepository.build('new', :path=>'....'),
FastGettext::TranslationRepository.build('old', :path=>'....')
\]
FastGettext.add_text_domain 'combined', :type=>:chain, :chain=>repos
Logging
It can be useful to keep track of the keys that could not be translated or that were used. In order to do so, a Logger should be added to a Chain:
repos = \[
FastGettext::TranslationRepository.build('app', :path=>'....')
FastGettext::TranslationRepository.build('logger', :type=>:logger, :callback=>lamda{|key_or_array_of_ids| ... }),
}
FastGettext.add_text_domain 'combined', :type=>:chain, :chain=>repos
If the Logger is the first in the chain, it will record all the used translations. However if it is last, it will record only those that were not found.
Some other useful Ruby gettext packages
Ok, so far we have covered i18n gettext backend (in one of the previous articles) and fast gettext. It does not end there, there are some other solutions as well:
- Gettext for Ruby - an original package; fast_gettext was created as a response to it; this project has not been updated for some time now
- GettextI18nRailsJs - an extension of gettext_i18n_rails that makes the PO files available client side via javascript (as JSON)
Conclusion
This brings us to the end of our internationalization for Ruby series. To sum up the various alternatives:
- Ruby i18n - comes as a Rails default and would be natural first choice for the Rails developers. Can be easily used in plain Ruby or other frameworks as well. Supports multiple backends and resource files. It is quite easy to use.
- r18n - an alternative to i18n, can be used as a superset to it, has somewhat different approach to translation lookup and also add some interesting features to yaml files (filters).
- Ruby gettext - has not been maintained for a while. Several other packages have risen as a response to it.
- Ruby gettext using fast_gettext - very light and yet powerful. Also supports backends and resource files other then gettext, with no or not much additional cost in performance.
Further reading
- fast_gettext at github
- gettext_i18n_rails at github
- Example Rails application
- get_pomo - mo/po parser/generator on github
- A good tutorial on using fastgettext with database backend
- Ruby Gettext
- Ruby gettext package on github
- GNU gettext
- GettextI18nRailsJs