Skip to main content
Reinventing the wheel

Internacionalización en PHP

En el desarrollo de un sitio web muchas veces nos hemos encontrado con el desafío de la traducción, servir páginas en diferentes idiomas sin que esto suponga un calvario por duplicar contenidos y/o hacer el código más difícil de mantener (y como consecuencia romper una de las reglas básicas para el buen mantenimiento del código). En informática de software el proceso de llevar a buen término esta tarea se conoce como Internacionalización: diseñar un programa capaz de adaptarse a distintas lenguas sin realizar cambios en el código.

Si hablamos de un microsite sencillo de páginas estáticas, una opción rápida y barata es utilizar el Traductor de sitios de Google, un fragmento de JavaScript de esos plug-and-play que hace todo el trabajo por nosotros sin tocar el codigo HTML. La parte negativa es que la traducción no es muy fiel, y además hacemos publicidad gratuita a los de Mountain View.

Cuando se trata de sitios complejos, dinámicos, y cuya traducción posiblemente se haya contratado a profesionales, es necesario entonces programar el sitio de manera que los textos traducidos sean independientes de la maquetación, para una fácil modificación de los mismos en caso de correcciones o erratas, permitiendo además que alguien ajeno al mundo de la programación sea capaz de actualizar ese texto (pues eso, i18n).
Trabajando con PHP como tecnología del servidor, había recurrido en varios proyectos a uno de estos dos métodos:

En ambos casos, un recurso se carga y es mostrado al usuario el texto correspondiente.

<?php  
$lang = ‘es_ES’; // Define el idioma del usuario  
$locale = $locales[$lang]; // Obtiene los textos de algún recurso  
?>  
<p><?php echo $locale[‘intro’]; ?></p>  

Entonces, buscando como otros habían resuelto éste mismo problema en PHP, me topé con gettext, los archivos PO y los MO; una solución sencilla y elegante.

GNU gettext

gettext es un conjunto de herramientas para ayudar a la localización de un programa, desarrollado dentro del proyecto GNU y disponible en tu servidor Linux más cercano. La principal ventaja que ofrece es que agiliza y automatiza las tareas de internacionalización de un sitio web, ya que PHP da soporte a estas herramientas a través de una API, con funciones como gettext() o ngettext().

Procedimiento a seguir

En un proyecto PHP, tras maquetar todas las páginas e incluir los textos en el idioma original (en nuestro ejemplo en latín clásico), se han de reemplazar estos por un echo de la función gettext() con ese mismo texto como argumento.

Dado el fragmento de código…

<h1>Lorem ipsum dolor sit amet</h1>  
<p>Consectetur adipiscing elit. Donec convallis mauris eu est hendrerit luctus…</p>  

… reemplazar el texto por…

<h1><?php echo gettext(‘Lorem ipsum dolor sit amet’); ?></h1>  
<p><?php echo gettext(‘Consectetur adipiscing elit. Donec convallis mauris eu est hendrerit luctus…’); ?></p>  

O si preferimos por el alias _(), más corto y legible

<h1><?php echo _(‘Lorem ipsum dolor sit amet’); ?></h1>  
<p><?php echo _(‘Consectetur adipiscing elit. Donec convallis mauris eu est hendrerit luctus…’); ?></p>  

El siguiente paso es extraer el texto de los archivos y con él generar una plantilla o «catálogo» con el que trabajar y traducir. Estos catálogos son un tipo de archivo especial (PO) creados con la herramienta xgettext (consultar el enlace para más información). Por suerte, para usuarios de Windows como nosotros, existe un pequeño pero potentísimo programa con interfaz gráfica que nos servirá de mucha ayuda: Poedit.

Una vez descargado y ejecutado, creamos un nuevo catálogo y en la ventana emergente vamos a la pestaña Carpetas. En este apartado es donde hemos de incluir la ruta a nuestros archivos del proyecto que queremos traducir.

Creación de un nuevo catálogo con Poedit

Tras aceptar la configuración y guardar el archivo con extensión .POT (esto es importante), Poedit buscará en los archivos PHP del proyecto cualquier coincidencia con las funciones gettext() o _() (o las que hayamos definido), extraerá el texto y nos presentará en pantalla el resultado.

Traducir con Poedit

Este archivo POT con todos los textos de nuestro proyecto servirá de plantilla para la traducción a otros idiomas, desde el mismo programa, facilitando incluso que personas ajenas puedan realizar la traducción sin mancharse las manos con el código.

Para empezar a traducir, desde Poedit se ha de crear un nuevo catálogo por idioma utilizando la plantilla POT anterior (Archivo > Nuevo catálogo desde un archivo POT…). Esta vez lo único que se cambiará en las propiedades serán los campos Idioma y País acordes al texto de destino (seleccionar un País es opcional, pero ayuda a identificar mejor la traducción y poder crear otras en un mismo idioma pero para regiones diferentes, como Quenya-Noldor y Quenya-Vanyar). Una vez acometidas las traducciones, guardamos el archivo siguiendo unas reglas especiales:

Al guardar con Poedit, se genera un archivo PO y otro MO: el primero es el catálogo editable, y el segundo es ese mismo catálogo en formato binario que gettext carga y utiliza para la traducción.
La estructura de directorios de la carpeta de L10n quedaría así:
/locale/es_ES/LC_MESSAGES/example_gettext.po /locale/ca_ES/LC_MESSAGES/example_gettext.po /locale/xx_YY/LC_MESSAGES/example_gettext.po

Ahora llega el momento de utilizar estos catálogos de traducción en nuestro proyecto. Existe mucha literatura sobre la inicialización de gettext, como los comentarios en la página de referencia de la función o este artículo de Bartolomé Sintes, pero he aquí mi receta:

<?php
// Este código ha de ir antes que cualquier llamada a gettext()

// Definimos el idioma del usuario  
$user_locale = ‘es_ES’;

// Establece variables de entorno en el idioma del usuario  
putenv("LANGUAGE=$user_locale");  
putenv("LANG=$user_locale"); // Por si LANGUAGE falla  
// No es recomendable establecer LC_ALL  
// http://www.php.net/manual/es/ref.gettext.php#68853  
//putenv("LC_ALL=$user_locale");

// En el caso que utilicemos Apache bajo Windows (con XAMPP por  
// ejemplo), la constante no está definido por defecto  
if (!defined(LC_MESSAGES)) define(LC_MESSAGES, 5);  
// Establece la información de la configuración regional  
setlocale(LC_MESSAGES, $user_locale);

// Especifica la ruta a los catálogos de traducción y el "dominio"  
// al que pertenecen: el nombre de nuestros archivos MO  
bindtextdomain("example_gettext", "./locale");  
textdomain("example_gettext");
?>  

Ahora, cuando visualicemos nuestro sitio todos los textos se traducirán al idioma definido, obteniéndolos de los archivos MO.

Nota:  Si PHP dice algo tan feo como «Warning: putenv(): Safe Mode warning: Cannot set environment variable ‘LANGUAGE’«, es que se está ejecutando en modo safe_mode. Para evitar este error, se ha de añadir al archivo php.ini la siguiente línea:
safe_mode_allowed_env_vars = PHP_,LANG,LANGUAGE

Feliz i18n.