Migrazione¶
Se il tuo progetto usa già gettext, le domande che decidono se questa libreria sia adottabile sono poche e precise: invalida i cataloghi che hai, può convivere con il codice che non sei pronto a cambiare, e quanta parte dello spostamento deve avvenire tutta insieme. Le risposte, dalla più breve:
| Domanda | Risposta |
|---|---|
I file .po e .mo esistenti funzionano ancora? |
Sì. Stessi file, stessi strumenti. |
| Chiamate vecchie e nuove possono stare in un solo file? | Sì, e una sola mappatura dell'estrattore le copre entrambe. |
| Il msgid cambia? | Non partendo da .format(). Sì partendo dal %-format. |
| Tutto il progetto deve spostarsi in una volta? | No. Un solo punto di chiamata è una modifica valida. |
| E Jinja, i template Django, JavaScript? | Intoccati, stessi cataloghi. |
Il resto di questa pagina è il dettaglio dietro ciascuna di quelle risposte.
Da .format(): il msgid non cambia¶
È il caso in cui la migrazione non costa quasi nulla. Un messaggio
str.format e un messaggio t-string derivano la stessa chiave di catalogo,
perché la chiave è comunque il testo con dentro {name}:
# Before
_("Hello {name}").format(name=name)
# After — the msgid is still "Hello {name}"
tr(t"Hello {name}")
Quindi la traduzione esistente resta attaccata. Partendo da un catalogo che contiene
cambia la chiamata, riestrai e aggiorna:
$ 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
La voce che torna indietro differisce in due righe di metadati e in nient'altro — un commento marcatore che la identifica come messaggio t-string e un numero di riga sorgente:
Nessun flag fuzzy, nessuna ritraduzione, in nessuna lingua. Il messaggio
viene reso subito:
$ 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 segnalerà i cataloghi come non aggiornati
Quel commento marcatore e i numeri di riga spostati bastano perché
pybabel update --check dica che un catalogo va rigenerato, dato che
confronta l'intera voce e non solo la traduzione. Esegui il vero
pybabel update nello stesso commit della modifica al codice, e committa i
cataloghi insieme a essa — la stessa abitudine che il
controllo in CI già richiede.
Dal %-format: il msgid cambia, quindi le traduzioni diventano fuzzy¶
La sintassi printf vive dentro il messaggio, quindi sostituirla riscrive la
chiave del catalogo. Non c'è modo di evitarlo, ed è il costo onesto di
lasciarsi alle spalle %(name)s:
pybabel update riconosce nel nuovo messaggio uno stretto parente di quello
rimosso e porta avanti la vecchia traduzione, marcandola fuzzy:
#. gettext-tstrings
#: app.py:4
#, fuzzy, python-brace-format, python-format
msgid "Hello {name}"
msgstr "こんにちは %(name)s"
Tre cose da sapere su quello stato:
- Niente si rompe a runtime. Le voci fuzzy sono escluse dal
.mocompilato, quindi l'applicazione rende il messaggio sorgente finché una persona non conferma la coppia — lo stesso degrado che attraversa qualunque messaggio riformulato. pybabel compilele segnala una per una, perché il%(name)sportato avanti non è un segnaposto brace valido, ed esce con codice diverso da zero. Quell'elenco è la tua lista di lavoro, non un falso allarme; le voci che contiene hanno davvero bisogno di essere modificate.- Il vecchio flag
python-formatviaggia con loro e va cancellato insieme al flagfuzzy, altrimentimsgfmt --check-formatcontinuerà ad applicare le regole di printf a un messaggio in formato brace.
Per i segnaposto printf con nome la modifica è meccanica — %(name)s diventa
{name} e nient'altro si muove — quindi un catalogo grande è una passata
scriptata seguita da una revisione del traduttore, e non una ritraduzione. Il
%s posizionale non è meccanico: non ha nessun nome da portare avanti, e
sceglierne uno è il punto stesso della modifica.
Per questo l'ordine pratico è migrare i messaggi in %-format con
deliberazione — un modulo, una release, una lingua alla volta — invece che in
una sola passata che fa diventare rossi tutti i cataloghi insieme.
Chiamate vecchie e nuove convivono¶
L'estrattore che legge le t-string legge anche le normali chiamate gettext, quindi una sola mappatura copre un file a metà migrazione:
from gettext_tstrings import tr
from myapp.i18n import _
name = "Ada"
print(_("Save changes"))
print(tr(t"Hello {name}"))
Entrambi i messaggi finiscono nello stesso template, e solo quello t-string porta il commento marcatore che attiva i controlli aggiuntivi di questa libreria:
#: app.py:5
msgid "Save changes"
msgstr ""
#. gettext-tstrings
#: app.py:6
#, python-brace-format
msgid "Hello {name}"
msgstr ""
Riconosce _(), i quattro nomi gettext standard, gli alias tr() / ntr() e
le funzioni differite lazy_gettext() / lazy_pgettext(). Un helper tuo deve
essere nominato nella mappatura.
A runtime i due stili sono altrettanto indipendenti: gettext.translation()
restituisce un solo oggetto translations, e sia _ sia gli entry point di
questa libreria leggono da lì.
Che cosa non si sposta¶
- I linguaggi di template. Il
{% trans %}di Jinja2, i tag di template di Django e i loro estrattori Babel continuano a funzionare immutati e continuano ad alimentare gli stessi cataloghi PO. Le t-string sono sintassi Python; valgono per il sorgente Python. - I tuoi file di catalogo. Nessun cambio di formato, nessun file nuovo, nessun passo di conversione.
- La tua piattaforma di traduzione. L'interscambio in
.poè identico, e il flagpython-brace-formatche porta un messaggio t-string è lo stesso flag che porta un messaggio.format()— quindi la QA sui segnaposto continua a funzionare. - Il codice non Python. Un catalogo JavaScript o C nello stesso progetto non ne risente.
Una checklist di migrazione¶
- Aggiungi l'extra
babeldove girapybabel, e cambia la mappaturapythoninbabel.cfgcon il metodogettext_tstrings— una sola mappatura copre allora entrambi gli stili, e-kcontinua a funzionare per le chiamate ordinarie. - Converti per primi i punti di chiamata con
.format(). Riestrai, eseguipybabel updatee committa i cataloghi insieme al codice; non aspettarti nessuna voce fuzzy. - Converti i punti di chiamata in
%-format a lotti che riesci a far revisionare, riscrivendo i segnaposto portati avanti e cancellando i flagfuzzyepython-format. - Sistema ciò che la restrizione rifiuta: un'interpolazione deve essere un
nome semplice, quindi
t"Hello {user.name}"diventa prima una variabile locale. È una modifica al punto di chiamata, non al catalogo. - Attiva
strict = truenella mappatura dell'estrattore una volta finita la passata, così un messaggio che non si riesce a estrarre fa fallire la build invece di sparire dal template. - Aggiungi il controllo a runtime da In produzione:
rendi un messaggio per ogni lingua distribuita attraverso un
Translatorstrict.
I passi 2 e 3 sono commit ordinari. Niente in questo elenco richiede un giorno di fermo generale.