Przewodnik¶
Ta strona to dokumentacja czasu działania: wszystko, co kod aplikacji robi z tą biblioteką, gdy katalogi już istnieją. Jeśli pełna pętla — oznacz, wyodrębnij, przetłumacz, skompiluj, uruchom — jest Ci jeszcze obca, samouczek przechodzi ją raz w pięć minut; tworzenie i walidację katalogów opisuje Ekstrakcja, a to, jak zespół utrzymuje pętlę w ruchu — cykle aktualizacji, CI, platformy tłumaczeniowe — strona W produkcji.
Którego punktu wejścia użyć?¶
Pakiet udostępnia kilka sposobów tłumaczenia komunikatu, bo aplikacje wiążą język na kilka różnych sposobów. Wybieraj według tego, jak Twój program decyduje, w jakim jest języku:
| Twoja sytuacja | Użyj |
|---|---|
| Jeden język na cały proces — CLI, aplikacja desktopowa, skrypt | Translator, wywoływany jako _ |
| Jeden język na żądanie lub na zadanie asynchroniczne — aplikacja webowa | use_translations() wokół pracy, a potem tr() |
| Komunikat zdefiniowany w czasie importu — etykieta formularza, enum, stała | lazy_gettext() albo lazy_pgettext() |
| O brzmieniu decyduje liczba | ngettext() / npgettext(), w dowolnej z powyższych form |
| Renderowanie wzorca bez udziału katalogu | compile_template() |
Wszystko poniżej to te pięć przypadków, w tej kolejności.
Wiązanie katalogu¶
Zalecany kształt odzwierciedla klasowe użycie gettext: zwiąż standardowy
obiekt tłumaczeń raz i używaj wywoływalnego procesora jako _.
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
Funkcje na poziomie modułu podążają za nazwami z biblioteki standardowej i jej konwencją argumentów wyłącznie pozycyjnych:
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 i ntr to dokładne aliasy gettext i ngettext.
Język na żądanie¶
Framework webowy wybiera język per żądanie. Zwiąż tłumaczenia żądania z bieżącym kontekstem, a każde wywołanie na poziomie modułu rozwiąże się do tego języka, bezpiecznie przy współbieżnych żądaniach:
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) wiąże bez bloku with, dla frameworków,
które same zarządzają cyklem życia żądania; get_translations() odczytuje
bieżące wiązanie. Jawny argument translations= zawsze wygrywa z kontekstem,
a niezwiązany kontekst wraca do globalnie zainstalowanych funkcji gettext z
biblioteki standardowej. Opracowane przykłady dla Flaska i middleware ASGI
znajdują się na stronie
W produkcji.
Tłumaczenie odroczone¶
T-string przechwytuje swoje wartości zachłannie, co jest złe dla łańcucha zdefiniowanego w czasie importu — etykiety formularza, wartości enuma, stałej modułu — który musi wyrenderować się w języku aktywnym w chwili jego użycia.
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
LazyString renderuje się przez str(), format() i f-stringi oraz jest
równy swojemu wyrenderowanemu tekstowi.
Celowo niehashowalny
Tekst LazyString zależy od aktywnego języka, więc hash zmieniałby się
przy przełączeniu języka i po cichu psuł każdy zbiór lub słownik, który
go przechowuje. Jeśli potrzebujesz klucza, najpierw wywołaj str().
O strict decyduje się tam, gdzie komunikat jest zapisany, a nie tam, gdzie
się renderuje:
Łańcuch odroczony renderuje się tam, gdzie zostanie ostatecznie użyty — w
szablonie, w formularzu, w linii logu — a to miejsce rzadko wie, czy jest to
przebieg testowy, czy produkcja. Przekazanie strict=True przy definicji
pozwala zastosować ten sam wybór głośno w CI, łagodnie w
produkcji do łańcucha, który nie
renderuje się w miejscu swojego wywołania.
Formy liczby mnogiej zależą od licznika znanego w czasie działania, więc
renderuj je zachłannie przez ngettext tam, gdzie licznik jest znany.
Kilka języków naraz¶
Jedno żądanie często potrzebuje więcej niż jednego języka: strona renderowana dla czytelnika, która przy okazji kolejkuje powiadomienie na konto ustawione na inny, albo zestawienie cytujące każdego uczestnika w jego własnym. Wiązania zagnieżdżają się, a wyjście z wewnętrznego bloku przywraca zewnętrzne.
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
Przy liście odbiorców robotę wykonują łańcuchy odroczone: komunikat jest zapisany raz, w czasie importu, i renderuje się raz na język.
SUBJECT = lazy_gettext(t"Your order shipped")
for user in users:
with use_translations(load_translations(user.locale)):
send(user.email, str(SUBJECT))
Wiązanie jest ContextVar, a nie stosem trzymanym na współdzielonym
obiekcie, więc nakładające się żądania nie mogą przejąć swoich języków
nawzajem — łącznie z przypadkiem, w którym opuszczają swoje bloki w tej
samej kolejności, w jakiej do nich weszły, czyli tym przeplotem, który stos
rozstrzyga błędnie. Wczytywanie katalogu na każdy język jest tanie:
gettext.translation() parsuje każde .mo raz i wydaje kopie współdzielące
sparsowany katalog.
To, czy wątek roboczy dziedziczy wiązanie, zależy od builda
Goły threading.Thread albo ThreadPoolExecutor.submit startuje albo od
kopii kontekstu wywołującego, albo od pustego, a o tym, który z nich to
będzie, decyduje sys.flags.thread_inherit_context — domyślnie prawda w
buildach free-threaded, fałsz wszędzie indziej. Ten sam kod renderuje więc
związany język na 3.14t, a globalny dla procesu katalog na 3.14. Przekaż
kontekst, zamiast polegać na wartości domyślnej:
asyncio.to_thread już robi to za Ciebie.
Wartości zależne od locale¶
Ta biblioteka decyduje o tym, gdzie wartość pojawia się w przetłumaczonym
komunikacie. Nie lokalizuje samej wartości. {amount:,.2f} to pythonowa
specyfikacja formatu o ustalonym zachowaniu — przecinek co trzy cyfry i kropka
przed częścią dziesiętną — i daje te same znaki niezależnie od tego, w jakim
języku jest komunikat:
Po niemiecku ta liczba zapisuje się 1.234,50, po francusku 1 234,50, a
hindi grupuje 1234567 jako 12,34,567, a nie 1,234,567. Liczby, waluty,
daty, godziny i jednostki należą do Babel. Najpierw sformatuj
wartość, potem wstaw gotowy łańcuch:
from babel.numbers import format_currency
total = format_currency(amount, "EUR", locale=locale)
tr(t"Your order comes to {total}")
W komunikacie z licznikiem liczba pełni dwie role — wybiera formę liczby mnogiej i pojawia się w tekście — a lokalizowana jest tylko ta druga. Zachowaj surowy licznik do wyboru formy, a do wyświetlenia przekaż sformatowany łańcuch:
from babel.numbers import format_decimal
shown = format_decimal(n, locale=locale)
_.ngettext(t"One file", t"{shown} files", n)
Formatowanie przed wywołaniem jest też tym, co trzyma specyfikację formatu poza katalogiem: tłumacz widzi gotowy kawałek tekstu, a nie liczbę wraz z instrukcjami jej renderowania.
Co się dzieje, gdy katalog jest błędny¶
Jeśli symbole zastępcze tłumaczenia nie pasują do źródła — brakujące, nieznane albo przeformatowane pole, które prześlizgnęło się przez walidację, z ręcznie edytowanego MO, katalogu od dostawcy albo potoku pomijającego checker — domyślnym zachowaniem jest wyrenderowanie komunikatu źródłowego, a nie zgłoszenie wyjątku. To odzwierciedla kontrakt samego gettext, że zły katalog nigdy nie psuje aplikacji.
Przy Hello {name} przetłumaczonym jako こんにちは {nombre} renderowanie
się udaje, a do loggera gettext_tstrings trafia jedno ostrzeżenie:
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
Ostrzeżenie pojawia się raz na komunikat i wzorzec, nie raz na renderowanie, więc uszkodzony wpis katalogu nie zalewa loga.
Do testów i CI możesz włączyć głośne niepowodzenia:
strict = Translator(translations, strict=True)
tr(t"Hello {name}", translations=translations, strict=True)
To samo wyszukanie wtedy zgłasza wyjątek, niosący to samo zdanie bez połowy „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
Te komunikaty są pisane dla tego, kto może na nie zareagować, a przy problemie
z katalogiem jest to częściej tłumacz niż programista — więc tam, gdzie symbol
zastępczy wygląda na obecny, a nie jest, komunikat wyjaśnia dlaczego, zamiast
powtarzać, że go brakuje. Nawiasy klamrowe pełnej szerokości, podwojone
{{name}}, niewidoczna twarda spacja, cyrylicka litera wśród łacińskich:
każdy z tych przypadków ma własne brzmienie, wypisane z przykładami na stronie
Dla tłumaczy. Ta strona jest
napisana tak, by przekazać ją osobie edytującej .po.
Renderowanie wzorca bez katalogu¶
compile_template udostępnia tę samą maszynerię poziom niżej: zamienia
t-string na jego msgid plus związany zestaw wartości i renderuje dowolny
wzorzec, który mu podasz.
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 waliduje według tych samych reguł i przy niedopasowaniu zawsze
zgłasza wyjątek. Nie ma tu trybu łagodnego: łagodność istnieje po to, by
wyszukanie w katalogu mogło zdegradować się do tekstu źródłowego, a
wzorzec, który przekazujesz samodzielnie, nie ma z czego się degradować.
Bezpieczeństwo i zakres¶
To jest poprawne:
Te są odrzucane celowo:
Najpierw oblicz znaczącą wartość:
Ograniczenie daje stabilne klucze katalogu, daje tłumaczom użyteczne nazwy i powstrzymuje przetłumaczony łańcuch przed staniem się językiem wyrażeń.
Gwarancja dotyczy struktury i formatowania: tłumaczenie nigdy nie jest wykonywane i nigdy nie może dodać dostępu do atrybutów, wywołań, konwersji ani specyfikacji formatu. Dwie rzeczy pozostają odpowiedzialnością wywołującego, dokładnie tak jak przy stdlibowym gettext — escapowanie wyrenderowanego wyniku dla jego celu (HTML, powłoka, terminal) oraz integralność katalogu, bo wrogi katalog może powtarzać symbol zastępczy, by zwielokrotnić rozmiar wyniku, co jest nieodłączne dla każdego i18n opartego na symbolach zastępczych.