Wprowadzenie

Zend_Locale to odpowiedź frameworka na pytanie: "Jak jedna aplikacja może być używana na całym świecie?". Większość odpowie: "To proste. Wystarczy przetłumaczyć wszystkie napisy na inne języki.". Jednak użycie prostych tabel mapujących frazy jednego języka na drugi nie jest wystarczające. Różne regiony mogą mieć różne konwencje dotyczące imion, nazwisk, zwrotów grzecznościowych, formatu liczb, dat, czasu, waluty itp.

Niezbędna jest Lokalizacja i internacjonalizacja. Zwroty te często są przedstawiane skrótowo jako odpowiednio L10n oraz I18n. Internacjonalizacja zakłada przystosowanie do użycia systemu niezależnie od specjalnych wymogów charakterystycznych dla użytkowników w zakresie języka, regionu, sposobu zapisu liczb, konwencji finansowych, dat i czasu. Lokalizacja określa dodanie do systemu funkcjonalności obsługujących określone wymogi dotyczące języka, konwencji dat, czasu, waluty, nazw, symboli, sortowania itp. L10n i I18n uzupełniają się nawzajem. Zend Framework udostępnia ich obsługę poprzez szereg komponentów. M. in.: Zend_Locale, Zend_Date, Zend_Measure, Zend_Translate, Zend_Currency, and Zend_TimeSync.

Zend_Locale i setlocale()

W dokumentacji PHP można przeczytać, że funkcja setlocale() nie jest bezpieczna wątkowo (thread-safe) ponieważ działa w zasięgu procesu a nie wątku. To oznacza, że w środowisku wielowątkowym może dojść do sytuacji w której locale ulegnie zmianie pomimo braku odwołań do funkcji setlocale() w skrypcie. To, z kolei, może prowadzić do nieoczekiwanych rezultatów działania programu.

Podczas używania Zend_Locale takie ograniczenia nie występują ponieważ klasa Zend_Locale jest całkowicie niezależna od funkcji PHP setlocale().

Co to jest lokalizacja

Lokalizacja oznacza, że aplikacja (lub strona) może być używana przez różnych użytkowników, którzy mówią w różnych językach. Jednak przetłumaczenie napisów to tylko jedno z wielu zagadnień z tym związanych. W Zend Framework składają się na nią:

  • Zend_Locale - Główna klasa wspierająca locale dla pozostałych komponentów Zend Framework.

  • Zend_Translate - Tłumaczenie łańcuchów znaków.

  • Zend_Date - Lokalizacja dat, czasów.

  • Zend_Calendar - Lokalizacja kalendarzy (ze wsparciem dla systemów kalendarzy innych niż Gregoriański)

  • Zend_Currency - Lokalizacja walut.

  • Zend_Locale_Format - Przetwarzanie i generowanie zlokalizowanych liczb.

  • Zend_Locale_Data - Pozyskiwanie zlokalizowanej formy standardowych łańcuchów znaków - nazw państw, języków i innych z CLDR.

  • TODO - Lokalizacja porządków sortowania

Czym jest locale

Każdy użytkownik komputera używa locale, nawet wtedy gdy o tym nie myśli. Aplikacje nie posiadające obsługujące wielu zestawów językowych przeważnie wspierają jedno określone locale (locale autora). Kiedy klasa lub funkcja używają lokalizacji, mówi się że jest świadoma locale. W jaki sposób kod "wie" jakiego locale użytkownik się spodziewa?

Łańcuch znaków lub obiekt identyfikujący locale daje klasie Zend_Locale i jej klasom pochodnym dostęp do informacji dotyczących języka oraz regionu, których użytkownik się spodziewa. Na podstawie tych informacji dokonywane jest poprawne formatowanie, normalizacja oraz konwersje.

Jak locale są reprezentowane

Identyfikatory locale składają się z informacji dotyczących języka użytkownika oraz preferowanego/podstawowego regionu geograficznego (np. stan, województwo, land). Łańcuchy identyfikatorów locale używane w Zend Framework przestrzegają międzynarodowych standardów dotyczących skrótów języków i regionów. Zapisane są jako język_REGION. Obie części są utworzone z liter znaków wchodzących w skład ASCII.

Uwaga

W przeciwieństwie do popularnego osądu istnieją identyfikatory locale składające się z więcej niż 2 liter. Dodatkowo istnieją języki i regiony których skróty również są zawarte w więcej niż 2 literach. Mając to na uwadze, należy wystrzegać się własnoręcznego wydobywania oznaczenia języka czy regionu z pełnego\ identyfikatora locale. Zamiast tego należy skorzystać z Zend_Locale. W przeciwnym przypadku efekty działania kodu mogą okazać się niespodziewane.

Użytkownik z USA może spodziewać się języka angielskiego (English) oraz regionu USA. Daje to identyfikator locale: "en_US". Użytkownik w Niemczech będzie się spodziewał języka niemieckiego (Deutsch) oraz regionu Niemcy (Deutschland) co da locale "de_DE". Aby zasięgnąć szczegółowych informacji o konkretnym identyfikatorze locale do użytku w Zend Framework należy zapoznać się z listą identyfikatorów locale.

Przykład 519. Wybranie konkretnego locale

$locale = new Zend_Locale('de_DE'); // Język niemiecki _ Niemcy

Użytkownik z Niemiec przebywający w Ameryce mógłby oczekiwać języka Deutsch i regionu USA ale podobne niestandardowe połączenia nie są wspierane w takim stopniu jak pełnoprawne locale. Jeśli podana zostanie nieprawidłowa kombinacja to nastąpi automatyczne odrzucenie kodu regionu. Dla przykładu, "de_IS" zostałoby ograniczone do "de" a "xh_RU" - do "xh" ponieważ żadna z tych kombinacji nie jest poprawna. Dodatkowo, jeśli język podanej kombinacji nie jest wspierany (np. "zz_US") lub nie istnieje to zostanie użyte domyślne locale "root", które ma domyślny zestaw definicji międzynarodowych oznaczeń dat, czasów, liczb, walut itp. Proces odrzucania kodu regionu zależy również od żądanych informacji. Niektóre kombinacje języków i regionów mogą być odpowiednie dla określonego rodzaju danych (np. dat) ale nie dla innych (np. format waluty).

Należy mieć na uwadze zmiany historyczne ponieważ komponenty Zend Framework mogą nie być "świadome" częstych zmian stref czasowych na przestrzeni lat w wielu regionach. Przykładowo, pod tym linkiem widać historyczną listę szeregu zmian jakich dokonywały rządy w stosunku do występowania tzw. czasu letniego a nawet obowiązującej strefy czasowej. Przez to, podczas obliczeń na datach, komponenty Zend Framework nie będą brały tych zmian pod uwagę. Zamiast tego zwrócony zostanie rezultat wynikający z użycia strefy czasowej wg. obecnych zasad dotyczących czasu letniego i strefy czasowej konkretnego regionu.

Wybranie odpowiedniego locale

W większości przypadków new Zend_Locale() automatycznie wybierze poprawne locale zachowując uprzywilejowanie w stosunku do danych udostępnionych przez przeglądarkę użytkownika. W przypadku użycia new Zend_Locale(Zend_Locale::ENVIRONMENT) pierwszym źródłem informacji o locale stanie się konfiguracja serwera hostingowego tak jak opisano niżej.

Przykład 520. Automatyczny wybór locale

$locale  = new Zend_Locale();

// domyślne zachowanie, tak samo jak wyżej
$locale1 = new Zend_Locale(Zend_Locale::BROWSER);

// preferencja ustawień serwera
$locale2 = new Zend_Locale(Zend_Locale::ENVIRONMENT);

// perferencja ustawień frameworka
$locale3 = new Zend_Locale(Zend_Locale::FRAMEWORK);

Algorytm automatycznego szukania locale używany przez Zend_Locale używa trzech źródeł danych:

  1. const Zend_Locale::BROWSER - Przeglądarka użytkownika przy każdym żądaniu, dostarcza informacji, które przekładane są na zmienną globalną PHP o nazwie $_SERVER['HTTP_ACCEPT_LANGUAGE']. Jeśli brak jest odpowiedniego locale to następnym źródłem branym pod uwagę jest ENVIRONMENT i w ostateczności FRAMEWORK.

  2. const Zend_Locale::ENVIRONMENT - PHP udostępnia locale serwera hostingowego poprzez wewnętrzną funkcję setlocale(). Jeśli brak jest odpowiedniego locale to kolejnym źródłem branym pod uwagę staje się FRAMEWORK i w ostateczności BROWSER.

  3. const Zend_Locale::FRAMEWORK - W momencie, w którym Zend Framework będzie miał wystandaryzowany sposób określania domyślnych wartości komponentów (cecha jeszcze niedostępna) wtedy użycie tej stałej spowoduje wybranie locale na podstawie tych domyślnych wartości. W przypadku braku odpowiedniego locale kolejnym źródłem branym pod uwagę będzie ENVIRONMENT i na końcu BROWSER.

Użycie automatycznego locale

Zend_Locale oferuje trzy dodatkowe locale. Nie należą one do żadnego języka ani regionu. Są to locale "automatyczne" co oznacza, że działają na podobnej zasadzie co metoda getDefault() ale bez potrzeby tworzenia instancji. Locale "automatyczne" mogą być użyte w każdym momencie, w którym możliwe byłoby zdefiniowanie locale w standardowy sposób. Dzięki temu można ich używać w łatwy sposób np. w sytuacji pracy z locale udostępnianym przez przeglądarkę.

Istnieją trzy rodzaje takich locale, których zachowanie nie jest standardowe:

  1. 'browser' - klasa Zend_Locale powinna użyć informacji dostarczanych przez przeglądarkę użytkownika. Te dane są udostępniane przez PHP w globalnej zmiennej $_SERVER['HTTP_ACCEPT_LANGUAGE'].

    Jeśli przeglądarka użytkownika informuje o więcej niż jednym locale Zend_Locale użyje pierwszego z listy. W przypadku nie podania żadnego locale przez przeglądarkę (lub gdy skrypt jest uruchomiony z linii poleceń) zostanie użyte "automatyczne" locale 'environment'.

  2. 'environment' - klasa Zend_Locale powinna brać pod uwagę informacje przekazane przez serwer hostingowy. Są one udostępnione przez PHP poprzez wewnętrzną funkcję setlocale().

    Jeśli środowisko serwera informuje o więcej niż jednym locale Zend_Locale użyje pierwszego z listy. W przypadku nie podania żadnego locale zostanie użyte "automatyczne" locale 'browser'.

  3. 'auto' - klasa Zend_Locale powinna automatycznie wykryć locale, które nadaje się do użycia. W pierwszej kolejności będzie próbować znaleźć locale użytkownika, w przypadku niepowodzenia pod uwagę brane będzie środowisko serwera hostingowego.

    Jeśli nie uda się wykryć locale, klasa rzuci wyjątek i powiadomi o niepowodzeniu.

Przykład 521. Użycie automatycznego locale

// bez automatycznej detekcji
//$locale = new Zend_Locale(Zend_Locale::BROWSER);
//$date = new Zend_Date($locale);

// z automatyczną detekcją
$date = new Zend_Date('auto');

Użycie domyślnego locale

W niektórych środowiskach automatyczna detekcja locale nie jest możliwa. Można się tego spodziewać w przypadku uruchomienia skryptu z linii poleceń lub gdy przeglądarka użytkownika nie ma zdefiniowanego języka a domyślnym locale serwera hostingowego jest 'C' lub inne niestandardowe locale.

W takim przypadku Zend_Locale rzuci wyjątek z informacją o niepowodzeniu w automatycznym wykryciu locale. Istnieją dwa wyjścia z tej sytuacji. Pierwszym jest ręczne ustawienie nowego locale, drugim - zdefiniowanie domyślnego locale.

Przykład 522. Obsługa wyjątków locale

// w pliku bootstrap
try {
    $locale = new Zend_Locale('auto');
} catch (Zend_Locale_Exception $e) {
    $locale = new Zend_Locale('de');
}

// w modelu/kontrolerze
$date = new Zend_Date($locale);

To podejście ma jedną wadę. Wymusza ustawienie obiektu locale poprzez Zend_Locale w każdej klasie. W przypadku używania wielu klas może to stanowić duży problem.

Od Zend Framework 1.5 istnieje o wiele lepszy sposób na obsługę takiej sytuacji. Można ustawić domyślne locale za pomocą statycznej metody setDefault(). Oczywiście każde nieznane lub źle sformułowane locale również spowoduje rzucenie wyjątku. setDefault() powinna być wywołana przed uruchomieniem dowolnej klasy używającej Zend_Locale. Tak jak w poniższym przykładzie:

Przykład 523. Ustawienie domyślnego locale

// w pliku bootstrap
Zend_Locale::setDefault('de');

// w modelu/kontrolerze
$date = new Zend_Date();

W przypadku niepowodzenia automatycznego wykrycia locale użyte zostanie locale de. W przeciwnym przypadku - klasa użyje wykrytego locale.

Klasy ZF "świadome" locale

Klasy, które są "świadome" locale w Zend Framework polegają na Zend_Locale w kwestii automatycznego wybrania locale na zasadach opisanych wcześniej. Np. w aplikacji webowej Zend Framework, utworzenie obiektu klasy Zend_Date bez podania locale spowoduje, że powstały obiekt będzie miał locale ustawione na podstawie przeglądarki użytkownika.

Przykład 524. Format daty domyślnie dostosowuje się do przeglądarki

$date = new Zend_Date('2006',Zend_Date::YEAR);

Aby zmienić domyślne zachowanie i zmusić komponenty Zend Framework "świadome" locale do użytku określonego locale (obojętnie od ustawień przeglądarki użytkownika) należy podać nazwę locale w trzecim argumencie konstruktora.

Przykład 525. Zmiana domyślnego wykrywania locale

$usLocale = new Zend_Locale('en_US');
$date = new Zend_Date('2006', Zend_Date::YEAR, $usLocale);
$temp = new Zend_Measure_Temperature('100,10',
                                     Zend_Measure::TEMPERATURE,
                                     $usLocale);

Jeśli jest wiadomo, że wiele obiektów powinno użyć określonego locale to należy się upewnić, że będą miały to locale określone. Dzięki temu można uniknąć dodatkowego czasu pracy skryptu wynikającego z wyszukiwania odpowiedniego locale.

Przykład 526. Optymalizacja wydajności dzięki określeniu locale

$locale = new Zend_Locale();
$date = new Zend_Date('2006', Zend_Date::YEAR, $locale);
$temp = new Zend_Measure_Temperature('100,10',
                                     Zend_Measure::TEMPERATURE,
                                     $locale);

Locale obowiązujące w całej aplikacji

Zend Framework umożliwia zdefiniowanie locale obowiązującego w całej aplikacji. Polega to na wywołaniu obiektu klasy Zend_Locale i umieszczeniu go w rejestrze z kluczem 'Zend_Locale'. Dzięki temu ta instancja będzie używana we wszystkich klasach "świadomych" locale Zend Framework. Ten sposób umożliwia ustawienie locale w rejestrze i uwolnienie się od potrzeby zwracania uwagi na to zagadnienie. Ustawione locale zostanie automatycznie użyte we wszystkich przypadkach. Przykładowe użycie znajduje się w poniższym przykładzie:

Przykład 527. Użycie locale obowiązującego w całej aplikacji

// w pliku bootstrap
$locale = new Zend_Locale('de_AT');
Zend_Registry::set('Zend_Locale', $locale);

// w modelu lub kontrolerze
$date = new Zend_Date();
// print $date->getLocale();
echo $date->getDate();

Zend_Locale_Format::setOptions(array $options)

Opcja 'precision' służy do obcięcia lub wydłużenia liczby. Wartość '-1' wyłącza możliwość modyfikacji ilości liczb po przecinku. Opcja 'locale' jest pomocna podczas przetwarzania liczb i dat z użyciem separatorów oraz nazw miesięcy. Opcja formatów dat 'format_type' umożliwia wybór pomiędzy standardem CLDR/ISO oraz wyrażeniami funkcji PHP date(). Opcja 'fix_date' umożliwia podanie formatu liczb do użytku z metodą toNumber() (więcej informacji znajduje się w rozdziale „Number localization”).

Opcja 'date_format' może być użyta do określenia domyślnego formatu daty ale należy zachować ostrożność przy używaniu getDate(), checkdateFormat() and getTime(). Aby ich pomyślnie używać należy umieścić w ich opcjach następującą tablicę: array('date_format' => null, 'locale' => $locale).

Przykład 528. Daty przyjmują domyślny format przeglądarki

Zend_Locale_Format::setOptions(array('locale' => 'en_US',
                                     'fix_date' => true,
                                     'format_type' => 'php'));

Aby uzyskać standardowe definicje locale można użyć stałej Zend_Locale_Format::STANDARD. Ustawienie w opcji date_format na Zend_Locale_Format::STANDARD spowoduje użycie standardowej definicji formatu zawartej w bieżącym locale. Umieszczenie tej stałej w opcji number_format spowoduje użycie standardowego formatu liczb z bieżącego locale. Ustawienie stałej w opcji locale spowoduje użycie standardowego locale dla środowiska bądź przeglądarki.

Przykład 529. Użycie standardowych definicji w metodzie setOptions()

Zend_Locale_Format::setOptions(array('locale' => 'en_US',
                                     'date_format' => 'dd.MMMM.YYYY'));
// nadpisanie globalnego formatu daty
$date = Zend_Locale_Format::getDate('2007-04-20',
                                    array('date_format' =>
                                              Zend_Locale_Format::STANDARD);

// globalne ustawienie standardowego locale
Zend_Locale_Format::setOptions(array('locale' => Zend_Locale_Format::STANDARD,
                                     'date_format' => 'dd.MMMM.YYYY'));

Przyspieszenie Zend_Locale i jej klas pochodnych

Można przyspieszyć działanie klasy Zend_Locale i jej klas pochodnych poprzez użycie Zend_Cache. W przypadku używania Zend_Locale osiągnąć to można za pomocą statycznej metody Zend_Locale::setCache($cache). Klasę Zend_Locale_Format można przyspieszyć używając opcji cache w Zend_Locale_Format::setOptions(array('cache' => $adapter));. Jeśli używane są obie klasy należy ustawić cache jedynie dla Zend_Locale. W przeciwnym przypadku ostatni ustawiony cache nadpisze wcześniejszy. Dla ułatwienia istnieją również statyczne metody getCache(), hasCache(), clearCache() oraz removeCache().

Jeśli deweloper nie ustawi własnoręcznie cache'u Zend_Locale automatycznie zrobi to za niego. W niektórych sytuacjach może zajść potrzeba rezygnacji z użycia cache'u, nawet za cenę wydajności. Wtedy należy użyć statycznej metody disableCache(true). Oprócz wyłączenia ewentualnego wcześniej ustawionego cache'u (bez usuwania jego zawartości) metoda ta zapobiega generowaniu cache'u (jeśli nie był wcześniej ustawiony).