DEV Community

Cover image for The Basics of Rails I18n - Translate errors, models, and attributes
Risa Fujii
Risa Fujii

Posted on • Updated on

The Basics of Rails I18n - Translate errors, models, and attributes

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

I set mine to Japanese.

# config/application.rb
config.i18n.default_locale = :ja
Enter fullscreen mode Exit fullscreen mode

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: 'パスワード'
Enter fullscreen mode Exit fullscreen mode

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)
=> "名前"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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: 'を入力してください'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'])
# => "を入力してください"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}より大きい値にしてください"
Enter fullscreen mode Exit fullscreen mode

You can just pass it in as an argument:

I18n.t('errors.messages.greater_than', count: 5)
# => "は5より大きい値にしてください"
Enter fullscreen mode Exit fullscreen mode

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!

Discussion (1)

Collapse
swiknaba profile image
Lud • Edited on

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.