Real fun: Get on Rails with Globalize
Warning! Proceed with caution! At your own risk only :)
While this post indeed reflects my own experience and therefor probably might be worth reading as a study case about "newbies in the wild" ... it is also based on misunderstanding and lack of insight (which does seem to be a constituent of what a "newbie" is, doesn't it?).
As I'm a bit short of free time right now, I'll just mark these flawed points and leave it up to you, dear reader, to finally figure out what to do with this post.
I promise to come up with a second version or follow up post correcting these newbie mistakes and hopefully offering some useful tips.
You'll need to have a working Rails app up and running. That is, if you want to start from scratch first make sure that your conf/database.yml configures an existing database, valid credentials etc. and everything else is nice.
Install and init globalize
Make your way to your Rails app directory and install the plugin:
script/plugin install http://svn.globalize-rails.org/svn/globalize/globalize/trunk
Although the following stuff has worked for me (at least it seemed to do), in the meanwhile I've been kindly told by Joshua Harvey, that this is not the preferred way to go these days.
So if your trying to get up your Globalize while reading this line-by-line, just type:
... and you're done with the setup. In this case - skip the following lines and continue with "Configure Globalize")
--------------- start: un-preferred usage --------------
Then generate the globalize migration:
script/generate globalize tiny
This creates whopping 250kb migration file with over ten thousand records of pre-arranged translation data that Globalize soon will deliver to a database near you - including several languages I'd bet you've never heard of like Avar, Manx or Wolof. Whops. These folks don't really expect my application to be translated to their native language over there in the Northeast-Caucasian Mountains, do they??
By the way, I wonder what happens if you ommit that "tiny" option and use "big" or even "huge" instead. Haven't tried that though ;)
Ok, next. Migrate that stuff:
--------------- end: un-preferred usage --------------
You'll probably want to do something useful in the meantime - like brew another pot of coffee, do some yoga or clean your keyboard or so. It's gonna take a little while, so be patient.
Now, that's it. You're already globalized.
There's nothing more to do than set the "base language" for your Globalize instance but it's easy to miss this step I think. At least it happened to me. I found some note that instructed me to "include xyz into your environment.rb" - but it didn't say, where exactly to include that.
By trial and error, I found this one to work:
Rails::Initializer.run do |config| # ... end include Globalize Locale.set_base_language('en-US')
If you're used to locale codes using an underscore (like I was) and miss this you'll get an exception: 'en_US' won't work. I will yet have to lookup some RFCs or specs to find out what's the difference.
As far as I understand the meaning of this setting Globalize treats the base_language you set here as the "primary" language and others as "secondary" - which means that content stored your model tables is stored in the primary language whereas translations are stored in the globalize_translations table.
Having written this I remembered having read about that in the mailinglist and found the following quote from Joshua Harvey:
"Base language entries are stored in the parent table itself, as opposed to the globalize_translations table. So if you have a table called 'products', and your base language is English, the English product name would be stored in products.name, whereas the German translation would be stored in globalize_translations.
It's important to never change the base language once you've starting populating the database."
I think this states it more clearly.
(I yet wonder what's the right choice or best practice for me here. From the standpoint of developing an ubiqous Domain Language it seems to me that an English base_language would be first choice. I therefor chose that option.
On the other hand ... from the standpoint of an agile process of getting real with an application that for the most forseeable time will deliver localized content in German (and probably only some content in other languages) this would be a waste of ressources, wouldn't it? I'll have to find this out.)
Check it out!
Already impatient to check out some working stuff? I were, too ...
There's a nice example in the Globalize wiki using Rails Unit tests and fixtures.
I probably should have tried that but I didn't. I've globalized an existing application that I had been working on before, so I decided to just open up a controller and write something to the screen.
The shortest statement I've found was:
def index Locale.set("es-ES") render :text => Time.now.localize("%d %B %Y") # ... end
07 Junio 2006
Wew, hey! This means Globalize already works. I was thrilled.
Of course my next step was to change the locale to "de-DE". Which resulted in:
07 June 2006
Wtf!? Too bad. :( Would have been too nice if this worked out of the box, wouldn't it?
But this forced me to read a bit more than I had yet. It took me a while to read through the Globalize wiki, inspect some code, query the database tables ... to finally find out that there simply is no default translation provided for "June" in German (which of course isn't that unusual at all).
You don't need to do the following. Well, I think, you could do. But you don't need.
In fact, there's already a record in the translations table with a NULL value for this string. You could look that up manually and change it to your translation.
Next of course I wondered how the heck I am suppose to add this stuff to the database? In the Globalize wiki I found something like this:
Locale.set_translation('Welcome', Language.pick('de-DE'), 'Willkommen')
So I added a similar line to my controller and inspected the new records that are created in my globalize_translations table.
I opened up a template and (having the Language.locale set to 'de-DE' in the controller index method for now) added the line:
<% 'Welcome'.t -%>
Expected result: "Willkommen". Actual result: the same. Great. :)
I changed the locale to 'en-US' and got the expected result "Welcome". I noticed that this string is recieved from the original string in the template because there's no translation for "Welcome" in the database of course.
The following section presents to you an example one of those situations when you simply try something out, think that it does work or does something useful but later on detect (or are told, like me in this case) that you're completely on the wrong track.
So, don't do this, it's silly :)
--------------- start: silly-and-uninformed-stuff --------------
My next test was to change an articles title to "Welcome" by hand (that is: in the articles table in the database). Then I added something like this to the template:
<%= @article.title.t -%>
... which resulted in the same expected results depending on the locale I set.
--------------- end: silly-and-uninformed-stuff --------------
So, why is this silly? I'll quote Joshuas hint from the mailinglist for now:
the preferred way of doing this is with the "translates" directive in the ActiveRecord model, and <%= @article.title %> in the view. Behind the scenes, Globalize treats this very differently, it's not just a matter of leaving off the .t, so [that stuff I wrote above] might give new users the wrong idea.
The "translates" directive could (following the example) look like this:
def article translates :title end
... which would allow you to directly access the articles title without even any extra .t method or anything else.
Now, that's way cool. Think about it for a moment.
Globalize lets you append .t to any string which then will transparently lookup and find available translations for it. In case there's no translation available it will simply return the original string (which I believe is the default behaviour most t10n/i18n tools show).
Just add hot water ...
As stated in the docs you can also specify for your models which fields will be translated and these will be automatically translated without calling the "t" method. I haven't tried this though.
Instead went ahead to apply this stuff to my experimental voting application. As far as I can tell it's really done in an instant.
I collected the hardcoded strings from the templates and let Globalize add translations for it. I simply did this from an extra method in the controller for now using above mentioned Locale.set_translation method. With portability in mind this should better be done by migrations I think. Will have to figure out though how exactly to do so.
Of course there's much, much more to Globalize. E.g. there's support for localized date and number formatting, "multiple plurals" (which some languages have) etc. and you can use the "slash trick" to insert data into your string keys, like so:
"Hey, %s! You're globalized." / 'Sven'
... will result in "Hallo, Sven! Du bist Globalized." (at least it will if you have added this translation of course).
I'm really thrilled about how easy and quickly I've been able to get this stuff up and running. Of course - you're used to this kind of experience like this using Rails, it's just it's unique featurewise selling-point or something. But I've been honestly surprised. That's definitely fun :)
In case this whole post seems a bit weird and far-from-optimal to you ... well, I could offer as an excuse that there are two ingredients that don't really mix up that good:
- my own un-informed, mis-lead, trial/error experience
- the intention to nevertheless come up with some useful text
So, I'll just let this alone for now and come up with a second version anytime soon.