Jak psát lokalizované programy -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= If the Tao is great, then the operating system is great. If the operating system is great, then the compiler is great. If the compiler is great, then the application is great. If the application is great, then the user is pleased and there is harmony in the world. The Tao gave birth to machine language. Machine language gave birth to the assembler. The assembler gave birth to the compiler. Now there are ten thousand languages. Each language has its purpose, however humble. Each language expresses the Yin and Yang of software. Each language has its place within the Tao. But do not program in COBOL if you can avoid it. -- Geoffrey James, "The Tao of Programming" Michael Mráka a Vladimír Michl, 9. června 1998 Tento článek by měl přinést (pokud možno jednoduchý) návod, jak psát programy snadno použitelné v různých jazykových mutacích. Prvním předpokladem pro správné chování takového programu je fungující lokalizace. Většina současných distribucí Linuxu je vystavěna nad knihovnami glibc, které by již s lokalizací neměly mít problémy. Zda je tomu skutečně tak, lze zkontrolovat pohledem do adresáře /usr/share/locale, který by měl obsahovat podadresář cs_CZ* (na mém počítači je to cs_CZ.ISO-8859-2) a v něm soubory LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME a adresář LC_MESSAGES. Pokud tomu tak není, ale v systému existuje alespoň definiční soubor cs_CZ (bývá standardně zahrnut do balíku glibc jako /usr/share/i18n/locale/cs\_CZ), je možné lokalizační soubory pomocí příkazu localedef -i cs_CZ -f ISO-8859-2 cs_CZ.ISO-8859-2 vygenerovat. Pokud systém nevlastní ani uvedený definiční soubor, je vhodné začít například na adrese ftp://ftp.fi.muni.cz/pub/localization/locale, kde najdete více informací :-). Nyní přistupme k tvorbě vlastního programu lcexample. Nejprve je potřeba program přimět, aby používal lokalizaci; to provedeme pomocí funkce setlocale(LC\_ALL, "") (viz funkce init()) - parametry uvedené v příkladu sdělí programu, aby použil nastavení určené proměnnými prostředí LC_* a LANG. Dále již stačí používat standardní knihovní funkce pro práci s lokalizovaným prostředím. Pro jednotlivé kategorie jsou k dispozici následující funkce: LC_COLLATE - lexikografické třídění (viz funkce lc_collate()). - int strcoll(const char *s1, const char *s2) - porovnávání řetězců s ohledem na lokalizaci; syntaxe a návratové hodnoty odpovídají funkci strcmp(). - size_t strxfrm(char *dest, const char *src, size_t n) - transformace řetězce tak, aby porovnání dvou takto vzniklých řetězců pomocí strcmp() mělo stejný výsledek, jako porovnání původních pomocí strcoll(). LC_CTYPE - rozdělení znaků do tříd (malá a velká písmena, oddělovače, čísla, bílá místa, ...) (viz funkce lc_ctype()). - int isalpha (int c) - písmena - int isascii (int c) - 7-bitová unsigned char hodnota z ASCII. - (podobně iscntrl(), isdigit(), isgraph(), islower(), isprint(), ispunct(), isspace(), isupper(), isxdigit()) LC_TIME - časové údaje (viz funkce lc_time()). - size_t strftime(char *s, size_t max, const char *format, const struct tm *tm) - nahradí sekvence %x v řetězci format časovými údaji podle časové zóny a zvolené lokalizace. Např. %A = den v týdnu, %a = zkratka dne v týdnu, %B = měsíc, ... "Nahrazuje" funkce ctime(), asctime(). LC_NUMERIC - formátování čísel (viz funkce lc_numeric()). - int printf(const char *format, ...) - pokud %-sekvence pro čísla obsahují apostrof (např. %'d) vytiskne oddělovač desetinných míst a tisícovek s ohledem na lokalizaci (nefunguje až v libc.5). LC_MONETARY - formátování peněžních informací (viz funkce lc_monetary()). - ssize_t strfmon(char *s, size_t maxsize, const char *format, ...) - nahradí sekvence %x peněžními údaji podle zvolené lokalizace. Ve formátovacím řetězci lze zadat: %i = mezinárodní symbol měny s peněžní částkou (CZK), %n = národní symbol měny s peněžní částkou (Kč). Peněžní částky musí být čísla typu double nebo long double. O této funkci se bohužel v info dokumentaci nedozvíte, navíc v libc.5 tato funkce vůbec nebyla. Více informací naleznete v souboru strfmon.man3. Pokud by pro některou z kategorií neexistovaly standardní funkce, je možné formátování udělat "ručně" za pomocí struktury lconv získané z funkce localeconv(). Samostatnou kapitolu pak tvoří překlad (nejen chybových) zpráv programu. V systémech s libc.6 (glibc) se provádí pomocí balíku gettext. (Ve starších systémech s libc.5, případně na jiných operačních systémech se k tomuto účelu používají funkce gencat; více viz ftp://ftp.fi.muni.cz/pub/linux/local/czech-howto/.) To, že program má používat katalog s českými překlady zpráv (kategorie LC_MESSAGES), už akceptoval při volání setlocale(); je ovšem potřeba ještě specifikovat jméno katalogu. To uděláme na začátku programu voláním funkcí bindtextdomain(package, localedir) - cesta ke katalogu - a textdomain(package) - jméno katalogu, který používáme (viz funkce init()). Všechny řetězce, které mají mít svůj ekvivalent v katalogu, je nutné "prohnat" funkcí gettext() (viz funkce invite()). Ještě zbývá napsat české překlady zpráv. Nejprve je potřeba pomocí programu xgettext vygenerovat katalog obsahující všechny originální zprávy. Ve vzorovém programu byl k tomu použit příkaz xgettext --default-domain=lc_example --output-dir=. --add-comments --keyword=_ lc_example.c kde --default-domain=lc_example -- ulož výsledek do souboru lc_example.po --output-dir=. -- cílový adresář --add-comments -- napiš, kde se jednotlivé řetězce vyskytují --keyword=_ -- místo gettext použij klíčové slovo _ Nyní lze ke každému řetězci připsat za klíčové slovo msgstr příslušný překlad. Pokud by váš katalog byl větší je velmi výhodné používat na překlad Emacs v tzv. po-módu (potřebný kód do Emacsu je přiložen v balíku gettext). Hlavní výhoda je, že po-mode umí na požádání zobrazit přímo místo zdrojového kódu, kde se zpráva nachází (klávesa s). Dále umožňuje kontrolu, zda jsou zprávy dobře přeloženy (zda sedí počet formátovacích značek, zda zprávy začínají a končí novým řádkem stejně), automatické vyplnění úvodní hlavičky i s datem revize (vše Shift-v). Samozřejmě editovat překlad lze pomocí klávesy Enter, ukončení Ctrl-c Ctrl-c. Pokud se vám v katalogu objeví zpráva označená jako "fuzzy", pak by tato zpráva měla být neúplně přeložena. Toto označení lze odstranit klávesou Tab. Zprávu lze jako "fuzzy" označit pomocí BackSpace. Pro instalaci po-módu je třeba umístit soubor po-mode.el} ( po-mode.elc) do adresáře /usr/share/emacs/site-lisp a následně do souboru HOME/.emacs přidat: (setq auto-mode-alist (cons '("\\.po[tx]?\\'\\|\\.po\\." . po-mode)\ auto-mode-alist)) (autoload 'po-mode "po-mode") Máme tedy přeložen celý katalog a pomocí msgfmt -v -o lc_example.mo lc_example.po ho přeložíme do binární podoby. Pak už stačí jen přesunout lc_example.mo do adresáře /usr/share/locale/cs_CZ*/LC_MESSAGES, zkompilovat program a vyzkoušet. gcc -o lc_example lc_example.c LC_ALL=cs_CZ.ISO-8859-2 ./lc_example Dobrý den! Setříděná slova: cikáda chroust pláně \ plaňka Plášil Úterý - to nesnáším! Čísla: 12 345 678 1 234 567,125000 Napište nějaký text, prosím: Umíš česky, ježku? ++++_+++++-_+++++-_ Zapomněl jste zaplatit 1 234,00Kč autorovi \ tohoto programu. LC_ALL=en_US ./lc_example Hello! Sorted words: chroust cikáda Plášil \ pláně plaňka Tuesday - I hate it! Numbers: 12,345,678 1,234,567.125000 Wrote some text, please: Umíš česky, ježku? ++--_-++++-_++-++-_ You have forgotten to pay 1,234.00 to the \ author of this program. Je vidět, že funkce strfmon() zatím není dokonalá (zbytečná mezera na začátku, mezi částkou a Kč není mezera, ač by podle definice v souboru cs_CZ být měla), stejně jako funkce printf() (desetinná čísla nejsou dělena do skupin). Časem se ale tyto chyby určitě vyladí. A na závěr ještě jedna užitečná funkce, která se může při lokalizaci hodit a to #include <langinfo.h> char *nl_langinfo(nl_item item); Tato funkce vrátí hodnotu, kterou po ní požadujeme - např: první den v týdnu nl_langinfo(DAY_1) nebo první měsíc v roce nl_langinfo(MON_1). Argumenty které můžete požadovat naleznete v souboru /usr/include/langinfo.h. výheň