I18n questions · Localization files
Split the files by concern instead of keeping one en.yml per language, and let the key hierarchy follow the code: view keys mirror the view path, model and attribute names live under activerecord, and shared words go into a defaults file. Rails merges every file into one dictionary per locale, so the split is for people, not for the framework. The Rails i18n guide suggests a layout like this:
config/locales/
defaults/en.yml # shared words, date and number formats
models/user/en.yml # activerecord.models and activerecord.attributes
views/users/en.yml # users.index.title, users.create.success
views/layout/en.yml # navigation, footer
Since Rails 7.0, config/locales is loaded recursively (the registered glob is **/*.{rb,yml}), so this works with no configuration. Rails 6.1 and older read only the top level; there you add the nested files yourself in config/application.rb:
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]
Every file starts with the locale key (en:), and a key defined in two files is silently overwritten by the file loaded later, so give each file its own branch of the tree.
View keys follow the template path, which is what lazy lookup expects. In app/views/users/index.html.erb, t(".title") resolves to users.index.title, and in UsersController#create, t(".success") resolves to users.create.success:
en:
users:
index:
title: "Users"
create:
success: "User created"
Model and attribute names belong under activerecord. User.model_name.human and User.human_attribute_name(:name) read them, and so do form.label :name and validation messages such as "Name can't be blank". Error messages are looked up from the most specific key, activerecord.errors.models.user.attributes.name.blank, down to errors.messages.blank. Form helpers have their own namespace: helpers.label.user.name overrides the label text, and helpers.submit.user.create and update set the button text with %{model} available for interpolation.
en:
activerecord:
models:
user: "Customer"
attributes:
user:
name: "Full name"
helpers:
submit:
user:
create: "Add %{model}"
update: "Save %{model}"
Naming rules that keep the files maintainable:
users.form.save), never after the English text, which changes while the key stays.t("status.#{state}") works, but static analysis cannot see the keys (i18n-tasks only infers them with --no-strict), so use such dynamic keys sparingly.The i18n-tasks gem checks all of this: i18n-tasks missing lists keys used in code but absent from a locale, i18n-tasks unused finds dead keys, i18n-tasks normalize sorts and formats the files, and its test template fails CI when a key is missing. A TMS such as Localizely reads and writes these per-language Rails YAML files, so the layout you pick is kept through translation. The file format itself is described at Rails YAML file.
Published Oct 9, 2026 · Markdown version