Providing a personalized and accessible experience to a global audience, it is essential for applications to support multiple languages and cultural formats. This process, known as internationalization (i18n), is a critical aspect for those aiming to create inclusive technological solutions.
Internationalization is a notoriously complex challenge. Each language has its unique characteristics, such as pluralization rules, sentence structures, date and number formats. Therefore, creating tools that perfectly cater to all languages worldwide is not a simple task.
In Ruby on Rails, internationalization is simplified with the powerful i18n framework, which enables the creation, customization, and extension of translations, date and number formatting, and validation messages in Active Record models.
Configuration
To enable internationalization (i18n) support in a Rails application, some basic configurations are necessary to manage and customize translations.
I18n Module Configuration
By default, Rails automatically adds all .rb
and .yml
files from the config/locales
directory to the translation load path (I18n.load_path
). There’s even a default en.yml
file demonstrating how to create a key-value pair for strings.
en:
hello: "Hello world"
Here, in the :en
language, the hello
key corresponds to the text "Hello world." Validation messages, date formats, and other Rails default translations follow this structure.
Configuring Languages
To create a new internationalization file for a specific language, you need to register the path to the directory containing the files. This can be done in config/initializers/locale.rb
:
I18n.load_path += Dir[Rails.root.join("lib", "locale", "*.{rb,yml}")]
To define the available locales
, you can use the following rule:
I18n.available_locales = [:en, :pt]
You can also set a default locale
for your application with:
I18n.default_locale = :pt
Managing Application Language
To support multiple languages in a localized application, you must set the language at the beginning of each request. This ensures that all strings are translated according to the desired language during the request’s execution.
By default, the language used is the one defined in I18n.locale
. However, to prevent language settings from leaking into subsequent requests on the same thread/process, it is recommended to use I18n.with_locale
, which avoids this issue. The language can be set in an around_action
in the ApplicationController
, for example, using a URL parameter to define the application’s language:
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
By Domain
One way to set an application’s language is by using the domain name to identify the language. For instance, you can configure www.example.com
to load the default language (like English) and www.example.br
to load Brazilian Portuguese. This approach has several advantages, such as making the language obvious from the URL.
To implement this in Rails, you can use the following code in the ApplicationController
, where the language is extracted from the top-level domain (TLD). If the language is not found in the TLD, the default language is used. The code also includes the extract_locale_from_tld
function, which checks if the TLD is available in the configured language list.
around_action :switch_locale
def switch_locale(&action)
locale = extract_locale_from_tld || I18n.default_locale
I18n.with_locale(locale, &action)
end
def extract_locale_from_tld
parsed_locale = request.host.split(".").last
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
By URL Parameters
A common way to define and manage the language in a Rails application is by including it in the URL parameters. In this case, URLs like www.example.com/users?locale=br
or www.example.com/br/users
are used. This approach adheres to the RESTful pattern and web principles but requires a bit more work to implement.
Rails offers a centralized solution with default_url_options
in the ApplicationController
to include the language in all URLs without manually updating each link. This allows the language to be automatically added to all generated URLs, such as in link_to(users_url(locale: I18n.locale))
. The code for this is:
# app/controllers/application_controller.rb
def default_url_options
{ locale: I18n.locale }
end
With this, URLs like http://localhost:3001/?locale=br
are generated automatically. If you prefer cleaner URLs, such as http://www.example.com/en/users
for English and http://www.example.com/br/books
for Brazilian Portuguese, you can use scope
in routes to include the language directly in the URL:
# config/routes.rb
scope "/:locale" do
resources :users
end
By User Preference
In applications with user authentication, it’s possible to let the user set a language preference directly through the interface. With this approach, the user’s language preference is saved in the database and used to define the language for subsequent requests made by that authenticated user.
To implement this feature, you can use an around_action
in the ApplicationController
to set the language based on the user’s preference. The logic involves checking the stored language preference for the current user (if any) and, if not available, using the default language.
around_action :switch_locale
def switch_locale(&action)
locale = current_user.try(:locale) || I18n.default_locale
I18n.with_locale(locale, &action)
end
In this example, the current_user.try(:locale)
method attempts to access the authenticated user’s language preference. If available, the language changes accordingly; otherwise, the default language is used.
Translation Files
Translation files define how each language translates or adapts words/strings from one language to another. For example, we can have two translation files, one default in English and another in Brazilian Portuguese:
# config/locales/en.yml
en:
hello_world: Hello, world!
success:
'true': 'True!'
'on': 'On!'
'false': 'False!'
failure:
true: 'True!'
off: 'Off!'
false: 'False!'
# config/locales/pt-BR.yml
pt-BR:
hello_world: Olá, mundo!
success:
'true': 'Verdadeiro!'
'on': 'Ligado!'
'false': 'Falso!'
failure:
true: 'Verdadeiro!'
off: 'Desligado!'
false: 'Falso!'
If the locale
is set to pt-BR
(e.g., http://localhost:3000?locale=pt-BR
), the application displays strings in Portuguese. Otherwise, the default language (English) is used.
Using Variables
When internationalizing an application, it’s crucial to avoid making incorrect assumptions about grammatical rules when abstracting localized code. Rules that seem fundamental in one language might not be valid in another, as shown below.
<!-- app/views/products/show.html.erb -->
<%= t('product_price', price: @product.price) %>
The translation file for English would look like this:
# config/locales/en.yml
en:
product_price: "$%{price}"
While for Brazilian Portuguese:
# config/locales/pt-BR.yml
pt-BR:
product_price: "R$ %{price}"
Thus, for each locale, the currency symbol adapts appropriately to the language.
File Structure
Organizing translation files in an application can be essential for maintainability and scalability. In the standard simple storage system (SimpleStore) provided by the i18n library, translations are stored in plain text files. Keeping all translations in a single file per language may become difficult to manage as the application grows. Therefore, it’s advisable to organize these files in a folder hierarchy that makes sense for your project.
For example, the config/locales
directory can be organized as follows:
|-defaults
|---en.yml
|---pt-BR.yml
|-models
|---user
|-----en.yml
|-----pt-BR.yml
|-views
|---defaults
|-----en.yml
|-----pt-BR.yml
|---users
|-----en.yml
|-----pt-BR.yml
|---products
|-----en.yml
|-----pt-BR.yml
|---navigation
|-----en.yml
|-----pt-BR.yml
This structure separates translations by context. For example, translations related to models and their attributes can be stored in the models
folder, while view translations can go in views
. The defaults
directory can contain standard translations, such as date and time formats, used throughout the application. This organization simplifies the management and expansion of translations, allowing different parts of the application to have more specific and well-structured translations.
Final Thoughts
Internationalization is crucial for making applications accessible to a global audience, enabling users to interact in the language and format they are most comfortable with. In Rails, this is efficiently addressed with i18n, a system that simplifies the translation of texts, formatting of dates, numbers, and other local conventions. It centralizes translations in organized files and allows the language to be set dynamically, based on user preferences or application parameters. This approach ensures flexibility and scalability, simplifying translation into other languages without major changes to the code.
References
We want to work with you. Check out our Services page!