← Home

How to set up your routes? - Get on Rails with Globalize! Part 4 of 8

When you're starting to Globalize your application one essential design question is: how to incorporate the different locales that you're going to support into your application.

You might store your users locale in a session to persist it between subsequent requests. You might determine it from the accept-language header or even geolocate the users IP address.

In these cases you probably don't need to touch your routes setup at all. You just stuff the users locale into the session, retrieve it from there (or somewhere else) on every request and ... are done: your URLs and therefor your routes remain the same.

If you're going to go with a more RESTful approach though where the locale is designed to be an essential part of the URL some simple extra work might be necessary.

Storing the locale in the URL ...

There are basically two options. You can keep the locale as:

  1. a parameter (like in http://site.com/articles?locale=fr)
  2. a part of the path (like in http://site.com/fr/articles)

(Of course you can also encode the locale as a part of the domain name, too. That's basically the same option like putting it in the path. Rails' support is somewhat limited here, though.)

Now, in case 1. you don't necessarily need to change your routes setup, too.

... as a parameter

Putting the locale as a query-string parameter into the URL could look like this:


E.g. let's say you have scaffolded an ArticlesController, Article model and the usual suspects of views. Now you can easily use the (anonymous) Rails default route:

map.connect ':controller/:action/:id'

... you just need to make sure that in your views the locale parameter is added to your URLs everywhere, like in:

<%= link_to 'Index', url_for(:action => :index, :locale => params[:locale]) %>

That looks a little cluttered, doesn't it? The explicit keys and the specification of the locale both seem to be necessary to append the locale parameter to the URL though.

You can also use named routes like they are automatically set up for those shiny new resources in Rails 1.2 (of course you could to set them up manually instead):

map.resources :articles

... and then use the more concise

<%= link_to 'Index', articles_path(:locale => params[:locale]) %>

Why not?

Some people don't like to append information like this to the actual URL as a parameter in this manner though because they think it just looks ugly.

From a more RESTful perspective the basic difference is whether or not you see the locale to designate different resources. I.e. you might see your - say - three available translations of a certain article as different representations of the same resource. In this case, using a request parameter would adequately express this perception. Or you might see the available translations more as discrete resources that have multiple views on their own. In this case, using the locale as part of the path or domainname would be more appropriate.

One very practical disadvantage of this approach is that it won't work well with Rails' build-in page and action caching as these don't respect URL parameters (i.e. without further efforts Rails wouldn't cache different versions of pages for different locales).

... as a part of the path

So depending on your application and preferences you probably want to move the locale to the beginning of the URL path like in http://site.com/fr/articles. As you already guessed, this time you'll definitely have to adjust your routes setup accordingly.

This one would cut it with a classic, unnamed route:

map.connect ':locale/:controller/:action/:id'

Yet, this time you don't need to specify the locale for url_for: the Rails routing mechanism already knows that a parameter named :locale is needed and will find it in the controller's parameters (where you've set it within your ApplicationController#before_filter).

So, the following will work:

<%= link_to 'Index', url_for(:action => :index) %>

Again, you can use Rails 1.2 resources with this approach, too, of course. This time, you'd need to append the special :path_prefix option to the resouce definition:

map.resources :articles, :path_prefix => ':locale'

And boom! ... that's it. You're ready to go with fully globalized RESTful resources mappings.

Depending on your usage of url_for or the autogenerated [model]_path methods you might need to adjust these a little bit though. For example, the following quite concise usage works perfectly without a locale on the path:

# in routes.rb:
map.ressources articles
# in your views: <%= link_to "Show", article_path(@article) %>

But with a resource routes setup using :path_prefix like above, this will throw the error:

article_url failed to generate from {:action=>"show", :locale=>"1",
:controller=>"articles"}, expected: {:action=>"show", :controller=>"articles"},
diff: {:locale=>"1"}

This seems to indicate that the article instance has been used to fill in the :locale parameter instead of :id. So we'd need to complete this as follows:

# in routes.rb:
map.ressources articles, :path_prefix => ":locale"
# in your views: <%= link_to "Show", article_path(:id => @article) %>

So there's quite a handful of options.

If you ask me the latter one is the one with the most advantages in most cases (as always, mileages vary):

  • better compling with REST (almost always a big plus)
  • using named routes (less extra work and better decoupled code)
  • being friendly to Rails caching mechanisms (in case you're going to use them)

Boom. That's it :-)

What do you think? Does your routes setup follow a different approach? If there are additional options to add, please let me know.

Next time I'll (hopefully) better respect the original outline of this series again and come up with the scheduled "Hints, tips and tricks" collection installment. If there's something useful ... a tip, trick, technique, ... that you think would make a useful addition to that installment: please use the comments or send me an email!