Migration¶
Wenn dein Projekt schon gettext verwendet, sind die Fragen, die über die Einführbarkeit dieser Bibliothek entscheiden, eng umrissen: Entwertet sie die Kataloge, die du hast? Kann sie neben dem Code bestehen, den du noch nicht ändern willst? Und wie viel von der Umstellung muss auf einmal geschehen? Die Antworten, die kürzeste zuerst:
| Frage | Antwort |
|---|---|
Funktionieren vorhandene .po- und .mo-Dateien weiter? |
Ja. Dieselben Dateien, dieselben Werkzeuge. |
| Dürfen alte und neue Aufrufe in einer Datei stehen? | Ja, und ein Extraktor-Mapping deckt beide ab. |
| Ändert sich der msgid? | Aus .format() nicht. Aus %-Format schon. |
| Muss das ganze Projekt auf einmal umziehen? | Nein. Eine einzige Aufrufstelle ist eine gültige Änderung. |
| Und Jinja, Django-Templates, JavaScript? | Unberührt, dieselben Kataloge. |
Der Rest dieser Seite ist das Detail hinter jeder dieser Antworten.
Aus .format(): der msgid ändert sich nicht¶
Das ist der Fall, in dem die Migration fast nichts kostet. Eine
str.format-Nachricht und eine t-string-Nachricht leiten denselben
Katalogschlüssel ab, denn der Schlüssel ist so oder so der Text mit dem darin
verbliebenen {name}:
# Before
_("Hello {name}").format(name=name)
# After — the msgid is still "Hello {name}"
tr(t"Hello {name}")
Die vorhandene Übersetzung bleibt also daran hängen. Ausgehend von einem Katalog mit
änderst du den Aufruf, extrahierst neu und aktualisierst:
$ pybabel extract -F babel.cfg -o locales/messages.pot .
extracting messages from app.py (encoding="utf-8")
writing PO template file to locales/messages.pot
$ pybabel update -i locales/messages.pot -d locales
updating catalog locales/ja/LC_MESSAGES/messages.po based on locales/messages.pot
Der Eintrag, der zurückkommt, unterscheidet sich in zwei Zeilen Metadaten und sonst in nichts — ein Markierungskommentar, der ihn als t-string-Nachricht ausweist, und eine Quellzeilennummer:
Kein fuzzy-Flag, keine Neuübersetzung, in keiner Sprache. Die Nachricht
rendert sofort:
$ pybabel compile -d locales
compiling catalog locales/ja/LC_MESSAGES/messages.po to locales/ja/LC_MESSAGES/messages.mo
$ python app.py
こんにちは Ada
update --check wird die Kataloge als veraltet melden
Dieser Markierungskommentar und die verschobenen Zeilennummern genügen
schon, damit pybabel update --check sagt, ein Katalog müsse neu erzeugt
werden, denn es vergleicht den ganzen Eintrag und nicht nur die
Übersetzung. Führe das echte pybabel update im selben Commit wie die
Codeänderung aus und committe die Kataloge mit — dieselbe Gewohnheit, um
die die CI-Schranke ohnehin bittet.
Aus %-Format: der msgid ändert sich, also werden Übersetzungen fuzzy¶
Printf-Syntax steht innerhalb der Nachricht, sie zu ersetzen schreibt also
den Katalogschlüssel um. Daran führt kein Weg vorbei, und das ist der ehrliche
Preis dafür, %(name)s hinter sich zu lassen:
pybabel update erkennt die neue Nachricht als nahe Verwandte der entfernten
und trägt die alte Übersetzung hinüber, markiert als fuzzy:
#. gettext-tstrings
#: app.py:4
#, fuzzy, python-brace-format, python-format
msgid "Hello {name}"
msgstr "こんにちは %(name)s"
Drei Dinge, die man über diesen Zustand wissen sollte:
- Zur Laufzeit geht nichts kaputt. fuzzy-Einträge sind von der
kompilierten
.moausgeschlossen, die Anwendung rendert also die Quellnachricht, bis ein Mensch das Paar bestätigt — dieselbe Degradation, die jede umformulierte Nachricht durchläuft. - Die CI bleibt grün, solange sie fuzzy sind. Der Platzhalter-Checker
überspringt fuzzy-Einträge, genau wie
msgfmt --check-formates tut, denn ein Eintrag, der die Laufzeit gar nicht erreichen kann, sollte keinen Build scheitern lassen. Sobald eine übersetzende Person das Flag entfernt, wird der Eintrag geprüft wie jeder andere — ein in einer bestätigten Übersetzung stehengebliebenes%(name)swird also genau dann gefunden, wenn es sonst anfinge zu rendern. - Das alte
python-format-Flag reist mit und sollte zusammen mit demfuzzy-Flag gelöscht werden, sonst wendetmsgfmt --check-formatweiterhin Printf-Regeln auf eine brace-format-Nachricht an.
Bei benannten Printf-Platzhaltern ist die Bearbeitung mechanisch — aus
%(name)s wird {name}, und sonst bewegt sich nichts —, ein großer Katalog
ist also ein skriptgesteuerter Durchlauf mit anschließendem Review durch eine
übersetzende Person und keine Neuübersetzung. Positionsbezogenes %s ist
nicht mechanisch: Es hat keinen Namen, den man übernehmen könnte, und genau
diesen zu wählen ist der Zweck der Änderung.
Die Migration kann deshalb in dem Tempo laufen, das das Review zulässt: Ein noch nicht umgestellter fuzzy-Eintrag ist ein sichtbares Stück Arbeit im Katalog und kein kaputter Build.
Alte und neue Aufrufe nebeneinander¶
Der Extraktor, der t-strings liest, liest auch gewöhnliche gettext-Aufrufe; ein Mapping deckt also eine Datei mitten in der Migration ab:
from gettext_tstrings import tr
from myapp.i18n import _
name = "Ada"
print(_("Save changes"))
print(tr(t"Hello {name}"))
Beide Nachrichten landen in derselben Vorlage, und nur die t-string-Nachricht trägt den Markierungskommentar, der die zusätzliche Prüfung dieser Bibliothek einschaltet:
#: app.py:5
msgid "Save changes"
msgstr ""
#. gettext-tstrings
#: app.py:6
#, python-brace-format
msgid "Hello {name}"
msgstr ""
Erkannt werden _(), die vier Standard-gettext-Namen, die Aliase tr() /
ntr() sowie die verzögerten lazy_gettext() / lazy_pgettext(). Ein
eigener Helper muss
im Mapping benannt werden.
Zur Laufzeit sind die beiden Stile gleichermaßen unabhängig:
gettext.translation() liefert ein Übersetzungsobjekt, und sowohl _ als
auch die Einstiegspunkte dieser Bibliothek lesen daraus.
Was sich nicht bewegt¶
- Template-Sprachen. Jinja2s
{% trans %}, die Template-Tags von Django und ihre Babel-Extraktoren arbeiten unverändert weiter und speisen dieselben PO-Kataloge. t-strings sind Python-Syntax; sie gelten für Python-Quelltext. - Deine Katalogdateien. Kein Formatwechsel, keine neue Datei, kein Konvertierungsschritt.
- Deine Übersetzungsplattform. Der
.po-Austausch ist identisch, und daspython-brace-format-Flag, das eine t-string-Nachricht trägt, ist dasselbe Flag, das eine.format()-Nachricht trägt — die Platzhalter-QA funktioniert also weiter. - Nicht-Python-Code. Ein JavaScript- oder C-Katalog im selben Projekt bleibt unberührt.
Eine Migrations-Checkliste¶
- Füge das
babel-Extra dort hinzu, wopybabelläuft, und stelle daspython-Mapping inbabel.cfgauf die Methodegettext_tstringsum — ein Mapping deckt dann beide Stile ab, und-kfunktioniert für die gewöhnlichen Aufrufe weiter. - Stelle zuerst die
.format()-Aufrufstellen um. Neu extrahieren,pybabel updatelaufen lassen und die Kataloge mit dem Code committen; es sind keine fuzzy-Einträge zu erwarten. - Stelle die
%-Format-Aufrufstellen in Portionen um, die du reviewen lassen kannst, schreibe dabei die übernommenen Platzhalter um und entferne die Flagsfuzzyundpython-format. - Repariere, was die Einschränkung ablehnt: Eine Interpolation muss ein
einfacher Name sein, aus
t"Hello {user.name}"wird also zuerst eine lokale Variable. Das ist eine Änderung an der Aufrufstelle, nicht am Katalog. - Schalte
strict = trueim Extraktor-Mapping ein, sobald der Durchlauf erledigt ist, damit eine nicht extrahierbare Nachricht den Build scheitern lässt, statt aus der Vorlage zu verschwinden. - Ergänze die Laufzeitprüfung aus
Im Produktivbetrieb: eine Nachricht pro
ausgelieferter Sprache durch einen strikten
Translatorrendern.
Die Schritte 2 und 3 sind gewöhnliche Commits. Nichts auf dieser Liste braucht einen Stichtag.