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:
- Setting the Locale from the Domain Name
- Setting the Locale from URL Params (covered in this post)
- Setting the Locale from User Preferences
- 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:
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:
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.
# 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:
# 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:
# config/locales/en.yml
en:
signup: "Sign up"
login: "Login"
logout: "Logout"
login_status: "Logged in as %{username}."
# 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:
<%= 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:
<% 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:
# 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:
t('sessions.login')
Or another way is to reference the scope:
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:
<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.
# config/locales/en.yml
en:
activerecord:
models:
user: "User"
attributes:
user:
username: "Username"
email: "Email"
password: "Password"
password_confirmation: "Confirm Password"
# 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:
# 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.
Top comments (5)
Ur article is the best! Great thanks to U
Glad it was helpful!
This is a great resource, thanks for sharing!
You are welcome! Thanks for reading :)
Great article!
How do you build the link to switch language on view?
Thanks!