Guía Zend: Construyendo aplicaciones multi idioma con Zend_Translate
Zend_Translate es el componente de ZF encargado de facilitarnos la creación de aplicaciones multilenguaje. Entre sus principales características, se encuentra la posibilidad de leer la información desde distintas fuentes de datos (array php, CSV, gettext, etc); su simplicidad de uso; la facilidad para cambiar de un adapter por otro; detección automática del lenguaje del usuario, etc.
Adapters disponibles
Como dijimos anteriormente los datos se pueden cargar desde distintas fuentes. Cada una de estas fuentes cuenta con un adapter propio que se encarga de procesar el archivo y devolverle a Zend_Transate los datos en el formato correcto. Así es que recomiendo leer el manual para conocer los pros y contras de cada adapter antes de elegir el que queremos usar. En este artículo trabajaremos con archivos de configuración en formato php ya que son los más simples y los que usaremos en la mayoría de nuestros proyectos.
Creando los archivos
Es una buena idea poner las traducciones dentro de application/configs/languages. Allí podemos crear un subdirectorio nuevo por cada lenguaje si es que vamos a tener varios archivos para cada lenguaje, o crear directamente los archivos con las traducciones, por ejemplo en.php, es.php, etc.
Usando el adapter Zend_Translate_Adapter_Array, los archivos de configuración tendrían la siguiente estructura:
/* configs/languages/en.php */ return array("uno" => "one", "dos" => "two", "tres" => "three"); /* configs/languages/es.php */ return array("uno" => "uno", "dos" => "dos", "tres" => "tres");
Como vemos la estructura es simple, seguramente si alguna vez tuvimos que crear algún script propio para que un sitio nuestro sea multi idioma usamos algún archivo similar.
Obteniendo las traducciones
El siguiente paso es crear una nueva instancia de Zend_Translate, pasando como parámetro el adapter que usamos, la ubicación del archivo y el idioma correspondiente:
$translate = new Zend_Translate('array', APPLICATION_PATH . '/configs/languages/en.php', 'en');
Si queremos cargar otro idioma usamos el método addTranslation:
$translate->addTranslation(APPLICATION_PATH . '/configs/languages/de.php', 'de', $options);
Entre las $options más interesantes tenemos:
- clear (bool): al agregar traducciones para un idioma ya existente determina si se borran todos los datos existentes o no.
- disableNotices (bool): por defecto cuando se pide una traducción que no existe se genera un error del tipo E_USER_NOTICE, en ambientes de producción hay que poner este flag a false.
- reload (bool): indica que se regeneren los archivos de cache.
Para obtener la traducción tenemos el método translate($messageId, $locale = null), si tenemos varios idiomas cargados podemos especificar que traducción queremos obtener en el segundo parámetro. Este método tiene un alias muy usado que es el método _(), por lo que para obtener la traducción hacemos:
$translate->_("uno"); // obtenemos la traducción en el idioma actual
Otros métodos que nos serán útiles para conocer, ya que seguramente alguna vez necesitemos, son:
- getLocale() / setLocal($locale): para obtener / setear el idioma actual.
- isAvailable($locale): para saber si un lenguaje esta disponible.
- isTranslated($messageId, $original, $locale): para saber si una palabra esta traducida.
Cacheando archivos
Supongamos que usamos un formato como por ejemplo XML, si tenemos cientos o miles de palabras para procesar, el XML puede llegar a ser bastante pesado y su procesamiento puede llegar a ser costoso en términos de tiempo, por lo que podemos setearle a Zend_Translate una instancia de Zend_Cache para que procese el XML solo una vez y las veces siguientes lea los datos directamente desde la cache; ahorrándose tener que procesar de nuevo la información. Simplemente tenemos que crear una instancia de Zend_Cache con el frontend y el backend que queramos y setearla con el método estático setCache:
$cache = new Zend_Cache("Core", "File", $frontendOptions, $backendOptions); Zend_Translate::setCache($cache);
Métodos relativos a la cache: getCache(), setCache(Zend_Cache_Core $cache), hasCache(), removeCache(), clearCache().
Integración con otros componentes
Como veremos más adelante, Zend_Translate tiene una gran integración con otros componentes del framework como Zend_View, Zend_Form, etc., por lo cual podemos guardar nuestra instancia de Zend_Translate en Zend_Registry y esta será automáticamente detectada por estos otros componentes para hacer uso de él.
Zend_Registry::set('Zend_Translate', $translate);
Con esto ya estamos listo para trabajar con otros componentes. Veamos los casos más importantes.
Integración con Zend_View: Zend_View_Helper_Translate
Una vez registrada la instancia en Zend_Registry, desde las distintas vistas podemos obtener una traducción haciendo:
$this->translate("dos");
Integración con Zend_Form
Los formularios son una parte importante en todo sitio web, en cuanto a traducción nos interesa especialmente traducir labels, descriptions, legends, mensajes de error, etc. Ya tenemos registrada la instancia de Zend_Translate en el registry ¿tenemos que hacer algo más? No, simplemente creemos el formulario:
class Mdw_Form_Translate extends Zend_Form { public function init() { $this->addElement("text", "nombre", array("label" => "label_nombre")); $this->addElement("submit", "enviar", array("label" => "label_enviar")); $this->setDescription("description_form"); } }
En nuestro archivo de configuración tendremos algo así:
/* es.php */ return array(..., "label_nombre" => "Nombre", "label_enviar" => "Enviar", "descripton_form" => "Descripcion!"); /* en.php */ return array(..., "label_nombre" => "Name", "label_enviar" => "Send", "description_form" => "Description");
Así, al momento de renderizar el form automáticamente se buscará una traducción existente para labels, descriptions, etc., y si existe se mostrará el texto correcto.
Traducir mensajes de error
Para traducir los mensajes de error de los validators el tema es un poco más complejo (para poner un poco de suspenso… ¿no? :P). Tomando como ejemplo el validator NotEmpty (Zend_Validate_NotEmpty), abrimos dicho archivo y buscamos el atributo $_messageTemplates:
protected $_messageTemplates = array( self::IS_EMPTY => "Value is required and can't be empty", self::INVALID => "Invalid type given, value should be float, string, array, boolean or integer", );
Aquí se hace referencia a algunas constantes, entonces las buscamos:
const INVALID = 'notEmptyInvalid'; const IS_EMPTY = 'isEmpty';
Y con ellas armamos nuestro array con las traducciones:
/* es.php */ return array(..., "isEmpty" => "Este valor es obligatorio y no puede estar vacío", "notEmptyInvalid" => "Tipo de dato incorrecto, debe ser float, string, array, boolean o integer");
Lo mismo con todos los demás validators que vayamos a usar. Zend Framework 1.10 viene con dichos mensajes ya traducidos a varios idiomas pero, lamentablemente, español no es uno de ellos así que, por ahora, tendremos que hacerlo por nuestra cuenta.
Continuando con la guía:
Ya son 7 capítulos publicados de la Guía Zend, nos gustaría saber cómo vas, qué se te ha dificultado y qué inquietudes te han surgido del tema, para abordarlo en los próximos capítulos.
[…] Guía Zend: Construyendo aplicaciones multi idioma con Zend_Translate […]
Estupendos aportes y en nuestro idioma !!
Vas a publicar algo sobre Dojo, que te parece esta integración que trae ZF?
La verdad que hasta el momento nunca hice uso de la integracion que ZF trae on Dojo, me segui manejando por “afuera” con jQuery o Mootools, para los prox capitulos no hay nada sobre el tema pero mas adelante podria ser =)
Saludos!
hola. estaba tratando de tomar una decision sobre que adapter tomar para una pagina que tengo que armar con varios idiomas, mucho contenido de textos y que pueden crecer tanto los textos como los idiomas y me preguntaba cual seria la solucion optima para esto.
Me parecia que la mejor forma de hacerlo era utilizar base de datos para resolverlo, pero me encuentro que no existe ningun Adapter en ZF y me hace dudar.
Veo a mi criterio que la alternativa mas rapida es usar gettext, pero me pregunto: es la opcion mas rapida? es mejor que crear un adapter para myql?
Que opinas al respecto?