Java na webovém serveru: lokalizace a formátování

Po dvou databázových dílech seriálu, které mohly být pro některé čtenáře trochu náročnější, dnes přistoupíme k o něco lehčímu tématu. Budeme se zabývat lokalizací naší webové aplikace a formátováním výstupu. A na závěr si ukážeme jeden tip pro příznivce otevřeného softwaru.
Seriál: Java na webovém serveru (16 dílů)
- Java na serveru: úvod 8. 1. 2010
- Java na webovém serveru: první web 15. 1. 2010
- Java na webovém serveru: práce s databází 29. 1. 2010
- Java na webovém serveru: práce s databází II 12. 2. 2010
- Java na webovém serveru: lokalizace a formátování 19. 2. 2010
- Java na webovém serveru: autorizace a autentizace 26. 2. 2010
- Java na webovém serveru: autorizace a autentizace II 5. 3. 2010
- Java na webovém serveru: porovnání Javy a PHP 10. 3. 2010
- Java na webovém serveru: Vlastní JSP značky a servlety 17. 3. 2010
- Java na webovém serveru: posílání e-mailů a CAPTCHA 24. 3. 2010
- Java na webovém serveru: píšeme REST API 7. 4. 2010
- Java na webovém serveru: SOAP webové služby 14. 4. 2010
- Java na webovém serveru: hlasování a grafy v SVG 28. 4. 2010
- Java na webovém serveru: Komentáře a integrace s Texy 9. 6. 2010
- Java na webovém serveru: AJAX formuláře 23. 6. 2010
- Java na webovém serveru: implementujeme Jabber 30. 6. 2010
Nálepky:
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.
Diky za vsechny dily serialu, tj. za konzistentni a primocary uvod do problematiky.
S properties souborem to je vůbec docela zábavné. On totiž to „poněkud neobvyklé kódování“ vytváří NetBeans (viz. FaqI18nProjectEncoding). V Eclipse tyto soubory mám v běžném UTF-8 a funguje to bez problémů.
Takže vzniká situace, kdy vyvíjím v Eclipse, properties soubory mám v UTF-8 a pak potřebuji vytvořit nějaký swingový formulář, spustím NetBeans, ten mi properties překóduje do svého „kódování“ ISO-8859–1 + entity. V IDE je vše v pořádku, uložím, zobrazím v Eclipse a tím končím. Od té doby musím properties soubor editovat jen v NetBeans.
Nedoporučí někdo nějaký editor properties souboru do Eclipse, který si poradí s tímto NetBeans „kódováním“ ?
Chtěl bych tento svůj komentář smazat…
Ono je to standardní chování, iso + entity…
http://propedit.sourceforge.jp/index_en.html
tenhle odkaz je i přímo v článku :-)
Properties Editor je i jako plugin do Eclipse – i když pro Eclipse je asi lepší použít plugin ResourceBundle Editor
Za přečtení stojí taky blog Překonaný ResourceBundle, Spring MessageSource vítězí v prvním kole KO – aneb jak to řeší Spring. Akorát je pak potřeba používat zvláštní JSP značky místo těch standardních.
Zajímalo by mě, jak se v Javě řeší překlady do jazyků které mají různý počet množných čísel. Pokud možno s ukázkou kódu. Jak by se vyřešil příklad:
Máte 1 nový email.
Máte 2 nové emaily.
Máte 5 nových emailů.
You have 1 new email.
You have 2(5) new emails.
New e-mail messages: X
Počet nových zpráv: X
Nemůžu se zbavit pocitu, že problém pouze obcházíš a že původní znění mi zní lépe. Asi by to chtělo řešit nějakou komplexnější knihovnou.
Zvláštních knihoven netřeba, na tohle stačí standardní Java.
Asi jsem to měl v článku zmínit – tak jsem doplnil formatovani.jsp (zdroják).
Lokalizační soubor v takovém případě obsahuje:
pocetEmailu={0,choice,0#Nemáte žádnou novou zprávu.|1#Máte jednu nepřečtenou zprávu.|2#Máte {0} nepřečtené zprávy.|4<Máte {0} nepřečtených zpráv.}
A v JSP parametrizujeme pomocí:
Ok, lokalizační soubor pro různé varianty vět v češtině vidím, jen mi není jasné za jakých okolností se volí jaká varianta. Vidím tam, že varianta začíná # nebo <.
Jak se docílí toho aby čísla končící na 1 s výjimkou 11 bylo zprávu. (Máte třicet jednu zprávu…)
Čísla končící 2, 3 a 4 (s výjimkou končících na 12, 13 a 14) byly zprávy. (Máte 102 zprávy…)
Ostatní čísla bylo zpráv. (Máte 112 zpráv…)