I18n for Rails: Set-up for Using Locales from URL Params and Basic I18n Usage
Tags: rails, beginners, ruby
Published: 2018-12-19 23:46:46


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](https://guides.rubyonrails.org/i18n.html):

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
```

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: 

```ruby
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
```

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:

```ruby
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
```

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.

```ruby
# 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
```

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:

```ruby
# 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
```

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:

```yaml
# config/locales/en.yml

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

```

```yaml
# config/locales/ja.yml

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

``` 

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:

```erb
<%= link_to t('login'), login_path %>
```

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:

```erb
<% if logged_in? %>
  <div id="login_status">
    <%= t('login_status', username: current_user.username) %>
  </div>
<% end %>
```

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

```yaml
# config/locales/en.yml

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

```

They can now be looked up with `.` notation:
```ruby 
t('sessions.login')
```

Or another way is to reference the scope:
```ruby
t('login', scope: :sessions)
```


### 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:

```erb
<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>
```

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.

```yaml
# config/locales/en.yml

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

```

```yaml
# config/locales/ja.yml

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

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:

```yaml
# config/locales/en

en:
  welcome:
    index:
      greeting: "Welcome!"
      catchphrase: "The best app. Ever."
      
  users:
    index:
      title: "All Users"
    show:
      title: "My Dashboard"
    
```

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](https://guides.rubyonrails.org/i18n.html)
[The Last Rails I18n Guide You’ll Ever Need](https://phraseapp.com/blog/posts/rails-i18n-guide/)