Hogyan működik¶
Ezen az oldalon semmi nem szükséges a könyvtár használatához — azt az oktatóanyag és a kézikönyv fedi le. Ez az oldal ehelyett első elvekből építi fel a könyvtárat: mi is valójában egy t-string, hogyan esik ki belőle egy msgid, mitől érvényes egy fordítás, és hogyan éri el a megvalósítás, hogy ez az egész ellenőrzés a mikroszekundum tizedeibe kerüljön. Olvasd el, ha kíváncsi vagy, ha hozzá szeretnél járulni, vagy ha magad készülsz megvalósítani a konvenciót.
Mi is valójában egy t-string¶
Az f-string str értéket állít elő, méghozzá azonnal — mire bármelyik
függvény megkapja, az érték már interpolálódott, és a mondat lezárult. A
t-string (PEP 750) ugyanezzel a szintaxissal és a kifejezései ugyanilyen
mohó kiértékelésével él, de más típust állít elő:
>>> name = "Ada"
>>> f"Hello {name}!"
'Hello Ada!'
>>> t"Hello {name}!"
Template(strings=('Hello ', '!'), interpolations=(Interpolation('Ada', 'name', None, ''),))
Ez a Template objektum elkülönítve őrzi meg azokat a részeket, amelyekre egy
katalógusfolyamatnak szüksége van:
>>> template = t"Total: {amount:,.2f}"
>>> template.strings
('Total: ', '')
>>> template.interpolations[0].expression
'amount'
>>> template.interpolations[0].value
1234.5
>>> template.interpolations[0].format_spec
',.2f'
strings— az interpolációk körüli literális szöveg, sorrendben.- Minden interpolációra: a kifejezés forrásszövegként (
'amount'), a kiértékelt értéke (1234.5), valamint az esetleges konverzió (!r) és formátumleíró (,.2f) — külön hordozva, nem alkalmazva.
Minden, amit ez a könyvtár tesz, ennek a szerkezetnek a fegyelmezett felhasználása. A nyelv már elvégezte azt az egyetlen szétválasztást, amelyre az i18n-nek szüksége van — statikus szöveg külön az értékektől —, így a könyvtár soha nem elemzi a forráskódodat, és soha nem találgatja, hol áll egy érték a mondaton belül. Ami hátramarad, az három döntés: hogyan lesz a szerkezetből katalóguskulcs, mit mondhat ennek a kulcsnak a fordítása, és hogyan renderelődik a kettő újra együtt.
A sablontól a msgidig¶
A msgid — a kulcs, amely szerint egy katalógus indexelve van — kizárólag a
sablon statikus részeiből származik. Járd be a strings és az
interpolations elemeit forrásbeli sorrendben; escape-eld kapcsos
zárójelekkel az egyes literális szakaszokat (a { {{ lesz); minden
interpolációra bocsáss ki egy {name} tokent, ahol a name a kifejezés
szövege a körülvevő térköz nélkül. A t"Total: {amount:,.2f}" esetében:
strings ('Total: ', '')
interpolations expression 'amount' conversion None format_spec ',.2f'
msgid 'Total: {amount}'
Ennek a szabálynak minden részéhez tartozik egy ok:
- A kifejezésnek puszta névnek kell lennie — a
str.isidentifier()igaz rá, és nem Python-kulcsszó. At"Hello {user.name}"már a hívás helyén elutasításra kerül. A msgid egy kulcs: minden futáskor és minden kinyeréskor azonosan kell kijönnie, és fordítók olvassák, ezért a helyőrzőnek stabil, értelmes szónak kell lennie — nem olyan kódtöredéknek, amely arra hívja a katalógust, hogy kifejezésnyelvvé váljon. - A konverzió és a formátumleíró sosem kerül be a msgidbe. A fordítóknak
nem kellene a
:,.2fleírót olvasniuk, és egyetlen fordítás se változtathassa meg. Az ebből következő tanulság megéri: ha a kódodban a:,.2fleírót:,.0falakra szigorítod, az egyetlen msgidet sem változtatja meg, tehát egyetlen nyelven sem érvénytelenít fordítást. A katalóguskulcs azt követi, mit mond a mondat, nem azt, hogyan formázódik az érték. - Egy ismételt névnek pontosan meg kell ismételnie a formázását. A
t"{x:.2f} vs {x:.3f}"elutasításra kerül, mert mindkét előfordulás ugyanabba a{x}tokenbe olvad össze, és a msgid többé nem tudná megmondani, melyik formázást használja egy renderelés. - Az üres msgidet soha nem keressük ki, mert a gettext ezt a katalógus
saját metaadat-fejlécének tartja fenn. A
t""a katalógus érintése nélkül""alakban jelenik meg.
A teljes szabályrendszer, beleértve az ezen az oldalon kihagyott határeseteket, a SPEC 2. §.
Mit mondhat egy fordítás¶
A katalógusból visszatérő mintát a string.Formatter elemzi — ugyanaz az
elemző, amelyet a str.format használ. A nyelvtan szándékosan kölcsönzött, és
nem kitalált: az a minta, amelyet ez a könyvtár elfogad, olyan, amelyet a
tágabb ökoszisztéma már ért. Ezután két ellenőrzés következik.
Alak: minden mezőnek puszta {name} alakúnak kell lennie. A konverzió
vagy a formátumleíró — beleértve a kifejezetten üres {name:} alakot is —
elutasításra kerül, ahogy a pozicionális mezők ({0}, {}) és a térközzel
kipárnázott nevek ({ name }) is. Az utolsó többet nyom, mint amennyinek
látszik: a str.format és a GNU msgfmt egyaránt elutasítja a { name }
alakot, így ha itt elfogadnánk, olyan katalógusok születnének, amelyeket a
lánc egyetlen más eszköze sem tud validálni.
Nevek: a minta helyőrzőhalmazát a forráséhoz hasonlítjuk. Egyes számú üzenetnél minden forrásnév kötelező, és semmi más nem megengedett. Többes számú üzenetnél a két ágat egyesítjük:
- megengedett = a két ág neveinek uniója
- kötelező = a metszetük
Így a t"One file" / t"{n} files" ellenében az n név mindkét alak
fordításában megengedett, de egyikben sem kötelező. Épp ez az aszimmetria
teszi lehetővé, hogy a célnyelv többesszám-rendszere eltérjen a
forrásnyelvétől — a japán mindkét ágat egyetlen alakkal fordítja, amely
alighanem használja az {n} helyőrzőt; egy angolnál több alakot ismerő
nyelvnek szüksége lehet az {n} helyőrzőre olyan alakban, amilyen az angolban
nincs is.
Semmi ebből nem elméleti: ennek a webhelynek a saját keretkatalógusa maga is
tartalmazza a Built {n} localized page / Built {n} localized pages többes
számú üzenetet — két angol ágat —, a webhely kiadásai pedig ezt az egyetlen
üzenetet egy alaktól hatig terjedően fordítják le.
Kilenc ilyen kiadás, alakok szerinti sorrendben
| Katalógus | Alakok | A fordítások, alakok szerinti sorrendben |
|---|---|---|
| japán | 1 | ローカライズ済みページを{n}件ビルドしました |
| török | 2 | {n} yerelleştirilmiş sayfa oluşturuldu — kétszer, azonosan: a török főnevek számnév után egyes számban maradnak |
| olasz | 2 | Generata {n} pagina localizzata · Generate {n} pagine localizzate — a melléknévi igenév nemben és számban egyeztetve |
| lett | 3 | Izveidota {n} lokalizēta lapa · Izveidotas {n} lokalizētas lapas · Izveidots {n} lokalizētu lapu — a harmadik alak kizárólag a nullára való |
| orosz | 3 | Собрана {n} локализованная страница · Собраны {n} локализованные страницы · Собрано {n} локализованных страниц |
| lengyel | 3 | Zbudowano {n} zlokalizowaną stronę · Zbudowano {n} zlokalizowane strony · Zbudowano {n} zlokalizowanych stron |
| szlovén | 4 | Zgrajena {n} lokalizirana stran · Zgrajeni {n} lokalizirani strani · Zgrajene {n} lokalizirane strani · Zgrajenih {n} lokaliziranih strani — a második kettes szám, pontosan kettőre |
| ír | 5 | Tógadh {n} leathanach logánaithe · Tógadh {n} leathanaigh logánaithe — egy, kettő, 3–6, 7–10 és a többi; a szótő váltakozik, de a leathanach l-lel kezdődik, amelyre az ír nyelv egyik mutációt sem írja ki, így több alak egybeesik |
| arab | 6 | köztük تم إنشاء صفحة مترجمة واحدة ({n}) pontosan egyre és تم إنشاء {n} صفحات مترجمة néhányra |
Minden sor élő bejegyzés ennek a tárolónak az i18n/*/LC_MESSAGES/site.po
fájljaiban, amelyeket a többnyelvű build rendereli minden
kiadáskor — és egy teszt ehhez a táblázathoz szegezi azokat a katalógusokat,
így a kettő nem sodródhat szét.
Ezeken a határokon belül az átrendezés és az ismétlés szándékosan
korlátozatlan. Mindkettő nyelvtanilag szükséges valódi nyelvekben, és az
előfordulások számának korlátozása helyes fordításokat utasítana el, minden
biztonsági haszon nélkül: egy fordítás továbbra sem értékelhet ki semmit,
mert nincs kiértékelési útvonal — a helyőrzőket név szerint keressük ki a
sablon már kiszámított értékei közül, és soha nem adjuk át őket az eval, a
getattr vagy maga a str.format kezébe.
Renderelés¶
Egy validált minta renderelése végigjárás a darabjain: bocsásd ki az egyes
literális részeket, minden helyőrzőnél pedig vedd az interpoláció elkapott
értékét, és alkalmazd a forrásoldali konverziót és formátumleírót —
format(convert(value, conversion), format_spec). Eközben két garanciát
tartunk:
- Minden különböző érték renderelésenként legfeljebb egyszer formázódik,
még akkor is, ha a fordítás megismétel egy helyőrzőt. Az ismétlés azt
változtatja meg, hányszor kerül be az eredmény, nem azt, hányszor fut le a
__format__metódusod. - Többes számoknál egy helyőrző azt az ágat olvassa, amely definiálta. A
mindkét ágban jelen lévő név annak az ágnak az elkapott értékét olvassa,
amelyet a forrásnyelv választ (
singular, han == 1, egyébkéntplural); az ágspecifikus név mindig a saját ágát olvassa, még akkor is, ha a célnyelv többesszám-szabályai más alakban is elérhetővé tették.
Ha a validálás renderelési időben elbukik, a válasz aszerint válik szét, ki
adta a mintát. A katalógusból érkezett minta degradálódik: egy
figyelmeztetés a naplóba, és a forrásszöveg renderelése, megtartva a gettext
szerződését, hogy egy hibás katalógus soha nem viszi le az alkalmazást
(a kézikönyv mindkét módot bemutatja).
Az a minta, amelyet a hívó közvetlenül adott át — CompiledTemplate.render —
mindig kivételt vált ki, mert nincs forrásszöveg, amelyre degradálódhatna;
az elnézés a katalóguskeresésekért van, nem az argumentumokért.
A diagnosztika a terv része¶
Egy helyőrzőhiba rendszerint fordító elé kerül, nem programozó elé, és gyakran
olyan fájlban, ahol a probléma láthatatlan. Zsákutca azt mondani, hogy
{name} is missing, olyasvalakinek, aki pontosan ezeket a karaktereket látja
a szerkesztőjében, ezért az üzeneteket három szabály szerint számítjuk ki:
- Az láthatatlan karaktert tartalmazó nevet — egy beviteli módszer által
előállított nem törhető szóközt, egy nulla szélességű szóközt — úgy írjuk
ki, hogy azt a karaktert a kódpontjára cseréljük, a helyén:
{<U+00A0>name}. Az olvasónak azt kell látnia, hol. - Az írásrendszereket keverő betűkből álló nevet, a homoglifa-esetet,
kétszer mutatjuk meg — egyszer olvashatóan, egyszer escape-elve —, mert a
cirill
аbetűs{nаme}nyomtatásban megkülönböztethetetlen a{name}alaktól, és az escape-elt(nаme)írásmód az egyetlen, amely elárulja a különbséget. - Minden mást úgy mutatunk, ahogy írva van. A
{名前}és a{café}hétköznapi nevek; ha escape-elnénk őket, az olvasó nem találná meg, mire is gondoltak.
Ugyanezen elv alapján a „hiányzó” helyőrző, amely jelen lévőnek látszik,
megkapja a hiánya magyarázatát — kelet-ázsiai beviteli módszerből származó
teljes szélességű kapcsos zárójelek, escape-elési oda-vissza útból származó
{{name}} megkettőzés, a kapcsos zárójeleken kívülre került név. A
fordítóknak írt hibaolvasási táblázat
szó szerint mutatja mindegyik üzenetet.
A forró útvonal¶
Mindez minden egyes lefordított szövegnél lezajlik, amelyet egy alkalmazás lerenderel, ezért a megvalósítás egyetlen gondolat köré épül: a validálást sosem hagyjuk ki, tehát épp a validálásnak kell gyorsítótárazódnia.
flowchart LR
T["t-string"] --> S{"láttuk már ezt<br>a szerkezetet?"}
S -- "találat" --> G["katalóguskeresés<br>gyorsítótárazott msgiddel"]
S -- "nincs találat" --> D["msgid levezetése,<br>terv gyorsítótárazása"] --> G
G --> V{"láttuk már<br>ezt a mintát?"}
V -- "találat" --> R["renderelés"]
V -- "nincs találat" --> C["validálás,<br>verdikt gyorsítótárazása"] --> R
Három gyorsítótár, szakaszonként egy:
- Egy terv hívásihely-szerkezetenként. A sablon
stringstuple-je — egy objektum, amelyet az értelmező amúgy is felépített — a gyorsítótár kulcsa, így egy keresés semmit nem allokál. Találat esetén a rendszer minden interpoláció kifejezését, konverzióját és formátumleíróját ettől függetlenül összeveti a rögzítettekkel: két hívási hely, amely osztozik a literális szövegen, de a formázásban eltér (t"{x:.2f}"szemben at"{x:.3f}"alakkal), nem ütközhet, és ez az összevetés az ára annak, hogy olyan kulcsot használunk, amelyet az értelmező ingyen ad a kezünkbe. - Egy verdikt mintánként. Amikor egy katalógus először válaszol egy adott mintával, azt elemezzük és validáljuk; az eredmény — egy lefordított renderelési terv vagy az érvénytelenség feljegyzése — a terven marad. Ennek az üzenetnek minden későbbi renderelése egyetlen szótárkereséssel jut el hozzá. Az érvénytelen mintákat is megjegyezzük, és ezért figyelmeztet egy elromlott katalógusbejegyzés egyszer, nem pedig minden renderelésnél.
- Egy egyesített terv többesszám-páronként, amely az unió- és metszethalmazokat tartja, hogy az ágaritmetika üzenetenként egyszer történjen meg, ne hívásonként.
Minden gyorsítótár korlátos, és egyik sem tart meg interpolált értékeket —
csak statikus szerkezetet és mintaszöveget. Az eredmény, a
benchmarks/runtime.py
mérése szerint, CPython 3.14.6-on, macOS 26 alatt, arm64-es laptopon:
nagyjából 0,4 µs egy egymezős üzenetre, magának a t-stringnek a felépítésével
együtt, ami körülbelül 2,7-szerese egy semmit nem ellenőrző, sima
gettext(...).format(...) hívásnak. Ezek egyetlen gép számai — a szkript a
fejlécében kiírja az interpreterét és a platformját, tehát futtasd le azon a
hardveren, amelyre ténylegesen telepítesz, mielőtt bármelyik arányt a
sajátodnak tekintenéd. A
core.py
tetején lévő kommentár rögzíti az e mögött álló egyedi méréseket.
Megvalósítani máshogy¶
A fentiekből semmi nem csak erre a megvalósításra jellemző: a konvenciót leírtuk v1-es specifikációként, géppel olvasható konformitási készlete pedig lehetővé teszi, hogy egy kinyerő, egy IDE-bővítmény vagy egy másik nyelvű megvalósítás ellenőrizze magát az oldalon elmagyarázott minden szabály ellenében. Ez a megvalósítás a saját tesztjeiben futtatja a készletet, és épp ez tartja meg attól ezt az oldalt, a specifikációt és a kódot, hogy csendben szétsodródjanak.