zargony.com

#[desc = "Struggling through software development and server administration"];

Selecting the locale for a request

Rails I18N gives you a way to translate your views and easily switch between different languages. However, you still need to set the locale for each request, i.e. you have to choose a method to select the right locale for a request. This can be done in various ways, depending on how you perfer it to behave. Here are some examples.

  1. by browser-setting
  2. by toplevel domain
  3. by subdomain
  4. by manual selection

The default locale

The locale for a request can be changed by setting I18n.locale to the locale you desire. If no locale is set, the default locale in I18n.default_locale will be used. If the locale is set to nil (I18n.locale = nil), the default locale is also used (which is important for the below code examples to work). The default locale can be globally set in config/environment.rb:

config.i18n.default_locale = :en

Available locales

Most of the below code snippets rely on the recently added method #available_locales of the I18n gem. It’ll take some time until this is available in Rails (even on Edge Rails), so you might want to monkeypatch this feature in your application in the meantime. Put the following code into config/initializers/i18n.rb (untested):

module I18n
  module Backend
    class Simple
      def available_locales
        init_translations unless initialized?
        translations.keys
      end
    end
  end
  def available_locales
    backend.available_locales
  end
end

By browser-setting

Most browers send a HTTP_ACCEPT_LANGUAGE, that tells us which language the user prefers. The value of this header contains a list of preferred locales so we can choose one of them to satisfy the user. This results in a website, that auto-detects the language the visitor likes to see. While this sounds great at first (and e.g. was the default with ruby-gettext), there’s also a drawback: Search engine robots will see your site in only one language (the default locale), so that non-English content isn’t indexed.

preferred_locales = request.headers['HTTP_ACCEPT_LANGUAGE'].split(',').map { |l| l.split(';').first }
I18n.locale = preferred_locales.select { |l| I18n.available_locales.include?(l.to_sym) }

In case that none of the preferred user locales are available, the above code snippet sets the locale to nil and therefore the default locale is used.

By toplevel domain

Setting the locale base on the TLD is pretty easy and gives the possibility to have a site in different languages on different TLDs (e.g. example.com in English and example.de in German). This one doesn’t suffer from problems like the above, but requires you to register multiple domains.

First, define a TLD→locale lookup table, e.g. in config/initializers/i18n.rb or at the top of application.rb:

TLD_LOCALES = {
  '.com' => :en,
  '.de' => :de,
  # add more as desired
}

In application.rb, use a before-filter to set the locale for each request:

before_filter :set_locale
def set_locale
  I18n.locale = TLD_LOCALES[request.host.split('.').last]
end

If a domain is requested, that isn’t included in TLD_LOCALES, the locale is set to nil, which means the default locale is used.

By subdomain

As easy as selecting by TLD, but doesn’t require multiple domains: Select the locale based on a subdomain, e.g. en.example.com will be English, de.example.com will be German.

before_filter :set_locale
def set_locale
  requested_locale = request.domains.last
  I18n.locale = I18n.available_locales.include?(requested_locale.to_sym) ? requested_locale.to_sym : nil
end

By manual selection

Of course, you could always let the user manually choose the locale, e.g. by selecting from a dropdown or by clicking little flag icons. For guest visitors, a session variable or cookie can be used to hold the desired locale. If you build a site with user accounts, the desired locale could also be stored in the user’s settings so that it automatically applies if the user logs in.

Here’s a code snippet that can be added to ApplicationController and makes an application accept a parameter ?locale=xx to switch the locale and remembers it in a permanent cookie:

before_filter :set_locale
def set_locale
  if params[:locale] && I18n.available_locales.include?(params[:locale].to_sym)
    cookies['locale'] = { :value => params[:locale], :expires => 1.year.from_now }
    I18n.locale = params[:locale].to_sym
  elsif cookies['locale'] && I18n.available_locales.include?(cookies['locale'].to_sym)
    I18n.locale = cookies['locale'].to_sym
  end
end

So which way to use?

It depends… on how you like it to be on your site… The most user-friendly way is probably to auto-choose the locale based on the HTTP_ACCEPT_LANGUAGE header. But because of the disadvantages with search engines, I personally prefer to set the locale based on the domain name (either TLD or subdomain). In addition, one could compare the automatically chosen locale with the user-preferred locale from the request header and if they don’t match, display a notice to the user to remind him that there’s a site in his preferred language (e.g. Amazon and Ebay do it like this).

Btw, I almost always include the “manual way” for development (if Rails.env.development? ...) so that I’m able to easily switch locales during development.