Internationalisierung (i18n)
Internationalisierung bedeutet, Ihre App in der Sprache des Nutzers auszuliefern.
Rustango teilt sie in zwei Hälften: das Auflösen, welche Locale eine
Anfrage möchte (Cookie → Accept-Language → Standard — behandelt in
Middleware), und das Übersetzen von Strings in diese Locale,
worum es in diesem Leitfaden geht. Der Translator lädt Nachrichtenkataloge pro
Locale, ersetzt {placeholders}, behandelt Pluralformen und fällt elegant zurück
— Djangos gettext / {% trans %}, in Rust.
Ein Begriff hier neu? Locale, Katalog, Accept-Language, Pluralisierung, RTL — siehe das Glossar.
Quelle:
rustango::i18n(Translator,Locale,negotiate_language,plural_category,is_rtl_language,language_native_nameund dietera_tags-Template-Bindings) — immer kompiliert. Die Middleware für Locale/Zeitzone pro Anfrage liegt inrustango::i18n::middleware/::timezone(siehe Middleware).Lauffähige Version: Jeder Ausschnitt ist aus
i18n_doc.rskopiert (cargo test -p rustango --test i18n_doc); DB-gestützte Übersetzungs-Overrides werden im Realbetrieb voni18n_db_overrides_sqlite_live.rserprobt.
Inhaltsverzeichnis
- Die zwei Hälften von i18n
- Schritt 1 — Einen Translator bauen
- Schritt 2 — Strings übersetzen (gettext)
- Platzhalter
- Pluralisierung
- Fallback-Reihenfolge
- Die Sprache aushandeln
- Rechts-nach-links-Sprachen
- In Templates übersetzen (Tera)
- Kataloge laden + Laufzeit-Overrides
- Siehe auch
Die zwei Hälften von i18n
| Anliegen | Wo | Was es tut |
|---|---|---|
| Die Locale auflösen | i18n::middleware::LocaleMiddleware | wählt eine Locale pro Anfrage (Cookie → Accept-Language → Standard) und stellt den ActiveLocale-Extractor bereit |
| Strings übersetzen | i18n::Translator (dieser Leitfaden) | schlägt einen Nachrichtenschlüssel im Katalog der aktiven Locale nach |
| Daten in der TZ des Nutzers rendern | i18n::timezone | der {{ ts | localtime }}-Filter |
Sie verdrahten die Middleware einmal und übersetzen dann mit der von ihr aufgelösten Locale.
Schritt 1 — Einen Translator bauen
Ein Translator hält einen Katalog (eine key → message-Map) pro Locale.
Bauen Sie ihn mit einer Standard-Locale und fügen Sie dann Kataloge hinzu:
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);
Halten Sie den Translator in Ihrem App-State (er ist günstig zum Clone-Teilen)
und schlagen Sie Strings mit der von der Middleware aufgelösten
ActiveLocale nach.
Schritt 2 — Strings übersetzen (gettext)
gettext(locale, key) gibt die Nachricht für diese Locale zurück — und degradiert
sicher, wenn etwas fehlt:
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
Ein fehlender Schlüssel gibt den Schlüssel selbst zurück, sodass ein nicht übersetzter String sichtbar auftaucht, statt die Seite zum Absturz zu bringen.
Platzhalter
Nachrichten können {name}-Platzhalter tragen; translate ersetzt sie aus
Schlüssel/Wert-Paaren:
// catalog: "welcome" = "Welcome, {name}!"
translator.translate("en", "welcome", &[("name", "Ada")]); // "Welcome, Ada!"
gettext ist die Kurzform ohne Platzhalter; translate (und gettext_fmt)
nehmen Parameter.
Pluralisierung
Pluralformen unterscheiden sich nach Sprache und Anzahl. ngettext ist die
Zwei-Formen-Kurzform: Sie wählt den Singular-Schlüssel für die CLDR-Kategorie
one und ansonsten den Plural-Schlüssel und bindet {count} automatisch:
// 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"
Verwenden Sie ngettext_fmt, um zusätzliche Platzhalter neben {count} zu
übergeben.
Sprachen mit mehr als zwei Formen
Polnisch, Ukrainisch, Russisch, Arabisch und andere haben few / many-Formen,
die ein Singular/Plural-Paar nicht ausdrücken kann. plural_category(locale, n)
gibt die CLDR-Kategorie zurück (one / few / many / other), und
translate_plural wählt die passende Form aus einem Katalog pro Kategorie —
ein Eintrag pro Schlüssel, der alle seine Formen hält:
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)
Eine fehlende Form fällt auf other zurück, dann auf den skalaren Lookup (und
schließlich den Schlüssel), sodass eine nicht übersetzte Sprache dennoch rendert.
Ostasiatische Sprachen (zh, ja, …) haben eine einzige other-Form.
Fallback-Reihenfolge
Lookups laufen eine Kette entlang, sodass eine partielle Übersetzung nie eine
Lücke lässt: die angefragte Locale → ihre Basissprache → die Fallback-Kette →
die Standard-Locale → der Schlüssel selbst. So löst eine
frankokanadische Anfrage gegen den fr-Katalog auf:
// only "fr" is registered, not "fr-CA"
translator.gettext("fr-CA", "greeting"); // "Bonjour" — base-language fallback
Setzen Sie zusätzliche Fallbacks mit
Translator::new(default).with_fallback_chain(&["en"]).
Die Sprache aushandeln
Wenn Sie die Locale selbst auflösen (außerhalb der Middleware), parst
negotiate_language einen Browser-Accept-Language-Header und wählt die beste
Übereinstimmung aus den von Ihnen unterstützten Locales:
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
Genau das nutzt LocaleMiddleware unter der Haube.
Rechts-nach-links-Sprachen
is_rtl_language (und Locale::direction()) sagen Ihnen, ob Sie dir="rtl" auf
der Seite setzen sollten — für Arabisch, Hebräisch, Persisch usw.:
use rustango::i18n::is_rtl_language;
is_rtl_language("ar"); // true
is_rtl_language("en"); // false
In einem Template: <html dir="{{ direction }}">, gespeist aus der
ActiveLocale. Siehe die RTL-Notiz in Middleware.
In Templates übersetzen (Tera)
Alles Obige ist die Rust-API; in Tera-Templates — HTML-Views und die Admin-UI —
wird derselbe Translator als Filter/Funktionen bereitgestellt. Registrieren Sie
sie einmal gegen den Translator, und die aktive Locale (die von der Middleware
gesetzte Kontextvariable LANG) treibt jeden Lookup:
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) }}">
Der Schlüssel ist der englische Ausgangs-String (gettext-Stil), sodass ein nicht übersetzter String auf Englisch rendert statt leer — Sie umschließen einen UI-String zuerst und übersetzen ihn später, ohne eine Schlüssel-Registry zu pflegen.
Genau so lokalisiert rustango-cms seinen Admin: Jeder Chrome-String läuft
durch translate / translate_plural, Kataloge werden pro Locale unter
src/admin/locales/ ausgeliefert, die aktive Locale wird über die Kette Cookie →
Nutzerpräferenz → Accept-Language → Tenant-Standard aufgelöst, und ein
Seitenleisten-Umschalter plus eine Nutzer-Einstellung Preferences → Language
lassen Redakteure wählen. Betreiber-Änderungen über die DB-Override-Schicht (unten)
wirken ohne Redeploy.
Kataloge laden + Laufzeit-Overrides
In der Produktion bauen Sie selten Maps von Hand. Zwei Loader:
Translator::from_directory(dir, default)— lädt beim Start eine Katalogdatei pro Locale aus einem Verzeichnis.Translator::from_settings(&settings.i18n)— verdrahtet ihn aus Ihrer Konfiguration.
Und Betreiber können Übersetzungen zur Laufzeit ohne Redeploy bearbeiten:
set_override(locale, key, value) (oder load_overrides(rows) aus einer Tabelle)
legen sich über die Datei-Kataloge und gewinnen für diesen Schlüssel. Dieser
DB-Override-Fluss treibt den Admin-Übersetzungseditor und wird in
i18n_db_overrides_sqlite_live.rs im Realbetrieb erprobt.
Siehe auch
- Middleware — das Auflösen der Locale pro Anfrage
(
LocaleMiddleware,ActiveLocale) und der Zeitzonenfilter{{ ts | localtime }}. - HTML-Views · Der Admin — wo übersetzte Strings und der Übersetzungseditor auftauchen.
- Glossar — Locale, Katalog, RTL und Verwandte in klarer Sprache.
