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ň