Skip to main content

Rails i18n: Complete Ruby on Rails Internationalization Guide

From first locale file to production: configure Rails I18n, use the t() helper, handle CLDR pluralization, fix common pitfalls, and add smart locale fallback chains.

1

Understand Rails I18n Architecture

Rails ships with the I18n gem built in. Translation files live in config/locales/ as YAML (default) or Ruby files. The framework provides the t() helper (alias for I18n.translate) in views, controllers, models, and mailers. Rails I18n is simple by design — it handles basic translation, interpolation, and pluralization out of the box.

config/application.rb
# Rails includes i18n out of the box via the i18n gem
# config/application.rb
module MyApp
  class Application < Rails::Application
    # Default locale
    config.i18n.default_locale = :en

    # Available locales
    config.i18n.available_locales = [:en, :de, :ja, :es, :fr, :'pt-BR']

    # Fallback to default locale when translation is missing
    config.i18n.fallbacks = true

    # Load translations from nested directories
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
  end
end
config/routes.rb
# config/routes.rb
Rails.application.routes.draw do
  scope "/:locale", locale: /en|de|ja|es|fr|pt-BR/ do
    root "home#index"
    resources :products
  end

  root "home#index"
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_action :switch_locale

  private

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

  def default_url_options
    { locale: I18n.locale }
  end
end
Rails loads all .yml and .rb files from config/locales/ automatically. You can organize them however you want — by language (en.yml, de.yml), by feature (en/users.yml, en/orders.yml), or both (en/users.yml, de/users.yml). Rails merges all files at boot time.
2

Configure Locale Settings

Set default_locale, available_locales, and fallback behavior in config/application.rb or an initializer. Configure locale detection in your ApplicationController using before_action to set the locale from the URL, session, cookie, or Accept-Language header.

config/locales/en.yml
# config/locales/en.yml
en:
  nav:
    home: "Home"
    about: "About"
    settings: "Settings"
  greeting: "Hello, %{name}!"
  cart:
    item_count:
      one: "%{count} item"
      other: "%{count} items"

# config/locales/de.yml
de:
  nav:
    home: "Startseite"
    about: "Über uns"
    settings: "Einstellungen"
  greeting: "Hallo, %{name}!"
  cart:
    item_count:
      one: "%{count} Artikel"
      other: "%{count} Artikel"
Setting I18n.locale in a before_action is per-request safe, but setting I18n.default_locale at runtime is global and affects all threads. Always use I18n.locale (thread-local) for request-scoped locale changes, never I18n.default_locale.
3

Use the t() Helper

The t() helper is available everywhere in Rails — views, controllers, models, mailers, and jobs. It accepts a key, optional interpolation variables, and options like default values and scope. In views, Rails supports lazy lookups that automatically scope keys to the current controller and action.

app/views/example.html.erb
# In views (ERB)
<h1><%= t('nav.home') %></h1>
<p><%= t('greeting', name: @user.name) %></p>

# In controllers
flash[:notice] = t('flash.product_created')

# In models
validates :name, presence: { message: I18n.t('errors.blank') }

# With HTML (safe)
<%= t('terms_html', link: link_to(t('terms_link'), '/terms')) %>
4

Handle Pluralization

Rails I18n uses CLDR plural categories: zero, one, two, few, many, other. English only needs one and other, but other languages need more forms. Define plural translations as nested YAML keys under the count categories your target languages require.

Plural forms
# In YAML:
en:
  cart:
    item_count:
      zero: "No items"
      one: "%{count} item"
      other: "%{count} items"

# Arabic (6 forms):
ar:
  cart:
    item_count:
      zero: "لا عناصر"
      one: "عنصر واحد"
      two: "عنصران"
      few: "%{count} عناصر"
      many: "%{count} عنصرًا"
      other: "%{count} عنصر"

# Usage in views:
<%= t('cart.item_count', count: @cart.items.size) %>
Rails raises I18n::InvalidPluralizationData if a required plural category is missing for the active locale. If your Russian locale only defines one and other (copied from English), counts like 2, 3, 4 will crash because Russian requires a few category. Always define all CLDR categories for each language.
5

Add Locale Fallback Chains

Rails' built-in I18n.fallbacks only provides a basic fallback from regional to default locale. A pt-BR user with a missing key sees English instead of pt-PT. rails-locale-chain adds configurable deep-merge chains so regional users always see the closest available translation.

Lazy lookups
# Lazy lookups use the controller/action as scope
# app/views/products/index.html.erb
# Instead of t('products.index.title'), just use:
<h1><%= t('.title') %></h1>
<p><%= t('.description') %></p>

# Rails looks up: products.index.title and products.index.description

# config/locales/en.yml
en:
  products:
    index:
      title: "All Products"
      description: "Browse our catalog"
rails-locale-chain ships with 75+ built-in fallback chains covering 11 language families. Add it to your Gemfile, configure in an initializer, and regional users immediately see parent-locale translations instead of English gaps.
6

Automate Translations

With your Rails I18n setup complete, translate your YAML locale files using AI. i18n Agent supports YAML natively — point it at your source locale file and it generates all target languages, preserving nested keys, interpolation variables, and plural forms.

config/initializers/locale_chain.rb
# Gemfile
gem 'rails-locale-chain'

# config/initializers/locale_chain.rb
Rails.application.config.i18n.fallbacks = {
  'pt-BR': ['pt', 'en'],
  'zh-Hant-TW': ['zh-Hant', 'zh', 'en'],
  'es-419': ['es', 'en'],
}

# The gem deep-merges translations across the chain
# pt-BR -> pt -> en
# Missing keys in pt-BR are filled from pt, then en
Translate incrementally — when you add new keys, translate just the diff. This preserves existing translations and avoids regenerating unchanged strings.

Common Pitfalls

InvalidPluralizationData Errors

Rails crashes with I18n::InvalidPluralizationData when a required CLDR plural category is missing. Most often happens when English plural forms (one/other) are copied to languages that need more categories (Russian needs few, Arabic needs zero/two/few/many). Install rails-i18n for correct CLDR rules and define all categories.

Lazy Lookups Outside Views

Lazy lookups (t('.key')) only work in views where Rails knows the controller and action. Using t('.key') in a model, mailer, or service object returns a missing translation error. Use full keys (t('users.show.key')) outside of views.

YAML Syntax Errors Break All Translations

A single YAML syntax error (bad indentation, unquoted special characters, tab instead of spaces) prevents the entire locale file from loading. All translations in that file return missing key errors. Validate YAML files in CI with a linter, and quote strings that contain colons, brackets, or leading special characters.

Try i18n Agent Now

Drop your translation file here

JSON, YAML, PO, XML, CSV, Markdown, Properties

or click to browse

Target languages

No signup requiredInstant estimate

Rails i18n FAQ