Pułapki¶
Ta strona jest przetłumaczona na trzydzieści pięć języków, a każde z tych tłumaczeń powstało przez przejście pętli, której uczy ta dokumentacja. Jak na branżowe standardy to niewielki korpus, a i tak wystarczył, by trafić na większość pułapek, przez które i18n jest trudniejsze, niż wygląda.
Każdy poniższy rozdział to coś, co naprawdę poszło tu nie tak, jak to wtedy wyglądało i gdzie przebiega granica między tym, co biblioteka sprawdza za ciebie, a tym, co pozostaje twoją oceną.
Zmiana nazwy zmiennej tłumaczy zdanie od nowa¶
msgid jest kluczem katalogu, a interpolowana nazwa jest w jego wnętrzu.
Przeniesienie jednej stałej do zakresu modułu i zapisanie jej wielkimi
literami, tak jak każe styl Pythona — author na AUTHOR — zmieniło
Copyright © 2026 {author} · MIT License w komunikat, którego żaden katalog
nigdy nie widział. Każde tłumaczenie tej linii przeszłoby ponownie przez cykl
fuzzy, w każdym języku, z powodu zmiany nazwy, która nie zmieniła nic, co
czytelnik mógłby zobaczyć.
Biblioteka cię nie powstrzyma: obie pisownie są poprawnymi nazwami symboli zastępczych. Robi natomiast to, że czyni tę nazwę wartą ochrony — interpolacja musi być zwykłą nazwą, więc to, co trafia do klucza katalogu, jest słowem, które tłumacz może przeczytać, a nie wyrażeniem.
Przypadek odwrotny jest bezpieczny z założenia. Konwersje i specyfikatory
formatu nie są częścią msgid, więc zaostrzenie {amount:,.2f} do
{amount:,.0f} nie zmienia żadnego klucza i nie unieważnia nigdzie żadnego
tłumaczenia.
nplurals=2 nie oznacza dwóch różnych łańcuchów¶
Turecki, węgierski, perski i bengalski deklarują po dwie formy liczby mnogiej
i we wszystkich czterech obie formy policzalnego komunikatu to zgodnie z
prawidłami ten sam łańcuch — rzeczownik po liczebniku zostaje w liczbie
pojedynczej, więc {n} sayfa jest poprawne i dla jednej strony, i dla
dziesięciu. Recenzent, który „naprawi" to powtórzenie, psuje tłumaczenie.
Odwrotna pomyłka jest równie łatwa. Trzecia forma łotewskiego istnieje
wyłącznie dla zera; druga forma słoweńskiego to liczba podwójna, dla
dokładnie dwóch; ostatnia forma rumuńskiego wymaga słowa de, którego dwie
pierwsze mieć nie mogą. Wypełnienie tych miejsc liczbą pojedynczą i mnogą daje
katalog błędny tylko dla liczebności, których nikt nie testuje.
Co gorsza, kolejność miejsc nie jest semantyczna. Walijski indeksuje swoje
pięć form tak, że msgstr[0] to przypadek ogólny, a msgstr[1] to liczba
pojedyncza. Wypełnianie ich w oczywistej kolejności umieszcza liczbę
pojedynczą tam, gdzie znajdzie ją każdy niepoliczalny komunikat.
Biblioteka nie bierze niczego z tego na siebie i o to właśnie chodzi: reguła liczby mnogiej języka docelowego mieszka w nagłówku jego własnego katalogu, a reguła sumy/części wspólnej pozwala tłumaczeniu mieć więcej form niż źródło albo mniej. Sprawdza jedyną rzecz, jaką da się sprawdzić bez znajomości języka — że każda forma zachowuje potrzebne jej symbole zastępcze.
Dwie formy mogą być identyczne nie bez powodu¶
Irlandzki ma pięć form liczby mnogiej i w raporcie z buildu tej strony kilka
z nich zapisanych jest tak samo. To nie wpadka przy kopiuj-wklej:
leathanach zaczyna się na l, a żadna z dwóch mutacji nagłosowych
wywoływanych przez irlandzkie liczebniki nie jest na l zapisywana. Formy
nadal wykonują prawdziwą pracę — temat wymienia się między leathanach a
leathanaigh, a liczebności powyżej dziesięciu wracają do liczby pojedynczej
— ale żaden rzeczownik o znaczeniu „strona" nie pokazałby tego kontrastu.
Każde sprawdzenie oznaczające zduplikowane formy jako podejrzane oznaczy poprawny irlandzki. Jedynym recenzentem jest tu człowiek, który zna ten język.
Komunikat może uzgodnić się tylko z jedną liczbą¶
Raport z buildu tej strony mówi, ile stron wyrenderowano i ile to zajęło.
Zapisanie tego jako „Rendered {n} pages in {seconds} seconds" wygląda
niewinnie i nie jest przetłumaczalne: gettext wybiera jedną formę na
podstawie jednej liczby, a tą liczbą jest n. Słowo seconds musiałoby
uzgodnić się z liczbą, której maszyneria liczby mnogiej nigdy nie widzi.
Rozwiązaniem jest uczynić drugą wielkość symbolem jednostki, a nie słowem — a
symbole jednostek same podlegają lokalizacji: katalogi tej strony niosą s,
с, ث, שנ׳ i mp, a francuska, hiszpańska i szwedzka typografia chce
spacji przed symbolem tam, gdzie angielska nie. Nic z tego nie jest sprawą
biblioteki — ale zauważenie, że komunikat wymaga dwóch uzgodnień, już tak, a
jedynym narzędziem do tego jest zapisanie komunikatu inaczej.
Zmiana angielskiego zdania zmienia obcą gramatykę¶
Strona główna mówiła kiedyś „all ten language editions". Usunięcie liczby — jednosłowna angielska poprawka, zrobiona dlatego, że liczba wciąż się dezaktualizowała — zmieniło podmiot z mnogiego na pojedynczy. Hiszpański, włoski, portugalski, rosyjski, ukraiński, grecki, niderlandzki i hebrajski musiały na nowo uzgodnić czasownik; kilka potrzebowało też zmiany imiesłowu.
Zmiana w źródle, która po angielsku czyta się jak drobiazg, nie jest
drobiazgiem dalej w potoku. Oznaczenie jej jako fuzzy, co robi
pybabel update, jest mechanizmem dającym każdemu tłumaczowi szansę, by to
zauważył.
Niewidoczne różnice przeżywają każde kopiuj-wklej¶
Przewodnik cytuje diagnostykę zawierającą (nаme) — celową sekwencję
ucieczki, bo znak, który nazywa, to cyrylickie а, którego żaden czytelnik
nie odróżni od łacińskiego. Tłumacze tej strony zamienili tę sekwencję
ucieczki na rzeczywisty znak pięć osobnych razy, w pięciu różnych
językach, za każdym razem tworząc stronę, która wyglądała poprawnie i była
błędna.
Akurat to biblioteka wychwytuje i to jest powód, dla którego diagnostyka ma taki kształt: symbol zastępczy, którego litery mieszają systemy pisma, jest raportowany dwukrotnie, raz czytelnie i raz z ucieczką, bo forma z ucieczką jest jedynym zapisem, który je rozróżnia. Twarda spacja wewnątrz nawiasów klamrowych jest z tego samego powodu wypisywana punktem kodowym. Checker katalogów odrzuca komunikat, zanim ten zdąży trafić na produkcję.
Niepusty to nie przetłumaczony¶
Katalog wygenerowany z msgidami skopiowanymi do msgstrów przechodzi każde naiwne sprawdzenie: nic nie jest puste, nic nie jest fuzzy, zbiór komunikatów zgadza się dokładnie. Jedno wydanie tej strony działało tak przez kilka godzin. Podobnie osiem stron innego wydania, które były kopiami angielskiego źródła co do bajtu — co przechodzi sprawdzenie porównujące bloki kodu między nimi, bo to ten sam plik.
Żadnej z tych rzeczy biblioteka tłumaczeń nie jest w stanie zobaczyć. Obie są
tanie do przetestowania — ale nie przez wymaganie, żeby każdy wpis różnił się
od swojego źródła: OK, nazwy produktów, imiona i nazwiska, akronimy oraz
identyfikatory z kodu tłumaczą się same na siebie, a sprawdzenie, które tego
zabrania, produkuje fałszywe alarmy na zawsze.
Mierz zamiast tego odsetek, na całym katalogu albo całej stronie, i wysyłaj przypadki odstające do człowieka. Test tej strony robi dokładnie to — porównuje linie prozy każdego wydania z angielskim źródłem i nie przechodzi powyżej 25% identycznych. Sfałszowane wydanie miało 87%; każde autentyczne tłumaczenie mieści się między 4% a 8%, co jest tym małym ogonem linii, które zbiegają się zgodnie z prawem — takich jak URL-e i cytowane wyjście programu. Te dwie populacje są od siebie na tyle daleko, że próg nie musi być precyzyjny.
Katalog to nie jedyna przetłumaczona rzecz¶
Dwie tutejsze awarie nie miały nic wspólnego z gettextem.
Przetłumaczenie nagłówka zmienia kotwicę z niego generowaną, więc każdy link międzystronicowy prowadzący do tej sekcji się psuje — po cichu i tylko w tym języku. Ta strona przypina angielską kotwicę do każdego nagłówka, a test wyprowadza oczekiwaną listę ze strony angielskiej.
A generator strony dostarcza tłumaczenia interfejsu dla sześćdziesięciu ośmiu języków, wśród których nie ma suahili ani irlandzkiego. Bez takiego tłumaczenia build nie degraduje się do angielskiego; include szablonu zawodzi i wydania w ogóle nie da się zbudować. Dwa własne pliki tego repozytorium istnieją po to, by wypełnić tę lukę.
Twoje narzędzia też mają błędy¶
Krok CI, który ta dokumentacja poleca do wychwytywania nieaktualnych
katalogów, pybabel update --check, nie jest w stanie wykonać tego zadania w
żadnym projekcie używającym pgettext lub npgettext. W Babelu 2.18.0 zgłasza
każdy katalog z msgctxt jako nieaktualny, przy każdym uruchomieniu.
Porównanie przechodzi przez Catalog.is_identical, które wyszukuje każdy
komunikat po kluczu, pod którym jest przechowywany — a dla komunikatu z
kontekstem tym kluczem jest para (id, context), której Catalog.get nie
przyjmuje. Wyszukiwanie nie zwraca nic i katalogi nigdy nie okazują się równe:
>>> from babel.messages.catalog import Catalog
>>> c = Catalog(locale="ja")
>>> c.add("Guide", "ガイド", context="navigation")
<Message 'Guide' (flags: [])>
>>> c.is_identical(c)
False
Został tu znaleziony przy próbie użycia, zgłoszony do upstreamu, a zastępczy test znajdziesz na stronie o produkcji.
Ogólna lekcja jest ta niewygodna: bramka, która zawsze świeci na czerwono, jest gorsza niż jej brak, bo zespół ją wyłącza. Sprawdź, czy twój test CI naprawdę potrafi przejść, zanim zaufasz mu, że zgłosi błąd.
Po co jest ta biblioteka, w jednym zdaniu¶
Większość tej strony to oceny, których żadne narzędzie nie przejmie. To, co narzędzie potrafi, to zagwarantować, że tłumaczenie nie może zmienić struktury zdania, które tłumaczy — nie może pominąć wartości, wymyślić jej, przeformatować ani sięgnąć do twoich obiektów — i powiedzieć o tym zdaniem, na podstawie którego osoba mająca to naprawić może działać. To całość tego, co ta biblioteka obiecuje, a reszta tej strony to sposób, w jaki tego dotrzymuje.