Iteration K1: Selecting the Locale

We start by creating a new configuration file that encapsulates our knowledge of what locales are available and which one is to be used as the default:

rails51/depot_t/config/initializers/i18n.rb
 #encoding: utf-8
 I18n.default_locale = ​:en
 
 LANGUAGES = [
  [​'English'​, ​'en'​],
  [​"Español"​.html_safe, ​'es'​]
 ]

This code is doing two things.

The first thing it does is use the I18n module to set the default locale. I18n is a funny name, but it sure beats typing out internationalization all the time. Internationalization, after all, starts with an i, ends with an n, and has eighteen letters in between.

Then the code defines a list of associations between display names and locale names. Unfortunately, all we have available at the moment is a U.S. keyboard, and Español has a character that can’t be directly entered via our keyboard. Different operating systems have different ways of dealing with this, and often the easiest way is to copy and paste the correct text from a website. If you do this, make sure your editor is configured for UTF-8. Meanwhile, we’ve opted to use the HTML equivalent of the n con tilde character in Spanish. If we didn’t do anything else, the markup itself would be shown. But by calling html_safe, we inform Rails that the string is safe to be interpreted as containing HTML.

For Rails to pick up this configuration change, the server needs to be restarted.

Since each page that’s translated will have an en and an es version (for now—more will be added later), it makes sense to include this in the URL. Let’s plan to put the locale up front, make it optional, and have it default to the current locale, which in turn will default to English. To implement this cunning plan, let’s start by modifying config/routes.rb:

rails51/depot_t/config/routes.rb
 Rails.application.routes.draw ​do
  get ​'admin'​ => ​'admin#index'
  controller ​:sessions​ ​do
  get ​'login'​ => ​:new
  post ​'login'​ => ​:create
  delete ​'logout'​ => ​:destroy
 end
 
  resources ​:users
  resources ​:products​ ​do
  get ​:who_bought​, ​on: :member
 end
 
» scope ​'(:locale)'​ ​do
  resources ​:orders
  resources ​:line_items
  resources ​:carts
  root ​'store#index'​, ​as: ​​'store_index'​, ​via: :all
»end
 end

We’ve nested our resources and root declarations inside a scope declaration for :locale. Furthermore, :locale is in parentheses, which is the way to say that it’s optional. Note that we didn’t choose to put the administrative and session functions inside this scope, because it’s not our intent to translate them at this time.

What this means is that http://localhost:3000/ will use the default locale (namely, English) and therefore be routed exactly the same as http://localhost:3000/en. http://localhost:3000/es will route to the same controller and action, but we’ll want this to cause the locale to be set differently.

At this point, we’ve made a lot of changes to config.routes, and with the nesting and all the optional parts to the path, the gestalt might be hard to visualize. Never fear: when running a server in development mode, Rails provides a visual aid. All you need to do is navigate to http://localhost:3000/rails/info/routes, and you’ll see a list of all your routes. You can even filter the list, as shown in the screenshot, to quickly find the route you’re interested in. More information on the fields shown in this table can be found in the description of rails routes.

images/s_1_routes.png

With the routing in place, we’re ready to extract the locale from the parameters and make it available to the application. To do this, we need to create a before_action callback. The logical place to do this is in the common base class for all of our controllers, which is ApplicationController:

rails51/depot_t/app/controllers/application_controller.rb
 class​ ApplicationController < ActionController::Base
» before_action ​:set_i18n_locale_from_params
 # ...
 protected
»def​ set_i18n_locale_from_params
»if​ params[​:locale​]
»if​ I18n.available_locales.map(&​:to_s​).include?(params[​:locale​])
» I18n.locale = params[​:locale​]
»else
» flash.now[​:notice​] =
»"​​#{​params[​:locale​]​}​​ translation not available"
» logger.error flash.now[​:notice​]
»end
»end
»end
 end

This set_i18n_locale_from_params does pretty much what it says: it sets the locale from the params, but only if there’s a locale in the params; otherwise, it leaves the current locale alone. Care is taken to provide a message for both the user and the administrator when a failure occurs.

With this in place, we can see the results in the following screenshot of navigating to http://localhost:3000/en.

images/s_2_en.png

At this point, the English version of the page is available both at the root of the website and at pages that start with /en. If you try another language code, say “es” (or Spanish), you can see that an error message appears saying no translations are available. The screenshot shows what this might look like when navigating to http://localhost:3000/es:

images/s_3_es_error.png