Dobre praktyki internacjonalizacji na przykładzie generatora scamu w Pythonie

Marta Bartnicka

Zdarzyło mi się uczestniczyć jesienią 2020 w bardzo dobrym szkoleniu z podstaw programowania w języku Python, jaki prowadził mój znakomity kolega Wojciech Ł. Birula dla Fundacji CODE:ME. Napisanie niniejszego tekstu sprowokowała krótka acz treściwa dyskusja nad kwestią łączenia tekstów, którymi program komunikuje się z użytkownikiem, a konkretnie przykład, który pojawił się jako bodaj trzeci na 3-godzinnych zajęciach:

zakupione_przedmioty=3
print ("Dziękuję, że kupiłeś u mnie "+str(zakupione_przedmioty)+" przedmioty")

Przyjrzyjmy się, na czym polega internacjonalizacja takich tekstów, czyli stworzenie możliwości zlokalizowania ich na różne języki i wykorzystania w różnych krajach.

Za przykład posłuży program do generowania treści scamu (maili służących naciąganiu odbiorców na niewielkie wpłaty za pomocą opowieści o ogromnych kwotach, w których mieliby zdobyć udział). Wyimaginowany autor tego programu pisze teksty w języku polskim, bo jeszcze nie wie, że produkt jego pracy ma być tłumaczony na inne języki.

Załóżmy, że na początek wystarczą dwa wzorce maila o różnej treści, przy czym każdy z nich ma wspominać o kwocie z zakresu od 1 do 5 milionów dolarów. Co robi programista? Zwykle zaczyna tak:

import random

odziedziczona_suma=random.randrange(1000000,5000000,1000000)
print ("Jestem wdową po prezydencie Mali. Mój mąż zostawił mi "+str(odziedziczona_suma)+" dolarów spadku.")

odziedziczona_suma=random.randrange(1000000,5000000,1000000)
print ("Jestem następcą tronu pewnego państwa. Środki w wysokości "+str(odziedziczona_suma)+" dolarów pozostają do mojej dyspozycji.")

Co tu się dzieje?

  • Tworzone są losowo wybrane liczby w zakresie od 1 do 5 milionów (z dokładnością do miliona).
  • Generowane są dwa teksty o zadanej treści, w środek każdego z nich wstawiania jest wylosowana kwota.

Efekt pracy programu wygląda jak naturalny tekst w języku polskim:

Jestem wdową po prezydencie Mali. Mój mąż zostawił mi 3000000 dolarów spadku.
Jestem następcą tronu pewnego państwa. Środki w wysokości 4000000 dolarów pozostają do mojej dyspozycji.

WTEM! pojawia się możliwość rozsyłania scamu na inne rynki niż polski. Podstawowe informacje o internacjonalizacji i lokalizacji programów w Pythonie można znaleźć w dokumentacji – nie będziemy ich tu powielać; przyjrzymy się, jakie niejawne (nieświadome?) założenia z powyższego przykładu warto zmienić, żeby lokalizacja była prostsza.

Co tu jest trudnego do lokalizacji?

  • Teksty są wprowadzone bezpośrednio w kodzie. Do tłumaczenia trzeba je będzie jakoś wydobyć (czasem robi się to poprzez gettext, czasem przez copy-paste do Excela).
  • Dodanie kolejnych wersji językowych programu będzie wymagało zmian w kodzie, wprowadzających kolejne stringi w zależności od tego, jaki język jest wybrany. Ten element programu pomijamy, żeby go nie zaciemniać.
  • Szyk zdania jest polski. Nie w każdym języku liczba znajduje się akurat na tej pozycji w zdaniu. W niektórych językach nazwa lub symbol waluty muszą znaleźć się przed liczbą, a nie po niej.
  • Informacja, że chodzi o dolary, jest nieprecyzyjna – w naszej strefie można domniemywać, że to dolary USA, ale w takiej np. w Australii jest własna waluta o tej samej nazwie i innej wartości.
  • Co więcej: jeśli zechcemy zlokalizować tę aplikację na inne rynki scamowania, nawet bez jej tłumaczenia, to zamiana waluty np. na funty szterlingi wymaga wyedytowania wszystkich (no dobrze… obydwu) tekstów w kodzie.

Przyjrzyjmy się, jak nasz programista może poprawić swoje dzieło zgodnie z zasadami internacjonalizacji. Poprawki omówimy od najłatwiejszych.

1. Przeredagowanie komunikatów w taki sposób, żeby kwota znalazła się na końcu.

import random

odziedziczona_suma=random.randrange(1000000,5000000,1000000)
print ("Jestem wdową po prezydencie Mali. Mój mąż zostawił mi w spadku "+str(odziedziczona_suma)+" dolarów.")

odziedziczona_suma=random.randrange(1000000,5000000,1000000)
print ("Jestem następcą tronu pewnego państwa. Dysponuję środkami na kwotę "+str(odziedziczona_suma)+" dolarów.")

Co wnosi taka zmiana?

  • Komunikaty łatwiej będzie przetłumaczyć na różne języki: są teraz złożone z 2 kawałków (tekst+liczba), a nie – jak dotąd – z 3 kawałków (tekst+liczba+tekst).
  • Bardziej zaawansowanym rozwiązaniem, którego tu nie omawiamy, jest przerobienie tekstów w taki sposób, żeby liczba była dostępna dla tłumaczy jako zmienna, którą mogą przenieść w inne miejsce w zdaniu.

Wynik działania programu nadal wygląda jak normalny tekst w języku polskim:

Jestem wdową po prezydencie Mali. Mój mąż zostawił mi w spadku 4000000 dolarów.
Jestem następcą tronu pewnego państwa. Dysponuję środkami na kwotę 2000000 dolarów.

2. Zastąpienie potocznej nazwy waluty uniwersalnym symbolem.

import random
znak_waluty="USD"

odziedziczona_suma=random.randrange(1000000,5000000,1000000)
print ("Jestem wdową po prezydencie Mali. Mój mąż zostawił mi w spadku "+str(odziedziczona_suma)+" "+znak_waluty+".")

odziedziczona_suma=random.randrange(1000000,5000000,1000000)
print ("Jestem następcą tronu pewnego państwa. Dysponuję środkami na kwotę "+str(odziedziczona_suma)+" "+znak_waluty+".")

Dolary zostały zastąpione symbole USD, który dodatkowo wrzuciliśmy w zmienną dołączaną do komunikatów.

Dlaczego tak jest lepiej?

  • Nawet bez tłumaczenia tekstów na inne języki można teraz generować częściowo zlokalizowany scam z różnymi walutami, zmieniając tylko jedną zmienną.
  • W przypadku tłumaczenia będzie wiadomo dokładnie, o które dolary chodzi.
  • Uwaga na marginesie: w tym przypadku nie próbujemy powiązać waluty z wartością locale określającą kraj/region i język. Zakładamy, że walutę w scamie dla danego kraju wybierze tłumacz – w taki sposób, żeby brzmiała imponująco 🙂
  • Druga uwaga na marginesie: obsługa locale zapewni natomiast format liczb właściwy dla kraju i języka.
  • Bonus: Jeśli zleceniodawca zdecyduje się uwiarygodnić scam przez losowanie kwot z większą dokładnością niż do miliona, to unikniemy w ten sposób walki z wariantami odmiany słowa “dolarów” dla liczb zakończonych 1, 2, 3, 4, 5 i więcej. W wielu językach innych niż polski takie rozróżnienie występuje, przy czym nie zawsze różne formy są rozłożone tak samo jak u nas.

Wynik działania programu wygląda tak jak poprzednio – zmiany są tylko w kodzie programu.

3. Wyjęcie tekstów do osobnego pliku.

import random

from xml.dom import minidom
mydoc = minidom.parse('scam.xml')
waluty = mydoc.getElementsByTagName('waluta')
zajawki = mydoc.getElementsByTagName('zajawka')

for elem in zajawki:
    odziedziczona_suma=random.randrange(1000000,5000000,1000000)
    print(elem.firstChild.data+" "+str(odziedziczona_suma)+" "+waluty[0].firstChild.data+".")

Duża zmiana! Program nie zawiera już żadnych tekstów do wyświetlenia, bo zamieszkały w pliku scam.xml razem ze znakiem waluty:

<scam>
    <waluty>
        <waluta>USD</waluta>
    </waluty>
    <zajawki>
	<zajawka>Jestem wdową po prezydencie Mali. Mój mąż zostawił mi w spadku</zajawka>
	<zajawka>Jestem następcą tronu pewnego państwa. Dysponuję środkami na kwotę</zajawka>
	<zajawka>Jestem emerytowanym kontradmirałem US Navy. Obecnie mój roczny dochód wynosi</zajawka>
    </zajawki>
</scam>

Dlaczego warto było zrobić tę zmianę:

  • Jeśli chcemy wysłać teksty do tłumaczenia, wystarczy teraz przekazać tłumaczom plik scam.xml – nie trzeba niczego kopiować z kodu ani potem do niego wklejać. Duuużo mniejsze ryzyko błędu.
  • Zamiast wywoływać kolejne teksty, program wczytuje ich tyle, ile jest w pliku scam.xml, i dla każdego generuje losową kwotę oraz dopisuje walutę. Jak widać powyżej – można dodawać do pliku scam.xml kolejny tekst, a program nie wymaga zmian.
  • Trzeba oczywiście dodać obsługę różnych plików scam.xml dla różnych wersji językowych, ale to pomijamy w niniejszym przykładzie, podobnie jak wcześniej pominęliśmy wybieranie tekstów zależnie od wyboru języka.
  • Podobną rolę, co plik scam.xml, spełnia gettext – o ile wszelkie teksty do komunikacji z użytkownikiem są wydzielane z kodu programu do pliku zasobów od samego początku, a nie dopiero kiedy trzeba pomyśleć o tłumaczeniu.

Wynik działania programu nadal wygląda tak jak przedtem, tyle że generuje się trzeci komunikat:

Jestem wdową po prezydencie Mali. Mój mąż zostawił mi w spadku 3000000 USD.
Jestem następcą tronu pewnego państwa. Dysponuję środkami na kwotę 4000000 USD.
Jestem emerytowanym kontradmirałem US Navy. Obecnie mój roczny dochód wynosi 1000000 USD.

Podsumowując, dobre praktyki internacjonalizacji to:

  • jak najmniejsza fragmentacja tekstów (np. tekst+liczba),
  • jednoznaczne, łatwe do zmiany ustawienia lokalne (np. USD),
  • teksty podlegające tłumaczeniu wydzielone do pliku (np. XML).