Rails embarque un framework i18n compétent, et quasiment aucun avis sur la façon de l’utiliser. Ce guide couvre la configuration qui tient la route au-delà des premières centaines de chaînes.
Les bases
Les traductions vivent dans config/locales, indexées par locale :
# config/locales/en.yml
en:
dashboard:
title: "Dashboard"
welcome: "Welcome back, %{name}"
Et sont récupérées avec t :
<h1><%= t("dashboard.title") %></h1>
<p><%= t("dashboard.welcome", name: current_user.first_name) %></p>
Le piège du load path
Rails charge config/locales/*.{rb,yml} — uniquement au premier niveau. Placez un fichier dans config/locales/admin/en.yml et il sera ignoré silencieusement. C’est la raison la plus fréquente pour laquelle une traduction qui existe pourtant de façon évidente semble manquante.
Corrigez cela dans config/application.rb :
config.i18n.load_path += Dir[Rails.root.join("config/locales/**/*.{rb,yml}")]
Faites-le tôt. Une fois que vous avez plus de quelques centaines de clés, vous voudrez des sous-dossiers, et découvrir cette règle à ce moment-là signifie déplacer des fichiers.
Une configuration qui vaut le coup
# config/application.rb
config.i18n.available_locales = %i[en fr de]
config.i18n.default_locale = :en
config.i18n.fallbacks = [:en]
available_locales combiné à enforce_available_locales — activé par défaut — signifie qu’une locale inattendue lève une exception plutôt que de ne rien afficher silencieusement. Gardez-le activé.
Levez une exception sur les traductions manquantes en développement et en test :
# config/environments/development.rb and test.rb
config.i18n.raise_on_missing_translations = true
C’est le réglage le plus rentable de ce guide. Sans lui, une clé manquante affiche un span translation missing jaune que personne ne remarque dans une langue qu’il ne lit pas. Avec lui, une clé manquante fait échouer le test qui affiche la vue.
En production, gardez les fallbacks actifs afin qu’un manque se replie sur la locale par défaut plutôt que d’afficher du balisage brut à un utilisateur.
Définir la locale
Autour de chaque requête, avec une remise à zéro ensuite :
class ApplicationController < ActionController::Base
around_action :switch_locale
private
def switch_locale(&)
I18n.with_locale(locale_from_request, &)
end
def locale_from_request
params[:locale] ||
current_user&.locale ||
http_accept_language.compatible_language_from(I18n.available_locales) ||
I18n.default_locale
end
end
Utilisez I18n.with_locale plutôt que d’assigner I18n.locale =. L’assignation est locale au thread et persiste dans la requête suivante traitée par ce même thread, ce qui produit ce bug mémorable où un utilisateur voit parfois la langue d’un autre.
Interpolation
Rails utilise %{name} :
en:
invitation: "%{inviter} invited you to %{project}"
Des placeholders nommés plutôt que positionnels, ce qui compte car l’ordre des mots diffère selon les langues — voir l’aide-mémoire des formats de placeholder.
Deux choses à savoir. Un argument d’interpolation manquant lève I18n::MissingInterpolationArgument, ce qui est le comportement souhaité. Et un % littéral dans une chaîne avec interpolation doit être échappé en %%.
Pour des valeurs formatées, utilisez %<name>s avec un spécificateur de format :
en:
progress: "%<percent>.1f%% complete"
Pluralisation
Passez count et imbriquez les catégories :
en:
messages:
one: "1 message"
other: "%{count} messages"
t("messages", count: 5) # => "5 messages"
Le point critique : les catégories requises dépendent de la langue cible, pas de l’anglais. Le polonais a besoin de one, few, many, other ; l’arabe en a besoin de six. Un fichier polonais avec seulement one et other lèvera I18n::InvalidPluralizationData pour la plupart des nombres.
Rails n’intègre que les règles anglaises. Pour tout le reste, ajoutez la gem rails-i18n, qui embarque la pluralisation CLDR pour une longue liste de locales, ou le backend de pluralisation de la gem i18n. Voir les règles de pluriel par langue pour ce dont chaque langue a besoin.
Notez aussi que zero est prise en charge par Rails comme une clé de confort, vérifiée avant la catégorie CLDR quand count vaut 0 — utile pour formuler un état vide différemment, mais ce n’est pas une catégorie CLDR dans la plupart des langues.
Lazy lookup
Dans une vue, un point en tête se résout par rapport au chemin du template :
<%# app/views/posts/index.html.erb %>
<h1><%= t(".title") %></h1> <%# => posts.index.title %>
Cela garde les clés courtes et reflète votre structure. Le prix à payer, c’est que posts.index.title n’apparaît jamais littéralement dans le code, donc grep pour une clé trouvée dans un fichier de locale ne renvoie rien. Choisissez une convention et appliquez-la de façon cohérente — mélanger les deux est ce qui rend les clés vraiment difficiles à retrouver.
Le même mécanisme fonctionne dans les contrôleurs pour les messages flash, en se résolvant par rapport à controller.action.
Structurer les clés
Deux écoles, et le choix compte davantage que celle que vous choisissez :
Refléter la structure des vues — posts.index.title. S’associe naturellement au lazy lookup, les clés sont évidentes d’après leur emplacement, et déplacer une vue signifie déplacer ses clés.
Grouper par sens — errors.card_declined, nav.settings. Encourage la réutilisation, survit aux refactorings, et exige un nommage réfléchi.
En pratique, la plupart des applications finissent par combiner les deux : des clés structurelles pour le texte spécifique à une page, et un espace de noms partagé pour tout ce qui est réutilisé. Ce qui compte, c’est qu’une chaîne partagée vive à un seul endroit — la même phrase dupliquée sous trois clés, ce sont trois traductions à payer et trois occasions de diverger.
Modèles et attributs
Rails recherche automatiquement les noms de modèles et d’attributs :
en:
activerecord:
models:
user:
one: "User"
other: "Users"
attributes:
user:
email: "Email address"
errors:
models:
user:
attributes:
email:
blank: "is required"
C’est ce qui rend les messages d’erreur de validation traduisibles sans toucher au modèle. Cela vaut le coup de le mettre en place tôt, car le faire après coup implique d’auditer chaque message d’erreur personnalisé.
Vues, mailers et tout le reste
Les chaînes que les développeurs oublient, dans l’ordre où elles sont habituellement oubliées :
- Sujets et corps des mailers. Définissez la locale à partir de la préférence du destinataire, pas de la requête en cours — un job en arrière-plan qui envoie une notification n’a pas de locale de requête.
- Jobs en arrière-plan. Même problème. Passez la locale explicitement dans les arguments du job.
- Exports PDF et CSV. Souvent construits séparément et jamais internationalisés.
- Messages d’erreur d’API JSON, s’ils sont un jour exposés à un utilisateur.
- Données de seed et libellés d’enum.
t("statuses.#{status}")plutôt questatus.humanize.
Synchroniser les traductions
Dès que la traduction se fait hors du dépôt, il faut que les fichiers circulent automatiquement, sinon ils dérivent. Avec le CLI de WebTranslateIt :
wti push # envoie config/locales/en.yml
wti pull # récupère les locales traduites
wti status # niveau d’avancement par langue
wti diff # ce qu’un push changerait, sans le faire
Intégrez-le à la CI : poussez les chaînes source à chaque merge sur votre branche désignée, récupérez les traductions chaque nuit dans une pull request. wti diff en tant que vérification de pull request est la pièce qui vaut le coup d’ajouter tôt — elle révèle que « ce refactoring supprime 40 segments traduits » pendant que le changement est encore en relecture.
Un fichier .wti à la racine du dépôt contient le token du projet et le mapping des fichiers, si bien que la configuration voyage avec le code. Voir les flux de travail de localisation basés sur Git pour la stratégie de branches qui évite que les push se marchent dessus.
Garder les fichiers honnêtes
Deux problèmes s’accumulent silencieusement : des clés référencées dans le code qui n’existent dans aucun fichier de locale, et des clés qui dorment dans les fichiers de locale sans que rien ne les référence plus. Les deux sont invisibles jusqu’à ce que quelqu’un tombe sur la manquante en production.
La gem i18n-tasks trouve les deux :
i18n-tasks missing # référencées dans le code, absentes des fichiers de locale
i18n-tasks unused # présentes dans les fichiers de locale, référencées nulle part
i18n-tasks normalize # trie les clés de façon cohérente pour garder des diffs lisibles
i18n-tasks health # tout en une fois
Ajoutez i18n-tasks missing à la CI et une clé manquante devient un build en échec plutôt qu’un ticket support. normalize vaut aussi le coup d’être lancé — sans un ordre de clés cohérent, chaque fichier de locale régénéré produit un diff qui touche chaque ligne, ce qui rend les pull requests de traduction impossibles à relire.
La seule réserve, c’est que les clés dynamiques — t("statuses.#{status}") — semblent inutilisées pour une analyse statique. i18n-tasks permet de déclarer ces motifs dans sa config afin qu’ils ne soient plus signalés.
Une checklist
config.i18n.load_pathélargi pour inclure les sous-dossiers.raise_on_missing_translationsactivé en développement et en test.- Fallbacks configurés pour la production.
I18n.with_localedans unaround_action, jamais une simple assignation.rails-i18najoutée si vous prenez en charge une locale autre que l’anglais.- Catégories de pluriel générées pour chaque langue cible, pas copiées depuis l’anglais.
- Mailers et jobs qui reçoivent une locale explicite.
- Synchronisation automatisée dans les deux sens.
Frequently asked questions
- Où placer les fichiers de locale Rails ?
- Dans config/locales. Par défaut, Rails ne charge que config/locales/*.{rb,yml} — les fichiers dans des sous-dossiers sont ignorés à moins de les ajouter à config.i18n.load_path, ce qui est la raison la plus fréquente pour laquelle une traduction semble manquante.
- Comment pluraliser avec Rails i18n ?
- Passez une option count et imbriquez les catégories de pluriel sous la clé. Rails sélectionne la catégorie selon les règles de pluralisation de la locale, si bien qu’une clé en polonais a besoin de one, few, many et other, contrairement à une clé en anglais qui ne nécessite que one et other.
- Qu’est-ce que le lazy lookup dans Rails ?
- Dans une vue, t('.title') avec un point en tête se résout par rapport au chemin du template — dans app/views/posts/index.html.erb, cela recherche posts.index.title. Cela garde les clés courtes et reflète la structure de vos vues, au prix d’une difficulté accrue à retrouver les clés par recherche textuelle.
- Comment gérer les traductions manquantes dans Rails ?
- Configurez des fallbacks pour qu’une clé manquante se replie sur la locale par défaut plutôt que d’afficher un span translation-missing. En développement et en test, levez plutôt une exception sur les traductions manquantes, afin que le manque échoue bruyamment là où quelqu’un le verra.
Keep reading
-
Règles de pluriel par langue : le guide CLDR complet
Un tableau de référence des catégories de pluriel CLDR pour 163 langues, plus pourquoi one ne veut pas dire 1 et comment chaque framework i18n attend que vous écriviez les règles.
-
Aide-mémoire des formats de placeholders : %s, %@, %{name}, {{name}}, {0}
Toutes les syntaxes de placeholder que vous rencontrerez, quel langage ou framework les utilise, et ce qui casse quand un traducteur en retape une à la main.
-
La localisation continue expliquée
Comment mener la traduction comme un processus continu en parallèle du développement, que faire des branches de fonctionnalités, et quand la localisation agile convient mieux.
-
Traduire des fichiers YAML (documentation)
Comment WebTranslateIt analyse les fichiers de locale Rails, y compris les règles de pluriel et le typage.
Translate your app without the spreadsheet round-trip
WebTranslateIt reads the file formats and placeholder syntax described on this page, validates them as translators work, and syncs the results straight back into your repository.