= Using Gettext To Translate Your Rails Application
h1. Using Gettext To Translate Your Rails Application
This guide will show you how to use the Ruby library Ruby-GetText and a
combination of external tools to create and manage the translation of your
Rails app.
== Using Gettext To Translate Your Rails Application
=== Translating Rails Apps
h1. Using Gettext To Translate Your Rails Application
p{color: red}. The version of this document is optimized for Ruby-GetText 1.9.0
and Rails 1.2.
h2. Introduction
h3. Stand on the shoulders of Giants
"Gettext":http://www.gnu.org/software/gettext/ is a great tool for translating
user interfaces of applications into different languages. It has been around
for a long time and is very well established for this task. Gettext helps you
to open up your application to a much wider user base than you would normaly
reach in only one language. Since it is used in many open source projects it
has a lot of useful tools you can use to your own advantage. It would be
possible to "roll your own" solution, but this would consume a large amount of
time. Time you would lose for the development of your app. And this is not
something you would want, right?
So in this tutorial I am going to show you how to effectively use Gettext for
all your translation needs in your Ruby on Rails application.
h3. What can Gettext do for you?
Here is a quick explanation of Gettext for those of you who haven't worked with
it, yet. Gettext is a set of tools that provide help when translating strings
in your software. It gives you (straight from the "gettext
manual":http://www.gnu.org/software/gettext/manual/html_node/gettext_2.html#SEC2):
* A set of conventions about how programs should be written to support message
catalogs. * A directory and file naming organization for the message catalogs
themselves. * A runtime library supporting the retrieval of translated
messages. * A few stand-alone programs to massage in various ways the sets of
translatable strings, or already translated strings.
The only thing you have to do to your program sources is wrap all translatable
strings with a method call: gettext(text) or shorter
_(text). Example:
Without gettext:
notice = 'Thank you for buying our product.'With gettext:
notice = _('Thank you for buying our product.')
If you have wrapped everything between the method call Gettext will provide you
with the tools to extract these messages (called a harvester) from your source
code and save the results to a portable file format. A runtime library will
allow you to display a translated message whenever _() is called. In essence
this is what Gettext does. It can do a couple of more things (like update /
merge message catalogs, handling plurals, ...) but essentially it tries to make
it as easy and as unobtrusive as possible to translate the language strings in
your source code and then get out of your way.
h2. Preperation is everything -- use UTF-8 everywhere
h3. Edit your files with UTF-8 only
The W3C has an awesome page with all you need to know about character sets
(choosing, declaring, serving, editing, ...) and other important stuff
concerning internationalization on their site called "W3C I18N Topic
Index":http://www.w3.org/International/resource-index.html. There you will find
a lot of good help and suggestions on everything you need to know about general
internationalization topics. You really want to spend at least a couple of
hours reading through the resources they offer or link to.
If you happen to use VIM like I do you can put these two lines in your
.vimrc and it will from then on treat your files as UTF-8 and
convert everything to UTF-8 whenever you save a file:
set encoding=utf8 set fileencoding=utf8If your editor of choice doesn't provide support for UTF-8 it is probably time to get a new one ;) h3. Feed your database with UTF-8 If I need a database I usually go for MySQL. At least most of the time. Since version 4.1.x MySQL has very good support for different character sets. Please read the extensive "MySQL Character Set Support":http://www.quepublishing.com/articles/printerfriendly.asp?p=328641&rl=1 article. It will give you a very good idea about the kind of support MySQL has to offer. Here is a table definition I use in a current project:
CREATE TABLE `pages` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(255) default NULL, `keywords` varchar(255) default NULL, `description` varchar(255) default NULL, `block1` text, `block2` text, `block3` text, `block4` text, `block5` text, `lang` varchar(10) NOT NULL default 'en_EN', `category` varchar(255) default NULL, `path` varchar(255) default '/', `updated_at` datetime default NULL, `created_at` datetime default NULL, `published_at` datetime default NULL, `layout` varchar(255) default 'main.rhtml', `template` varchar(255) default NULL, `access` tinyint(3) unsigned default '3', `version` int(10) unsigned default '1', `is_published` tinyint(3) unsigned default '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;There is nothing really special about it except the
DEFAULT
CHARSET=utf8 in the last line. It will make MySQL use UTF-8 for every
field that holds text like varchar and text.
Add the following line to your database.yml to connect with UTF-8.
encoding: UTF8If you use another maybe more mature and feature rich database like PostgreSQL UTF-8 support should also be available. Just make sure you configure everything upfront, because it might save you time later on. If you still think you are going to tack on support for UTF-8 later you might reconsider after reading David's and Jamis' experiences: "Forty-four grueling hours (or Welcome to 37s!) by David Heinemeier Hansson":http://www.loudthinking.com/arc/000415.html and "On the job by Jamis Buck":http://weblog.jamisbuck.org/2005/3/5/on-the-job. If for any reason you need to use anything other than UTF-8 you might want to know that you can use the Iconv module to convert between different character sets. This module should be installed by default on most distributions. If it is not it is usually easy to get. Just be sure to get it if you need charcter set conversion. On Debian you might need a
apt-get install
libiconv-ruby to get started. Here is a little example stolen from
"Pickaxe 2":http://pragmaticprogrammer.com/titles/ruby/ page 686:
Example: Convert olé from UTF-8 to ISO-8859-1
$ irb -riconv >> Iconv.conv('ISO-8859-1', 'UTF-8', "ol\303\251") =>
"ol\351"
h3. Installing Ruby-GetText
After everything in your setup will accept UTF-8 we can go ahead and install
Gettext support in Ruby. We will use the
"Ruby-GetText-Package":http://gettext.rubyforge.org/ which is a Ruby
implementation of the Gettext interface that also has some c-bindings for the
Locale.
If you have rubygems, you can easily install Ruby-GetText:
$ gem install gettext Select which gem to install for your platform (...) 1. gettext 1.9.0 (ruby) 2. gettext 1.9.0 (mswin32) ... >Be sure to choose option 2 if you want Ruby-GetText to work on native Windows (with "One-Click Ruby Installer":http://rubyforge.org/projects/rubyinstaller/) as you are most likely not able to compile otherwise. For all other platforms use 1. Alternative: If you need the source you can get it from the author's site: "Ruby-GetText-Package":http://gettext.rubyforge.org/. Besides "installation instructions":http://ponx.s5.xrea.com/hiki/ruby-gettext.html#Install you will find a small "HOWTO":http://ponx.s5.xrea.com/hiki/ruby-gettext-howto.html, "API Reference":http://ponx.s5.xrea.com/hiki/ruby-gettext-api.html and documentation on "how to use provided tools":http://ponx.s5.xrea.com/hiki/ruby-gettext-tools.html. Check the sample Rails application that is delivered with Ruby-GetText. If you installed via RubyGems it is most likely here: /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/samples/rails/, but it could be somewhere else depending on where your Ruby is installed. Later I will call that path $RUBYGETTEXT_RAILS_SAMPLE. So keep this in mind when you read. h3. Create the "po" directory structure Create a
po dir right in your $RAILS_ROOT. In it you will create a
subdir for every language/dialect combination (actually the second part of this
abbreviation stands for "geographic region", but I will just call it dialect
throughout this document). If you don't know the right code for your language
you can seek help at the "Language section of the W3C I18N Topic
Index":http://www.w3.org/International/resource-index.html#lang.
Ultimately your directory structure is going to look like this:
simplepages@colinux: /home/simplepages/rails/po: $ d -T /home/simplepages/rails/po/: |-myapp.pot |-de_DE/: | `-myapp.po |-en_GB/: | `-myapp.po |-en_US/: | `-myapp.po |-fr_FR/: | `-myapp.po |-fr_CH/: | `-myapp.po `-ja/: `-myapp.poThe
myapp.pot file is the original file created by the
rgettext script introduced later. The po files will contain the
translation messages that your application is going to use depending on the
language that is requested.
h3. Convert pofiles to mofiles
After creating pofiles, you need to convert them to mofiles. If you haven't
yet, please read the the "GNU Gettext manual with the explanation of what mo
and po stands
for":http://www.gnu.org/software/gettext/manual/html_node/gettext_5.html#SEC5.
Add the code below to Rakefile:
simplepages@colinux: /home/simplepages/rails/Rakefile require 'gettext/utils' desc "Create mo-files for L10n" task :makemo do GetText.create_mofiles(true, "po", "locale") endThen,
$ rake makemoIt will create locale directories and subdirectroies such as:
simplepages@colinux: /home/simplepages/rails/locale: $ d -T /home/simplepages/rails/locale/: |-de_DE/: | `-LC_MESSAGES/: | |-myapp.mo |-en_GB/: | `-LC_MESSAGES/: | |-myapp.mo |-en_US/: | `-LC_MESSAGES/: | |-myapp.mo |-fr_FR/: | `-LC_MESSAGES/: | |-myapp.mo |-fr_CH/: | `-LC_MESSAGES/: | |-myapp.mo `-ja/: `-LC_MESSAGES/: |-myapp.moThese files are used by Ruby-GetText. You don't need to touch these files. h2. Tools of trade h3. Gettext You will need to install Gettext. On Debian I would do
apt-get install
gettext to do that. It contains a couple of tools that will be handy
later on, most importantly msgmerge which can merge different po
files so updating your message files will be a snap and
msginitwhich can set default values to the header of pofile in
your locale.
h3. Ruby-GetText and rgettext
After you have installed Ruby-GetText and tools you should able to call
rgettext on the command line. rgettext is a replacement for
xgettext which comes with the main Gettext application. Beyond
xgettext, rgettext supports not only ruby
scripts(.rb) but also ERB files (.rhtml), ActiveRecord(.rb) directly.
h4. ActiveRecord support rgettext extracts all the table names and
field names within subclasses of ActiveRecord::Base.
Notice: You need to run your database server and configure the
config/database.xml correctly before executing rgettext.
h3. poEdit
"poEdit":http://www.poedit.net is a great tool to manage and edit your
translations. It gives you a nice graphical frontend to translate all your
messages. It is available for many different platforms. A nice side effect
about using an easy to use GUI tool is that you can tell non-programmers to
install it, open the po file and just start translating. They might have
nothing to do with the coding in your project but will be able to help you
translate your software. So if your grandma can translate English to Chinese
she can maybe help you with your software project :)
h2. Collecting messages
Add the code below to Rakefile:
simplepages@colinux: /home/simplepages/rails/Rakefile desc "Update pot/po
files to match new version." task :updatepo do MY_APP_TEXT_DOMAIN = "myapp"
MY_APP_VERSION = "myapp 1.1.0" GetText.update_pofiles(YOUR_APP_TEXT_DOMAIN,
Dir.glob("{app,lib}/**/*.{rb,rhtml}"), MY_APP_VERSION) end
Running this task will either create or update your po/myapp.pot
and po/*/myapp.po files in the relevant directories. It will go
through all the important directories of your rails app and harvest all the
Gettext strings in files ending in $ rake updatepoh3. Translating and compiling with and without poEdit After you have successfully harvested your files you should have a
myapp.po file in every locale dir. Now you need to translate them.
Since the myapp.po files are mere text files you could just use
your favourite text editors to translate them. Given that your text editor can
edit in UTF-8 mode and you know the escaping rules of Gettext this is all you
actually need. Open the file, translate the text and save it. After you have
saved the file compile it. Gettext doesn't work with the text files
(myapp.po) directly. It wants a compiled version of it
(myapp.mo). Use the rake makemo command to compile:
simplepages@colinux: /home/simplepages/rails/po/de_DE: $ ls myapp.po simplepages@colinux: /home/simplepages/rails: $ rake makemo simplepages@colinux: /home/simplepages/rails/locale/de_DE/LC_MESSAGES: $ ls myapp.moHowever, it is way more comfortable to use an application like poEdit for this. With poEdit you can also easily open up the
myapp.po file. It will
give you a nice side by side view your original strings and the translated
version, telling what is already translated and what is not. You click on a
message and start translating it in a special field. Hit the save button and
poEdit will automatically compile the myapp.mo file for you (check
the preferences if it doesn't do it by default). That's it. With a compiled
myapp.mo you can start to teach your rails app how to translate
your user interface.
See "Documents for
Translators":http://ponx.s5.xrea.com/hiki/ruby-gettext-translate.html for more
details to translate the po-file.
When creating a po file it is useful to include header information at the top
of the po file. This adds useful information such as character set, last
translator etc. If you are not using poEdit add something similar to the top of
the po file.
msgid "" msgstr "" "POT-Creation-Date: 2007-02-19 17:15-0000\n" "PO-Revision-Date: 2003-04-03 10:49--500\n" "Last-Translator: Alastair Bruntonh2. Implementing Ruby-GetText into your rails app By now you should have a translated and compiled\n" "Language-Team: fr_FR \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n"
myapp.mo in your
locale dir. For example my German translation of SimplePages is at
$RAILS_ROOT/locale/de_DE/LC_MESSAGES/myapp.mo.
h3. Including Ruby-GetText
Edit application.rb to bind textdomain to your application.
simplepages@colinux: /home/simplepages/rails/app/controllers/application.rb: require 'gettext/rails' class ApplicationController < ActionController::Base init_gettext "myapp" #init_gettext "myapp", "UTF-8", "text/html" # <= Also you can set charset and content_type. endIn this sample, the textdomain name is "myapp". Replace it as you like to fit your application. Maybe you want to have different textdomains for your site and the admin section. h3. Selecting the scope of your textdomain # If you call
bindtextdomain in ApplicationControler, the
textdomain applies to the entire application. # If you call
bindtextdomain in any other controller with a different
textdomain, this textdomain only applies to this specific controller. For
example if you call a different textdomain in myapp_controller.rb
it will only be used in myapp_controller.rb.
The textdomains are applied to each controller/view/model.
h3. Choosing the right language on every request
Since we are developing a web application we want to be able to choose the
current language by request. Additionally we might want to offer the user the
possibilty to choose the language from a menu.
Ruby-GetText chooses the current language by following these rules in the given
order:
# the first value passed to the 'locale' parameter of GetText.bindtextdomain
method call # 'lang' value of QUERY_STRING # 'lang' value of the
Cookie # the value of HTTP_ACCEPT_LANGUAGE # or default 'en'
(English).
The script $RUBYGETTEXT_RAILS_SAMPLE/vendor/plugins/lang_helper.rb
is a sample that selects locale using the cookie value of the user.
It may be useful to implement a simple controller action to change between
languages eg.
class CookieController < ApplicationController def set_cookie code =
params[:id] cookies[:lang] = { :value => code, :expires => Time.now + 1.year,
:path => '/' } redirect_to home_url end end
So /cookie/set_cookie/fr_FR would change the language to French.
h3. Using the Locale in your templates
Depending on the selected locale you will want to customize the language and
character set in your templates.
File: $RAILS_ROOT/app/view/layouts/main.rhtml
h3. Have your own textdomain for plugin applications If you are a plugin developer and want to have your own textdomain, you need to separate the Class/Module from ActionView::Base/ApplicationController.<%= @page["title"] %> ...
simplepages@colinux:
/home/simplepages/rails/vendor/plugins/gettext/lib/gettext_plugin.rb: require
'gettext/rails'
module LangHelper # If you need to bind yet another textdomain to your plugin.
# Separate the name space from ActionView::Base/ApplicationController. class
YetanotherTextDomain include GetText::Rails
def initialize # You need to call bindtextdomain in an instance of
ActionView::Base. # The locale is used a same values which define
ApplicationController#init_gettext instead of # the textdomain.
bindtextdomain("gettext_plugin") end
def show_language(actionview) langs = ["en"] +
Dir.glob(File.join(RAILS_ROOT,"locale/*")).collect{|item| File.basename(item)}
langs.delete("CVS") langs.uniq! ret = "" + _("Select locale") + "
"
langs.sort.each do |lang| ret << actionview.link_to("[#{lang}]", :action =>
"cookie_locale", :lang => lang) end ret end
def cookie_locale(cookies, flash, params) cookies["lang"] = params["lang"]
flash[:notice] = _('Cookie "lang" is set: %s') % params["lang"] end
end
# This function shows supported languages with link to set cookie # action
(cookie_locale). def show_language YetanotherTextDomain.new.show_language(self)
end
# This function is called when the language link is set. def cookie_locale
YetanotherTextDomain.new.cookie_locale(cookies, flash, params) redirect_to
:action => 'list' end end
Simply put gettext_plugin.po into the po directory.
h2. Conclusion
That's it. If you have your translated message catalogs (myapp.mo)
in all the right places your application should show your message strings in
your favourite language.
You can now easily start to translate your application into all the different
languages you want. I hope this guide helps you to get started. There are
certainly many more aspects of internationalization that you will have to learn
and apply. Remember that this is only one of many possible ways to do it. If
you find any mistakes, shortcomings or have good suggestions on how to improve
this guide I would be more than happy to hear from you.
If you want you can download the "original Textile
document":http://www.digitale-wertschoepfung.de/artikel/gettext/gettext.txt,
make modifications and send them back to me. I will be sure to include you in
the credits section.
Sascha Ebach "se at digitale-wertschoepfung dot
de":mailto:se at digitale-wertschoepfung dot de
h3. Credits
* "David Heinemeier Hansson":http://www.loudthinking.com for creating the best
web application framework that has ever existed P-E-R-I-O-D * Masao Mutoh - The
Author of Ruby-GetText: "mutoh at highway dot ne dot
jp":mailto:mutoh at highway dot ne dot jp. Thanks
to Masao for extending the old version and making it more flexible. * "Alastair
Brunton":http://www.simplyexcited.co.uk for providing some sorely needed
modifications to this article.
h3. Author
Sascha Ebach is the owner and lead developer of a small "web design and
development shop":http://www.digitale-wertschoepfung.de "Digitale
Wertschöpfung":http://www.digitale-wertschoepfung.de in Cologne, Germany.
Together with his two partners he develops and designs complete online
solutions for small to medium sized businesses. Up until the surfacing of Rails
he used to develop everything in PHP although he has already fallen deeply in
love with Ruby since version 1.6.2 came out. For him it is very clear that
Rails and Ruby will be *The Future Way* of developing web applications and he
already looks forward to the day when he has ported his last line of PHP to
Ruby.
h2. Appendix A: Downloads
h3. Used files
Download the archive with files and scripts I use and talk about in this guide.
The file includes the complete skeleton of files that you need to get started.
/home/simplepages/using-gettext-with-rails/: |-app/: | `-controllers/: | `-application.rb (the ApplicationController with the .init_gettext method) `-po/: (sample directory structure) |-de_DE/: |-en_GB/: `-en_US/:"Download using-gettext-with-rails.tgz":http://www.digitale-wertschoepfung.de/artikel/gettext/using-gettext-with-rails.tgz