In Ruby On Rails By Leigh Halliday November 13, 2014 Leigh Halliday

Translating Your Ruby on Rails Application

Goal

If you're planning to take over the world (or a smaller but no less noble task of taking over Canada), you'll either need to convince everyone to speak your language, or better yet, why don't you offer them a website that speaks their language. The goal is to have a website available in multiple languages, and for the visitors to be able to toggle over to the language they are most comfortable with.

What This Isn't

This isn't a replacement for having read the Rails guide to I18n which will explain things in more general terms. This is supposed to be more of a guide on how to set up the entire Rails application.

Routing

I like to nest all of my routes inside of a :locale scope. I also limit the locale options to a set that I have predefined in an initializer. You could also choose to deal with locale validity later on in the flow (before_action method), but it is up to you.

# config/routes.rb
scope "(:locale)", locale: /#{MySite::ROUTE_LOCALES.keys.join("|")}/ do
  root :to => 'home#index'
  resources :pages
  # etc...
end
# config/initializers/my_site.rb
module MySite
end

MySite::LOCALES = {
  en:    "English",
  fr:    "French",
  en_ca: "Canadian English",
  en_us: "US English",
  en_uk: "UK English",
  fr_ca: "Canadian French",
  fr_lu: "Luxembourg French"
}

MySite::ROUTE_LOCALES = MySite::LOCALES.keys.each_with_object({}) do |locale, hsh|
  hsh[locale.to_s.tr("_","-")] = locale if locale.to_s.length > 2
end
# {"en-ca"=>:en_ca, "fr-ca"=>:fr_ca, "en-us"=>:en_us, "en-uk"=>:en_uk, "fr-lu"=>:fr_lu}

This line of code will make sure all of your routes have the current locale in them.

# app/controllers/application_controller.rb
def default_url_options(options={})
  { locale: I18n.locale }.merge(options)
end

Determining Locale

I usually have a before_action filter which does its best to determine the URL of the application. You might have other things in here too if you keep track of the preferred locale in a cookie, or if it is attached to the user's session data or account. You may also want to use the HTTP_ACCEPT_LANGUAGE to determine if there is a match.

class ApplicationController < ActionController::Base
  before_action :determine_locale

  protected 

  def determine_locale
    locale = if params.include?(:new_locale) && MySite::ROUTE_LOCALES.keys.include?(params[:new_locale])
      params[:new_locale]
    elsif params.include?(:locale)
      params[:locale]
    else
      locale_from_url(request.host) || I18n.default_locale
    end

    set_locale(locale)
  end

  def locale_from_url(host)
    # ... determine locale from host if you have different domains
    # for different locales
  end

  def set_locale(locale)
    I18n.locale = locale.to_s.gsub("-","_").to_sym
  end
end

Static Text

The developers should be entering their keys into the yml files located in the locales folder if your rails application. I normally only have one for English, and then use other I18n backends (Redis for example) serve up the other translations. If the translations are in Redis, you will obviously need code that puts them there. I am working on an engine for this called Idioma which persists the translations using ActiveRecord and also to Redis at the same time.

Dynamic Content

Because I18n comes built in to Rails, you won't need to install many gems. But for dynamic content I recommend Globalize. Along with this one is a handy gem called Globalize Accessors which will help you when creating forms to enter this data in.

class Page < ActiveRecord::Base

  translates :title, :body, fallbacks_for_empty_translations: true
  globalize_accessors locales: MySite::LOCALES.keys, attributes: [:title, :body]

end

Configuration

One thing I set up are fallbacks... this is so you can translate English once, and only when there is a locale that differs from the default do you need to specifically translate it for that locale. Example, in the US colour is spelled like color.

# config/application.rb
config.i18n.default_locale = :en_ca
config.i18n.fallbacks = {
  en_us: :en,
  en_ca: :en,
  en_uk: :en,
  fr_lu: :fr,
  fr_ca: :fr,
  fr:    :en
}

Having Your Website Translated

Because the translation team probably isn't the same as the dev team, and they probably don't have access to your code repository nor know how to edit yml files, you will want to have another way of giving them access to the translations. There is an established tool called Tolk and also a gem I am in the progress of building called Idioma. I'll write another full blog post on how to use my Idioma gem.