Dados los problemas de uso de la biblioteca de funciones gettext() incorporadas dentro mismo de PHP mencionadas en el artículo anterior, mi sugerencia es usar PHP-gettext, un paquete que usan muchas aplicaciones, incluso WordPress, en que corre este blog. No sólo su uso es más simple sino que tiene un README donde explica rápidamente su uso, dispone de ejemplos e incluye un archivo para hacer una emulación completa de la funcionalidad de la biblioteca gettext(), aunque vistos los problemas comentados, lo que menos puedo recomendar es usar una emulación que los reproduce hasta el último dolor de cabeza.

Usar este paquete requiere, básicamente, incluir un par de archivos que se proveen, como se ve en el ejemplo:

include 'php-gettext-1.0.7/streams.php';
include
'php-gettext-1.0.7/gettext.php';

La razón de dividir el paquete en dos archivos es que el segundo es el que provee la funcionalidad de gettext mientras que el primero es el que lee el archivo de traducciones y puede ser reemplazado por otro que acceda a este archivo por otro mecanismo.

La carga del archivo de traducciones en sí se hace mediante el siguiente código:

if (file_exists($_SESSION['language'] . '.mo')) {
$gettext_tables = new gettext_reader(
new
CachedFileReader($_SESSION['language'] . '.mo'
)
);
$gettext_tables->load_tables
();
}

Como se puede ver, el archivo de traducciones se puede ubicar donde uno prefiera, el control es total. En este caso, primero verifico que exista y luego genero una instancia del CachedFileReader indicándole la ubicación del archivo. A su vez, este objeto se usa en el constructor de gettext_reader, que es el objeto que proveerá toda la funcionalidad de gettext. Finalmente, se invoca el método que carga las tablas. El paquete permite tanto cachear las tablas en memoria (predeterminado) o accederlas directamente a disco, en cuyo caso el FileReader almacena los punteros a las traducciones accediéndolas directamente del archivo. Esto se determina mediante un argumento opcional en el lector o instanciando distintos lectores.

La idea de usar el nombre _() para la función de traducción es muy bueno, pero como ya está tomado y no es posible redefinirlo, usamos __(), que es igualmente breve y legible, donde la legibilidad está en la facilidad de leer los textos a traducir, pues a la parte ejecutable no le afecta.

function __($texto) {
global $gettext_tables;

if (is_null($gettext_tables)) return $texto;
else return $gettext_tables->translate($texto);
}

Esta función verifica que el objeto haya sido instanciado con éxito y si es así, llama al método . En cualquier caso, ya fuera que la traducción no existiera, un texto individual o el archivo de traducciones completo, la función devuelve el texto original con lo que, al menos, el usuario no se queda mirando una pantalla en blanco.A partir de allí, el resto del programa es simple, cada vez que se quiere usar una cadena localizable, se la encierra en una llamada a la función y listo.Probablemente ya hayan notado que no es necesario inventar identificadores para las cadenas a traducir, el texto en el idioma original es la clave de búsqueda por lo que en el caso de no haber una traducción, siempre se dispone de algún texto, aunque fuera en un idioma que no corresponde al pedido por el usuario.

A todo esto, no hemos aclarado cómo se genera un archivo .mo, ni hemos explicado la estructura del archivo de traducciones.

La clave del uso del paquete gettext es una serie de programas que se encargan de rastrear los fuentes buscando cadenas a traducir y actualizarlas cuando cambian. Existen programas con interfaces gráficas para facilitar esta tarea, tal como poEdit, que está disponible para Linux, Macintosh y Windows y, obviamente, internacionalizado, aunque también están los comandos de línea que existen en todas las plataformas Linux (aunque pueden no estar instalados por defecto) y también hay versiones para Windows (gnuwin32).

El proceso, resumido, es el siguiente:

1) se extraen las cadenas a traducir con el programa xgettext:

xgettext *.php --output=index.pot --keyword=__ --from-code=ISO-8859-1

En todos los casos he usado las versiones largas de las opciones para que sean más claras. A xgettext se le indica el o los fuentes de los que extraer las cadenas. Se le debe indicar el archivo de salida o redireccionar de la salida estándar, el set de caracteres de los fuentes y, en nuestro caso, dado que hemos usado el nombre de función __() en lugar de _() se le debe indicar expresamente que busque este nombre de función. El programa reconoce el lenguaje de los fuentes por su extensión e interpreta la sintáxis según se trate, pero si se hubiera usado alguna extensión que no reconociera, se le puede indicar expresamente.

En este caso la salida ha sido un archivo .pot, extensión que corresponde a un ‘template’ o ‘modelo’ de la traducción. Es habitual entregar este archivo de modelo como parte de una aplicación para que el traductor disponga de un original limpio a partir del cual comenzar su trabajo.

2) Se genera un archivo de traducción a partir del modelo:

msginit --input=index.pot --output=en.po --locale=en

Este programa simplemente toma el archivo .pot que se le indica y genera un archivo .po para la localización que se indica. Completa varios de los campos con datos que obtiene del sistema, por ejemplo, el nombre de usuario, la fecha de la traducción y pone como traducción el mismo texto que el original. El traductor puede tanto comenzar a partir del archivo .pot o del .po según prefiera.

3) Traducción. Esta se puede hacer con cualquier editor de texto simple. Existen macros para EMACS y hay IDEs que tienen modos de edición específicos para este tipo de archivo. Aplicaciones como poEdit disponen de su propio editor. Este programa en particular permite guardar una base de datos de traducciones de tal manera que en sucesivas traducciones puede intentar por sí solo traducir parte de los textos.

De todas formas, el proceso es simple, el texto tras la palabra clave msgid es el original, el que sigue a msgstr es la traducción. Para facilitar la tarea, los comentarios que preceden a cada cadena muestran el fuente y número de línea en que ha encontrado esta cadena. El programa xgettext se encarga de identificar cadenas duplicadas por sí mismo.

4) Compilación:

msgfmt en.po --output-file=en.mo

Esta es la parte más simple del proceso, compilarlo implica llamar a msgfmt, indicarle el nombre del archivo de entrada y el de salida.

Esta última etapa puede automatizarse fácilmente dentro de un archivo makefile, no así las primeras. En particular, llamar a msginit una segunda vez destruiría el archivo de traducciones ya hecho. El paquete dispone de un mecanismo de actualización a través del programa msgmerge. El proceso de actualización es similar al ya descripto, comenzando por el punto 1 hasta el 4, excepto que en lugar del paso 2 anterior se utiliza el comando siguiente:

msgmerge --update en.po index.pot

El comando msgmerge lee tanto el .pot como el .po y actualiza este último sin dañar las traducciones ya presentes.

Adviértase que solo es necesario un único archivo de traducciones para toda una aplicación, no es necesaria un archivo asociado a cada fuente, de allí que para el modelo usé el nombre index.pot, adoptando index como predeterminado, aunque no esto no es necesario.

Gettext tiene varias otras facilidades que no he mencionado, como ser el manejo de traducciones ambiguas (fuzzy), plurales (permite ofrecer distintas traducciones según el número de ítems que se le indiquen) y múltiples dominios, lo cual es indispensable cuando se utilizan paquetes de diferentes orígenes o ‘plugins’, cada uno con sus conjunto de traducciones independientes. Para esto se recomienda ver la documentación original.

Viene de: Internacionalización y localización: usando gettext()

Continúa en: Internacionalización y localización: bases de datos

Indice: Internacionalización y Localización: índice