Jak robić wersje językowe w PHP
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:
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?