Home / Blog / Internationalization for Ruby - i18n gem

Internationalization for Ruby - i18n gem

Developer resources
Helmut Juskewycz
CEO & Founder of LingoHub

Last updated

8/23/2022

Read time

8 min

Best for

Developers

Table of content

chevron

In July we published a series of articles on internationalization for PHP. This month we will focus on tutorials about internationalization for Ruby, starting out with a comprehensive tutorial on getting started with the i18n gem and various frameworks.

If you missed any of the prior articles, you can have a look here:

Localization and Internationalization are recurring themes in our blog posts. If you are not 100% sure what they refer to, you can find an explanation in this article.

ruby

For August: Internationalization in the Ruby world

For August, we have prepared a series of articles, and to start out we will demonstrate how internationalization can be achieved in the Ruby world.

Ruby is a dynamic, reflective, general-purpose object-oriented programming language that combines syntax inspired by Perl with Smalltalk-like features. It was also influenced by Eiffel and Lisp. Ruby was first designed and developed in the mid-1990s by Yukihiro “Matz” Matsumoto in Japan. [1]


Topics covered in this article


Since the emergence of web application frameworks like Ruby on Rails and DSLs like Sinatra, Ruby has been used to reach a wide international public. Early in August 2013, there were nearly 200,000 websites worldwide using Ruby on Rails [2] alone.

There is no better way of reaching a wide international audience than in their own individual languages, and this series of articles will show you how. Let’s start with the basics and then move on to the details.


Internationalization for Ruby: The Ruby i18n gem

One of the most popular Ruby gems for internationalization is the Ruby I18n gem. It allows translation and localization, interpolation of values in translations, pluralization, customizable transliteration to ASCII, flexible defaults, bulk lookup, lambdas as translation data, custom key/scope separators, and custom exception handlers.

The gem is split into two parts:

  • the public API

  • a default backend (Simple backend)

Other backends can be used, such as Chain, ActiveRecord, KeyValue, or custom implementations.

YAML (.yml) or plain Ruby (.rb) files are used for storing translations in the Simple backend, but YAML is the preferred option among Ruby developers.


Internationalization and the YAML resource file format

YAML is a human-readable data serialization format. Its syntax is designed to map easily to common data structures such as lists, associative arrays, and scalars. Unlike some other formats, YAML has a well-defined standard (specification).

Key features of YAML resource files

  • Information is stored in key-value pairs separated by :

  • Keys can be nested (scoped)

  • The root key corresponds to the locale (e.g., en-US, de)

  • Leaf keys must have values

  • Values can be escaped

  • Correct indentation is required for hierarchy

  • Lines starting with # are comments

  • Placeholder syntax: %{name}

  • UTF-8 encoding is typically used

Before demonstrating i18n methods, let’s create an example YAML file we will load and test:

👉 Gist


Installation and setup

After installing the gem, change directory to where the sample YAML file is saved and start the IRB (Interactive Ruby Shell).

First, require the library:

require 'i18n'

Check the current locale (default is English):

I18n.locale
# => :en

Change the locale:

I18n.locale = :de
# => :de

Translation lookup

Translation lookup is done via I18n.translate (alias: I18n.t).

Example:

I18n.translate :world, scope: 'greetings.hello'
# => "translation missing: en.hello.world"

Load translation files:

I18n.load_path = Dir['./*.yml', './*.rb']
# => ["./en.yml"]

Retry lookup:

I18n.translate :world, scope: 'greetings.hello'
# => "Hello world!"

Explicit locale:

I18n.translate :world, scope: 'greetings.hello', locale: :en
# => "Hello world!"

Equivalent calls:

I18n.translate 'greetings.hello.world'
I18n.translate 'hello.world', scope: :greetings
I18n.translate 'hello.world', scope: 'greetings'
I18n.translate :world, scope: 'greetings.hello'
I18n.translate :world, scope: [:greetings, :hello]

Defaults:

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

Interpolation:

I18n.translate :user, scope: [:greetings, :hello], user: 'Ela'
# => "Hello Ela!"

Multiple keys:

I18n.translate [:world, :friend], scope: [:greetings, :hello]
# => ["Hello World!", "Hello Friend!"]

Nested hash result:

I18n.translate :hello, scope: [:greetings]
# => {:world=>"Hello World!", :user=>"Hello %{user}", :friend=>"Hello Friend!"}

Pluralization options in Ruby i18n

English uses singular and plural forms (e.g., “1 message” vs “2 messages”). Other languages have more complex rules.

The :count variable is used for interpolation and plural selection:

I18n.translate :messages, scope: :inbox, count: 1
# => "You have one message in your inbox."

I18n.translate :messages, scope: :inbox, count: 39
# => "You have 39 messages in your inbox."

Pluralization rule (English):

entry[count == 1 ? 0 : 1]
  • one → singular

  • other → plural


Setting up date and time localization

Localize time using:

I18n.localize Time.now
# => "Wed, 14 Aug 2013 13:34:49 +0200"

Custom format:

I18n.localize Time.now, format: :short
# => "14 Aug 13:34"

Alias:

I18n.l Time.now

i18n – default internationalization solution for Ruby on Rails

I18n in Ruby on Rails

I18n is the default internationalization solution for Ruby on Rails and is localized with the use of the rails-i18n gem.

In accordance with the Rails philosophy of convention over configuration, Rails applications come with some reasonable defaults already set.


Default Rails I18n setup

Instead of doing this manually:

require 'i18n'
I18n.locale = :en
I18n.default_locale = :en
I18n.load_path = Dir['./*.yml']

Rails automatically:

  • Loads all .rb and .yml files from config/locales

  • Sets English (:en) as the default locale


Locale file organization in Rails

By default, Rails expects all locale files in:

config/locales

However, it is often better to organize files into subdirectories per locale, or even separate translations by domain (models vs views).


Customizing I18n settings in Rails

You can override defaults in application.rb:

config.i18n.load_path += Dir[Rails.root.join('config/locales/**/*.{rb,yml}').to_s]
config.i18n.default_locale = :de
config.i18n.available_locales = [:en, :de, :fr]

Available locales in Rails

Rails itself is localized into many languages via the rails-i18n project, including translations for built-in framework text.


Managing locale selection in Rails applications

Multilingual applications need to allow users to switch locales and persist that choice.

However, storing locale in sessions or cookies is not recommended. Instead, locale should be part of the URL to ensure:

  • Shareable URLs preserve language

  • SEO-friendly structure

  • RESTful design


Ways to pass locale in Rails

  • Query parameter
    http://example.com/?locale=sr

  • URL path
    http://example.com/sr/

  • Domain
    http://example.sr

  • Subdomain
    http://sr.example.com

  • Client information (headers, profile, etc.)


Using locale from query parameters

In ApplicationController:

before_action :set_locale

def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
end

Keeping locale in URLs

Override default_url_options:

def default_url_options(options = {})
  { locale: I18n.locale }
end

This ensures all generated URLs include the locale automatically.


Example routing:

scope "(:locale)", locale: /en|sr/ do
  resources :books
end

This allows:

  • /en/books

  • /sr/books

  • /books (default locale fallback)


Locale from domain or subdomain

before_action :set_locale

def set_locale
  I18n.locale = extract_locale_from_tld || I18n.default_locale
  # or:
  # I18n.locale = extract_locale_from_subdomain || I18n.default_locale
end

def extract_locale_from_tld
  parsed_locale = request.host.split('.').last
  I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
end

def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
end

Locale from client-supplied data

From user profile:

I18n.locale = current_user.locale

From browser headers:

def set_locale
  I18n.locale = extract_locale_from_accept_language_header
end

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

For production systems, consider:


Rails I18n helpers

Rails provides shorthand helpers:

  • t → translate

  • l → localize

Instead of:

I18n.t :hello
I18n.l Time.now

You can write:

t :hello
l Time.now

Missing translations are automatically wrapped in helpful error output.


Inflection rules in Rails

Rails allows locale-specific inflection rules (pluralization, singularization, etc.) in:

config/initializers/inflections.rb

Localized views in Rails

Rails supports localized templates:

index.html.erb
index.de.html.erb

Rails will automatically select the correct version based on locale.


Safe HTML translations

Keys ending in _html or named html are treated as HTML-safe.

Example locale file:

en:
  welcome: "<b>welcome!</b>"
  hello_html: "<b>hello!</b>"
  title:
    html: "<b>title!</b>"

Example view:

<div><%= t('welcome') %></div>
<div><%= raw t('welcome') %></div>
<div><%= t('hello_html') %></div>
<div><%= t('title.html') %></div>

ActiveRecord translations

Rails supports model and attribute translations:

en:
  activerecord:
    models:
      user: Dude
    attributes:
      user:
        login: "Handle"

Usage:

User.model_name.human
# => "Dude"

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

Error message translation lookup

Rails searches multiple namespaces for validation messages:

  • activerecord.errors.models.[model].attributes.[attribute]

  • activerecord.errors.models.[model]

  • activerecord.errors.messages

  • errors.attributes.[attribute]

  • errors.messages


Error message interpolation

Rails supports interpolation variables like:

"Please fill in your %{attribute}"

Action Mailer translations

If no subject is provided, Rails looks it up:

class UserMailer < ActionMailer::Base
  def welcome(user)
  end
end

Locale file:

en:
  user_mailer:
    welcome:
      subject: "Welcome to LingoHub!"

Internationalization for Sinatra with the i18n gem

Internationalization in Sinatra with the i18n gem

SINATRA can be easily set up to use the i18n gem for internationalization.


Basic setup

require 'i18n'
require 'i18n/backend/fallbacks'

configure do
  I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)

  I18n.load_path = Dir[File.join(settings.root, 'locales', '*.yml')]
  I18n.backend.load_translations
end

Passing the locale

As previously described for Ruby on Rails, there are several ways to pass locale information.


1. Locale via URL path

before '/:locale/*' do
  I18n.locale = params[:locale]
  request.path_info = '/' + params[:splat][0]
end

2. Locale via subdomain

before do
  if (locale = request.host.split('.')[0]) != 'www'
    I18n.locale = locale
  end
end

3. Locale via browser preference (Rack middleware)

Requires rack-contrib:

use Rack::Locale

Using translation helpers

Once the i18n gem is configured and locale is set, you can use:

  • I18n.t for translations

  • I18n.l for localization

To avoid repeating I18n. everywhere, define helpers:

helpers do
  def t(*args)
    I18n.t(*args)
  end

  def l(*args)
    I18n.l(*args)
  end
end

Localized template rendering in Sinatra

To support localized views, override find_template so templates match the user’s locale.

Templates are typically named like:

  • index.en.erb

  • index.de.erb

helpers do
  def find_template(views, name, engine, &block)
    I18n.fallbacks[I18n.locale].each do |locale|
      super(views, "#{name}.#{locale}", engine, &block)
    end

    super(views, name, engine, &block)
  end
end

Internationalization for Padrino with i18n gem

i18n in Padrino

The i18n gem is used as the default internationalization solution for the Padrino framework. By default, Padrino will search for all .yml or .rb files located in app/locale.

Localization is fully supported in:

  • padrino-core (date formats, time formats, etc.)

  • padrino-admin (admin language, ORM fields, ORM errors, etc.)

  • padrino-helpers (currency, percentage, precision, duration, etc.)


Supported languages in Padrino

Padrino has been localized into the following languages:

Czech, Danish, German, English, Spanish, French, Italian, Dutch, Norwegian, Russian, Polish, Brazilian Portuguese, Turkish, Ukrainian, Traditional Chinese, Simplified Chinese, Japanese.


Translation approach

Translation and localization in Padrino work similarly to the approaches described in previous sections.


Setting the default locale

You can set the default locale in config/boot.rb:

Padrino.before_load do
  I18n.locale = :en
end

Summary

The i18n gem is simple to use yet very powerful, and it provides most functionality developers need. Because of this, it has become the most widely used internationalization solution in the Ruby ecosystem.

In most cases, i18n works practically “out of the box”. It is easy to set up and use, and once configured, the main remaining requirement is a reliable localization service.


Conclusion

In this article, we covered the basic usage of the i18n gem. The next article in the Ruby internationalization series will explore more advanced use cases.


Sources


Further reading

Related articles