Jak robić wersje językowe w PHP

Spis treści

Tym razem kilka słów o robieniu wersji językowych dla naszych aplikacji. Praktycznie każdy framework oferuje jakieś wbudowane rozwiązanie. Wbudowane rozwiązania wiadomo – raz działają szybciej, raz wolniej.

Tak naprawdę mamy do wyboru kilka opcji – trzymanie wszystkiego w osobnych szablonach dla każdej wersji językowej (widoki), wykorzystanie stałych (define('zapisz', 'save') – i odpowiedni plik dla każdego języka), trzymanie danych w tablicach globalnych (array('zapisz' => 'save') – reszta jw).

Potem w grę wchodzą rozwiązania dedykowane – pliki .ini i parsery (m.in. Smarty) no i co tam sobie wymyślimy – można tłumaczenia trzymać w bazie, potem generować jakieś pseudo konfiguracje i podmieniać. Wszystko opiera się na jednej zasadzie – musimy mieć gdzieś oryginał i tłumaczenia.

Jak pisałem wczęśniej są wbudowane rozwiązania –  CodeIgniter robi to tradycyjnie – w plikach PHP trzymamy oryginalną wartośc klucz a w szablonach, widokach i modelach możemy korzystać ze zmiennej przez załadowanie odpowiedniego klucza. Dosyć upierdliwe, bo przy każdym użyciu trzeba stworzyć odpowiedni klucz, a potem pamiętać albo znać jego nazwę.

W CakePHP widziałem coś podobnego, z tym że po drodze była baza danych z tłumaczeniami – też dosyć upierdliwe. Jak to zrobić lepiej?

Przedstawiamy Gettext

Jest to wbudowana biblioteka w większości instalacji PHP. Warto jednak sprawdzić czy na pewno występuje na naszym serwerze. Zaczynamy od Gettext w manualu. Idea jest bardzo prosta i wygodna. Każdy ciąg który wyświetlamy zamiast wyświetlac go przez zwykłe echo('Zapisz') wyświetlamy przez echo gettext('Zapisz') albo krócej echo _('Zapisz'). Wygodne bo nie trzeba tworzyć i pamiętać kluczy, a nasze  tłumaczenie może rosnąć według potrzeb.

Co potem? Potem potrzebujemy wyciągnąć wszystkie klucze z plików PHP – Jak? Można bawić się w linię poleceń, ale jesteśmy leniwi – skorzystamy z małego prostego programu, który ma GUI no i działa na każdej platformie: Poedit .

Poedit  jest edytorem gotowych już tłumaczeń, ale potrafi też parsować pliki .php i znaleźć w nich wszystko co trzeba podmienić. Pech chce, że korzystam z wersji EN, więc krótka instrukcja jak wystartować:

Uruchamiamy program, następnie klikamy File -> New catalog… Następnie uzupełniamy podstawowe informacje o projekcie, czyli nazwę, team i adres e-mail. Ważne żeby nie pominąć kodowania – Charset – musi być ustawiony odpowiedni, polecam UTF-8 🙂

Potem upierdliwa zakładka Paths obsługa tego nie jest intuicyjna – trzeba wskazać główną ścieżkę, gdzie nasze pliki .php się znajdują, a potem każdu katalog który ma być dodatkowo czytany – nie działa rekurencyjnie, więc musisz dodać WSZYSTKIE katalogi.

Program następnie generuje nam wszystkie hasła które użyliśmy w programie – tłumaczenia ustawiamy po prawej stronie. Mniej więcej wygląda to tak:

Poedit

Po zapisaniu zmian mamy dwa pliki – jeden z rozszerzeniem .mo drugi z .po. Jak teraz je wykorzystać w naszym projekcie?

putenv("LC_ALL=en_US");
setlocale(LC_ALL, 'en_US');
bindtextdomain("messages", 'tlumaczenia/languages');
textdomain("messages");

I teraz kilka istotnych informacji:

  • dla tego przykładu moje pliki nazywają się messages.po i messages.mo,
  • dla języka en_US muszą znajdować się w katalogi “tlumaczenia/languages/en_US/LC_MESSAGES
  • gettext cache’uje zawartość tych plików, więc zmiany będzie widać po restarcia Apacha
  • nie można wymusić inaczej odświeżenia jak przez zmianę nazwy plików, albo restart serwera WWW – dlatego warto zrobić tłumaczenia od razu poprawnie i od początku do końca.

Wydajność

Dawno, dawno temu jakiś miły człowiek przeprowadził testy wydajności różnych sposobów pobierania tłumaczeń – Benchmarking PHP Localization – warto zapoznać się z tym artykułem. Wnioski są raczej jasne – gettext jest najszybszy, ponieważ jest to moduł PHP, który działa na bardzo “niskim” poziomie. Pliki tłumaczeń są ładowane do pamięci i tam podmieniane. Każde inne rozwiązanie będzie zawierało narzut PHP.

Można oczywiście dyskutować z wydajnością tego rozwiązania – pobije je tylko trzymanie osobnych wersji językowych w osobnych plikach / strukturze dla wybranego języka, ale wymaga to większego nakładu pracy i jest bardziej problematyczne.

A jak wy rozwiązujecie wersje językowe? Baza? Pliki?