Rustango docs
← Guides

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.

i18n dans Rustango : un Translator détient des catalogues par locale ; gettext recherche une clé avec repli (locale → langue de base → défaut → la clé elle-même), substitue les placeholders {name}, et ngettext choisit entre singulier et pluriel

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 templates tera_tags) — toujours compilé. Le middleware de locale/fuseau horaire par requête se trouve dans rustango::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 par i18n_db_overrides_sqlite_live.rs.

Table des matières


Les deux moitiés de l'i18n

PréoccupationCe que ça fait
Résoudre la localei18n::middleware::LocaleMiddlewarechoisit une locale par requête (cookie → Accept-Language → défaut) et expose l'extracteur ActiveLocale
Traduire les chaînesi18n::Translator (ce guide)recherche une clé de message dans le catalogue de la locale active
Afficher les dates dans le fuseau horaire de l'utilisateuri18n::timezonele 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.