I18n for Rails: Set-up for Using Locales from URL Params and Basic I18n Usage

morinoko

Felice Forby

Posted on December 19, 2018

I18n for Rails: Set-up for Using Locales from URL Params and Basic I18n Usage

Setting up I18n for Ruby/Rails apps is fairly simple, but as a beginner, I often run into some hiccups with the configuration (and trying to decipher what exactly is being said in the Rails Guides and other people's blogs). As a reference for myself and hopefully other developers who are just starting out, I'm going to document the steps I take for using I18n in my apps.

First, you'll have to decide how your app is going to know which locale, and thus language, to use. There are a couple of different strategies you can use, with the most common ones outlined in the Rails Guides:

  1. Setting the Locale from the Domain Name
  2. Setting the Locale from URL Params (covered in this post)
  3. Setting the Locale from User Preferences
  4. Choosing an Implied Locale like the HTTP Language Header or IP Geolocation

I personally like to use the URL params to set my locales (options 2 above), which means you have the locale code show at the end of the URLs like so:

# For the English version of the site:
https://www.myapp.com/en
https://www.myapp.com/en/login

# For the Japanese version:
https://www.myapp.com/ja
https://www.myapp.com/ja/login
Enter fullscreen mode Exit fullscreen mode

I like this strategy because it doesn't take much configuration and it makes the language obvious to the user as they can see it in the URL. You could do something similar by using strategy 1 from above by setting the locale from the domain or subdomain name (so URLs would look like https://www.myapp.ja or https://www.ja.myapp.com) but you would need multiple domains and for me, I simply don't like changing the main part of the domain (e.g. the .com part) nor do I like sticking things onto the beginning of it.

Setting up your application to get locales from URL params

In order to let the application know which locale to use, you need to make sure the locale gets set at the very start of each request to a new page. If not, Rails will use the default locale (:en unless otherwise configurated). One way to do so is to add the following code to the application_controller.rb file:

class ApplicationController < ActionController::Base
  before_action :set_locale

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end

  def default_url_options
    { locale: I18n.locale }
  end
end
Enter fullscreen mode Exit fullscreen mode

What's going on here? First, a method called set_locale is defined which will set I18n.locale variable to the :locale variable in the params. In this case, the params[:locale] is going to come directly from the url: for example, the en in https://www.myapp.com/en or ja in https://www.myapp.com/ja/login.

If for some reason there is no params[:locale] variable, Rails will use the default locale as specified by the || I18n.default_locale part of the code above.

The line before_action :set_locale makes sure that the set_locale method is executed at the start of every single controller request (that is, whenever you go to any page/view in your app).

The default_url_options method makes sure that the locale is included in the url when using helpers for routes like users_path. Otherwise, you would have to include the locale param every single time like this, for example: users_url(locale: I18n.locale).

By the way, the default language for the default_locale is going to be English unless you specify otherwise. To change the default, you can set it in your config/application.rb file within the Application class:

module MyApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Set the default locale to Japanese
    config.i18n.default_locale = :ja

    # more code...
end
Enter fullscreen mode Exit fullscreen mode

Next, you need to make sure that URLs route properly and contain a URL param called :locale. One way to do this is to use a scope in the routes.rb file.

# config/routes.rb

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

  scope "/:locale" do
    resources :users, only: [:new, :show]
    root 'welcome#index'

    #... more routes
  end
end
Enter fullscreen mode Exit fullscreen mode

The problem with the scope above is that if someone leaves off the locale part of the URL, for example, https://www.myapp.com or https://www.myapp.com/login, they will get a routing error. To make the :locale parameter optional, you can pass in a regular expression with the available routes as in the code below, so if the locale portion of the URL is left off, the app will use the default locale:

# config/routes.rb

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

  scope "(:locale)", locale: /en|ja/ do
    resources :users, only: [:new, :show]
    root 'welcome#index'

    #... more routes
  end
end
Enter fullscreen mode Exit fullscreen mode

Now the set up should be ready to go and you just need to make sure to make the translations for each locale available in special yaml files saved in the config/locales/ folder.

Locale files, displaying translations, and a couple tips

The translation files (the yaml files for each locale) contain all the text for your app mapped as key/value pairs. Each file contains the same keys with the different values for each language. For example, here are a few phrases which might be used for English and Japanese:

# config/locales/en.yml

en:
  signup: "Sign up"
  login: "Login"
  logout: "Logout"
  login_status: "Logged in as %{username}."

Enter fullscreen mode Exit fullscreen mode
# config/locales/ja.yml

ja:
  signup: "登録する"
  login: "ログイン"
  logout: "ログアウト"
  login_status: "%{username}としてログインしています。"

Enter fullscreen mode Exit fullscreen mode

These translations can now be accessed in your views by passing the key into the t method, which is the alias for translate. So if you used t('signup'), it would print out Sign up for English pages and 登録する for Japanese pages.

Here's an example of using the translation to create a login link:

<%= link_to t('login'), login_path %>
Enter fullscreen mode Exit fullscreen mode

The login_status key above demonstrates how you can pass variables into the translation strings. Just put the variable, in this case username between %{} and pass it in like so: t('login_status', username: current_user.username). Here is an example from a view file:

<% if logged_in? %>
  <div id="login_status">
    <%= t('login_status', username: current_user.username) %>
  </div>
<% end %>
Enter fullscreen mode Exit fullscreen mode

To organize your translations, keys can also be scoped. For example, let's organize the above translations under a sessions scope:

# config/locales/en.yml

en:
  sessions:
    signup: "Sign up"
    login: "Login"
    logout: "Logout"
    login_status: "Logged in as %{username}."

Enter fullscreen mode Exit fullscreen mode

They can now be looked up with . notation:

t('sessions.login')
Enter fullscreen mode Exit fullscreen mode

Or another way is to reference the scope:

t('login', scope: :sessions)
Enter fullscreen mode Exit fullscreen mode

Translations for ActiveRecord objects for use in forms.

I18n also gives you a convenient way to translate ActiveRecord model attributes. Recently, I found this useful when making a form. Let's say you need a sign-up form that would create a new user in ActiveRecord. Using Rails' form_for, it looked something like this:

<div id="form-signup">
  <h1><%= t 'signup' %></h1>

  <%= form_for @user do |f| %>
    <div class="form-group">
      <%= f.label :username %>
      <%= f.text_field :username %>
    </div>

    <div class="form-group">
      <%= f.label :email %>
      <%= f.email_field :email %>
    </div>

    <div class="form-group">
      <%= f.label :password %>
      <%= f.password_field :password %>
    </div>

    <div class="form-group">
      <%= f.label :password_confirmation %>
      <%= f.password_field :password_confirmation %>
    </div>

    <%= f.submit t('signup') %>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

As you know, the label helper will automatically create the labels based on the attributes for the user: :username, :email, :password, etc. You can also have it automatically translate the attribute names by properly setting up your translation files.

To do so, simply add to the translation files an activerecord scope key nested with a scope key that has the same name as the model you need translations for (in this case user), which furthermore have nested named key for the corresponding attributes. These attribute keys should be named the same as ActiveRecord database attributes.

# config/locales/en.yml

en:
  activerecord:
    models:
      user: "User"
    attributes:
      user:
        username: "Username"
        email: "Email"
        password: "Password"
        password_confirmation: "Confirm Password"

Enter fullscreen mode Exit fullscreen mode
# config/locales/ja.yml

ja:
  activerecord:
    models:
      user: "ユーザー"
    attributes:
      user:
        username: "ユーザー名"
        email: "メールアドレス"
        password: "パスワード"
        password_confirmation: "パスワード確認"
Enter fullscreen mode Exit fullscreen mode

I18n will now know what text to use for each form label!

Lazy lookup for views

Lazy lookup allows you to map out translations for your controller views so that you reference keys without having to type out long key chains like t('welcome.index.greeting').

To set up a "dictionary" for a particular controller and its view, add a key with the same name as the controller and then keys nested below that that correspond to the name of the view. In the example below, there are dictionaries for the welcome and users controllers:

# config/locales/en

en:
  welcome:
    index:
      greeting: "Welcome!"
      catchphrase: "The best app. Ever."

  users:
    index:
      title: "All Users"
    show:
      title: "My Dashboard"

Enter fullscreen mode Exit fullscreen mode

Now when you are in your view files, you can reference a translation with just a single key. So, if you were in the views/users/index.html.erb and needed the title translation, simply use t('.title') and it would print out All Users. In views/users/show.html.erby, t(.title) would give you My Dashboard instead.

Conclusion

There are so many things you can do with I18n--error messages for ActiveRecord objects, pluralization, localized dates/times, etc, etc-- this is just the tip of the iceberg, but hopefully, this article will help you get started with some basic set up :) Check out the resources below for more coverage.

Resources

Rails Guides
The Last Rails I18n Guide You’ll Ever Need

💖 💪 🙅 🚩
morinoko
Felice Forby

Posted on December 19, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related