Poesía Binaria

Traducciones con gettext para nuestros proyectos en PHP


Es importante a la hora de realizar un proyecto que este pueda estar disponible en varios idiomas sin que sea muy dolorosa la traducción, es decir, lo deseable es que nuestra aplicación tenga la posibilidad de ser traducida a varios idiomas sin tocar el código fuente y que alguien no experto pueda introducir/modificar traducciones en nuestro proyecto (en el caso de un proyecto libre es importante que otras personas puedan colaborar en esto para poder llegar a más público). Esto es todo el rolo sobre i18n (internacionalización) y l10n (localización) que podemos leer en el enlace de Wikipedia.

Vamos a empezar con un programa sencillo en PHP:

1
2
3
<?php
echo "Hello world";
?>

Nuestro objetivo es ofrecer español (Hola mundo) y francés (Bonjour le monde). Podríamos poner una sentencia if o switch para seleccionar el idioma, tal vez con este programa tardemos menos, pero una aplicación real puede tener miles de mensajes que deben ser traducidos.

gettext

Vamos a utilizar gettext para internacionalizar nuestra aplicación por su experiencia de muchos años de depuración de errores, porque es un estándar de facto en su campo. En este caso utilizaremos el componente gettext para PHP, normalmente porque cualquier implementación que podamos hacer en lenguaje PHP irá más lenta que el componente (no tendrá que ser implementado).

La traducción con gettext se basa en dos ficheros con extensión .po y .mo; el primero en texto plano y el segundo binario, es el mismo archivo .po , pero compilado. El archivo .po recogerá todos los textos que se encuentran en la página en un idioma por defecto, por ejemplo «Hello world» y la correspondencia del mismo texto en el idioma que elijamos. Debemos crear un fichero .po por cada idioma que queramos insertar.

Esto nos va a obligar a modificar ligeramente el programa.

1
2
3
4
5
6
7
8
9
<?php
$user_locale = 'es_ES.UTF-8';
putenv("LC_MESSAGES=$user_locale");
setlocale(LC_MESSAGES, $user_locale);
bindtextdomain("miProyecto", "/var/www/miproyecto/locale");
textdomain("miProyecto");

echo _('Hola mundo!');
?>

Con setlocale() y putenv() hemos definido la localización que queremos para nuestra aplicación. Normalmente con setlocale() es suficiente, aunque algunos sistemas requieren que putenv() esté también definido.

bindtextdomain() asocia un dominio con una ruta donde se encuentra el dominio. Si tenemos un proyecto pequeño, el dominio puede ser un nombre que identifique nuestro proyecto (miProyecto), pero cuando el proyecto es más grande tal vez queramos dividirlo en módulos y asignarle un dominio a cada módulo, y así tener un fichero de traducciones por cada uno de los módulos. La ruta que especificamos es donde encontraremos los archivos de traducciones, aunque tenemos que respetar una estructura de directorios.

Estructura de directorios

Si especificamos que nuestras traducciones se encuentran en «/var/www/miproyecto/locale» (podemos poner una ruta relativa), dentro de locale tendremos que crear tantos directorios como idiomas, un idioma, irá especificado por dos letras del idioma en minúscula, un guión bajo y dos letras representativas del país en mayúscula; por tanto español de España, será es_ES, en Argentina sería es_AR, inglés de Reino Unido sería en_UK, y así sucesivamente. Podemos obtener una lista de las locales disponibles del sistema de esta forma.
Dentro del directorio con la localización, debemos encontrar un directorio llamado LC_MESSAGES. Dentro de LC_MESSAGES debemos tener los ficheros po y mo de nuestro dominio o proyecto (en realidad con tener sólo el archivo mo nos vale).

En definitiva:
/var/www/miproyecto/locale/es_ES/miProyecto.po|.mo para idioma español de España
/var/www/miproyecto/locale/fr_FR/miProyecto.po|.mo para idioma francés de Francia
/var/www/miproyecto/locale/it_IT/miProyecto.po|.mo para idioma italiano de Italia.

Crear los archivos po y mo

Hay muchos programas, pero mi preferido es poedit, que además es multiplataforma. A partir de aquí tenemos varias formas de hacer las cosas:

Crear directamente los archivos po

Para eso, iniciamos el programa, veremos una pantalla parecida a la que muestro a continuación:

A continuación creamos un nuevo catálogo de idioma:

Luego rellenamos el formulario con la información necesaria para la creación de nuestra localización, en este caso, es importante indicar el idioma y el juego de caracteres (en mi caso utf-8):

En la siguiente pestaña, carpetas, debemos indicar todas las rutas de directorios donde encontramos código fuente de nuestro proyecto, así poedit, lo escaneará en busca de cadenas de texto traducibles:

En la siguiente pestaña, palabras clave, a priori no tenemos que tocar nada, en definitiva es por si cambiamos las funciones de traducción de nuestro programa, por si queremos completarlas, o deshacernos de gettext y utilizar otro sistema que también utilice ficheros po y mo. Pongo la captura de pantalla de los valores por defecto:

Cuando aceptamos, nos pedirá guardar el archivo, y lo guardaremos en la ruta especificada antes: /var/www/miproyecto/locale/es_ES/miProyecto.po:

Ahora el programa revisará nuestros archivos de código fuente en busca de mensajes traducibles (aquellos que hayan sido escritos llamando a la función _() o gettext() (las que encontramos en la pestaña de palabras claves) y creará un resumen de los mensajes encontrados. En este caso encontramos el mensaje «Hello world» y «Hello» de una antigua prueba que hice y que se encuentra en el mismo directorio, pero en otro fichero. Aceptamos

Ahora sólo nos queda ponernos a traducir y salvar los cambios:

Si utilizamos el programa con las opciones por defecto, se creará el archivo mo automáticamente.

Crear un catálogo antes de crear los archivos po y mo

Otra opción que podemos seguir, que será útil si manejáis muchos ficheros de traducción, es crear un catálogo pot. El beneficio que obtenemos es que sólo tenemos que escanear los ficheros fuente una vez en busca de mensajes traducibles, y sólo nos tenemos que limitar a crear los ficheros po y cargar el catálogo cada vez que vayamos a traducir.

El método se parece mucho al seguido anteriormente, la diferencia es que antes de salvar el archivo por primera vez, en lugar de salvarlo con la extensión po, debemos hacerlo con la extensión pot. Seguidamente lo salvamos y cerramos el archivo.

Ahora creamos un nuevo catálogo desde un archivo POT y seleccionamos el archivo, nos podremos saltar unos cuantos pasos, ahora sí podemos traducirlo y salvarlo como un archivo po.

Posibles problemas

Puede que hayamos traducido un texto a un idioma es_ES, pero puede que en nuestro sistema no exista el idioma es_ES, y exista en cambio es_ES.UTF-8, debemos cambiar la variable $user_locale por «es_ES.UTF-8» como viene en el ejemplo de arriba. Esta configuración me ha funcionado en servidores remotos, aunque por si las moscas, podremos definir una constante en nuestra aplicación para definir esta locale.

Hemos traducido al francés nuestra aplicación, tenemos los po y los mo en regla, pero aún así no vemos la página en francés, la vemos en el idioma por defecto, como si gettext no cogiera la configuración. Una causa común es que la locale no está definida en el sistema (podemos verlo con $ locale -a) en este caso tendremos que crearla, podemos utilizar:

$ sudo locale-gen fr_FR.UTF-8

Sustituyendo fr_FR.UTF-8 por la locale que queramos generar.

También puede ocurrir que tus traducciones no se actualicen, eso es porque gettext guarda en caché los mensajes que ha traducido, para ir más rápido, y no tenemos forma de limpiar esa caché de forma elegante, para ello podemos reiniciar el servidor, o crear un script que mueva los archivos po y mo a otro lado, ejecute la aplicación y los vuelva a poner donde estaban, lo sé, es muy guarro, pero a falta de un método elegante, siempre podemos usar el método de la abuela.

Hemos terminado!

Ahora sólo tenemos que ejecutar el programa que hicimos en PHP. Y veremos el texto «Hola mundo» o el mensaje en el idioma que lo hayáis traducido… y armarnos de paciencia para traducir nuestras aplicaciones.

Actualizar las traducciones

Las aplicaciones crecen, hay mensajes nuevos y mensajes que quedan obsoletos, y poedit nos permite llevar ese control, así como añadir las nuevas traducciones al archivo po o al catálogo pot, dependiendo de nuestro modo de trabajo, para ello. Seleccionamos Catálogo / Actualizar desde fuentes:

Veremos los textos nuevos y los textos obsoletos que hemos ido generando:

Aceptamos, podremos traducir los nuevos textos incluidos y salvamos.

Si hemos creado un catálogo pot, actualizamos desde los fuentes este catálogo, y tras ello en cada archivo po, damos a Catálogo / Actualizar desde archivo POT.

También podría interesarte....