The Basics of Rails I18n - Translate errors, models, and attributes
Tags: ruby, rails, i18n, internationalization
Published: 2021-01-05 06:02:57

---
title: The Basics of Rails I18n - Translate errors, models, and attributes
published: true
description: 
tags: ruby, rails, i18n, internationalization
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/8fpdw59irv4iz3vwjvmt.jpg
---
_Header photo by [@joaosilas](https://unsplash.com/@joaosilas)_

_This post has also been translated into Spanish by [Ibidem Group](www.ibidemgroup.com). 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](https://guides.rubyonrails.org/i18n.html) 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](https://github.com/svenfuchs/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](#0)
- [1. Translating model and attribute names](#1)
  - [1.1 Defining your translations](#1.1)
  - [1.2 Accessing model and attribute translations](#1.2)
- [2. Translating ActiveRecord errors](#2)
  - [2.1 Error message breakdown](#2.1)
  - [2.2 Defining your translations](#2.2)

# 0. Setting your default locale<a name="0"></a>
I set mine to Japanese.
```ruby
# config/application.rb
config.i18n.default_locale = :ja
```

# 1. Translating model and attribute names<a name="1"></a>
Docs: https://guides.rubyonrails.org/i18n.html#translations-for-active-record-models

## 1.1 Defining your translations<a name="1.1"></a>
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.

```yml
# 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<a name="1.2"></a>

```rb
# 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<a name="2"></a>
Docs: https://guides.rubyonrails.org/i18n.html#error-message-scopes

## 2.1 Error message breakdown<a name="2.1"></a>
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:

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

```rb
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](https://github.com/rails/rails/blob/968de9cc6d48ca026303566d7d1f437d40360820/activerecord/lib/active_record/locale/en.yml#L17)). __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](https://github.com/rails/rails/blob/968de9cc6d48ca026303566d7d1f437d40360820/activemodel/lib/active_model/locale/en.yml#L16)). It turns out, the default error message format is an interpolation of two elements: `"%{attribute} %{message}"` ([source code](https://github.com/rails/rails/blob/968de9cc6d48ca026303566d7d1f437d40360820/activemodel/lib/active_model/locale/en.yml#L4)). __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`. 

```rb
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<a name="2.2"></a>
According to the official [guide](https://guides.rubyonrails.org/i18n.html#error-message-scopes), 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:

```yml
# 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`](https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/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](https://github.com/svenfuchs/rails-i18n) for more details on installation and usage.

## 2.3 Accessing your translations with `I18n.t`<a name="2.3"></a>
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?

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

```ruby
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')`.

```rb
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<a name="2.4"></a>
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`?

```rb
I18n.t('errors.messages.greater_than')
# => "は%{count}より大きい値にしてください"
```
You can just pass it in as an argument:
```rb
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!