Python, gettext i tłumaczenia z kontekstem

Miniaturka wpisu podzielona na skos na dwie części. Lewą górną zajmuje kod w Pythonie, prawą dolną okno aplikacji pogodowej Mousam. Nad tym fioletowy pasek z białym napisem „Zagwozdki tłumacza”.
,

Dzisiaj krótki tekst, który może się przydać osobom przygotowującym tłumaczenie programu w Pythonie przy użyciu biblioteki gettext.

Zabrałem się niedawno za tłumaczenie kolejnej aplikacji, tym razem padło na wspominaną w ostatnim odcinku Programów Obadania Wartych apkę pogodową o nazwie Mousam.

Dość szybko natrafiłem na problem. Niektóre angielskie słowa, takie jak „hight”, „moderate” i „low” są używane w kilku miejscach i w zależności od kontekstu, należałoby przetłumaczyć używając różnych form gramatycznych.

Angielskie stringi są wpisane bezpośrednio w kod, a ich tłumaczeniem zajmuje się gettext na podstawie plików .po. Przykład:

def classify_uv_index(uv_index):
    if uv_index <= 2:
        return _("Low")
    elif uv_index <= 5:
        return _("Moderate")
    elif uv_index <= 7:
        return _("High")

Znak podkreślenia to wywołanie gettext z podanym stringiem do przetłumaczenia. Narzędzie o nazwie xgettext zbiera wszystkie te ciągi razem z informacją o ich położeniu (nazwa pliku i numer linii) w jeden plik mousam.pot, który służy jako podstawa do wszystkich tłumaczeń:

#: src/mousam.py:276 src/mousam.py:286 src/weatherData.py:120
#: src/weatherData.py:133 src/weatherData.py:142
msgid "High"
msgstr ""

#: src/mousam.py:277 src/mousam.py:287 src/weatherData.py:116
#: src/weatherData.py:129 src/weatherData.py:138
msgid "Low"
msgstr ""

Jak widać, każdy ze stringów z tego fragmentu jest użyty kilka razy, w dwóch różnych plikach.

To był pierwszy raz, gdy zajmowałem się tłumaczeniem już na etapie przygotowywania plików do przełożenia. Wszystkiego uczyłem się „po drodze” i najpierw miałem nadzieję, że różne wersji tłumaczenia tego samego ciągu można zdefiniować po prostu w pliku .po jeżeli będą miały podaną inną lokalizację.

Niestety przy budowaniu aplikacji wyskoczyły błędy duplikacji stringów i musiałem poszukać innego rozwiązania.

Zacząłem czytać dokumentację gettext i znalazłem informację o funkcji pgettext, pozwalającej na dodanie kontekstu dla ciągu do tłumaczenia. Przerobiłem odpowiednie fragmenty kodu zgodnie z tym, co wyczytałem, podając najpierw kontekst, a potem wersję angielską.

def classify_uv_index(uv_index):
    if uv_index <= 2:
        return gettext.pgettext("uvindex", "Low")
    elif uv_index <= 5:
        return gettext.pgettext("uvindex", "Moderate")
    elif uv_index <= 7:
        return gettext.pgettext("uvindex", "High")

Wygenerowałem xgettextem świeży plik mousam.pot, podając informację, jakich słów kluczowych ma szukać:

xgettext --keyword=_ --keyword=pgettext:1c,2 --output=po/mousam.pot -f po/POTFILES

Teraz w mousam.pot miałem już informacje o kontekście powtarzających się ciągów oznaczonych przez msgctxt:

#: src/weatherData.py:118
msgctxt "uvindex"
msgid "Low"
msgstr ""

#: src/weatherData.py:120
msgctxt "uvindex"
msgid "Moderate"
msgstr ""

#: src/weatherData.py:122
msgctxt "uvindex"
msgid "High"
msgstr ""

Na jego podstawie zaktualizowałem plik polskiego tłumaczenia przy użyciu:

msgmerge -D po -U pl.po mousam.pot

I mogłem już przetłumaczyć nowe stringi.

Po tych zmianach aplikacja budowała się bez problemów i odpalała z polskim tłumaczeniem. Niestety, wszystkie stringi, które w pl.po miały podany msgctxt wyświetlały się bez tłumaczenia, w wersji podanej w pliku .py.

Tu mnie wcięło na jakiś czas i szukałem pomocy w fediwersum i na discordzie. Nawet napisałem malutki programik, żeby sprawdzić, czy to, co robię w ogóle działa. Działało, a w tłumaczonej aplikacji już nie.

Przy okazji, żebym nie musiał w przyszłości wyszukiwać w dokumentacji: do wygenerowania plików .mo z tłumaczeniem używalnym przez aplikacje służy polecenie msgfmt po/pl.po -o po/pl/LC_MESSAGES/nazwa_apki.mo.

Wreszcie na Polskim forum Pythona dostałem podpowiedź, że może brakować prawidłowego ustawienia opcji biblioteki gettext. Okazało się, że brakowało mi dwóch linijek:

gettext.bindtextdomain('mousam', localedir)
gettext.textdomain('mousam')

Wcześniej tego nie zauważyłem, bo w mousam.in są prawie identyczne linijki dla biblioteki locale (locale.bindtextdomain i locale.textdomain), a ja po pobieżnym rzucie okiem wziąłem je za te od gettextu. Po dodaniu i ponownym zbudowaniu aplikacji wszystko zaczęło działać i ciągi ze zdefiniowanym kontekstem były również przetłumaczone.

Zachęcony tym sukcesem przejrzałem jeszcze cały program, wyszukałem stringi, które nie były oznaczone jako przetłumaczalne i dodałem do nich wywołania gettext, zaktualizowałem plik .pot i przetłumaczyłem nowe ciągi.

Okno programu Mousam pokazującego aktualną pogodę: mgła, 2 stopnie, odczuwalna -1,8, lekki wiatr z południowego zachodu.

Na koniec wysłałem wrzuciłem wszystkie te zmiany do forka Mousam, przygotowałem pull request i po zatwierdzeniu przez developera miałem robotę z głowy, a inne osoby tłumaczące ten program będą mogły wykorzystać to, co przygotowałem do poprawy swoich przekładów.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *