Header photo by @joaosilas
This post has also been translated into Spanish by Ibidem Group. Traducción a Español aquí: https://www.ibidemgroup.com/edu/traducir-rails-i18n/
The i18n (internationalization) API is the standard way to support localization in Rails. The official guide has all the information you need, but it's also very long. This post is based on the notes I took when I was first learning how to set up i18n, and my goal here is to provide a more approachable walkthrough. This post covers the YAML file setup for error messages and model names/attribute names, and lookup using the I18n.t
method.
Why I wrote this post
My first exposure to i18n was the rails-i18n gem, which provides default translations for commonly used error messages, days of the week, etc. While you can install this gem and call it a day, knowing how to set up i18n yourself is necessary if:
- you want to use translations that are different from those provided by rails-i18n
- you want to include languages not covered by rails-i18n
- you only need translations for a few languages, and don't want to install the whole gem including dozens of languages
In my case, it was the third reason - I only needed translations for Japanese.
Table of Contents
- 0. Setting your default locale
- 1. Translating model and attribute names
- 2. Translating ActiveRecord errors
0. Setting your default locale
I set mine to Japanese.
# config/application.rb
config.i18n.default_locale = :ja
1. Translating model and attribute names
Docs: https://guides.rubyonrails.org/i18n.html#translations-for-active-record-models
1.1 Defining your translations
First, define translations for your model names and attributes in a YAML file like below. This example is for a User
model with two attributes.
# config/locales/ja.yml
ja:
activerecord:
models:
user: 'ユーザ' # <locale>.activerecord.models.<model name>
attributes:
user:
name: '名前' # <locale>.activerecord.attributes.<model name>.<attribute name>
password: 'パスワード'
1.2 Accessing model and attribute translations
# How to look up translations for model names
User.model_name.human
=> "ユーザ"
# How to look up translations for attributes (you can use symbols or strings)
User.human_attribute_name('name')
=> "名前"
User.human_attribute_name(:name)
=> "名前"
2. Translating ActiveRecord errors
Docs: https://guides.rubyonrails.org/i18n.html#error-message-scopes
2.1 Error message breakdown
Translating error messages is a bit more complicated than models. Let's talk about the error message structure first. ActiveRecord has some built-in validation errors that are raised if your record is invalid. Consider this example:
class User < ApplicationRecord
validates :name, presence: true
end
If your locale is :en
, this error message is returned when you try to create an invalid record.
User.create!(name: '')
=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
This message actually consists of a few parts.
1. ActiveRecord::RecordInvalid:
The error type, which is the same regardless of your locale. No translation is required.
2. Validation failed:
The description for this RecordInvalid
error in English, defined in the Rails gem's en.yml
(source code). Translation is required for your locale(s).
3. Name can't be blank
This is the error message for records that violate presence: true
. It consists of two parts - the attribute Name
and message can't be blank
(source code). It turns out, the default error message format is an interpolation of two elements: "%{attribute} %{message}"
(source code). Translation is required for your locale(s).
Note: The translation for attribute
will be taken from the model attribute translations defined in Section 1. If there's no translation, Rails will print the attribute name in English.
What happens if you change the locale from :en
to :ja
without defining the corresponding translations? The error message is returned as translation missing
.
User.create!(name: '')
# => ActiveRecord::RecordInvalid: translation missing: ja.activerecord.errors.messages.record_invalid
So let's look at how to provide translations next.
2.2 Defining your translations
According to the official guide, there are a few places that Rails will look in order to find a translation for your error, in this order.
- activerecord.errors.models.[model_name].attributes.[attribute_name]
- activerecord.errors.models.[model_name]
- activerecord.errors.messages
- errors.attributes.[attribute_name]
- errors.messages
This means that if you want to set model-specific or attribute-specific error messages, you can do so too. But in this case, let's say we want the record_invalid
and blank
to be translated the same way regardless of the model. Here is a sample configuration:
# config/locales/ja.yml
ja:
activerecord:
errors:
messages:
record_invalid: 'バリデーションに失敗しました: %{errors}'
errors:
format: '%{attribute}%{message}'
messages:
# You should also include translations for other ActiveRecord errors such as "empty", "taken", etc.
blank: 'を入力してください'
About the rails-i18n
gem
The configuration above is taken from the ja.yml
file in the rails-i18n
gem which I mentioned in the intro. Installing this gem is a quick way to set up default translations. It does not come pre-installed in your Rails project, so check the documentation for more details on installation and usage.
2.3 Accessing your translations with I18n.t
Now that you've provided translations for error messages, Rails will actually print the error messages instead of translation missing
.
The next question is, how can you look up the translations you defined? For example, what if you want to assert
that some message is being raised in a test?
test 'user is invalid if name is blank' do
invalid_user = User.new(name: '')
assert invalid_user.errors.messages[:name].include?(<cannot be blank message>)
end
This is where the very convenient I18n.t
method comes in. The t
stands for "translate", and it allows you to access any translation defined in your YAML files. For this example, we want to access the errors.messages.blank
message (refer to 2.2 for the YAML file). There are two ways to do this.
I18n.t('errors.messages.blank')
# => "を入力してください"
I18n.t('blank', scope: ['errors', 'messages'])
# => "を入力してください"
Just like that, you can look up any translation you've defined!
Note: You can look up model names and attribute names without using the human
method too, like I18n.t('activerecord.models.user')
.
test 'user is invalid if name is blank' do
invalid_user = User.create(name: '')
expected_error = I18n.t('errors.messages.blank')
assert invalid_user.errors.messages[:name].include?(expected_error)
end
2.4 Looking up errors with string interpolation
https://guides.rubyonrails.org/i18n.html#error-message-interpolation
If you take a look at any of the YAML files in the rails-i18n
gem, you may notice that some messages use string interpolation. For example, if your validation error message is for greater_than
, you would want to say must be greater than %{count}
, and fill in the number for count
. Rails will fill it in for you when the actual error is raised, but how can we fill in the count
when you look up the error message using I18n.t
?
I18n.t('errors.messages.greater_than')
# => "は%{count}より大きい値にしてください"
You can just pass it in as an argument:
I18n.t('errors.messages.greater_than', count: 5)
# => "は5より大きい値にしてください"
I know this doesn't come close to covering everything you can do with i18n in Rails, but I hope it provides a useful introduction. Thanks for reading!
Top comments (1)
The fast way (if you cannot extract your desired language from e.g. the rails-i18n library): Use a tool like lokalise.com/ or translation.io/, sync the default English messages to that tool. Set up your desired language (e.g. Japanese) using that tool. Then let the tool auto-translate everything into your target language. Optionally, pay a professional translator to improve the quality of your translations (simply hand out access to Lokalise/Translation). Sync translated strings back into your codebase (will already be properly formatted and separated into files per language). There is also this amazing gem: github.com/glebm/i18n-tasks to help you sort/extract/reformat/... your existing translations.