Guida¶
Questa pagina è il riferimento a runtime: tutto ciò che il codice applicativo fa con questa libreria una volta che i cataloghi esistono. Se non hai ancora visto il ciclo completo — marcare, estrarre, tradurre, compilare, eseguire — il tutorial lo percorre una volta in cinque minuti; la creazione e la validazione dei cataloghi sono coperte in Estrazione, e come un team tiene in moto il ciclo — cicli di aggiornamento, CI, piattaforme di traduzione — è In produzione.
Quale entry point dovrei usare?¶
Il pacchetto esporta diversi modi di tradurre un messaggio perché le applicazioni legano una lingua in diversi modi. Scegli in base a come il tuo programma decide in che lingua si trova:
| La tua situazione | Usa |
|---|---|
| Una lingua per l'intero processo — una CLI, un'app desktop, uno script | Translator, chiamato come _ |
| Una lingua per richiesta o per task asincrono — un'applicazione web | use_translations() attorno al lavoro, poi tr() |
| Un messaggio definito al momento dell'import — l'etichetta di un form, un enum, una costante | lazy_gettext() o lazy_pgettext() |
| Un conteggio decide la formulazione | ngettext() / npgettext(), in una qualunque delle forme sopra |
| Rendere un pattern senza coinvolgere nessun catalogo | compile_template() |
Tutto quel che segue sono quei cinque casi, in quest'ordine.
Legare un catalogo¶
La forma raccomandata rispecchia l'uso a classi di gettext: lega una volta un
normale oggetto di traduzione e usa il processore chiamabile come _.
import gettext
from gettext_tstrings import Translator
translations = gettext.translation("messages", localedir="locales", languages=["ja"])
_ = Translator(translations)
name = "Ada"
print(_(t"Hello {name}")) # こんにちは Ada
n = 3
print(_.ngettext(t"One file", t"{n} files", n)) # picks the right plural form for n
filename = "report.txt"
print(_.pgettext("button", t"Open {filename}")) # "button" disambiguates homonyms
Le funzioni a livello di modulo seguono i nomi della libreria standard e la sua convenzione di chiamata con soli argomenti posizionali:
from gettext_tstrings import gettext, ngettext, npgettext, pgettext
gettext(t"Hello {name}", translations=translations)
ngettext(t"One file", t"{n} files", n, translations=translations)
pgettext("button", t"Open {filename}", translations=translations)
npgettext("inbox", t"One message", t"{n} messages", n, translations=translations)
tr e ntr sono alias esatti di gettext e ngettext.
Lingua per richiesta¶
Un framework web sceglie una lingua per ogni richiesta. Lega le traduzioni della richiesta al contesto corrente e ogni chiamata a livello di modulo si risolve in quella lingua, in sicurezza anche tra richieste concorrenti:
from gettext_tstrings import tr, use_translations
def handle(request):
name = request.user.display_name
translations = load_translations(request.locale)
with use_translations(translations):
return render(tr(t"Hello {name}"))
set_translations(translations) lega senza un blocco with, per i framework
che gestiscono da soli il ciclo di vita della richiesta; get_translations()
legge il binding corrente. Un argomento esplicito translations= vince
sempre sul contesto, e un contesto non legato ripiega sulle funzioni gettext
installate globalmente dalla libreria standard. Esempi svolti per Flask e per
un middleware ASGI sono nella pagina
In produzione.
Traduzione differita¶
Una t-string cattura i suoi valori subito, il che è sbagliato per una stringa definita al momento dell'import — l'etichetta di un form, il valore di un enum, una costante di modulo — che deve essere resa in qualunque lingua sia attiva quando viene usata.
from gettext_tstrings import lazy_gettext, lazy_pgettext, use_translations
SAVE = lazy_gettext(t"Save changes") # defined once, at import
OPEN = lazy_pgettext("button", t"Open file")
with use_translations(japanese):
assert str(SAVE) == "変更を保存" # rendered here, in this language
Una LazyString si rende attraverso str(), format() e le f-string, e
risulta uguale al suo testo reso nei confronti.
Deliberatamente non hashabile
Il testo di una LazyString dipende dalla lingua attiva, quindi un hash
cambierebbe a ogni cambio di lingua e corromperebbe in silenzio qualunque
set o dict la contenga. Chiama prima str() se ti serve una chiave.
strict si decide dove il messaggio viene scritto, non dove viene reso:
Una stringa differita viene resa dovunque finisca per essere usata — dentro un
template, un form, una riga di log — e quel punto raramente sa se si tratta di
un'esecuzione di test o della produzione. Passare strict=True alla
definizione è ciò che permette di applicare la stessa scelta
rumorosa in CI, tollerante in produzione
anche a una stringa che non viene resa nel punto in cui è chiamata.
Le forme plurali dipendono da un conteggio a runtime, quindi rendile subito
con ngettext dove il conteggio è noto.
Più lingue insieme¶
Una sola richiesta ha spesso bisogno di più di una lingua: una pagina resa per chi legge che accoda anche una notifica a un account impostato su un'altra, o un digest che cita ogni partecipante nella propria. I binding si annidano, e uscire dal blocco interno ripristina quello esterno.
with use_translations(reader):
page = tr(t"Hello {name}")
with use_translations(recipient):
notice = tr(t"Hello {name}") # the recipient's language
footer = tr(t"Hello {name}") # the reader's again
Su una lista di destinatari sono le stringhe differite a fare il lavoro: il messaggio si scrive una volta sola, all'import, e viene reso una volta per lingua.
SUBJECT = lazy_gettext(t"Your order shipped")
for user in users:
with use_translations(load_translations(user.locale)):
send(user.email, str(SUBJECT))
Il binding è una ContextVar, non uno stack tenuto su un oggetto condiviso,
quindi richieste che si sovrappongono non possono prendere l'una la lingua
dell'altra — compreso il caso in cui escano dai loro blocchi nello stesso
ordine in cui vi sono entrate, che è l'intreccio sbagliato da uno stack a
pila. Caricare un catalogo per lingua costa poco: gettext.translation()
analizza ogni .mo una sola volta e restituisce copie che condividono il
catalogo già analizzato.
Se un thread di lavoro erediti il binding dipende dalla build
Un semplice threading.Thread, o ThreadPoolExecutor.submit, parte o da
una copia del contesto del chiamante o da uno vuoto, e quale dei due lo
decide sys.flags.thread_inherit_context — vero per impostazione
predefinita sulle build free-threaded, falso ovunque altrove. Lo stesso
codice rende quindi la lingua legata su 3.14t e il catalogo globale del
processo su 3.14. Passa il contesto invece di dipendere dal valore
predefinito:
asyncio.to_thread lo fa già per te.
Valori sensibili al locale¶
Questa libreria decide dove un valore compare in un messaggio tradotto. Non
localizza il valore in sé. {amount:,.2f} è una specifica di formato Python
dal comportamento fisso — una virgola ogni tre cifre e un punto prima dei
decimali — e produce gli stessi caratteri in qualunque lingua sia il messaggio:
Il tedesco scrive quel numero 1.234,50, il francese 1 234,50, e l'hindi
raggruppa 1234567 come 12,34,567 anziché 1,234,567. Numeri, valute,
date, orari e unità di misura appartengono a Babel. Formatta
prima il valore, poi colloca la stringa già pronta:
from babel.numbers import format_currency
total = format_currency(amount, "EUR", locale=locale)
tr(t"Your order comes to {total}")
In un messaggio con conteggio il numero svolge due compiti — seleziona la forma plurale e compare nel testo — e solo il secondo viene localizzato. Tieni il conteggio grezzo per la selezione e passa la stringa formattata per la visualizzazione:
from babel.numbers import format_decimal
shown = format_decimal(n, locale=locale)
_.ngettext(t"One file", t"{shown} files", n)
Formattare prima della chiamata è anche ciò che tiene una specifica di formato fuori dal catalogo: quello che un traduttore vede è un pezzo di testo già pronto, non un numero più le istruzioni per renderlo.
Che cosa succede quando un catalogo è sbagliato¶
Se i segnaposto di una traduzione non corrispondono alla sorgente — un campo mancante, sconosciuto o riformattato che è sfuggito alla validazione, da un MO modificato a mano, un catalogo di terze parti o una pipeline che salta il checker — il comportamento predefinito è rendere il messaggio sorgente invece di sollevare un'eccezione. Questo rispecchia il contratto di gettext stesso: un catalogo danneggiato non rompe mai l'applicazione.
Con Hello {name} tradotto come こんにちは {nombre}, il rendering riesce e
un avviso va al logger gettext_tstrings:
WARNING gettext_tstrings: invalid translation for msgid 'Hello {name}'; using
source text: translation does not match the source placeholders: {name} is
missing; {nombre} is not in the source message
L'avviso scatta una volta per messaggio e pattern, non una volta per rendering, così una voce di catalogo danneggiata non inonda un log.
Scegli di fallire rumorosamente per i test e la CI:
strict = Translator(translations, strict=True)
tr(t"Hello {name}", translations=translations, strict=True)
La stessa ricerca allora solleva un'eccezione, portando la stessa frase senza la metà "using source text":
>>> strict(t"Hello {name}")
Traceback (most recent call last):
...
gettext_tstrings.errors.InvalidTranslationError: translation does not match the
source placeholders: {name} is missing; {nombre} is not in the source message
Questi messaggi sono scritti per chi può intervenire, e per un problema di
catalogo è più spesso un traduttore che un programmatore — perciò, dove un
segnaposto sembra presente ma non lo è, il messaggio spiega il perché invece
di ripetere che manca. Graffe a larghezza intera, un {{name}} doppio, uno
spazio unificatore invisibile, una lettera cirillica tra lettere latine: ogni
caso ha la sua formulazione, elencata con esempi in
Per i traduttori. Quella pagina è
scritta per essere consegnata a chi modifica il .po.
Rendere un pattern senza un catalogo¶
compile_template espone lo stesso meccanismo un livello più in basso:
trasforma una t-string nel suo msgid più un insieme di valori legati, e rende
qualunque pattern tu gli passi.
from gettext_tstrings import compile_template
name = "Ada"
compiled = compile_template(t"Hello {name}")
compiled.msgid # "Hello {name}"
compiled.placeholders # ("name",)
compiled.render("こんにちは {name}") # "こんにちは Ada"
render valida con le stesse regole e solleva sempre su una mancata
corrispondenza. Qui non esiste una modalità permissiva: la permissività
esiste perché una ricerca in un catalogo possa degradare al testo sorgente,
e un pattern che hai passato tu stesso non ha nulla da cui degradare.
Sicurezza e ambito¶
Questo è valido:
Questi sono rifiutati di proposito:
Calcola prima un valore significativo:
La restrizione produce chiavi di catalogo stabili, dà ai traduttori nomi utili e impedisce a una stringa tradotta di diventare un linguaggio di espressioni.
La garanzia è limitata a struttura e formattazione: una traduzione non viene mai valutata, e non può mai aggiungere accesso agli attributi, chiamate, conversioni o specifiche di formato. Due cose restano responsabilità del chiamante, esattamente come con il gettext della stdlib — l'escaping dell'output reso per la sua destinazione (HTML, shell, terminale), e l'integrità del catalogo, dato che un catalogo ostile può ripetere un segnaposto per amplificare la dimensione dell'output, cosa inerente a qualunque i18n basata su segnaposto.