#18 – tłumaczenie aplikacji

By 16 lutego 2015Kurs Javy
Wpis-Header lekcje (1)

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.

Zadanie

Zmodyfikuj program, który już napisałaś tak, aby wszystkie widoki korzystały z tłumaczeń. Dodaj przyciski pozwalające zmienić wersję językową.

Licencja Creative Commons

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!

  •  
  •  
  •  
  •  
  •  
  • Ola

    Cześć,
    Mam pytanie, co jeśli mam frazę po polsku: Zaloguj a po angielsku: Log in? Można jakoś sprawić by we frazach były spacje?
    Pozdrawiam

    • Ola

      Już znalazłam rozwiązanie :)

      • Super! Ale odpowiadając dla innych czytelników – w pliku z tłumaczeniami mogą być spacje, nie trzeba dodawać żadnych specjalnych znaków ani czegokolwiek zmieniać, wszystko od znaku równości do końca linijki jest traktowane jako tłumaczenie

  • Ola

    Cześć,
    Mam pytanie, co zrobić jeżeli chce wpisać frazę po polsku ze znakami typu ę, ą?
    Pozdrawiam

    • Ola

      W eclipse weszłam w Preferences-General/ContentTypes-Java Properties File i zmieniłam kodowanie. To tak jakby ktoś też miał z tym problem.

      • Super, że sobie poradziłaś! :) Ale ogólnie tak, kodowanie można ustawić właśnie poprzez konfigurację messageSource