Concise, localized Rails URL helpers? Solved (twice).
posted: May 13th, 2007 · by: Sven
Originally triggered by Jeremy Hubert I’ve posted some thoughts about more concise and transparently localized Rails url_helper methods last month.
Basically Jeremy nailed a problem that occurs as soon as you define a Rails route that includes the locale as the leftmost parameter: you now can’t use Rails’ url_helper methods in a reasonable DRY way any more! A reasonable solution was pending.
Plugins to the rescue. By the end of this article you’ll be able to choose between two different solutions to this problem.
The Problem
For example you might have defined a route like this:
map.connect ':locale/:controller/:action/:id'
Or with Rails’ RESTy resources:
map.resources :articles, :path_prefix => ":locale"
How would you now use url_helper methods without specifying the locale every single time you’re calling an url_helper? You can’t. Without any further efforts you’d have to say, e.g.:
article_path(@current_locale, @article)
instead of being able to just use:
article_path(@article)
And the latter clearly is what one would expect from a Rails solution, isn’t it? It is.
The solutions
There are two different solutions to this problem (that I know of, of course):
- the
resource_fuplugin of Trevor Squires - my own
localized_url_helpersplugin
With respect to the problem at hand and their solution to it they both have quite something in common: Both of them achieve largely the same, particulary both provide a reasonable flexibility concerning the Rails url_helpers that triggered this quest. Both of the solutions are available as plugins and are installed super-easily. Both are quite young, too.
On the other hand they both take a pretty different route to solve our problem and thus, there’s a series of difference too, of course. Let’s see …
The resource_fu plugin
I hope that I get things right when I say that ressource_fu basically does two things and thereby solves our original problem:
- Most importantly it tries to “guess” (or “infer”) parameters that you haven’t passed to your url_helper, but that are necessary to build the URL. E.g., when you ommit the :locale parameter, it looks for a (controller) instance variable with the same name.
- Secondly it “anchors” positioned arguments on the right side. That means that if your route has three segments and you only supply two positional arguments, the url helper assumes you are supplying the *last* two segments.
This way both of these will work as long as you have defined a controller instance variable @locale (from which the parameter will be “inferred”):
article_path(@article) # positioned arguments
article_path(:id => @article) # verbose hash syntax
Hurray. Mission complete!
Resource_fu has more benefits than this. Read more about it in its readme file. You can download/install the resource_fu plugin from here:
http://svn.protocool.com/public/plugins/resource_fu/
The localized_url_helper plugin
The localized_url_helper plugin tackles things seperatly and targeted:
- For the hash argument syntax it will smuggle the locale past the Rails routes’ “parameter expiry” mechanism. It thereby tricks Rails into simply using the locale parameter and then proceeding as if it wasn’t there at all.
- For the positioned argument syntax it’s sufficient to just push the locale onto the front of the arguments list.
Two very simple steps and that’s it.
You can download/install the localized_url_helper plugin from here:
http://svn.artweb-design.de/stuff/rails/localized_url_helpers/
Which solution should you choose?
I really don’t know :)
Both plugins achieve largely the same thing with respect to this problem but they are using two quite different approaches. Even at this microscopic scale there’s no silver bullet, I guess. Look at the differences.
Resource_fu does more than just solving the original problem - and it is designed to do so. The main selling point of resource_fu is: it changes the way url helpers behave. In my personal opinion it does this in a very elegant, reasonable and useful way. Check it out! If that’s what you want (and if you’re probably starting a new project so don’t have to double-check all your url_helpers) resource_fu definitely is for you.
Localized_url_helper on the other hand is probably more of the laser-scalpel type of tool. It solves exactly the problem posed above and nothing else. Also it’s quite a bit less code and it’s coded as unobtrusive and careful as possible (as far as I can tell, that is). So it probably is a bit less prone to breakage through changes in future Rails updates.

Actually I learned about resource_fu after I’ve presented the first version of my plugin to the Globalize developers list.
I probably wouldn’t have started trying to come up with my own plugin at all if I would have known about Trevor’s resource_fu in the first place. But digging through the Rails routing code and trying to patch it to behave the way I wanted has been pretty instructive. I can only recommend that!
Anyways I think the results have been worth it: at the end of the day we can now choose between to solutions when it comes to localize our routes and keep our URL helpers DRY at the same time. :-)
Feedback?
What do you think?
Morgan Roderick said April 26th, 2008 at 07:28 PM ¶
It’s certainly a few good steps in the right direction, but it won’t handle old-school urls, created using :controller => ‘mycontroller’, :action => ‘myaction’ syntax, as they don’t use the fancy url helpers.
I’ve been trying to get my head around this all day, to no avail.
The two solutions above certainly helps quite a lot, but won’t solve all the issues with having language as a magic first-part of your paths. Try using some of the testing plugins, and you’ll see what I mean ;-)
Keep digging!
Stefano Grioni said July 3rd, 2008 at 12:11 AM ¶
I tried your Localizedurlhelper which helped me a lot. Great work. I just had a problem because I didn’t find any way (based on your helper) to set a default language for the website. And so when /foo was called, it complained that the route was defined for /locale/foo I figured out a little (and pretty nasty) workaround (If anyone has got anything better, I’ll be glad to change my code :) ) :
in routes.rb
instead of just putting
map.resources :controller, :path_prefix => ‘:locale’
I added map.resources :controller
That’s why I say it’s nasty .. Because you have 2 routes for 1 real url.
And then in application.rb
beforefilter :setlocale def set_locale default_locale = ‘en-US’ requestlanguage = request.env[‘HTTPACCEPT_LANGUAGE’] requestlanguage = requestlanguage.nil? ? nil :request_language[/[^,;]+/]
end
Hope it helps..
Anyway, thanks again for all your work
Mattias Ottosson said September 11th, 2008 at 03:08 PM ¶
Hi Sven. Thanks for your articles on globalize, they really helped me to get going with globalize. I’ve encountered a problem though and i’ve been banging my head in the wall the last two days cause I can’t come up with a decent working solution two rest routes were the urls are in two different languages.
Let’s save a product model/products controller and you want to have a rested route to this with a lang-prefix. It’s no problem with the soloution above and having map.resources :products, :pathprefix => ‘:locale’ and simply calling the productspath. You get for example /en/products and /sv/products.
Now to the problematic part. I want to localize the controller-name of the route => /en/products when the locale is set to english and /sv/produkter when the locale is set to swedish.
Is this possible?
Any help will be greatly appreciated!
Babarocap said October 22nd, 2008 at 04:51 PM ¶
Try this plugin : http://github.com/raul/translate_routes/tree/master
Pawel Barcik said August 7th, 2009 at 01:07 PM ¶
this looks like a better solution (simple flexible wrapper)
http://github.com/svenfuchs/routing-filter/tree/master
val web said August 16th, 2009 at 01:06 AM ¶
Hi Sven,
Thank you for the routing-filter’s code.
Could you please show how to use it in a project?
Thanks.
Sven said August 17th, 2009 at 09:31 AM ¶
Hi Val,
basically, you just tell your routes to use certain filters using “map.filter :whatever”.
You could have a look at adva-cms http://github.com/svenfuchs/advacms/blob/50a9aae6e4d72d30cdf3d16f7ac1606c55e96fa1/engines/advacms/config/routes.rb
Of course you might need different implementations of filters or entirely different filters.
val said August 17th, 2009 at 03:46 PM ¶
Thanks Sven,
I’m trying to auto-switch between locales by setting I18n.locale based on the url, that is http://example.com/en/post would switch to English. So I added to app’s controller a before_filter. Is there something like that in routing-filter?
val said August 17th, 2009 at 03:56 PM ¶
Anyway I did like in the example you gave (in base_controller.rb)
def set_locale params[:locale] =~ /^[\w]{2}$/ or raise ‘invalid locale’ if params[:locale] I18n.locale = params[:locale] || I18n.default_locale # TODO raise something more meaningful I18n.locale.untaint end
Just instead- I18n.locale = params[:locale] || I18n.default_locale I converted it to symbol I18n.locale = params[:locale].tosym || I18n.defaultlocale since it didn’t work the original way, and now my app is happy :)
Thanks again!
Frank said November 20th, 2009 at 10:39 PM ¶
Hey guys,
A great solution I found to this problem is this one : http://stackoverflow.com/questions/212425/creating-routes-with-an-optional-path-prefix