Dzisiejsza lekcja poświęcona będzie temu, jak przetłumaczyć naszą aplikację i udostępnić ją w kilku językach.
Zdecydowana większość aplikacji dostępna jest w kilku wersjach językowych — dzisiaj nauczymy się, jak samemu w taki sposób przygotować aplikację i bezproblemowo dodawać tłumaczenia.
Lekcja
Na początku jak zawsze trochę teorii i kilka słówek, które pomogą nam szukać rozwiązań w razie problemów i zorientować się w różnicach :)
i18n, l10n, g11n
Te dziwnie wyglądające nazwy to w rzeczywistości skrótowce od:
- internationalization ( i — 18 liter — n)
- localization (l — 10 liter — n)
- globalization (g — 11 liter — n)
Pojęcia te często bywają używane zamiennie, choć istnieją pewne ogólnie przyjęte różnice pomiędzy nimi. Nie ma jednak żadnej formalnej definicji, a najczęściej można się spotkać z używaniem i18n. Technicznie i18n to proces, który umożliwia (w tym wypadku oprogramowaniu) dostosowanie do warunków lokalnych (m.in. poprzez umożliwienie dodawania tłumaczeń, ale nie tylko — dotyczy to też formatów daty, liczby zmiennoprzecinkowych itp) podczas gdy l10n to proces faktycznego dopasowania do konkretnej kultury/języka (czyli np. faktyczne dodanie tłumaczeń w danym jezyku). Dość ciekawy jest dokument w3c na ten temat, polecam zapoznać się z jego treścią.
i18n w Javie (i Springu) — Locale
Na szczęście zarówno język Java jak i Spring Framework zostały zaprojektowane i zbudowane uwzględniając i18n. Centralną klasą jest tutaj tzw. Locale, która cytując definicję “odzwierciedla region geograficzny, polityczny lub kulturowy”. Ogólnie Locale w Javie zawiera (domyślnie) informacje na 3 poziomach — języka/kultury, kraju/regionu oraz dialektu/odmiany, przy czym obowiązkowa jest min. jedna część (musimy jednak podać wszystkie ‘na lewo’ od ostatniej części, którą definiujemy). Reprezentacja dla stanów zjednoczonych to np. en_us — oznacza to język angielski, i Stany Zjednoczone. Struktura ta jest hierarchiczna (co ważne z punktu widzenia użytkownika — o tym w dalszej części), czyli np. en_us jest podzbiorem en. Jak się już zapewne domyśliłaś, poszczególne części oddzielamy w reprezentacji tekstowej podkreślnikami.
Locale decyduje o wielu elementach związanych z prezentacją (więcej w javadocu klasy), o ile oczywiście poddamy je wcześniej procesowi i18n. W tej lekcji nauczymy się jedynie tłumaczyć widoki, ale zachęcam do poszukania na własną rękę i wdrożenia elementów takich jakich formatowanie liczb zmiennoprzecinkowych czy dat.
Tłumaczymy widoki
Aby przetłumaczyć widoki, wykonamy trzy proste kroki:
1. Konfiguracja Spring’a
W tym kroku skonfigurujemy Springa. Aby to zrobić dodamy trzy elementy:
- LocaleResolver, który przechowuje informacje o wybranym przez użytkownika języku (tą informację będziemy trzymali w tym wypadku w cookies)
- MessageSource, czyli informację, skąd tłumaczenia mają być pobierane
- LocaleChangeInterceptor — pozwoli nam przechwytywać zapytania od użytkownika, i zmieniać język w razie potrzeby
Te trzy elementy znajdują się w poniższej konfiguracji XML, którą należy umieścić we właściwym pliku konfiguracji 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 przypadku messageSource definiujemy tzw. basename — jest to prefiks, pod jakim Spring będzie szukał plików z tłumaczeniami. Sposób, w jaki to wyszukiwanie się odbywa oparty jest o hierarchię o któej mowa była wcześniej — załóżmy, że użytkownik naszej strony prosi o język ‘a_b_c’.
Spring szuka najpierw pliku i18n/messages_a_b_c.properties . Jeśli go nie znajdzie, szuka i18n/messages_a_b.properties . Trzecie wyszukiwanie to i18n/messages_a.properties . Po czym jeśli i tego nie znajdzie, odczytuje plik z domyślnymi tłumaczeniami (w tym wypadku i18n/messages_en.properties — domyślny język en zdefiniowaliśmy w localeResolver). Koncepcja ta jest jak zapewne sama przyznasz bardzo prosta, a jednocześnie bardzo funkcjonalna.
2. Piszemy tłumaczenia
Zgodnie z powyższą konfiguracją, nasze tłumaczenia umieszczamy w katalogu i18n, który tworzymy w src/main/resources w strukturze Mavena . Pliki te mają prostą strukturę, przykładowy wiersz to (zwróć uwagę na brak spacji!):
klucz=tłumaczenie
Każdą parę klucz-tłumaczenie umieszamy w nowej linijce. Klucz to dowolnie wybrany ciąg znaków (może zawierać kropki, raczej staramy się unikać polskich znaków), którego będziemy używać na widoku, żeby odwołać się do tłumaczenia. W różnych plikach tłumaczenia tej samej frazy powinny mieć te same klucze, inaczej nie będą poprawie wykryte.
Jeszcze tylko słowo rady dot. tworzenia kluczy — nie znam żadnej reguły, która by się do tego odnosiła, najważniejsze jest, aby używana koncepcja była zrozumiała nie tylko dla autora, spójna i prosta w modyfikacji/rozszerzaniu. Ja osobiście po wielu próbach stosuje konwencję moduł.kontroler.widok.nazwaFrazy . Wprawdzie powoduje to trochę powtórzeń, ale dzięki temu unikamy pułapki jezykowej, w której to w naszym jezyku pewne słowo/fraza jest identyczne w róznych kontekstach, ale już tłumacząc na inny jezyk potrzebujemy 2 różnych fraz. Kilka serwisów miało swego czasu tego rodzaju problemy, warto mieć to na uwadze.
3. Podmieniamy treści w widokach
W widokach wprowadzamy dwie modyfikacje — po pierwsze, dodajemy taglib Spring’a:
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
Niestety, musimy to zrobić na wszystkich widokach, które tłumaczymy. Tak działają tagliby, nic na to nie poradzę ;) Następnie podmieniamy teksty na tagi:
<spring:message code="web.layout.top.title" />
Gdzie code to klucz, którego użyliśmy w pliku z tłumaczeniami. Tag ten może przyjmować dodatkowy argument — var — który powoduje, że zamiast wypisać treść tłumaczenia, jest ona zapisywana do zmiennej o nazwie podanej jako wartość tego argumentu
Ostatnim krokiem modyfikacji widoków jest dodanie odnośników pozwalających zmienić język. Odnośnik taki może kierować na dowolny adres w naszej aplikacji (np. na stronę główną czy bieżącą stronę), jako parametr o nazwie określonej w konfiguracji interceptora (u nas jest to “language”) przekazujemy język, którego chcemy użyć, np:
<a href="/?language=en_us">Amerykański</a>
Przekieruje na stronę główną zmieniając język (locale), który widzi ten użytkownik, na en_us i jednocześnie zapisując nowowybrany w ciasteczku.
Podsumowanie
Teraz wiesz już, jak przetłumaczyć swoją stronę na inny jezyk :) Jeśli tworząc aplikację będziesz używała od początku tagów spring:message (nawet mają tylko jeden język), w przyszłości będziesz mogła w banalny sposób przetłumaczyć całą aplikację (np. dając tłumaczowi tylko plik z listą rzeczy do tłumaczenia).
Słowem zakończenia warto jeszcze wspomnieć o kodowaniu znaków — w konfiguracji Eclipse ustawialiśmy je na UTF‑8, ale pozwolę sobie tutaj to przypomnieć. Niezmiernie ważne jest, żeby używać kodowania, które wspiera potencjalnie dowolny język świata w zakresie znaków — takim kodowaniem jest np. UTF‑8 czy UTF-16, które są już standardami w branży. Nie idź na łatwizne i nie wybieraj domyślnego kodowania (szczególnie w systemach Windows!), bo zawód użytkowników w przyszłości i problemy z przekodowaniem nie są tego warte.
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!