Pimp your Globalize: Extensions, Plugins and Patches - Get on Rails with Globalize! Part 6 of 8
Multilingual URLs
Having your URLs translated? Does that make sense?
Sure! Sometimes it really does. For example, look at how much attention the usual SEO suspects pay at their URLs and the keywords that they contain. Obiviously when you translate your content you'll want your URL contain different keywords, too.
To the best of my knowledge the first complete plugin that accomplishes this is the LocalizedRoutes plugin by Saimon Moore.
Saimon's code hooks into the dispatch process so that it is able to determine and set the current locale even before the routes are defined. Thus, you can use the locale to translate parts of your routes like so:
ActionController::Routing::Routes.draw do |map|
# translate 'list'
map.list 'list'.t, :controller => 'blog', :action => 'list'
end
If that sounds interesting to you head over to Saimons blog article where he describes in detail how the plugin can be used with different URL formats (e.g. using the locale as a subdomain, part of the path or query parameter): Localized URLs.
(There's also been a earlier effort by Assente which is not that complete but deserves to be mentioned here, too. So there you go: you can read about Assente's shot at this feature here: Reverse URL translation with Globalize.)
Get Globalize working with :include and no base language
If you need Globalize to work with the ActiveRecord::Base#find features like :include and :select, for example because you need working has_many :through
or some plugin that relies on these features, than you might want to check out the following patch of Lourens Naudé.
Basically Lourens "refactored the Globalize::DbTranslate module to an AR::Associations pattern, override Model.find to always preload translations whilst removing all of the custom finder SQL."
The main advantage of this is that Globalize will work with the mentioned ActiveRecord functionality again. I.e. :include and :select will work and so will all those nice plugins that depend on these options.
Check out this article on Lourens' blog: Globalize::DbTranslate with :include and no base language patch.
Translate your application while browsing it
Different from other I18n tools Globalize stores your translations where the rest of your data is: in the database. Thus, there's a wide variety of tools that you can use to edit your translations - including Rails migrations, database web frontends, GUI applications and so on.
One tool that is targeted particulary to Globalize translations though is the Globalize Translator plugin by Joshua Sierles:
Once installed it hooks into your Rails views and presents a shiny DOM popup interface that let's you translate the strings on the current view: a convenient and integrated approach to this problem.
Installation is done easily - just follow the instructions in the README file. If you're looking for an on-site translation GUI, you should give it a go: http://svn.diluvia.net/rails_plugins/globalize_translator/
Localized, concise Rails URL helpers
Globalize itself doesn’t provide a solution for transparently adding the locale to Rails url_helpers where needed. What does that mean?
It means that if you define one of those RESTy Rails resources like this (and you really should use this stuff this way):
map.resources :articles, :path_prefix => ":locale"
... then you're in a big trouble because you can't use Rails' url_helper methods in a reasonable DRY way any more! For example instead of being able to say:
article_url(@article)
... you now have to specify the locale for each and every call to an url_helper, like so:
article_url(@current_locale, @article)
Needless to say that this caused some eyebrows to raise in the Rails community.
Yet, there are even two fire-and-forget solutions to this problem. You can find them described here: Concise, localized Rails URL helpers? Solved (twice).
Get Liquid templates to play nice with Globalize
There are two ways to get Liquid talk to Globalize. There's an canonical way that uses the official Liquid api. That's the less sexy one. And there's a reckless monkeypatch that even invents a new Liquid syntax element. That's less clutterish.
Let's start with the safer-use, conservative way. Put the following anywhere in your application:
module GlobalizeFilter
def t(input)
input.t
end
end
Liquid::Template.register_filter(GlobalizeFilter)
For example, you could just make a tiny plugin from this so you don't need to clutter your environment.rb or something like this. For my personal reference I've filed this away as a plugin here: Plugin Globalize Liquid filter.
Now, you can use the following syntax to translate strings from within Liquid templates:
welcome
But that looks a bit noisy, doesn't it? So probably what you really want is recklessly hack Liquid so that it gives you a new syntax like this:
{| 'welcome' |}
... and make it act just the same way as the standard way above. So if you're ready for hazardous moves like this, check out this (pretty small and relative unobtrusive) patch for Localized Liquid syntax.
Oh! And because it internally uses Globalize's compability method _()
instead of .t
it will work with just about any other Localization solution, too.
Globalize time_ago_in_words method of Rails
Globalize won't touch the distance_of_time_in_words
and time_ago_in_words
family of methods by default. It probably should, but it doesn't. So if you really need this kind of stuff, your screwed.
Just kidding. Of course you're not.
You're a developer and so you will just come up with your own solution. So did Rafael Lima who put together a nice piece of code so you can copy and paste it from his blog.
Multiple arguments to fetch
On your never-ending quest for cleaner and more concise code and templates it might be tempting to do something like this:
'How many %s of %s will the %s have?' / ['gigabytes', 'diskspace', 'iPhone']
"Danger, Will Robinson. Danger!"
Don't do this. This is a no-go. When you look at the implementation of method /
you see that it's just a proxy to String#translate
with the optional quantity argument. Each time you call this the String (that the method is called on) will be inserted into the translations table if it's not already present.
Thus, in the example above you'd end up with 3 entries in your database, namely:
How many %s of %s will the %s have? How many gigabytes of %s will the %s have? How many gigabytes of diskspace will the %s have?
In a real application where you'd call this with arbitrary data you'd get masses of entries that you'd need to translate. And that's not really what you wanted, is it?
But there's help. Instead you might want to have a look at the monkeypatch that you can find on the Globalize wiki.
This allows you to do the following:
'How many %s does a pack of %s have? %d' / ['cigarettes', 'Marlboro', 20]
Be aware though that this hasn't made its way into the Globalize core for a reason: it breaks Globalize's pluralization mechanism that otherwise works pretty nicely.