Pereiti prie turinio

Kaip tai veikia

Niekas šiame puslapyje nėra būtina bibliotekai naudoti — tam skirti pamoka ir vadovas. Šis puslapis biblioteką atstato nuo pirmųjų principų: kas iš tikrųjų yra t-eilutė, kaip iš jos iškrenta msgid, kas daro vertimą galiojantį ir kaip realizacija pasiekia, kad visas tas tikrinimas kainuotų mikrosekundės dalis. Skaitykite jį, jei jums smalsu, jei norite prisidėti arba jei planuojate susitarimą įgyvendinti patys.

Kas iš tikrųjų yra t-eilutė

F-eilutė pagamina str, ir pagamina jį iškart — kol funkcija ją gauna, reikšmė jau interpoliuota, o sakinys užantspauduotas. T-eilutė (PEP 750) turi tą pačią sintaksę ir tokį patį nedelsiamą savo reiškinių apskaičiavimą, bet pagamina kitą tipą:

>>> name = "Ada"
>>> f"Hello {name}!"
'Hello Ada!'
>>> t"Hello {name}!"
Template(strings=('Hello ', '!'), interpolations=(Interpolation('Ada', 'name', None, ''),))

Tas Template objektas išlaiko atskirtas tas dalis, kurių reikia katalogo konvejeriui:

>>> 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 — literalus tekstas aplink interpoliacijas, iš eilės.
  • Kiekvienai interpoliacijai: reiškinys kaip pirminis tekstas ('amount'), jo apskaičiuota reikšmė (1234.5) ir bet kokia konversija (!r) bei formato specifikacija (,.2f) — nešamos atskirai, o ne pritaikytos.

Viskas, ką ši biblioteka daro, yra drausmingas tos struktūros vartojimas. Kalba jau padarė tą vienintelį atskyrimą, kurio reikia i18n — statinis tekstas atskirai nuo reikšmių — todėl biblioteka niekada neanalizuoja jūsų pirminio kodo ir niekada nespėlioja, kur sakinyje sėdi reikšmė. Lieka trys sprendimai: kaip struktūra tampa katalogo raktu, ką to rakto vertimui leidžiama pasakyti ir kaip juodu atvaizduojami atgal drauge.

Nuo šablono iki msgid

Msgid — raktas, pagal kurį indeksuojamas katalogas — išvedamas tik iš šablono statinių dalių. Pereikite strings ir interpolations pirminio kodo tvarka; kiekvieną literalų segmentą ekranuokite skliaustais ({ tampa {{); kiekvienai interpoliacijai išleiskite po vieną {name} leksemą, kur name yra reiškinio tekstas be aplinkinių tarpų. Iš t"Total: {amount:,.2f}":

strings         ('Total: ', '')
interpolations  expression 'amount'   conversion None   format_spec ',.2f'
msgid           'Total: {amount}'

Kiekviena tos taisyklės dalis turi savo priežastį:

  • Reiškinys privalo būti paprastas vardasstr.isidentifier() yra teisingas ir tai nėra Python raktažodis. t"Hello {user.name}" atmetamas iškvietimo vietoje. Msgid yra raktas: jis privalo išeiti identiškas kiekvieną paleidimą ir kiekvieną ištraukimą, o jį skaito vertėjai, todėl vietaženklis turi būti stabilus, prasmingas žodis — o ne kodo nuotrupa, kviečianti katalogą tapti reiškinių kalba.
  • Konversija ir formato specifikacija į msgid nepatenka niekada. Vertėjams neturėtų tekti skaityti :,.2f, ir joks vertimas neturėtų galėti to pakeisti. Verta žinoti ir išvadą: :,.2f sugriežtinimas iki :,.0f jūsų kode nepakeičia jokio msgid, tad nepanaikina jokio vertimo jokia kalba. Katalogo raktas seka tai, ką sakinys sako, o ne kaip reikšmė formatuojama.
  • Pasikartojantis vardas privalo tiksliai pakartoti savo formatavimą. t"{x:.2f} vs {x:.3f}" atmetamas, nes abu pasitaikymai suplaukia į tą pačią {x} leksemą ir msgid nebegalėtų pasakyti, kurį formatavimą turėtų naudoti atvaizdavimas.
  • Tuščio msgid niekada neieškoma, nes gettext jį rezervavo katalogo metaduomenų antraštei. t"" atvaizduojamas kaip "", katalogo net nepaliečiant.

Visas taisyklių rinkinys, įskaitant kraštinius atvejus, kuriuos šis puslapis praleidžia, yra SPEC §2.

Ką vertimui leidžiama pasakyti

Iš katalogo grįžtantis šablonas analizuojamas su string.Formatter — tuo pačiu analizatoriumi, kurį naudoja str.format. Gramatika tyčia pasiskolinta, o ne išgalvota: šablonas, kurį ši biblioteka priima, yra toks, kurį platesnė ekosistema jau supranta. Tada taikomos dvi patikros.

Forma: kiekvienas laukas privalo būti plikas {name}. Konversija ar formato specifikacija — įskaitant aiškiai tuščią {name:} — atmetama, kaip ir poziciniai laukai ({0}, {}) bei tarpais išskirti vardai ({ name }). Paskutinis atvejis svarbesnis, nei atrodo: ir str.format, ir GNU msgfmt { name } atmeta, tad priimant jį čia atsirastų katalogai, kurių joks kitas grandinės įrankis nemokėtų patikrinti.

Vardai: šablono vietaženklių aibė lyginama su pirminio pranešimo aibe. Vienaskaitos pranešimui kiekvienas pirminis vardas yra privalomas, o niekas kita nėra leidžiama. Daugiskaitos pranešimui abi šakos sujungiamos:

  • leidžiama = abiejų šakų vardų sąjunga
  • privaloma = jų sankirta

Taigi prieš t"One file" / t"{n} files" vardas n yra leidžiamas abiejų formų vertime, bet neprivalomas nė vienoje. Būtent ta asimetrija leidžia tikslinės kalbos daugiskaitos sistemai skirtis nuo pirminės — japonų kalba abi šakas verčia viena forma, kuri tikriausiai naudoja {n}; kalbai, turinčiai daugiau formų nei anglų, {n} gali prireikti formoje, kurios anglų kalba neturi.

Nieko hipotetiško čia nėra: šios svetainės apvalkalo katalogas neša daugiskaitos pranešimą Built {n} localized page / Built {n} localized pages — dvi angliškas šakas — o svetainės leidimai tą vieną pranešimą verčia į nuo vienos iki šešių formų.

Devyni iš tų leidimų, formų tvarka
Katalogas Formos Vertimai formų tvarka
Japonų 1 ローカライズ済みページを{n}件ビルドしました
Turkų 2 {n} yerelleştirilmiş sayfa oluşturuldu — du kartus, identiškai: turkiški daiktavardžiai po skaitvardžio lieka vienaskaitos
Italų 2 Generata {n} pagina localizzata · Generate {n} pagine localizzate — dalyvis derinamas gimine ir skaičiumi
Latvių 3 Izveidota {n} lokalizēta lapa · Izveidotas {n} lokalizētas lapas · Izveidots {n} lokalizētu lapu — trečioji forma skirta vien tik nuliui
Rusų 3 Собрана {n} локализованная страница · Собраны {n} локализованные страницы · Собрано {n} локализованных страниц
Lenkų 3 Zbudowano {n} zlokalizowaną stronę · Zbudowano {n} zlokalizowane strony · Zbudowano {n} zlokalizowanych stron
Slovėnų 4 Zgrajena {n} lokalizirana stran · Zgrajeni {n} lokalizirani strani · Zgrajene {n} lokalizirane strani · Zgrajenih {n} lokaliziranih strani — antroji yra dviskaita, lygiai dviem
Airių 5 Tógadh {n} leathanach logánaithe · Tógadh {n} leathanaigh logánaithe — vienam, dviem, 3–6, 7–10 ir likusiems; kamienas kaitaliojasi, bet leathanach prasideda l, kurios jokia airiška mutacija nerašo, tad kelios formos sutampa
Arabų 6 tarp jų تم إنشاء صفحة مترجمة واحدة ({n}) lygiai vienam ir تم إنشاء {n} صفحات مترجمة keliems

Kiekviena eilutė yra gyvas įrašas šios saugyklos i18n/*/LC_MESSAGES/site.po faile, atvaizduojamas daugiakalbio kūrimo kiekvieno leidimo metu — o testas šią lentelę prisega prie tų katalogų, tad juodu negali išsiskirti.

Šiose ribose perstatymas ir kartojimas tyčia neribojami. Abu tikrose kalbose gramatiškai būtini, o pasitaikymų skaičiaus ribojimas atmestų teisingus vertimus be jokios saugumo naudos: vertimas vis tiek nieko negali apskaičiuoti, nes apskaičiavimo kelio paprasčiausiai nėra — vietaženkliai ieškomi pagal vardą tarp šablono jau apskaičiuotų reikšmių, o niekada neperduodami nei eval, nei getattr, nei pačiam str.format.

Atvaizdavimas

Patikrinto šablono atvaizdavimas yra pasivaikščiojimas per jo gabalus: išleisti kiekvieną literalią dalį, o kiekvienam vietaženkliui paimti interpoliacijos pagautą reikšmę ir pritaikyti pirminio kodo konversiją bei formato specifikaciją — format(convert(value, conversion), format_spec). Tai darant išlaikomos dvi garantijos:

  • Kiekviena skirtinga reikšmė per vieną atvaizdavimą formatuojama daugiausia kartą, net kai vertimas vietaženklį kartoja. Kartojimas keičia tai, kaip dažnai rezultatas įterpiamas, o ne tai, kaip dažnai paleidžiamas jūsų __format__.
  • Daugiskaitoje vietaženklis skaito tą šaką, kuri jį apibrėžė. Vardas, esantis abiejose šakose, skaito reikšmę, pagautą tos šakos, kurią parenka pirminė kalba (singular, kai n == 1, kitu atveju plural); šakai būdingas vardas visada skaito savo paties šaką — net kai tikslinės kalbos daugiskaitos taisyklės padarė jį prieinamą kitoje formoje.

Kai tikrinimas nepavyksta atvaizdavimo metu, atsakas priklauso nuo to, kas šabloną pateikė. Šablonas, atėjęs iš katalogo, nusileidžia: užrašomas vienas įspėjimas ir atvaizduojamas pirminis tekstas, išlaikant gettext kontraktą, kad sugadintas katalogas niekada nepargriauna programos (vadovas parodo abu režimus). Šablonas, kurį kviečiantysis perdavė tiesiogiai — CompiledTemplate.render — visada kelia klaidą, nes nėra pirminio teksto, nuo kurio būtų galima nusileisti; nuolaidumas egzistuoja katalogo paieškoms, o ne argumentams.

Diagnostika yra sprendimo dalis

Vietaženklio klaida paprastai atsiduria prieš vertėją, o ne prieš programuotoją, ir dažnai faile, kuriame problema nematoma. Pasakyti {name} is missing tam, kas mato būtent tuos simbolius savo redaktoriuje, yra aklavietė, todėl pranešimai apskaičiuojami pagal tris taisykles:

  • Vardas, turintis nematomą simbolį — nedalų tarpą, pagamintą įvesties metodo, nulinio pločio tarpą — spausdinamas su tuo simboliu, pakeistu jo kodo pozicija, toje pačioje vietoje: {<U+00A0>name}. Skaitytojui reikia pamatyti kur.
  • Vardas, kurio raidės maišo rašto sistemas, homoglifų atvejis, parodomas dukart — kartą skaitomai, kartą su kaitos sekomis — nes {nаme} su kirilicos а atspaude neatskiriamas nuo {name}, o ekranuota forma (nаme) yra vienintelis užrašymas, kuris juos atskiria.
  • Visa kita rodoma taip, kaip parašyta. {名前} ir {café} yra įprasti vardai; jų ekranavimas paliktų skaitytoją nebegalintį rasti, kas turėta omenyje.

Tuo pačiu principu „trūkstamas“ vietaženklis, kuris atrodo esantis, gauna savo nebuvimo paaiškinimą — viso pločio skliaustai iš Rytų Azijos įvesties metodo, {{name}} padvigubinimas po ekranavimo kelionės pirmyn atgal, vardas už bet kokių skliaustų. Vertėjams parašyta klaidų skaitymo lentelė parodo kiekvieną iš šių pranešimų pažodžiui.

Karštasis kelias

Visa tai, kas aukščiau, nutinka su kiekviena programos atvaizduojama išversta eilute, todėl realizacija sudėta aplink vieną mintį: tikrinimas niekada nepraleidžiamas, todėl kešuoti reikia būtent tikrinimą.

flowchart LR
  T["t-eilutė"] --> S{"struktūra<br>jau matyta?"}
  S -- "pataikyta" --> G["katalogo paieška<br>pagal podėlyje esantį msgid"]
  S -- "nepataikyta" --> D["išvesti msgid,<br>įsiminti planą"] --> G
  G --> V{"šablonas<br>jau matytas?"}
  V -- "pataikyta" --> R["atvaizduoti"]
  V -- "nepataikyta" --> C["patikrinti,<br>įsiminti verdiktą"] --> R

Trys podėliai, po vieną kiekvienam etapui:

  • Planas kiekvienai iškvietimo vietos struktūrai. Šablono strings kortežas — objektas, kurį interpretatorius jau sukūrė — yra podėlio raktas, todėl paieška nieko nealokuoja. Pataikius vis tiek palyginama kiekvienos interpoliacijos reiškinys, konversija ir formato specifikacija su užrašytosiomis: dvi iškvietimo vietos, dalijančiosi literaliu tekstu, bet besiskiriančios formatavimu (t"{x:.2f}" prieš t"{x:.3f}"), negali susidurti, o tas palyginimas yra kaina už raktą, kurį interpretatorius atiduoda nemokamai.
  • Verdiktas kiekvienam šablonui. Pirmą kartą, kai katalogas atsako tam tikru šablonu, jis išanalizuojamas ir patikrinamas; rezultatas — sukompiliuotas atvaizdavimo planas arba negaliojimo įrašas — laikomas prie plano. Kiekvienas vėlesnis to pranešimo atvaizdavimas jį pasiekia vienu žodyno kreipiniu. Negaliojantys šablonai taip pat įsimenami, todėl sugadintas katalogo įrašas įspėja kartą, o ne kiekvieno atvaizdavimo metu.
  • Sujungtas planas kiekvienai daugiskaitos porai, laikantis sąjungos ir sankirtos aibes, kad šakų aritmetika įvyktų kartą kiekvienam pranešimui, o ne kartą kiekvienam iškvietimui.

Kiekvienas podėlis yra ribotas, ir nė vienas nesaugo interpoliuotų reikšmių — tik statinę struktūrą ir šablonų tekstą. Rezultatas, išmatuotas benchmarks/runtime.py su CPython 3.14.6, macOS 26 arm64 nešiojamajame: maždaug 0,4 µs vieno lauko pranešimui, įskaitant pačios t-eilutės sukūrimą, apie 2,7 karto daugiau nei paprastas, nieko netikrinantis gettext(...).format(...). Tai vienos mašinos skaičiai — skriptas savo antraštėje išspausdina interpretatorių ir platformą, tad paleiskite jį toje aparatinėje įrangoje, į kurią iš tikrųjų diegiate, prieš laikydami kurį nors santykį savu. Komentarai core.py viršuje užrašo pavienius matavimus, iš kurių ta forma susideda.

Įgyvendinant iš naujo

Niekas iš to, kas aukščiau, nėra būdinga vien šiai realizacijai: susitarimas surašytas kaip spec v1, o jo mašininiu būdu skaitomas atitikties rinkinys leidžia ištraukikliui, IDE įskiepiui ar realizacijai kita kalba pačiam pasitikrinti pagal kiekvieną šio puslapio paaiškintą taisyklę. Ši realizacija tą rinkinį paleidžia savo pačios testuose, ir būtent tai neleidžia šiam puslapiui, specifikacijai ir kodui tyliai išsiskirti.