Kézikönyv¶
Ez az oldal a futásidejű referencia: mindaz, amit az alkalmazáskódod csinál ezzel a könyvtárral, ha már vannak katalógusok. Ha még nem láttad a teljes ciklust — megjelölés, kinyerés, fordítás, bináris fordítás, futtatás —, az oktatóanyag öt perc alatt végigjárja egyszer; a katalógusok létrehozását és ellenőrzését a Kinyerés tárgyalja, azt pedig, hogyan tartja egy csapat mozgásban a ciklust — frissítési körök, CI, fordítási platformok —, az Éles üzemben.
Melyik belépési pontot használjam?¶
A csomag több módot is exportál egy üzenet lefordítására, mert az alkalmazások többféleképpen kötnek nyelvet. Aszerint válassz, ahogy a programod eldönti, milyen nyelven van éppen:
| A helyzeted | Ezt használd |
|---|---|
| Egy nyelv az egész folyamatra — CLI, asztali alkalmazás, szkript | Translator, _ néven hívva |
| Kérésenként vagy aszinkron feladatonként egy nyelv — webalkalmazás | use_translations() a munka köré, majd tr() |
| Importáláskor definiált üzenet — űrlapfelirat, enum, konstans | lazy_gettext() vagy lazy_pgettext() |
| A megfogalmazást egy darabszám dönti el | ngettext() / npgettext(), a fenti alakok bármelyikében |
| Minta renderelése katalógus közreműködése nélkül | compile_template() |
Minden, ami alább jön, ez az öt, ebben a sorrendben.
Katalógus kötése¶
Az ajánlott felépítés a gettext osztályalapú használatát tükrözi: köss be
egyszer egy szabványos fordításobjektumot, és használd a hívható feldolgozót
_ néven.
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
A modulszintű függvények a standard könyvtár neveit és pozicionális-only hívási konvencióját követik:
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)
A tr és az ntr a gettext, illetve az ngettext pontos aliasa.
Kérésenkénti nyelv¶
Egy webes keretrendszer kérésenként választ nyelvet. Köss be a kérés fordításait az aktuális kontextusba, és minden modulszintű hívás arra a nyelvre oldódik fel, biztonságosan a párhuzamos kérések között is:
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}"))
A set_translations(translations) with blokk nélkül köt be, azoknak a
keretrendszereknek, amelyek maguk kezelik a kérés életciklusát; a
get_translations() az aktuális kötést olvassa ki. Egy explicit
translations= argumentum mindig felülírja a kontextust, a kötetlen kontextus
pedig a standard könyvtár globálisan telepített gettext-függvényeire esik
vissza. Flaskhoz és ASGI-köztesréteghez kidolgozott példák az
Éles üzemben oldalon vannak.
Késleltetett fordítás¶
A t-string mohón kapja el az értékeit, ami rossz egy importáláskor definiált szöveg — űrlapfelirat, enum-érték, modulkonstans — esetében, amelynek abban a nyelvben kell megjelennie, amely a használatakor aktív.
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
A LazyString a str(), a format() és az f-stringek révén jelenik meg, és
egyenlőnek bizonyul a renderelt szövegével.
Szándékosan nem hashelhető
Egy LazyString szövege az aktív nyelvtől függ, így a hasítóértéke
nyelvváltáskor megváltozna, és csendben megrongálná az őt tartalmazó
halmazokat és szótárakat. Hívd meg előbb a str() függvényt, ha kulcsra
van szükséged.
A strict ott dől el, ahol az üzenetet megírják, nem ott, ahol megjelenik:
Egy késleltetett szöveg ott jelenik meg, ahol végül felhasználják — egy
sablonban, egy űrlapon, egy naplósorban —, és az a hely ritkán tudja, hogy
tesztfutásról vagy éles üzemről van-e szó. A strict=True átadása a
definíciónál teszi lehetővé, hogy ugyanaz a
CI-ben hangos, éles üzemben elnéző
döntés érvényesüljön egy olyan szövegre is, amely nem a hívási helyén jelenik
meg.
A többesszám-alakok futásidejű darabszámtól függnek, ezért azokat mohón
rendereld az ngettext segítségével, ott, ahol a darabszám ismert.
Több nyelv egyszerre¶
Egy kérésnek gyakran több nyelvre is szüksége van: egy oldal, amely az olvasónak renderelődik, miközben értesítést tesz sorba egy másik nyelvre állított fiókhoz, vagy egy összefoglaló, amely minden résztvevőt a saját nyelvén idéz. A kötések egymásba ágyazódnak, és a belső blokkból kilépve visszaáll a külső.
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
Címzettek listáján a késleltetett szövegek végzik a munkát: az üzenetet egyszer írod meg, importáláskor, és nyelvenként egyszer renderelődik.
SUBJECT = lazy_gettext(t"Your order shipped")
for user in users:
with use_translations(load_translations(user.locale)):
send(user.email, str(SUBJECT))
A kötés ContextVar, nem közös objektumon tartott verem, így az egymást átfedő
kérések nem vehetik át egymás nyelvét — még abban az esetben sem, amikor abban
a sorrendben lépnek ki a blokkjaikból, ahogy beléptek; épp ezt az
átlapolódást rontja el a verem. Nyelvenként betölteni egy katalógust olcsó: a
gettext.translation() minden .mo fájlt egyszer olvas be, és olyan
másolatokat ad ki, amelyek osztoznak a beolvasott katalóguson.
Az, hogy a munkaszál örökli-e a kötést, a buildtől függ
Egy puszta threading.Thread, illetve a ThreadPoolExecutor.submit vagy
a hívó kontextusának másolatával, vagy egy üres kontextussal indul, és
hogy melyikkel, azt a sys.flags.thread_inherit_context mondja meg — a
szabad szálú buildeken alapértelmezés szerint igaz, mindenhol máshol
hamis. Ugyanaz a kód tehát 3.14t alatt a kötött nyelvet, 3.14 alatt a
folyamatszintű globális katalógust rendereli. Add át a kontextust ahelyett, hogy az
alapértelmezésre hagyatkoznál:
Az asyncio.to_thread ezt már megteszi helyetted.
Honosított értékek¶
Ez a könyvtár azt dönti el, hová kerül egy érték a lefordított üzenetben.
Magát az értéket nem honosítja. A {amount:,.2f} rögzített viselkedésű
Python-formátumleíró — vessző minden harmadik számjegynél, pont a tizedesek
előtt —, és ugyanazokat a karaktereket állítja elő, bármilyen nyelvű is az
üzenet:
A német ezt a számot 1.234,50, a francia 1 234,50 alakban írja, a hindi
pedig az 1234567 értéket 12,34,567 alakban csoportosítja, nem
1,234,567 alakban. A számok, pénznemek, dátumok, időpontok és mértékegységek
a Babelhez tartoznak. Előbb formázd meg az értéket, aztán
helyezd el a kész szöveget:
from babel.numbers import format_currency
total = format_currency(amount, "EUR", locale=locale)
tr(t"Your order comes to {total}")
Számlált üzenetnél a szám két feladatot lát el — kiválasztja a többesszám-alakot, és meg is jelenik a szövegben —, és csak a második honosítandó. A kiválasztáshoz tartsd meg a nyers darabszámot, a megjelenítéshez pedig a megformázott szöveget add át:
from babel.numbers import format_decimal
shown = format_decimal(n, locale=locale)
_.ngettext(t"One file", t"{shown} files", n)
A hívás előtti formázás egyben az is, ami a formátumleírót kívül tartja a katalóguson: amit a fordító lát, az kész szövegdarab, nem pedig egy szám plusz utasítások arra, hogyan kell renderelni.
Mi történik, ha egy katalógus hibás¶
Ha egy fordítás helyőrzői nem egyeznek a forráséival — hiányzó, ismeretlen vagy átformázott mező, amely átcsúszott az ellenőrzésen kézzel szerkesztett MO-ból, vendorolt katalógusból vagy az ellenőrzőt kihagyó folyamatból —, az alapértelmezés a forrásüzenet renderelése kivétel helyett. Ez a gettext saját szerződését tükrözi: rossz katalógus soha nem töri el az alkalmazást.
Ha a Hello {name} fordítása こんにちは {nombre}, a renderelés sikerül, és
egyetlen figyelmeztetés megy a gettext_tstrings naplózójába:
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
A figyelmeztetés üzenetenként és mintánként egyszer szólal meg, nem renderelésenként, így egy elromlott katalógusbejegyzés nem árasztja el a naplót.
Teszteléshez és CI-hez választhatod a hangos hibázást:
strict = Translator(translations, strict=True)
tr(t"Hello {name}", translations=translations, strict=True)
Ugyanaz a keresés ekkor kivételt vált ki, ugyanazt a mondatot hozva, csak a „using source text” fele nélkül:
>>> 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
Ezek az üzenetek annak szólnak, aki tehet ellenük, ami katalógusprobléma
esetén gyakrabban fordító, mint programozó — ezért ott, ahol egy helyőrző
jelen lévőnek látszik, de mégsem az, az üzenet megmagyarázza, miért, ahelyett
hogy megismételné, hogy hiányzik. Teljes szélességű kapcsos zárójelek,
megkettőzött {{name}}, láthatatlan nem törhető szóköz, cirill betű a latinok
között: mindegyiknek saját megfogalmazása van, példákkal együtt felsorolva a
Fordítóknak oldalon. Azt az oldalt
úgy írtuk, hogy oda lehessen adni annak, aki a .po fájlt szerkeszti.
Minta renderelése katalógus nélkül¶
A compile_template egy szinttel lejjebb teszi elérhetővé ugyanezt a
gépezetet: egy t-stringből előállítja a msgidjét és a hozzá kötött
értékkészletet, és bármilyen mintát rendereli, amit átadsz neki.
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"
A render ugyanazon szabályok szerint ellenőriz, és eltérés esetén mindig
kivételt vált ki. Itt nincs elnéző mód: az elnézés azért létezik, hogy egy
katalógusbeli keresés visszaeshessen a forrásszövegre, egy általad átadott
mintának viszont nincs mihez visszaesnie.
Biztonság és hatókör¶
Ez érvényes:
Ezeket szándékosan elutasítjuk:
Számold ki előbb az értelmes értéket:
A megszorítás stabil katalóguskulcsokat eredményez, használható neveket ad a fordítóknak, és megakadályozza, hogy egy lefordított szövegből kifejezésnyelv váljon.
A garancia a szerkezetre és a formázásra korlátozódik: egy fordítást soha nem értékelünk ki, és soha nem adhat hozzá attribútum-hozzáférést, hívásokat, konverziókat vagy formátumleírókat. Két dolog a hívó felelőssége marad, pontosan úgy, mint a stdlib gettextnél — a renderelt kimenet escape-elése a célja szerint (HTML, shell, terminál), valamint a katalógus sértetlensége, hiszen egy ellenséges katalógus megismételhet egy helyőrzőt a kimenet méretének felnagyítására, ami minden helyőrző-alapú i18n sajátossága.