Internacionalización y localización: usando PHP-gettext
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