#18 — tłumaczenie aplikacji

By 16 February 2015 February 24th, 2015 Kurs Javy

Dzisiejsza lekc­ja poświę­cona będzie temu, jak przetłu­maczyć naszą aplikację i udostęp­nić ją w kilku językach. 

Zde­cy­dowana więk­szość aplikacji dostęp­na jest w kilku wer­s­jach językowych — dzisi­aj nauczymy się, jak samemu w taki sposób przy­go­tować aplikację i bezprob­le­mowo dodawać tłumaczenia.

Lekcja

Na początku jak zawsze trochę teorii i kil­ka słówek, które pomogą nam szukać rozwiązań w razie prob­lemów i zori­en­tować się w różnicach :)

i18n, l10n, g11n

Te dzi­wnie wyglą­da­jące nazwy to w rzeczy­wis­toś­ci skró­tow­ce od:

  • inter­na­tion­al­iza­tion ( i — 18 liter — n)
  • local­iza­tion (l — 10 liter — n)
  • glob­al­iza­tion (g — 11 liter — n)

Poję­cia te częs­to bywa­ją uży­wane zami­en­nie, choć ist­nieją pewne ogól­nie przyjęte różnice pomiędzy nimi. Nie ma jed­nak żad­nej for­mal­nej definicji, a najczęś­ciej moż­na się spotkać z uży­waniem i18n. Tech­nicznie i18n to pro­ces, który umożli­wia (w tym wypad­ku opro­gramowa­niu) dos­tosowanie do warunk­ów lokalnych (m.in. poprzez umożli­wie­nie dodawa­nia tłu­maczeń, ale nie tylko — doty­czy to też for­matów daty, licz­by zmi­enno­przecinkowych itp) pod­czas gdy l10n to pro­ces fak­ty­cznego dopa­sowa­nia do konkret­nej kultury/języka (czyli np. fak­ty­czne dodanie tłu­maczeń w danym jezyku). Dość ciekawy jest doku­ment w3c na ten tem­at, pole­cam zapoz­nać się z jego treścią.

i18n w Javie (i Springu) — Locale

Na szczęś­cie zarówno język Java jak i Spring Frame­work zostały zapro­jek­towane i zbu­dowane uwzględ­ni­a­jąc i18n. Cen­tral­ną klasą jest tutaj tzw. Locale, która cytu­jąc definicję “odzwier­cied­la region geograficzny, poli­ty­czny lub kul­tur­owy”. Ogól­nie Locale w Javie zaw­iera (domyśl­nie) infor­ma­c­je na 3 poziomach — języka/kultury, kraju/regionu oraz dialektu/odmiany, przy czym obow­iązkowa jest min. jed­na część (musimy jed­nak podać wszys­tkie ‘na lewo’ od ostat­niej częś­ci, którą defini­u­je­my). Reprezen­tac­ja dla stanów zjed­noc­zonych to np. en_us — oznacza to język ang­iel­s­ki, i Stany Zjed­noc­zone. Struk­tu­ra ta jest hier­ar­chicz­na (co ważne z punk­tu widzenia użytkown­i­ka — o tym w dal­szej częś­ci), czyli np. en_us jest podzbiorem en. Jak się już zapewne domyśliłaś, poszczególne częś­ci odd­zielamy w reprezen­tacji tek­stowej podkreślnikami.

Locale decy­du­je o wielu ele­men­tach związanych z prezen­tacją (więcej w javadocu klasy), o ile oczy­wiś­cie pod­damy je wcześniej pro­ce­sowi i18n. W tej lekcji nauczymy się jedynie tłu­maczyć wido­ki, ale zachę­cam do poszuka­nia na włas­ną rękę i wdroże­nia ele­men­tów takich jakich for­ma­towanie liczb zmi­enno­przecinkowych czy dat.

Tłumaczymy widoki

Aby przetłu­maczyć wido­ki, wykon­amy trzy proste kroki:

1. Konfiguracja Spring’a

W tym kroku skon­fig­u­ru­je­my Springa. Aby to zro­bić dodamy trzy elementy:

  • LocaleRe­solver, który prze­chowu­je infor­ma­c­je o wybranym przez użytkown­i­ka języku (tą infor­ma­cję będziemy trzy­mali w tym wypad­ku w cookies)
  • Mes­sage­Source, czyli infor­ma­cję, skąd tłu­maczenia mają być pobierane
  • LocaleChangeIn­ter­cep­tor — poz­woli nam przech­wyty­wać zapy­ta­nia od użytkown­i­ka, i zmieni­ać język w razie potrzeby

Te trzy ele­men­ty zna­j­du­ją się w poniższej kon­fig­u­racji XML, którą należy umieś­cić we właś­ci­wym pliku kon­fig­u­racji Springa (web-context.xml):

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="i18n/messages"></property>
</bean>

<mvc:interceptors>
    <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
        <property name="paramName" value="language" />
    </bean>
</mvc:interceptors>

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
    <property name="defaultLocale" value="en" />
</bean>

W przy­pad­ku mes­sage­Source defini­u­je­my tzw. base­name — jest to pre­fiks, pod jakim Spring będzie szukał plików z tłu­maczeni­a­mi. Sposób, w jaki to wyszuki­wanie się odby­wa opar­ty jest o hier­ar­chię o któej mowa była wcześniej — załóżmy, że użytkown­ik naszej strony prosi o język ‘a_b_c’.

Spring szu­ka najpierw pliku i18n/messages_a_b_c.properties . Jeśli go nie zna­jdzie, szu­ka i18n/messages_a_b.properties . Trze­cie wyszuki­wanie to i18n/messages_a.properties . Po czym jeśli i tego nie zna­jdzie, odczy­tu­je plik z domyśl­ny­mi tłu­maczeni­a­mi (w tym wypad­ku i18n/messages_en.properties — domyśl­ny język en zdefin­iowal­iśmy w localeRe­solver). Kon­cepc­ja ta jest jak zapewne sama przyz­nasz bard­zo pros­ta, a jed­nocześnie bard­zo funkcjonalna.

2. Piszemy tłumaczenia

Zgod­nie z powyższą kon­fig­u­racją, nasze tłu­maczenia umieszcza­my w kat­a­logu i18n, który tworzymy w src/main/resources w struk­turze Mave­na . Pli­ki te mają prostą struk­turę, przykład­owy wier­sz to (zwróć uwagę na brak spacji!):

klucz=tłumaczenie

Każdą parę klucz-tłu­macze­nie umiesza­my w nowej lin­i­jce. Klucz to dowol­nie wybrany ciąg znaków (może zaw­ier­ać krop­ki, raczej staramy się unikać pol­s­kich znaków), którego będziemy uży­wać na widoku, żeby odwołać się do tłu­maczenia. W różnych plikach tłu­maczenia tej samej frazy powin­ny mieć te same klucze, inaczej nie będą popraw­ie wykryte.

Jeszcze tylko słowo rady dot. tworzenia kluczy — nie znam żad­nej reguły, która by się do tego odnosiła, najważniejsze jest, aby uży­wana kon­cepc­ja była zrozu­mi­ała nie tylko dla auto­ra, spój­na i pros­ta w modyfikacji/rozszerzaniu. Ja oso­biś­cie po wielu próbach sto­su­je kon­wencję moduł.kontroler.widok.nazwaFrazy . Wprawdzie powodu­je to trochę powtórzeń, ale dzię­ki temu unikamy pułap­ki jezykowej, w której to w naszym jezyku pewne słowo/fraza jest iden­ty­czne w róznych kon­tek­stach, ale już tłu­macząc na inny jezyk potrze­bu­je­my 2 różnych fraz. Kil­ka ser­wisów miało swego cza­su tego rodza­ju prob­le­my, warto mieć to na uwadze.

3. Podmieniamy treści w widokach

W widokach wprowadza­my dwie mody­fikac­je — po pier­wsze, doda­je­my taglib Spring’a:

<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>

Nieste­ty, musimy to zro­bić na wszys­t­kich widokach, które tłu­maczymy. Tak dzi­ała­ją tagli­by, nic na to nie poradzę ;) Następ­nie pod­mieni­amy tek­sty na tagi:

<spring:message code="web.layout.top.title" />

Gdzie code to klucz, którego użyliśmy w pliku z tłu­maczeni­a­mi. Tag ten może przyj­mować dodatkowy argu­ment — var — który powodu­je, że zami­ast wyp­isać treść tłu­maczenia, jest ona zapisy­wana do zmi­en­nej o nazwie podanej jako wartość tego argumentu

Ostat­nim krok­iem mody­fikacji widoków jest dodanie odnośników pozwala­ją­cych zmienić język. Odnośnik taki może kierować na dowol­ny adres w naszej aplikacji (np. na stronę główną czy bieżącą stronę), jako para­metr o nazwie określonej w kon­fig­u­racji inter­cep­to­ra (u nas jest to “lan­guage”) przekazu­je­my język, którego chce­my użyć, np:

<a href="/?language=en_us">Amerykański</a>

Przekieru­je na stronę główną zmieni­a­jąc język (locale), który widzi ten użytkown­ik, na en_us i jed­nocześnie zapisu­jąc nowowybrany w ciasteczku.

Podsumowanie

Ter­az wiesz już, jak przetłu­maczyć swo­ją stronę na inny jezyk :) Jeśli tworząc aplikację będziesz uży­wała od początku tagów spring:message (nawet mają tylko jeden język), w przyszłoś­ci będziesz mogła w banal­ny sposób przetłu­maczyć całą aplikację (np. dając tłu­mac­zowi tylko plik z listą rzeczy do tłumaczenia).

Słowem zakończenia warto jeszcze wspom­nieć o kodowa­niu znaków — w kon­fig­u­racji Eclipse ustaw­ial­iśmy je na UTF‑8, ale poz­wolę sobie tutaj to przy­pom­nieć. Niezmiernie ważne jest, żeby uży­wać kodowa­nia, które wspiera potenc­jal­nie dowol­ny język świa­ta w zakre­sie znaków — takim kodowaniem jest np. UTF‑8 czy UTF-16, które są już stan­dar­d­a­mi w branży. Nie idź na łatwiz­ne i nie wybier­aj domyśl­nego kodowa­nia (szczegól­nie w sys­temach Win­dows!), bo zawód użytkown­ików w przyszłoś­ci i prob­le­my z przekodowaniem nie są tego warte.

Zadanie

Zmody­fikuj pro­gram, który już napisałaś tak, aby wszys­tkie wido­ki korzys­tały z tłu­maczeń. Dodaj przy­cis­ki pozwala­jące zmienić wer­sję językową.

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przy­dat­ną, mamy małą prośbę: pol­ub nasz fan­page. Dzię­ki temu będziesz zawsze na bieżą­co z nowy­mi treś­ci­a­mi na blogu ( i oczy­wiś­cie, z nowy­mi częś­ci­a­mi kur­su Javy). Dzięki!