giovedì 17 gennaio 2008

Traduzione & Action Helpers

Il problema che mi ha fatto impazzire ieri notte è trovare un metodo comodo per gestire il sito naturalads.net in più lingue.

Il componente Zend_Translate fa proprio questo: permette di gestire facilmente le traduzioni prendendo le stringe da un adapter che può essere un semplice file csv oppure un file di tipo .mo (vedi gettext).

I problemi a cui sono giunto sono i seguenti:
  • come mantenere lo stato della lingua dell'utente
  • una volta definita la lingua come e dove inizializzare la classe Zend_Translate
  • come permettere all'utente di cambiare lingua senza perdere il contesto
Dopo vario smanettare sono giunto alla conclusione:

  • La lingua dell'utente si tiene dentro la sessione in questo modo non si creano problemi di passaggio parametri negli URL. Inoltre la sessione è comunque già attiva per gestire le autenticazioni e quindi una variabile in più di 2 caratteri non fa troppo male.
  • Quando un controller vede nell'url/get/post/cookie/env un parametro lang=xx si attiva per salvare il parametro nella sessione. In questo modo l'utente può cambiare la lingua senza perdere il contesto di quello che sta facendo
  • L'inzializzazione della classe Zend_translate, così come la modifica della variabile di sessione, è fatto nel controller attivo.
Ora la cosa funziona, ma il terzo punto prevede che l'inizializzazione sia fatta in 'ogni' controller. Sarà, ma scrivere così tanto a me non piace molto e quindi scartabellando il manuale Zend ho visto che una cosa utilizzabile è un Controller Action Helper che viene richiamato dal Front Controller prima di richiamare ogni controller(init).

OK. il posto giusto per non fare troppo lavoro e avere un oggetto facilmente manutenibile.

Perchè ho scartato i Front Controller Plugins invece...
In un primo tempo avevo pensato ai Plugin del Front Controller, però non si sono rivelati così flessibili.
Questo è un plugin, ad esempio, trovato sul blog di naneau.nl, che blocca l'accesso ad ogni pagina se non si è loggati:

class Plugins_CheckLogin extends Zend_Controller_Plugin_Abstract {
public function preDispatch() {
$login = false;
//you will probably want to implement this ;)
if (!$login) {
//no login
$request = $this->getRequest();
//the request
$request->setModuleName('default');
$request->setControllerName('login');
$request->setActionName('index');
//send to default/login/index
}
}

basta metterlo nella directory library/Plugins e richiamarlo nel bootstrap con

$frontController->registerPlugin(new Plugins_CheckLogin());
per vederlo funzionare in tutte le pagine del nostro sito.

L'unico problema è che il Plugin agisce 'fuori' dal controller e quindi non ha possibilità di accedere al controller e magari settare le variabili della view... Quindi se si vuole lavorare sulla request e magari modificarla, la soluzione migliore è sicuramente il Front Controller Plugin. Se invece si vuole agire a livello globale sui controller, come nel mio caso, è meglio usare un action helper che può essere richiamato in modo automatico sugli eventi.

... Aggiornamento .... dopo 4 ore (dovute soprattutto a convincere l'helper broker a digerire il nuovo helper) il mio helper 'translator' è funzionante e si adatta a qualsiasi controller (attuale o futuro) senza bisogno di scrivere una riga in più.
Anzi già che c'ero l'ho infarcito di tutte le cose che mi servono bene o male per tutti i controller...
(per i curiosi eccolo...)

<?php
require_once 'Zend/Controller/Action/Helper/Abstract.php';

class Helpers_Translator extends Zend_Controller_Action_Helper_Abstract {

public function init() {
$controller = $this->getActionController();
// utilità per le view
$controller->view->baseUrl = $controller->getRequest()->getBaseUrl();
//tutti i metodi della view possono usare $this->user per accedere all'utente
$controller->view->user = Zend_Auth::getInstance()->getIdentity();

//get the session
$sex = Zend_Registry::get('sex');
// numero di pagine
$controller->view->numberOfPageRequests =$sex->numberOfPageRequests;
// esiste un parametro di lingua allora lo mettiamo nella sessione
if($lang = $controller->getRequest()->getParam('lang', '')) {
$sex->lang = $lang;
}
//comunichiamo alla view il lang da usare
$controller->view->lang = $sex->lang;
// carichiamo il file di traduzione
$translationFile = '../languages/'. $sex->lang .'-source.csv';
// carichiamo il file di traduzione
if (file_exists($translationFile)) {
$controller->view->trans = new Zend_Translate('csv', $translationFile, $sex->lang, array('separator' => '|'));
} else {
$controller->view->trans = new Zend_Translate('csv', '../languages/nosource.csv');
}
}
}

2 commenti:

Anonimo ha detto...

Ciao Brian, molto utile quello che hai scritto, ma purtroppo ho qualche problemino...
una volta fatto il file translator.php e inserito nella cartella application/views/helpers
viene caricato automaticamente?ho devo inserire qualcosa nel file di bootstrap?
ho lo stesso problema con la registrazione dei plugin...non riesco a capire dove metterli e come richiamarli...dà sempre qualche errore...
ciao e grazie

fabrizio ha detto...

Quello che ho scritto io è un Action Helper e non un view helper...
I view helper vanno nella directory application/views/helpers come hai fatto tu e vengono caricati automaticamente.
Gli Action Helper invece bisogna caricarli a manina.

Io metto ogni Action Helper in library/Helpers (nota l'H maiuscola) e li carico nel bootstrap così:

...
$translator = new Helpers_translator();
Zend_Controller_Action_HelperBroker::addHelper($translator);
...

Naturalmente prima uso registerAutoload per evitare di mettere i require_once

require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

Ciao