**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON <https://guides.rubyonrails.org>.**

Internationalization (I18n)
===========================

In this guide you will learn how to translate your application into a single
non-English language or provide multi-language support.

After reading this guide, you will know:

* How I18n works in Ruby on Rails
* How to configure, set, and switch locales in a Rails application
* How to organize translations and use the I18n API in views, controllers, and
  models
* How to use I18n to translate Active Record errors or Action Mailer E-mail
  subjects
* How to customize backends and handle translation-related exceptions

--------------------------------------------------------------------------------
Internationalization and Localization
-------------------------------------

Internationalization (I18n) is the process of preparing your application to
support multiple languages and regional formats. In practice, this usually means
abstracting strings and other locale-specific elements, such as date or currency
formats, out of your application.

Localization (L10n) is the process of providing translations and locale-specific
formats for those abstracted pieces.

To internationalize a Rails application, you typically need to:

* ensure your application is configured for internationalization (I18n)
* tell Rails where to find translation files
* set, preserve, and switch the locale for each request

To localize your application, you typically need to:

* provide translations for application text
* add or customize locale-specific formats, such as dates, times, and currencies
* organize and maintain translation data

How I18n in Ruby on Rails Works
-------------------------------

Rails ships with the Ruby [i18n gem](https://github.com/ruby-i18n/i18n), which
provides the underlying internationalization framework. In this guide, the I18n
API refers to the interface your Rails application uses to work with that
framework, such as `I18n.t`, `I18n.l`, and Rails translation helpers. As part of
this system, static strings in Rails itself, such as Active Record validation
messages and time and date formats, are already internationalized.

Rails includes core I18n functionality by default. Additional gems, such as
[rails-i18n](https://github.com/svenfuchs/rails-i18n), can provide extra locale
data and other extensions, but they are not required for basic I18n support.

NOTE: The I18n API is primarily intended for translating user-facing text within
the application. Translating model content itself (for example, blog posts
stored in the database) requires a separate approach.

The Ruby I18n gem is split into two parts:

* The public API of the I18n framework - a Ruby module with public methods that
  define how the library works.
* A default backend (named
  [_Simple_](https://github.com/ruby-i18n/i18n/blob/master/lib/i18n/backend/simple.rb)
  backend) that implements these methods.

As a user you should only access the public methods on the I18n module, but it
is useful to know about the capabilities of the backend.

INFO: It is possible to swap the shipped `Simple` backend with a more powerful
one, which would store translation data in a relational database, GetText
dictionary, or similar. See section [Using alternate
backends](#using-alternate-backends) below.

The most important methods of the I18n API are:

* `translate`, which looks up translations for a given key and locale. It is
  aliased as `t`, so you can use `I18n.t("store.title")`. Additionally, this
  helper catches missing translations and wraps the resulting error message in a
  `<span class="translation_missing">`.
* `localize`, which localizes `Date` and `Time` objects to local formats. It is
  aliased as `l`, so you can use `I18n.l(Time.now)`.

The I18n module also exposes configuration through attribute readers and
writers:

```ruby
I18n.load_path << Rails.root.join("config", "locales", "es.yml")
I18n.locale = :es
I18n.default_locale = :en
I18n.available_locales = [:en, :es]
I18n.enforce_available_locales = true
I18n.exception_handler = MyExceptionHandler.new
I18n.backend = I18n::Backend::Simple.new
```

Internationalizing an Application
---------------------------------

In this section, we'll internationalize a basic Rails application.

### Creating Locale Dictionaries

Rails applications contain a `config/locales` directory with a `en.yml` locale
file by default. This file is a YAML dictionary that contains translations for
the English locale.

The default `en.yml` locale in this directory contains a sample translation:

```yaml
en:
  hello: "Hello world"
```

Looking up the translation for `hello` in the `:en` locale returns `"Hello
world"`:

```ruby
I18n.t("hello")
# => "Hello world"
```

The I18n library uses English as the default locale, so when a locale is not
explicitly set, `:en` will be used for looking up translations. To learn how to
change the current locale, see [Managing the Locale across
Requests](#managing-the-locale-across-requests).

NOTE: Many international applications use only the "language" element of a
locale such as `:cs`, `:th`, or `:es` (for Czech, Thai, and Spanish). However,
there are also regional differences within different language groups that may be
important. For instance, in the `:"en-US"` locale you would have $ as a currency
symbol, while in `:"en-GB"`, you would have £. Nothing stops you from separating
regional and other settings in this way: you just have to provide full "English
- United Kingdom" locale in a `:"en-GB"` dictionary.

### Organization of Locale Files

When using the default `Simple` backend shipped with the i18n library,
dictionaries are stored in plain-text files on disk. Putting translations for
all parts of your application in one file per locale can become hard to manage,
so it often helps to organize locale files in a hierarchy.

The hierarchy shown below is only an example. Choose a structure that makes
sense for your application, and split files further when a single locale file
becomes too large or hard to navigate.

For example, your `config/locales` directory could look like this:

```
|-defaults
|---es.yml
|---en.yml
|-models
|---book
|-----es.yml
|-----en.yml
|-views
|---defaults
|-----es.yml
|-----en.yml
|---books
|-----es.yml
|-----en.yml
|---users
|-----es.yml
|-----en.yml
|---navigation
|-----es.yml
|-----en.yml
```

This way, the model and model attribute names, text inside views, and global
defaults such as date and time formats are all kept separate. Other stores for
the i18n library could provide different means of separation.

### Using Translations in Views and Controllers

Given the following example, we have a `HomeController` with an `index` action
that sets a flash message and renders the `app/views/home/index.html.erb`
template:

```ruby
# config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
end
```

```ruby
# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = "Hello Flash"
  end
end
```

```html+erb
<!-- app/views/home/index.html.erb -->
<h1>Hello World</h1>
<p><%= flash[:notice] %></p>
```

To internationalize this code, replace the strings with calls to Rails' `#t`
helper using translation keys. The heading is translated in the view, while the
flash message is translated where it is set in the controller:

```ruby
# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = t("home_flash")
  end
end
```

```html+erb
<!-- app/views/home/index.html.erb -->
<h1><%= t("home_title") %></h1>
<p><%= flash[:notice] %></p>
```

Now, when this view is rendered, it will show an error message indicating that
the translations for the keys `"home_title"` and `"home_flash"` are missing.

```html+erb
<h1>
  <span class="translation_missing" title="translation missing: en.home_title">
    Home Title
  </span>
</h1>
<p>Translation missing: en.home_flash</p>
```

Now add the translations to your locale files:

```yaml
# config/locales/en.yml
en:
  home_title: Hello world!
  home_flash: Hello flash!
```

```yaml
# config/locales/es.yml
es:
  home_title: ¡Hola mundo!
  home_flash: ¡Hola flash!
```

With the default locale, the page renders the English strings.

```
Hello world!
Hello flash!
```

If the default locale is set to the Spanish locale:

```ruby
# config/application.rb
config.i18n.default_locale = :es
```

the response renders the Spanish strings:

```
¡Hola mundo!
¡Hola flash!
```

NOTE: You need to restart the server when you add new locale files.

### Passing Variables to Translations

One key consideration for successfully internationalizing an application is to
avoid making incorrect assumptions about grammar rules when abstracting
localized code. Grammar rules that seem fundamental in one locale may not hold
true in another.

Improper abstraction is shown in the following example, where assumptions are
made about the ordering of the different parts of the translation.

```erb
<!-- app/views/products/show.html.erb -->
<%= "#{t('currency')}#{@product.price}" %>
```

```yaml
# config/locales/nl.yml
nl:
  currency: "€ "
```

```yaml
# config/locales/es.yml
es:
  currency: "€ "
```

If the product’s price is 100, Dutch might want `"€ 100"` while Spanish might
want `"100 €"`, but this abstraction produces `"€ 100"` for both locales.

To create proper abstraction, the I18n gem ships with a feature called variable
interpolation that allows you to use variables in translation definitions and
pass the values for these variables to the translation method.

Proper abstraction is shown in the following example:

```erb
<!-- app/views/products/show.html.erb -->
<%= t('product_price', price: @product.price) %>
```

```yaml
# config/locales/nl.yml
nl:
  product_price: "€ %{price}"
```

```yaml
# config/locales/es.yml
es:
  product_price: "%{price} €"
```

All grammatical and punctuation decisions are made in the definition itself, so
the abstraction can give a proper translation.

```ruby
I18n.t("product_price", price: 100, locale: :nl)
# => "€ 100"

I18n.t("product_price", price: 100, locale: :es)
# => "100 €"
```

INFO: Rails provides helpers such as
[`number_to_currency`](https://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_currency)
for localizing numbers and currency values.

NOTE: The `default` and `scope` keywords are reserved and can't be used as
variable names. If used, an `I18n::ReservedInterpolationKey` exception is
raised. If a translation expects an interpolation variable, but this has not
been passed to `#translate`, an `I18n::MissingInterpolationArgument` exception
is raised.

### Adding Date/Time Formats

To localize the time format, you can pass the `Time` object to `I18n.l` or,
preferably, use Rails' `#l` helper. You can pick a format by passing the
`:format` option.

```erb
<!-- app/views/home/index.html.erb -->
<h1><%= t("home_title") %></h1>
<p><%= flash[:notice] %></p>
<p><%= l(Time.now, format: :short) %></p>
```

For example, you can define a custom short time format in a locale file:

```yaml
# config/locales/es.yml
es:
  time:
    formats:
      short: "%H:%M"
```

Then `l(Time.current, format: :short, locale: :es)` uses that format for the
Spanish locale.

Rails also provides
[`relative_time_in_words`](https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-relative_time_in_words)
for a localized past/future phrase rather than a formatted timestamp:

```erb
<p><%= relative_time_in_words(3.minutes.from_now) %></p>
<p><%= relative_time_in_words(15.seconds.ago, include_seconds: true) %></p>
```

This renders phrases such as:

```
in 3 minutes
less than 20 seconds ago
```

By default, this looks up translations in the `datetime.relative` scope and
combines them with the same localized distance strings used by
[`distance_of_time_in_words`](https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words).

TIP: You may need to add additional date and time formats for the I18n backend
to behave as expected. However, translations for Rails’ default formats are
often already available for many locales. See the [rails-i18n
repository](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale)
for an archive of various locale files. Placing these files in `config/locales/`
makes them automatically available to your application.

### Localized Views

In some cases, translating individual strings is not the best fit. If an entire
template differs by locale, Rails can render a localized view instead.

For example, consider a `BooksController` with an index action that renders the
`app/views/books/index.html.erb` template. If a localized variant of this
template, `index.es.html.erb`, is added to the same directory, Rails will render
that template when the locale is set to :es. When the locale is set to the
default locale, the generic `index.html.erb` template will be used.

```erb
<!-- app/views/books/index.html.erb -->
<h1>Books</h1>
```

```erb
<!-- app/views/books/index.es.html.erb -->
<h1>Libros</h1>
```

You can make use of this feature when working with a large amount of static
content, which would be clumsy to put inside YAML or Ruby dictionaries. Bear in
mind, though, that any change you make to the template must be propagated to all
localized variants.

### Inflection Rules for Other Locales

Rails allows you to define inflection rules such as singularization and
pluralization for locales other than English. These rules can be defined for
multiple locales in `config/initializers/inflections.rb`. The initializer
contains a default example for specifying additional rules for English; follow
that format for other locales as you see fit.

Rails already includes default English inflection rules, like the pluralization
of "person" to "people":

```ruby
"person".pluralize
# => "people"
```

You can add custom pluralization rules for Portuguese, like below:

```ruby
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:pt_br) do |inflect|
  inflect.irregular "aluguel", "aluguéis"
end
```

Then inflector-based helpers can use those rules for that locale:

```ruby
"aluguel".pluralize(:pt_br)
# => "aluguéis"
```

When regional locales are used together with [`I18n.fallbacks`][], inflection
rules can also come from a fallback locale. For example, `en-GB` can reuse `:en`
inflections when that fallback is mapped explicitly.

```ruby
config.i18n.fallbacks = { "en-GB": :en }
```

[`I18n.fallbacks`]: configuring.html#config-i18n-fallbacks

Managing the Locale across Requests
-----------------------------------

To support multiple locales, applications should set the locale at the beginning
of each request and ensure it stays consistent for the duration of that request.

The default locale is used for all translations unless `I18n.locale=` or
`I18n.with_locale` is used. In a controller, the safest way to do this is with
`I18n.with_locale`, typically in an `around_action`, so the locale is applied
only for that request.

`I18n.locale` can leak into subsequent requests served by the same
thread/process if it is not consistently set in every controller. For example,
executing `I18n.locale = :es` in one POST request will have effects for all
later requests to controllers that don't set the locale, but only in that
particular thread/process. For that reason, instead of `I18n.locale =` you can
use `I18n.with_locale` which does not have this leak issue.

The locale can be set using one of many different approaches, depending on where
your application stores or derives that information.

### Setting the Locale from a Request Parameter

A commonly used technique to set the locale is to include it in request
parameters.

For example, you can set the locale in a concern that is included in
`ApplicationController`:

```ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include Locale
end
```

```ruby
# app/controllers/concerns/locale.rb
module Locale
  extend ActiveSupport::Concern

  included do
    around_action :switch_locale
  end

  def switch_locale(&action)
    locale = params[:locale] || I18n.default_locale
    I18n.with_locale(locale, &action)
  end
end
```

This example uses a URL query parameter to set the locale, such as
`http://example.com/books?locale=pt`. With this approach,
`http://localhost:3000?locale=pt` renders the Portuguese localization, while
`http://localhost:3000?locale=de` loads a German localization.

### Setting the Locale from URL Params

The [previous section](#setting-the-locale-from-a-request-parameter) showed how
to read the locale from `params[:locale]`. In practice, that is only half of the
solution. If the locale comes from the URL, your application also needs to
include it in generated URLs so the choice is preserved from one request to the
next.

For example, you may want URLs such as `www.example.com/books?locale=ja` or
`www.example.com/ja/books`.

This approach has many of the same advantages as using a parameter for the
locale:

* the locale is visible in the URL
* the URL can be shared
* search engines can distinguish between language-specific pages

It does require a little more setup, though.

#### Preserving the Locale in Query Parameters

With this approach, we need to ensure that every generated link continues to
include the locale. Adding `locale: I18n.locale` manually to every call to
`link_to` or every route helper would be tedious and easy to miss.

Rails provides a central place for this through `default_url_options`. By
defining it, you can tell
[`url_for`](https://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for)
and the route helpers built on top of it to automatically include the current
locale.

For example, you can add this to your `ApplicationController`:

```ruby
# app/controllers/application_controller.rb
def default_url_options
  { locale: I18n.locale }
end
```

Every helper method that uses `url_for` (for example, named route helpers such
as `root_path` and `root_url`, or resource route helpers such as `books_path`
and `books_url`) will now automatically include the locale in the query string,
such as `http://localhost:3001/?locale=ja`.

You may find this sufficient. However, query parameters can make URLs less
readable, and the locale is often conceptually higher-level than the rest of the
path. In many applications, it makes more sense for the locale to appear in the
path itself.

#### Using a Locale Scope in the Path

You may prefer URLs like `http://www.example.com/en/books` for English and
`http://www.example.com/nl/books` for Dutch. You can do this with the same
`default_url_options` approach, combined with a
[`scope`](https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html)
in your routes:

```ruby
# config/routes.rb
scope "/:locale" do
  resources :books
end
```

Now `books_path` will return `"/en/books"` when the current locale is English.
Visiting `http://localhost:3001/nl/books` should set the locale to Dutch, and
subsequent calls to `books_path` during that request will return `"/nl/books"`.

WARNING: Since the return value of `default_url_options` is cached per request,
the URLs in a locale selector cannot be generated invoking helpers in a loop
that sets the corresponding `I18n.locale` in each iteration. Instead, leave
`I18n.locale` untouched, and pass an explicit `:locale` option to the helper, or
edit `request.original_fullpath`.

If you don't want to force the use of a locale in your routes you can use an
optional path scope (denoted by the parentheses):

```ruby
# config/routes.rb
scope "(:locale)", locale: /en|nl/ do
  resources :books
end
```

With this approach you will not get a `Routing Error` when accessing resources
such as `http://localhost:3001/books` without a locale. This is useful for when
you want to use the default locale when one is not specified.

You need to take special care of the root URL (usually "homepage" or
"dashboard") of your application. A URL like `http://localhost:3001/nl` will not
work automatically, because the `root to: "dashboard#index"` declaration in your
`routes.rb` doesn't take locale into account.

You would need to map URLs like these:

```ruby
# config/routes.rb
get "/:locale" => "dashboard#index"
```

WARNING: Be mindful of the order of your routes so that route declarations do
not unintentionally match other routes.

For applications with more complex path-based locale requirements, locale
extraction can be handled at the Rack middleware layer before the request
reaches Rails routing. This keeps locale handling separate from the routing
layer and is typically only needed when rewriting request paths or mounting the
application under a locale-specific prefix.

### Setting the Locale from the Domain Name

You can also set the locale from the domain or subdomain where your application
runs.

In order to set the locale from the domain name, you need to have a separate
domain for each locale. For example, `www.example.com` can load the English (or
the default) locale, and `www.example.es` the Spanish locale. Setting the locale
from the domain name has several advantages:

* The locale is an obvious part of the URL.
* People intuitively grasp in which language the content will be displayed.
* Search engines like that content in different languages lives at different,
  inter-linked domains.

You can implement it like this in your `ApplicationController`:

```ruby
# app/controllers/concerns/locale.rb
around_action :switch_locale

def switch_locale(&action)
  locale = extract_locale_from_domain || I18n.default_locale
  I18n.with_locale(locale, &action)
end

# Get locale from top-level domain or return +nil+ if such locale is not available.
# You can add the following in your /etc/hosts file to try this out locally:
#   127.0.0.1 application.com
#   127.0.0.1 application.it
#   127.0.0.1 application.pl
def extract_locale_from_domain
  parsed_locale = request.host.split(".").last
  I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
```

You can also set the locale from the subdomain in a very similar way. This is
often easier to configure and operate than a separate top-level domain for each
locale.

```ruby
# Get locale code from request subdomain (like http://it.application.local:3000)
# You can add the following in your /etc/hosts file to try this out locally:
#   127.0.0.1 it.application.local
#
# Additionally, you need to add the following configuration to your config/environments/development.rb:
#   config.hosts << 'it.application.local:3000'
def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
```

If your application includes a locale switching menu, you would then have
something like this in it:

```ruby
link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}")
```

assuming you would set `APP_CONFIG[:deutsch_website_url]` to some value like
`http://www.application.de`.

This solution has the advantages mentioned above, however, you may not be able
to or may not want to provide different localizations ("language versions") on
different domains. In those cases, you can include the locale in the query
parameters or request path, as shown above.

NOTE: Controller-based locale extraction works well for simple applications. For
production applications that derive the locale from host or path information,
Rack middleware can extract the locale before routing and store it in the Rack
environment. See [Rails on Rack](rails_on_rack.html) for more about Rack
middleware in Rails applications.

### Setting the Locale from User Preferences

An application with authenticated users may allow users to set a locale
preference through the application's interface. With this approach, a user's
selected locale is persisted in the database and used to set the locale for
authenticated requests by that user.

When using a stored preference, ensure that the value is included in
`I18n.available_locales` before applying it.

```ruby
# app/controllers/concerns/locale.rb
around_action :switch_locale

def switch_locale(&action)
  locale = Current.user&.locale
  locale = I18n.default_locale unless I18n.available_locales.map(&:to_s).include?(locale)

  I18n.with_locale(locale, &action)
end
```

If your application also allows the locale to be set from the URL or another
request-based source, decide which value takes precedence. In many applications,
an explicit locale in the URL should override the user's stored preference.

### Choosing an Implied Locale

When a request does not explicitly specify a locale, an application may try to
infer one. This is best treated as a fallback: an explicit locale from the URL,
domain, or user preference should usually take precedence.

#### Inferring Locale from the Language Header

The `Accept-Language` HTTP header indicates the languages the client prefers for
the response. Browsers typically [set this header based on the user's language
preferences](https://www.w3.org/International/questions/qa-lang-priorities), so
it is often the best first choice when inferring a locale.

A simple implementation might look like this:

```ruby
# app/controllers/concerns/locale.rb
def switch_locale(&action)
  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
  locale = extract_locale_from_accept_language_header || I18n.default_locale
  logger.debug "* Locale set to '#{locale}'"
  I18n.with_locale(locale, &action)
end

private
def extract_locale_from_accept_language_header
  request.env["HTTP_ACCEPT_LANGUAGE"]&.scan(/^[a-z]{2}/)&.first
end
```

This example is intentionally simple. In practice, more robust parsing is
usually necessary because the header can contain multiple languages, region
subtags such as `en-GB`, and quality values that indicate preference order.

Libraries such as [accept_language](https://github.com/cyril/accept_language.rb)
or [the locale Rack
middleware](https://github.com/rack/rack-contrib/blob/main/lib/rack/contrib/locale.rb)
can help implement this more reliably.

### Inferring the Locale from IP Geolocation

The IP address of the client making the request can be used to infer the
client's region and, in some cases, suggest a locale. Services such as [GeoLite2
Country](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) or gems
like [geocoder](https://github.com/alexreisner/geocoder) can be used to
implement this approach.

In practice, this is a less reliable signal than using the `Accept-Language`
header. A user's network location does not necessarily match their preferred
language, and IP-based lookups may be affected by VPNs, mobile carriers,
proxies, or corporate networks.

### Storing the Locale from the Session or Cookies

Sessions or cookies can be useful for remembering a user's previous locale
choice, especially when no explicit locale is present in the URL or another
request-based source. In many applications, they are best treated as a fallback
rather than the canonical source of the locale.

The locale is often best kept transparent and reflected in the URL, so that a
shared link shows the same content and language to everyone who opens it. This
also aligns with common RESTful expectations.


Overview of the I18n API Features
---------------------------------

The following sections cover the I18n API in more depth, using examples with
both `I18n.translate` and the [`translate` view helper
method](https://api.rubyonrails.org/classes/ActionView/Helpers/TranslationHelper.html#method-i-translate).


### Looking up Translations

#### Basic Lookup, Scopes, and Nested Keys

Translations are looked up by keys. String keys are a good default because they
work naturally with keys such as `"books.index.title"`:

```ruby
I18n.t "books.index.title"
```

The `translate` method also takes a `:scope` option which can contain one or
more additional keys that will be used to specify a "namespace" or scope for a
translation key:

```ruby
I18n.t "record_invalid", scope: "activerecord.errors.messages"
# => "Validation failed: Name can't be blank"
```

This looks up the `"record_invalid"` message in the Active Record error
messages.

Additionally, both the key and scopes can be specified as dot-separated keys as
in:

```ruby
I18n.translate "activerecord.errors.messages.record_invalid"
# => "Validation failed: Name can't be blank"
```

Thus the following calls are equivalent:

```ruby
I18n.t "activerecord.errors.messages.record_invalid"
# => "Validation failed: Name can't be blank"
I18n.t "errors.messages.record_invalid", scope: "activerecord"
# => "Validation failed: Name can't be blank"
I18n.t "record_invalid", scope: "activerecord.errors.messages"
# => "Validation failed: Name can't be blank"
I18n.t "record_invalid", scope: ["activerecord", "errors", "messages"]
# => "Validation failed: Name can't be blank"
```

#### Defaults

When a `:default` option is given, its value will be returned if the translation
is missing:

```ruby
I18n.t "missing", default: "Not here"
# => 'Not here'
```

If the `:default` value is a string key, it will be used as a key and
translated.

One can also provide multiple values as default. The first one that results in a
value will be returned. The example below first tries to translate the key
`"missing"` and then the key `"also_missing"`. Since both do not yield a result,
the string "Not here" will be returned:

```ruby
I18n.t "missing", default: ["also_missing", "Not here"]
# => 'Not here'
```

#### Bulk and Namespace Lookup

To look up multiple translations at once, an array of keys can be passed:

```ruby
I18n.t ["odd", "even"], scope: "errors.messages"
# => ["must be odd", "must be even"]
```

Also, a key can translate to a (potentially nested) hash of grouped
translations. For example, one can receive all Active Record error messages as a
Hash:

```ruby
I18n.t "errors.messages"
# => {:inclusion=>"is not included in the list", :exclusion=> ... }
```

If you want to perform interpolation on a bulk hash of translations, you need to
pass `deep_interpolation: true` as a parameter. When you have the following
dictionary:

```yaml
en:
  welcome:
    title: "Welcome!"
    content: "Welcome to the %{app_name}"
```

then the nested interpolation will be ignored without the setting:

```ruby
I18n.t "welcome", app_name: "book store"
# => {:title=>"Welcome!", :content=>"Welcome to the %{app_name}"}

I18n.t "welcome", deep_interpolation: true, app_name: "book store"
# => {:title=>"Welcome!", :content=>"Welcome to the book store"}
```

#### "Lazy" Lookup

Rails implements a convenient way to look up the locale inside views. When you
have the following dictionary:

```yaml
es:
  books:
    index:
      title: "Título"
```

you can look up the `books.index.title` value inside
`app/views/books/index.html.erb` template like this (note the dot):

```erb
<%= t ".title" %>
```

NOTE: Automatic translation scoping by partial is only available from the
`translate` view helper method.

"Lazy" lookup can also be used in controllers:

```yaml
en:
  books:
    create:
      success: Book created!
```

This is useful for setting flash messages:

```ruby
class BooksController < ApplicationController
  def create
    # ...
    redirect_to books_url, notice: t(".success")
  end
end
```

### Pluralization

In many languages, including English, there are only two forms, a singular and a
plural, for a given string, e.g. "1 message" and "2 messages".

Other languages
([Arabic](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar),
[Japanese](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ja),
[Russian](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru)
and many more) have different grammars that have additional or fewer [plural
forms](http://cldr.unicode.org/index/cldr-spec/plural-rules). Thus, the I18n API
provides a flexible pluralization feature.

The `:count` interpolation variable has a special role in that it both is
interpolated to the translation and used to pick a pluralization from the
translations according to the rules defined in the pluralization backend. By
default, only the English pluralization rules are applied.

```ruby
I18n.backend.store_translations :en, inbox: {
  zero: "no messages", # optional
  one: "one message",
  other: "%{count} messages"
}
I18n.translate "inbox", count: 2
# => '2 messages'

I18n.translate "inbox", count: 1
# => 'one message'

I18n.translate "inbox", count: 0
# => 'no messages'
```

The algorithm for pluralizations in `:en` is:

```ruby
lookup_key = :zero if count == 0 && entry.has_key?(:zero)
lookup_key ||= count == 1 ? :one : :other
entry[lookup_key]
```

The translation denoted as `:one` is regarded as singular, and the `:other` is
used as plural. If the count is zero, and a `:zero` entry is present, then it
will be used instead of `:other`.

If the lookup for the key does not return a Hash suitable for pluralization, an
`I18n::InvalidPluralizationData` exception is raised.

#### Locale-specific Rules

The I18n gem provides a `Pluralization` backend that can be used to enable
locale-specific rules. Include it to the `Simple` backend, then add the
localized pluralization algorithms to translation store, as `i18n.plural.rule`.

```ruby
I18n::Backend::Simple.include(I18n::Backend::Pluralization)
I18n.backend.store_translations :pt, i18n: {
  plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } }
}
I18n.backend.store_translations :pt, apples: {
  one: "one or none",
  other: "more than one"
}

I18n.t "apples", count: 0, locale: :pt
# => 'one or none'
```

Alternatively, the separate gem
[rails-i18n](https://github.com/svenfuchs/rails-i18n) can be used to provide a
fuller set of locale-specific pluralization rules.

### Setting and Passing a Locale

The locale can be either set pseudo-globally to `I18n.locale` (which uses
`Thread.current` in the same way as, for example, `Time.zone`) or can be passed
as an option to `#translate` and `#localize`.

WARNING: The i18n gem stores `I18n.locale` using `Thread.current`. Applications
serving multiple requests from the same thread should set the locale explicitly
around each request, for example with `I18n.with_locale`.

If no locale is passed, `I18n.locale` is used:

```ruby
I18n.locale = :de
I18n.t "foo"    # Looks up de.foo
I18n.l Time.now # Looks up de.time.formats.default
```

Explicitly passing a locale:

```ruby
I18n.t "foo", locale: :br    # Looks up br.foo
I18n.l Time.now, locale: :es # Looks up es.time.formats.default
```

The `I18n.locale` defaults to `I18n.default_locale` which defaults to `:en`. The
default locale can be set like this:

```ruby
I18n.default_locale = :de
```

### Using Safe HTML Translations

Keys with a '_html' suffix and keys named 'html' are marked as HTML safe. When
you use them in views the HTML will not be escaped.

```yaml
# config/locales/en.yml
en:
  welcome: <b>welcome!</b>
  hello_html: <b>hello!</b>
  title:
    html: <b>title!</b>
```

```html+erb
<!-- app/views/home/index.html.erb -->
<%= t('welcome') %>
<%= raw t('welcome') %>
<%= t('hello_html') %>
<%= t('title.html') %>
```

This renders:

```html
&lt;b&gt;welcome!&lt;/b&gt;
<b>welcome!</b>
<b>hello!</b>
<b>title!</b>
```

Interpolation escapes as needed though. For example, given:

```yaml
en:
  welcome_html: "<b>Welcome %{username}!</b>"
```

you can safely pass the username as set by the user:

```erb
<%# This is safe, it is going to be escaped if needed. %>
<%= t('welcome_html', username: @current_user.username) %>
```

Safe strings on the other hand are interpolated verbatim.

NOTE: Automatic conversion to HTML safe translate text is only available from
the `translate` (or `t`) helper method. This works in views and controllers.

Built-in Rails Translations
---------------------------

Rails uses I18n in several framework features beyond direct calls to `I18n.t`.
These sections cover the translation conventions used by Active Record, Action
Mailer, form helpers, and other Rails helpers.

### Active Record Models

You can use the methods `Model.model_name.human` and
`Model.human_attribute_name(attribute)` to transparently look up translations
for your model and attribute names.

For example, when you add the following translations:

```yaml
en:
  activerecord:
    models:
      user: Customer
    attributes:
      user:
        login: "Handle"
      # will translate User attribute "login" as "Handle"
```

Then `User.model_name.human` will return "Customer" and
`User.human_attribute_name("login")` will return "Handle".

```ruby
User.model_name.human
# => "Customer"

User.human_attribute_name("login")
# => "Handle"
```

You can also set a plural form for model names:

```yaml
en:
  activerecord:
    models:
      user:
        one: Customer
        other: Customers
```

Then, `User.model_name.human(count: 2)` will return "Customers". With `count: 1`
or without params, it will return "Customer".

```ruby
User.model_name.human(count: 2)
# => "Customers"

User.model_name.human
# => "Customer"
```

In the event you need to access nested attributes within a given model, you
should nest these under `model/attribute` at the model level of your translation
file:

```yaml
en:
  activerecord:
    attributes:
      user/role:
        admin: "Admin"
        contributor: "Contributor"
```

Then `User.human_attribute_name("role.admin")` will return "Admin".

```ruby
User.human_attribute_name("role.admin")
# => "Admin"
```

NOTE: If you are using a class which includes `ActiveModel` and does not inherit
from `ActiveRecord::Base`, replace `activerecord` with `activemodel` in the
above key paths.

#### Error Message Scopes

Active Record validation error messages can also be translated easily. Active
Record gives you a couple of namespaces where you can place your message
translations in order to provide different messages and translation for certain
models, attributes, and/or validations. It also transparently takes single table
inheritance into account.

This gives you a way to flexibly adjust your messages to your application's
needs.

Consider a User model with a validation for the name attribute:

```ruby
class User < ApplicationRecord
  validates :name, presence: true
end
```

The key for the error message in this case is `:blank`. Thus, in our example it
will try the following keys in this order and return the first result:

```
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank
```

To explain it more abstractly, it returns the first key that matches in the
order of the following list.

```
activerecord.errors.models.[model_name].attributes.[attribute_name].[key]
activerecord.errors.models.[model_name].[key]
activerecord.errors.messages.[key]
errors.attributes.[attribute_name].[key]
errors.messages.[key]
```

Additionally, when your models are using inheritance then the messages are
looked up in the inheritance chain.

For example, you might have an Admin model inheriting from `User`:

```ruby
class Admin < User
  validates :name, presence: true
end
```

Then Active Record will look for messages in this order:

```
activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank
```

This way you can provide special translations for various error messages at
different points in your model's inheritance chain and in the attributes,
models, or default scopes.

#### Error Message Interpolation

The translated model name, translated attribute name, and value are always
available for interpolation as `model`, `attribute` and `value` respectively.

For example, instead of the default error message `"can't be blank"` you could
use the attribute name like this : `"Please fill in your %{attribute}"`.
`count`, where available, can be used for pluralization if present:

| validation               | with option               | message                   | interpolation |
| ------------------------ | ------------------------- | ------------------------- | ------------- |
| confirmation             | -                         | :confirmation             | attribute     |
| acceptance               | -                         | :accepted                 | -             |
| presence                 | -                         | :blank                    | -             |
| absence                  | -                         | :present                  | -             |
| length                   | :within, :in              | :too_short                | count         |
| length                   | :within, :in              | :too_long                 | count         |
| length                   | :is                       | :wrong_length             | count         |
| length                   | :minimum                  | :too_short                | count         |
| length                   | :maximum                  | :too_long                 | count         |
| uniqueness               | -                         | :taken                    | -             |
| format                   | -                         | :invalid                  | -             |
| inclusion                | -                         | :inclusion                | -             |
| exclusion                | -                         | :exclusion                | -             |
| associated               | -                         | :invalid                  | -             |
| non-optional association | -                         | :required                 | -             |
| numericality             | -                         | :not_a_number             | -             |
| numericality             | :greater_than             | :greater_than             | count         |
| numericality             | :greater_than_or_equal_to | :greater_than_or_equal_to | count         |
| numericality             | :equal_to                 | :equal_to                 | count         |
| numericality             | :less_than                | :less_than                | count         |
| numericality             | :less_than_or_equal_to    | :less_than_or_equal_to    | count         |
| numericality             | :other_than               | :other_than               | count         |
| numericality             | :only_integer             | :not_an_integer           | -             |
| numericality             | :in                       | :in                       | count         |
| numericality             | :odd                      | :odd                      | -             |
| numericality             | :even                     | :even                     | -             |
| comparison               | :greater_than             | :greater_than             | count         |
| comparison               | :greater_than_or_equal_to | :greater_than_or_equal_to | count         |
| comparison               | :equal_to                 | :equal_to                 | count         |
| comparison               | :less_than                | :less_than                | count         |
| comparison               | :less_than_or_equal_to    | :less_than_or_equal_to    | count         |
| comparison               | :other_than               | :other_than               | count         |

### Action Mailer E-Mail Subjects

If you don't pass a subject to the `mail` method, Action Mailer will try to find
it in your translations. The performed lookup will use the pattern
`<mailer_scope>.<action_name>.subject` to construct the key.

```ruby
# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    #...
  end
end
```

```yaml
en:
  user_mailer:
    welcome:
      subject: "Welcome to Rails Guides!"
```

To send parameters to interpolation use the `default_i18n_subject` method on the
mailer:

```ruby
# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    mail(to: user.email, subject: default_i18n_subject(user: user.name))
  end
end
```

```yaml
en:
  user_mailer:
    welcome:
      subject: "%{user}, welcome to Rails Guides!"
```

### Form Helpers

Form helpers use model and attribute translations when they build labels and
error messages. For example, `form_with` calls `human_attribute_name` for label
text when you do not pass an explicit label:

```erb
<%= form_with model: @user do |form| %>
  <%= form.label :login %>
  <%= form.text_field :login %>
<% end %>
```

With the following translations:

```yaml
en:
  activerecord:
    attributes:
      user:
        login: "Handle"
```

the label renders as:

```html
<label for="user_login">Handle</label>
```

### Action View Helper Methods

Several Action View helpers use locale data for dates, times, numbers, and
currency formatting.

* [`distance_of_time_in_words`](https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words)
  translates and pluralizes its result and interpolates the number of seconds,
  minutes, hours, and so on.

    ```ruby
    distance_of_time_in_words(Time.current, Time.current + 3.minutes)
    # => "3 minutes"
    ```

    It uses translations from the `datetime.distance_in_words` scope.

* [`relative_time_in_words`](https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-relative_time_in_words)
  builds on `distance_of_time_in_words` and adds a localized prefix/suffix
  depending on whether the time is in the past or future.

    ```ruby
    relative_time_in_words(3.minutes.from_now)
    # => "in 3 minutes"
    relative_time_in_words(3.minutes.ago)
    # => "3 minutes ago"
    ```

    It uses translations from the `datetime.relative` scope.

* [`datetime_select`](https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-datetime_select)
  and
  [`select_month`](https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-select_month)
  use translated month names for populating the resulting select tag.

    ```erb
    <%= select_month(Date.new(2024, 3, 1)) %>
    ```

    The rendered `<option>` labels come from translated month names in the
    `date.month_names` scope. `datetime_select` also looks up the order option
    from `date.order` (unless you pass the option explicitly). All date
    selection helpers translate the prompt using the translations in the
    `datetime.prompts` scope if applicable.

* The
  [`number_to_currency`](https://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_currency),
  [`number_with_precision`](https://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_precision),
  [`number_to_percentage`](https://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_percentage),
  [`number_with_delimiter`](https://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_delimiter),
  and
  [`number_to_human_size`](https://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_human_size)
  helpers use the number format settings located in the `number` scope.

    ```ruby
    number_to_currency(10)
    # => "$10.00"
    ```

### Active Model Methods

Active Model uses translations for human-readable model names, attribute names,
and validation error messages.

* `model_name.human` and `human_attribute_name` use translations for model names
  and attribute names if available in the `activerecord.models` and
  `activerecord.attributes` scopes.

    ```ruby
    User.model_name.human
    # => "Customer"
    ```

    They also support translations for inherited class names (e.g. for use with
    STI) as explained above in "Error message scopes".

* `ActiveModel::Errors#generate_message` (which is used by Active Model
  validations but may also be used manually) uses `model_name.human` and
  `human_attribute_name`.

    ```ruby
    user.errors.generate_message(:name, :blank)
    # => "can't be blank"
    ```

    It also translates the error message and supports translations for inherited
    class names as explained above in "Error message scopes".

* [`ActiveModel::Error#full_message`](https://api.rubyonrails.org/classes/ActiveModel/Error.html#method-i-full_message)
  and
  [`ActiveModel::Errors#full_messages`](https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-full_messages)
  prepend the attribute name to the error message using a format looked up from
  `errors.format` (default: `"%{attribute} %{message}"`).

    ```ruby
    user.errors.full_messages
    # => ["Name can't be blank"]
    ```

    To customize the default format, override it in the app's locale files. To
    customize the format per model or per attribute, see
    [`config.active_model.i18n_customize_full_message`][].

[`config.active_model.i18n_customize_full_message`]:
    configuring.html#config-active-model-i18n-customize-full-message

### Active Support Methods

Active Support also includes helpers that use locale-specific formatting rules.

* [`Array#to_sentence`](https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence)
  uses format settings from the `support.array` scope.

    ```ruby
    ["apples", "oranges", "pears"].to_sentence
    # => "apples, oranges, and pears"
    ```

Advanced Configuration and Setup
---------------------------------

### Configuring the load paths and default locale

Rails automatically adds translation files from `config/locales` to the
translations load path. By default, this includes `.rb` and `.yml` files, and
for many applications that is all you need.

Rails' own translations are organized the same way. See, for example, the Active
Model validation messages in
[`activemodel/lib/active_model/locale/en.yml`](https://github.com/rails/rails/blob/main/activemodel/lib/active_model/locale/en.yml)
or the date and time formats in
[`activesupport/lib/active_support/locale/en.yml`](https://github.com/rails/rails/blob/main/activesupport/lib/active_support/locale/en.yml).
With the default `Simple` backend, you can store translations in YAML files or
in plain Ruby hashes.

The translations load path (`I18n.load_path`) is the list of translation files
Rails loads automatically. You can customize it if you want a different
directory structure or naming scheme.

NOTE: The backend lazy-loads these translations the first time a translation is
looked up. You can still swap the backend implementation later if needed.

To add more translation files or change the default locale, configure
`config.i18n` in `config/application.rb`:

```ruby
config.i18n.load_path += Dir[Rails.root.join("my", "locales", "*.{rb,yml}")]
config.i18n.default_locale = :de
```

The load path must be configured before any translations are looked up.

If you need to work directly with i18n instead, for example in an initializer,
you can do that as well:

```ruby
# config/initializers/locale.rb

# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join("lib", "locale", "*.{rb,yml}")]

# Permitted locales available for the application
I18n.available_locales = [:en, :pt]

# Set default locale to something other than :en
I18n.default_locale = :pt
```

In general, prefer `config.i18n.*` in application configuration. Appending
directly to `I18n.load_path` will _not_ override translations from external gems
in the same way.

You can use YAML (`.yml`) or plain Ruby (`.rb`) files to store translations in
the `Simple` backend. YAML is the format most Rails applications use, but it is
sensitive to whitespace and special characters, so syntax mistakes can prevent a
locale file from loading correctly.

Ruby locale files can be useful when you want syntax errors to fail fast, or
when you need values such as lambdas in your locale data.

If your translations are stored in YAML files, certain keys must be escaped.
They are:

* true, on, yes
* false, off, no

Examples:

```yaml
# config/locales/en.yml
en:
  success:
    'true':  'True!'
    'on':    'On!'
    'false': 'False!'
  failure:
    true:    'True!'
    off:     'Off!'
    false:   'False!'
```

```ruby
I18n.t "success.true"  # => 'True!'
I18n.t "success.on"    # => 'On!'
I18n.t "success.false" # => 'False!'
I18n.t "failure.false" # => Translation Missing
I18n.t "failure.off"   # => Translation Missing
I18n.t "failure.true"  # => Translation Missing
```

### Storing Custom Translations

The `Simple` backend shipped with Active Support allows you to store
translations in both plain Ruby and YAML format. Other backends may allow or
require different formats.

For example a Ruby Hash providing translations can look like this:

```ruby
{
  pt: {
    foo: {
      bar: "baz"
    }
  }
}
```

The equivalent YAML file would look like this:

```yaml
pt:
  foo:
    bar: baz
```

As you see, in both cases the top level key is the locale. `"foo"` is a
namespace key and `"bar"` is the key for the translation "baz".

Here is a "real" example from the Active Support `en.yml` translations YAML
file:

```yaml
en:
  date:
    formats:
      default: "%Y-%m-%d"
      short: "%b %d"
      long: "%B %d, %Y"
```

So, all of the following equivalent lookups will return the `:short` date format
`"%b %d"`:

```ruby
I18n.t "date.formats.short"                # => "%b %d"
I18n.t "formats.short", scope: "date"      # => "%b %d"
I18n.t "short", scope: "date.formats"      # => "%b %d"
I18n.t "short", scope: ["date", "formats"] # => "%b %d"
```

Generally we recommend using YAML as a format for storing translations. There
are cases, though, where you want to store Ruby lambdas as part of your locale
data, e.g. for special date formats.

### Using Alternate Backends

For several reasons the `Simple` backend shipped with Active Support only does
the "simplest thing that could possibly work" for Ruby on Rails. It is
guaranteed to work for English and, as a side effect, for languages that are
very similar to English. It is also only capable of reading translations and
cannot dynamically store them in another format.

That does not mean you're stuck with these limitations, though. The Ruby I18n
gem makes it very easy to exchange the `Simple` backend implementation with
something else that fits better for your needs, by passing a backend instance to
the `I18n.backend=` setter.

For example, you can replace the `Simple` backend with the `Chain` backend to
chain multiple backends together. This is useful when you want to use standard
translations with a `Simple` backend but store custom application translations
in a database or other backends.

With the `Chain` backend, you could use the Active Record backend and fall back
to the (default) `Simple` backend:

```ruby
I18n.backend = I18n::Backend::Chain.new(
  I18n::Backend::ActiveRecord.new,
  I18n.backend
)
```

In this configuration, lookups first check the Active Record backend and then
fall back to the locale files loaded by the `Simple` backend:

```ruby
I18n.t("welcome")
# Looks for "welcome" in the Active Record backend first,
# then in YAML/Ruby locale files.
```

This can be useful when you want translators or administrators to update some
translations at runtime while still keeping default translations in
version-controlled locale files.

### Handling I18n Exceptions

The I18n API defines the following exceptions that will be raised by backends
when the corresponding unexpected conditions occur:

| Exception                            | Reason                                                                                            |
| ------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `I18n::MissingTranslationData`       | no translation was found for the requested key                                                    |
| `I18n::InvalidLocale`                | the locale set to `I18n.locale` is invalid (e.g. `nil`)                                           |
| `I18n::InvalidPluralizationData`     | a count option was passed but the translation data is not suitable for pluralization              |
| `I18n::MissingInterpolationArgument` | the translation expects an interpolation argument that has not been passed                        |
| `I18n::ReservedInterpolationKey`     | the translation contains a reserved interpolation variable name (i.e. one of: `scope`, `default`) |
| `I18n::UnknownFileType`              | the backend does not know how to handle a file type that was added to `I18n.load_path`            |

#### Handling Missing Translations

If `config.i18n.raise_on_missing_translations` is `false` (the default in all
environments), the missing translation message is returned instead. It includes
the missing key and scope so you can find and fix the problem.

If `config.i18n.raise_on_missing_translations` is `true`, missing translations
raise errors instead of returning the usual `"Translation missing: ..."`
message. If the value is `:strict`, model translations also raise. It is a good
idea to enable this in your test environment so you can catch missing
translations early.

For example:

```ruby
I18n.t("missing")
# => "Translation missing: en.missing"

I18n.t!("missing")
# => I18n::MissingTranslationData: Translation missing: en.missing
```

When `config.i18n.raise_on_missing_translations` is `true`, the same lookup
raises:

```ruby
I18n.t("missing")
# => I18n::MissingTranslationData: Translation missing: en.missing

I18n.t("inbox", locale: nil)
# => I18n::InvalidLocale

I18n.t("product_price", locale: :nl)
# => I18n::MissingInterpolationArgument
```

If you want to customize this behavior further, set
`config.i18n.raise_on_missing_translations = false` and implement an
`I18n.exception_handler`. The custom exception handler can be a proc or a class
with a `call` method:

```ruby
# config/initializers/i18n.rb
module I18n
  class RaiseExceptForSpecificKeyExceptionHandler
    def call(exception, locale, key, options)
      if key == "special.key"
        "translation missing!" # return this, don't raise it
      elsif exception.is_a?(MissingTranslation)
        raise exception.to_exception
      else
        raise exception
      end
    end
  end
end

I18n.exception_handler = I18n::RaiseExceptForSpecificKeyExceptionHandler.new
```

This would raise all exceptions the same way the default handler would, except
for `I18n.t("special.key")`.

The `translate` view helper also routes missing translations through
`I18n.exception_handler`, so a custom handler applies there as well.
