Internationalisation (i18n)
L'internationalisation consiste à servir votre application dans la langue de
l'utilisateur. Rustango la divise en deux moitiés : résoudre la locale
voulue par une requête (cookie →
Accept-Language → valeur par défaut — traité dans Middleware), et
traduire les chaînes vers cette locale, ce qui est l'objet de ce guide. Le Translator
charge des catalogues de messages par locale, substitue les {placeholders},
gère les pluriels, et se replie avec élégance en cas d'absence — le gettext / {% trans %}
de Django, en Rust.
Nouveau ici ? locale, catalogue, Accept-Language, pluralisation, RTL — voir le glossaire.
Source :
rustango::i18n(Translator,Locale,negotiate_language,plural_category,is_rtl_language,language_native_name, et les liaisons de templatestera_tags) — toujours compilé. Le middleware de locale/fuseau horaire par requête se trouve dansrustango::i18n::middleware/::timezone(voir Middleware).Version exécutable : chaque extrait est copié depuis
i18n_doc.rs(cargo test -p rustango --test i18n_doc) ; les surcharges de traduction adossées à la base de données sont testées en conditions réelles pari18n_db_overrides_sqlite_live.rs.
Table des matières
- Les deux moitiés de l'i18n
- Étape 1 — Construire un Translator
- Étape 2 — Traduire des chaînes (gettext)
- Placeholders
- Pluralisation
- Ordre de repli
- Négocier la langue
- Langues de droite à gauche
- Traduire dans les templates (Tera)
- Charger les catalogues + surcharges à l'exécution
- Voir aussi
Les deux moitiés de l'i18n
| Préoccupation | Où | Ce que ça fait |
|---|---|---|
| Résoudre la locale | i18n::middleware::LocaleMiddleware | choisit une locale par requête (cookie → Accept-Language → défaut) et expose l'extracteur ActiveLocale |
| Traduire les chaînes | i18n::Translator (ce guide) | recherche une clé de message dans le catalogue de la locale active |
| Afficher les dates dans le fuseau horaire de l'utilisateur | i18n::timezone | le filtre {{ ts | localtime }} |
Vous branchez le middleware une seule fois, puis vous traduisez en utilisant la locale qu'il a résolue.
Étape 1 — Construire un Translator
Un Translator détient un catalogue (une table clé → message) par
locale. Construisez-le avec une locale par défaut, puis ajoutez des
catalogues :
use rustango::i18n::{Locale, Translator};
use std::collections::HashMap;
let mut en = HashMap::new();
en.insert("greeting".to_owned(), "Hello".to_owned());
en.insert("welcome".to_owned(), "Welcome, {name}!".to_owned());
let mut fr = HashMap::new();
fr.insert("greeting".to_owned(), "Bonjour".to_owned());
let translator = Translator::new(Locale::new("en")) // default locale
.add_locale(Locale::new("en"), en)
.add_locale(Locale::new("fr"), fr);
Conservez le Translator dans l'état de votre application (il est bon marché
à cloner/partager) et recherchez les chaînes avec l'
ActiveLocale résolue par le
middleware.
Étape 2 — Traduire des chaînes (gettext)
gettext(locale, key) renvoie le message pour cette locale — et se dégrade
sans risque quand quelque chose manque :
translator.gettext("en", "greeting"); // "Hello"
translator.gettext("fr", "greeting"); // "Bonjour"
translator.gettext("en", "missing.key"); // "missing.key" — returns the key, never panics
translator.gettext("de", "greeting"); // "Hello" — unknown locale → default
Une clé manquante renvoie la clé elle-même, si bien qu'une chaîne non traduite apparaît visiblement plutôt que de faire planter la page.
Placeholders
Les messages peuvent contenir des placeholders {name} ; translate les
substitue à partir de paires clé/valeur :
// catalog: "welcome" = "Welcome, {name}!"
translator.translate("en", "welcome", &[("name", "Ada")]); // "Welcome, Ada!"
gettext est le raccourci sans placeholder ; translate (et gettext_fmt)
prennent des paramètres.
Pluralisation
Les pluriels diffèrent selon la langue et le nombre. ngettext est le
raccourci à deux formes : il choisit la clé du singulier pour la catégorie
CLDR one et celle du pluriel sinon, en liant automatiquement {count} :
// catalog: "cart.one" = "1 item", "cart.other" = "{count} items"
translator.ngettext("en", "cart.one", "cart.other", 1); // "1 item"
translator.ngettext("en", "cart.one", "cart.other", 5); // "5 items"
Utilisez ngettext_fmt pour passer des placeholders supplémentaires en plus
de {count}.
Langues à plus de deux formes
Le polonais, l'ukrainien, le russe, l'arabe et d'autres ont des formes few /
many qu'une paire singulier/pluriel ne peut pas exprimer. plural_category(locale, n)
renvoie la catégorie CLDR (one / few / many / other), et
translate_plural choisit la forme correspondante dans un catalogue par
catégorie — une entrée par clé contenant toutes ses formes :
use rustango::i18n::plural_category;
plural_category("en", 1); // "one"
plural_category("fr", 0); // "one" — French treats 0 as singular
plural_category("pl", 2); // "few" — Polish 2–4
plural_category("pl", 5); // "many"
// pl plural catalog: "deleted_pages" → { one, few, many }
let n = 5;
translator.translate_plural("pl", "deleted_pages", n, &[("count", &n.to_string())]);
// → "Usunięto 5 stron." (the `many` form)
Une forme manquante retombe sur other, puis sur la recherche scalaire (et
enfin sur la clé), si bien qu'une langue non traduite s'affiche encore. Les
langues d'Asie de l'Est (zh, ja, …) n'ont qu'une seule forme other.
Ordre de repli
Les recherches parcourent une chaîne pour qu'une traduction partielle ne
laisse jamais un vide : la locale demandée → sa langue de base → la chaîne
de repli → la locale par défaut → la clé elle-même. Ainsi, une requête en
français canadien se résout dans le catalogue fr :
// only "fr" is registered, not "fr-CA"
translator.gettext("fr-CA", "greeting"); // "Bonjour" — base-language fallback
Définissez des repli supplémentaires avec
Translator::new(default).with_fallback_chain(&["en"]).
Négocier la langue
Si vous résolvez la locale vous-même (en dehors du middleware),
negotiate_language analyse un en-tête Accept-Language de navigateur et
choisit la meilleure correspondance parmi les locales que vous prenez en
charge :
use rustango::i18n::negotiate_language;
negotiate_language("fr-FR,fr;q=0.9,en;q=0.8", &["en", "fr"]); // Some("fr")
negotiate_language("de,ja;q=0.5", &["en", "fr"]); // None — fall back to default
C'est exactement ce que LocaleMiddleware utilise en interne.
Langues de droite à gauche
is_rtl_language (et Locale::direction()) vous indiquent s'il faut définir
dir="rtl" sur la page — pour l'arabe, l'hébreu, le persan, etc. :
use rustango::i18n::is_rtl_language;
is_rtl_language("ar"); // true
is_rtl_language("en"); // false
Dans un template : <html dir="{{ direction }}">, alimenté depuis
ActiveLocale. Voir la
note RTL dans Middleware.
Traduire dans les templates (Tera)
Tout ce qui précède est l'API Rust ; dans les templates Tera — vues HTML, et
l'interface d'administration — le même Translator est exposé sous forme de
filtres/fonctions. Enregistrez-les une seule fois auprès du translator, et la
locale active (la variable de contexte LANG que le middleware définit)
pilote chaque recherche :
rustango::i18n::tera_tags::register(&mut tera, translator.clone());
{{ "Save" | translate(locale=LANG) }}
<button title="{{ "Delete" | translate(locale=LANG) }}">…</button>
{# count-aware: pass the count both as the selector `n` and as a {count} arg #}
{{ translate_plural(key="deleted_pages", n=num, locale=LANG, count=num) }}
<html lang="{{ LANG }}" dir="{{ get_text_direction(locale=LANG) }}">
La clé est la chaîne source en anglais (à la manière de gettext), si bien qu'une chaîne non traduite s'affiche en anglais plutôt qu'en blanc — vous enveloppez d'abord une chaîne d'UI et la traduisez plus tard, sans registre de clés à maintenir.
C'est exactement ainsi que rustango-cms localise son administration :
chaque chaîne de l'habillage passe par translate / translate_plural, les
catalogues sont livrés par locale sous src/admin/locales/, la locale active
est résolue par la chaîne cookie → préférence par utilisateur →
Accept-Language → défaut par tenant, et un sélecteur dans la barre latérale
ainsi qu'un réglage Préférences → Langue par utilisateur permettent aux
éditeurs de choisir. Les modifications d'un opérateur via la couche de
surcharge en base de données (ci-dessous) prennent effet sans redéploiement.
Charger les catalogues + surcharges à l'exécution
Vous construisez rarement des tables à la main en production. Deux chargeurs :
Translator::from_directory(dir, default)— charge un fichier de catalogue par locale depuis un répertoire au démarrage.Translator::from_settings(&settings.i18n)— le branche depuis votre configuration.
Et les opérateurs peuvent modifier les traductions à l'exécution sans
redéploiement : set_override(locale, key, value) (ou load_overrides(rows)
depuis une table) se superposent aux catalogues de fichiers et l'emportent
pour cette clé. Ce flux de surcharge en base de données alimente l'éditeur de
traduction de l'administration et est testé en conditions réelles dans
i18n_db_overrides_sqlite_live.rs.
Voir aussi
- Middleware — résoudre la locale par requête (
LocaleMiddleware,ActiveLocale) et le filtre de fuseau horaire{{ ts | localtime }}. - Vues HTML · L'administration — où les chaînes traduites et l'éditeur de traduction apparaissent.
- Glossaire — locale, catalogue, RTL et compagnie en langage clair.
