Lokalizace softwaru neznamená jen překlad textů do příslušného jazyka, ale i další přizpůsobení aplikace místním podmínkám. S tím úzce souvisí formátování výstupu – jinak budete formátovat datum nebo měnu pro Angličany a jinak pro Čechy. A konec konců, lokalizační techniky se nám mohou hodit a při vývoji jednojazyčné aplikace – texty externalizované do zvláštních souborů se snáze upravují a udržují – např. když aplikaci používají různí zákazníci a každý má trochu jinou terminologii.
Jen pro připomenutí: jako obvykle si z Mercurialu stáhneme aktuální verzi zdrojových kódů k dnešnímu dílu seriálu:
$ hg pull $ hg up "5. díl"
Případně si je můžete stáhnout jako bzip2 archiv přes web.
Lokalizace
S lokalizací aplikace je dobré začít co nejdříve, abychom později nemuseli procházet všechny stránky a hledat v nich nepřeložené texty (s tím by nám mohly pomoci nástroje v IDE, ale přiznám se, že raději provádím překlad ručně). Ještě je třeba poznamenat, že překlady se týkají jednak aplikace (texty tlačítek, položky v nabídkách, hlášky atd.) a jednak dat – obsahu (typicky v databázi). V dnešním díle se budeme zabývat pouze lokalizací aplikace.
Properties soubory
Pro ukládání lokalizovaných textů se v Javě používají tzv. .properties
soubory. Jedná se o celkem obyčejné textové soubory se strukturou klíč=hodnota, ale v poněkud neobvyklém „kódování“ – místo např. jazyk=čeština
v nich najdeme jazyk=u010Deu0161tina
. Dříve se ke konverzi používal nástroj native2ascii
a bylo potřeba udržovat dvě verze souboru (nativní kódování a escapované). V současnosti je tento postup zbytečný – vyspělé editory a IDE umí pracovat přímo s escapovaným souborem a konverze není potřeba. Pokud IDE nemáte, můžete použít prográmek Properties Editor.
Chceme-li do .properties
souboru vložit text obsahující konec řádku, použijeme n
. Pokud naopak chceme zapsat nějaký dlouhý text, který má být ve výsledku na jednom řádku, můžeme řádek .properties
souboru ukončit zpětným lomítkem a pokračovat na řádku dalším. XML to sice není, ale jelikož sem nijak složité struktury nezadáváme, dá se s těmito konvencemi vystačit.
Na balík lokalizovaných textů se odkazujeme podobně jako na třídu – např. cz.frantovo.nekurak.preklady
a také tento soubor vedle tříd ukládáme. Výchozí jazyková mutace se nachází v souborupreklady.properties
a další jazyky v preklady_cs.properties
, preklady_en.properties
atd. Můžeme používat označení jazyka (cs, en) nebo upřesnit i stát (cs_CZ, en_US).
Vložení lokalizovaného textu
V JSP stránce, kterou chceme lokalizovat, musíme nejdřív importovat příslušný jmenný prostor:
xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
nastavit balík s překlady:
<fmt:setBundle basename="cz.frantovo.nekurak.preklady" scope="application"/>
a potom už jen vkládáme lokalizované texty pomocí této značky:
<fmt:message key="klic"/>
Který jazyk se použije?
Volba jazyka se odvíjí od hlavičky Accept-language
, kterou posílá prohlížeč v HTTP požadavku. Může v ní být uvedeno více jazyků (podle preferencí uživatele) a na serveru se vybere ten nejvhodnější (případně výchozí).
Jelikož někteří uživatelé mají prohlížeč špatně nastavený nebo pracují na cizím počítači, hodí se mít možnost tyto HTTP hlavičky přebít a nastavit jazyk programově (typicky uživatel klikne na odkaz a tím si zvolí požadovaný jazyk). Do JSP stránky si proto přidáme:
<c:if test="${param.jazyk != null}"> <fmt:setLocale value="${param.jazyk}"/> </c:if>
A uživatel si pak může nastavit jazyk pomocí GET parametru. Parametr není potřeba ošetřovat, protože pokud uživatel zadá neexistující jazyk, použije se ten výchozí a nic se neděje. Chyba nastane jen v případě, že by se pokoušel zadat něco jako cs_
nebo _xxx
. Tím dojde k výjimce IllegalArgumentException: Empty country component
nebo Missing language component
, která vyústí v zobrazení chybové stránky (500 Interní chyba serveru). Nicméně nehrozí, že by tímto způsobem útočník získal přístup k nějakým souborům.
Všimněte si také, že server nám k lokalizované stránce automaticky doplnil hlavičku HTTP odpovědi Content-Language
s příslušným jazykem.
Zaujaly vás možnosti Javy a chcete se dozvědět o tomto jazyce víc? Akademie Root nabízí školení Základy programovacího jazyka Java a Pokročilejší kurz jazyka Java, na nichž se naučíte, jak tento multiplatformní objektově orientovaný jazyk používat.
Formátování
Úlohou prezentační vrstvy je převést data (objekty) na tvar vhodný k zobrazení uživateli. Zatímco na nižších vrstvách pracujeme s objekty (např. instance java.util.Date
) v (X)HTML potřebujeme jejich textový tvar. S prostým výstupem metody toString()
si většinou nevystačíme, tak použijeme sofistikovanější přístup. Nejčastěji budeme řešit formátování data/času a čísel.
Číslo naformátujeme pomocí této značky:
<fmt:formatNumber value="1234567.123456" pattern="###,###.###"/>
Výsledkem je číslo zaokrouhlené na tři desetinná místa a s oddělovači tisíců. Automaticky se dodržují pravidla daného jazyka, a tak pro češtinu bude oddělovačem tisíců mezera a desetinných míst čárka, zatímco pro angličtinu to bude čárka a tečka.
Pro naformátování data a času použijeme takovéto značky:
<fmt:formatDate value="${datum}" pattern="dd.MM. yyyy HH:mm:ss"/> <fmt:formatDate value="${datum}" pattern="dd.MM. yyyy"/>
Více viz soubor formatovani.jsp
. Vyzkoušejte různé jazykové verze: formatovani.jsp?jazyk=cs a formatovani.jsp?jazyk=en
Chybějící hlavička Accept-language
Lidé se někdy ptají, JSTL fmt tag does not work in IE? nebo formatDate doesn’t work for Googlebot. Toto „záhadné“ chování aplikace je způsobeno poněkud zvláštní chybou. Pokud klient nepošle v HTTP požadavku hlavičku Accept-language
(byť třeba s neexistujícím jazykem), nefunguje formátování data a čísel. A tak se můžete setkat s tím, že se
<fmt:formatNumber value="1234567890" pattern="###,###.###"/>
naformátuje jako „1234567890“ místo požadovaného „1 234 567 890“ (případně „1,234,567,890“ pro angličtinu). Přestože lokalizace textů funguje správně (použije se výchozí jazyk).
Chybu můžete ošetřit tímto kouskem kódu:
<c:if test="${header['Accept-language'] == null}"> <fmt:setLocale value="cs"/> </c:if>
Nebo – lépe – tímto nastavením ve web.xml
:
<context-param> <param-name>javax.servlet.jsp.jstl.fmt.fallbackLocale</param-name> <param-value>cs</param-value> </context-param>
Přibalení zdrojových kódů
Aplikace, kterou v tomto seriálu vyvíjíme, je svobodný software licencovaný pod Affero GPL. Uživatelům dáme možnost, aby si mohli stáhnout zdrojové kódy přímo z aplikace (ať už bude nasazena kdekoli). Vytvářet ZIP soubor se zdrojáky ručně by byla otrava, a tak si tento proces zautomatizujeme pomocí antovského skriptu.
Do souboru build.xml si přidáme úlohu:
<target name="-pre-dist"> <property name="zdrojaky-archiv-soubor" value="web/nekurak.net-src.zip"/> <property name="zdrojaky-archiv-komentar" value="… Licence: GNU Affero GPL, verze 3"/> <property name="hashovani" value="SHA-512"/> <zip basedir="../../.." excludes="nekurak.net/html/** nekurak.net/.hg/** …" includes="nekurak.net/**" comment="${zdrojaky-archiv-komentar}" encoding="UTF-8" destfile="${zdrojaky-archiv-soubor}"/> <checksum file="${zdrojaky-archiv-soubor}" algorithm="${hashovani}" pattern="{0} {1}"/> <manifest file="src/conf/MANIFEST.MF" mode="replace"> <attribute name="Manifest-Version" value="1.0"/> <section name="Frantovo.cz"> <attribute name="Kompiloval" value="${user.name}"/> <attribute name="java-version" value="${java.version}"/> <attribute name="java-vm-version" value="${java.vm.version}"/> <attribute name="os-name" value="${os.name}"/> <attribute name="os-arch" value="${os.arch}"/> <attribute name="os-version" value="${os.version}"/> </section> </manifest> </target>
Úloha vytvoří ZIP archiv se zdrojovými kódy, vypočítá jeho SHA-512 hash a uloží do souboru (aby si uživatel mohl zkontrolovat, že během stahování nedošlo k chybě) a oba tyto soubory přibalí do výsledného .war
archivu. Uživatel si pak může stáhnout zdrojové kódy přesně té verze, která byla použita pro kompilaci aplikace, se kterou právě pracuje. Nezkrácenou verzi najdete v souboru build.xml. Při psaní této úlohy musíme pečlivě nastavit atribut excludes
, abychom do archivu nebalili věci, které tam nepatří (zkompilované třídy, .jar, .ear… soukromé soubory). Jméno a heslo k databázi naštěstí nastavujeme na úrovni aplikačního serveru, takže jejich únik nehrozí. Platí vlastně stejná pravidla jako pro posílání změn do verzovacího systému.
Závěr
Dnes jsme se naučili základy lokalizace a formátování výstupu a upozornili na jednu zajímavou chybu (snad vám to ušetřilo trochu času a nervů, které byste nad ní strávili). Příště se budeme věnovat autorizaci a autentizaci a také trochu pokročíme s naší aplikací, aby konečně dělala něco užitečného.
Odkazy
- ISO-639 – Kódy jazyků – seznam na Wikipedii.
- ISO-3166 – Kódy států – seznam na Wikipedii.
- Properties Editor – editor lokalizačních souborů.
- Affero GPL – článek „Affero GPLv3: Vydejte zdrojové kódy síťových aplikací!“
- SimpleDateFormat – JavaDoc obsahující vzory pro psaní formátovacího řetězce.
Přehled komentářů