W moim ostatnim projekcie tworzonym w oparciu o framework
CakePHP ważnym elementem była możliwość dodawania treści przez użytkowników w wielu językach. Naturalnym krokiem było więc wykorzystanie
TranslateBehavior ( link do API bo w manualu jeszcze
nic nie ma ) który znajduje się w CakePHP.
Dodanie TranslateBehavior do modelu jest tak samo proste jak w przypadku każdego innego behaviora i ogranicza się do :
- stworzenia tabeli w bazie
- dodania konfiguracji do modelu
- ustawienia wersji językowej w aplikacji
Tak więc, nasza konfiguracja w modelu mogłaby wyglądać w taki sposób:
public $actsAs = array(
'Translate' => array('title', 'content')
);
Naturalnie podczas odczytywania lub w zapisywania nowego rekordu w momencie gdy wykorzysujemy jedynie jedną wersję językową nie występują żadne komplikacje. Nie spotkałem się jednak nigdy z aplikacją która oferując tworzenie treści w różnych wersjach językowych rozbija to na osobne formularze lub coś w tym stylu. Logicznym podejściem jest stowrzenie formularza który pozwala na dodawanie treści równocześnie np. w języku polskim, niemieckim i angielskim. Tutaj jednak pojawia się problem.
Zgodnie z tym co można wyczytać w testach dołączonych do CakePHP aby zapisać dany rekord w kilku wersjach językowych jednocześnie należy dostarczyć dane w następujący sposób:
$data = array(
'slug' => 'new_translated',
'title' => array('eng' => 'New title', 'spa' => 'Nuevo leyenda'),
'content' => array('eng' => 'New content', 'spa' => 'Nuevo contenido')
);
Nie ma tutaj nic nadzwyczajnego. Aby uzyskać taki efekt musimy w formularzu stworzyć pola z lekką modyfikacją w porównaniu ze standardową procedurą.
echo $form->input('Article.title.eng');
echo $form->input('Article.title.spa');
Schody zaczynają się w momencie gdy chcemy te same dane odczytać ( przykładowo podczas edytowania danych ). Tutaj również posłużę się fragmentem kodu z testów:
array(
'TranslatedItem' => array('id' => 4, 'slug' => 'new_translated', 'locale' => 'eng', 'title' => 'New title', 'content' => 'New content'),
'Title' => array(
array('id' => 21, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'title', 'content' => 'New title'),
array('id' => 22, 'locale' => 'spa', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'title', 'content' => 'Nuevo leyenda')
),
'Content' => array(
array('id' => 19, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'content', 'content' => 'New content'),
array('id' => 20, 'locale' => 'spa', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'content', 'content' => 'Nuevo contenido')
)
);
Jak widać dane te są zwracane w zupełnie inny sposób, co uniemożliwia szybkie wstawienie tego do formularza. Jak widać CakePHP zwraca takie dane tak jakby tworzył relacje pomiędzy modelami dla każdej kolumny - jest to prawda. Aby zwrócić dane w kilku wersjach językowych jednocześnie nasz kod konfiguracyjny w modelu należy zmienić na :
public $actsAs = array(
'Translate' => array('title' => "Title", 'content' => "Title")
);
Zapis ten można odczytać tak : 'kolumna_ktora_bedzie_tlumaczona' => 'NazwaRelacji'.
Ok, fajnie że da się to zrobić, fajnie że zapisuje, fajnie że odczytuje. Jednak perspektywa przetwarzania teraz po każdym odczytaniu tablicy z danymi albo
ręczne podawanie wartości pola w formularzu może człowieka skutecznie zniechęcić.
Można jednak temu łatwo zaradzić. Wystarczy w AppModel dodać metodę
afterFind która zawsze gdy będzie to potrzebne sama przerobi dane z modelu tak aby miały one taką samą strukturę jak dane które trzeba dostarczyć do modelu aby je zapisać. Jej kod wygląda tak :
public function afterFind($results){
if( isset($this->Behaviors->Translate) ){
foreach( $this->Behaviors->Translate->settings[$this->alias] as $key => $value){
foreach($results as $index => $row){
if( array_key_exists($value, $row) ){
foreach($row[$value] as $locale){
if( isset($results[$index][$this->alias][$locale['field']])){
if( !is_array($results[$index][$this->alias][$locale['field']]) ){
$results[$index][$this->alias][$locale['field']] = array();
}
$results[$index][$this->alias][$locale['field']][$locale['locale']] = $locale['content'];
}
}
}
}
}
}
return $results;
}Metoda po wyciągnięciu danych z bazy sprawdza czy model zawiera jakieś pola które zostały przypisane do TranslateBehavior i jeżeli tak przetwarza odpowiednio tablicę. Dzięki temu możemy zapisywać, odczytywać dane w formularzach i całej aplikacji bez konieczności tworzenia modyfikacji w przypadku gdy dany model korzysta z TranslateBehavior.