Internacionalización en PHP (I). Internacionalizar el texto.

Facebooktwittergoogle_pluslinkedinmailFacebooktwittergoogle_pluslinkedinmail

Si desarrollamos un proyecto web medianamente aspiracional, es más que probable que deseemos que pueda ser visitado y utilizado por usuarios de distintos paises, con distintos idiomas. Por lo tanto, los textos de nuestra web deberán estar disponibles en dichos idiomas.

Cuando se planteo esta necesidad en los comienzos de la web, lo que se hacía era replicar las páginas en diferentes idiomas y, según lo que pidiera el usuario, se le redirigía a una página u otra. Así pues, por ejemplo, en lugar de existir un fichero index.html, tenías tantos cómo idiomas quisieras ofrecer a tus visitantes. Si tu página iba a estar disponible en español e inglés, creabas un index_es.html y un index_en.html. La página index.html sólo tenía dos enlaces para que el usuario escogiera una u otra versión.

Los principios siempre son duros. En el momento en que tu sitio web tenía cada vez más páginas, y los usuarios demandaban cada vez más idiomas, tenías que andar creando copias y copias y más copias de tus ficheros. Además, si había que cambiar un texto, tenías que cambiarlo en todas las copias de la página correspondiente. Un trabajo ímprobo y, en la práctica, chapucero e insostenible.

Con el advenimiento de PHP se pasó a ficheros de texto externos, que eran leídos por cada página, y que contenían matrices con los textos en diversos idiomas. En este sentido, se ha recurrido también mucho a grabar los textos en distintos idiomas en una base de datos MySQL, y leerlos según el idioma requerido. No era mala solución pero, cuando tu proyecto crecía, te encontrabas con tablas con miles de frases. A la larga, también era insostenible. No resultaba fácil llevar un mantenimiento preciso. Además, cuando se eliminaba una frase del texto, no siempre te acordabas de eliminarla en la base de datos, o, aunque te acordaras, directamente no lo hacías, por si esa variable era empleada en otro script. Al final, te acababas juntando con frases no traducidas, frases obsoletas que no se empleaban pero se seguían manteniendo, etc. Internacionalizar así una aplicación grande era un caos.

INTERNACIONALIZACIÓN HOY

Afortunadamente, las técnicas y herramientas necesarias han evolucionado. Hoy contamos con la posibilidad de emplear recursos que nos permiten una internacionalización fiable, flexible eficiente y cómoda. Estoy hablando de la función gettext() de PHP (disponible también en otros lenguajes, aunque aquí nos centraremos en PHP por ser, de lejos, el más empleado para la web). En este artículo vamos a aprender a usar esta función en nuestros sitios web. Para ello, vamos a crear una estructura de un sitio web, muy sencillo pero que nos permitirá ilustrar toda la operativa necesaria.

ATENCIÓN. Lo que vamos a aprender en este artículo SÓLO funciona sin problemas si el servidor es Linux. En servidores Windows (el típico localhost con wamp o xampp, por ejemplo) puede no funcionar. Fíjate que hablo de que el servidor web debe estar corriendo sobre Linux. Por supuesto, el equipo cliente desde el que nos conectemos puede ser cualquier plataforma. En realidad, esto no es un problema. El 99 % de los proyectos web de Internet corren sobre servidores Linux. Si tú tienes un ordenador en casa con Windows, por ejemplo, puedes instalar un arranque dual y arrancarlo con Linux. Quizá te interese echar un vistazo a este artículo. Si lo necesitas, además, en Internet existe gran cantidad de información para montar un arranque dual. Si no quieres tocar la tabla de arranque de tu disco duro, siempre puedes montar una máquina virtual con Linux, que puedes usar desde Windows. En cualquier caso, una vez desarrollado tu proyecto, sin duda lo colgarás en un servidor Linux, por ser este sistema más robusto, fiable y económico.

LA ESTRUCTURA DE ARCHIVOS

Vamos a ver la estructura de archivos de un proyecto de prueba, donde tendremos unos mensajes definidos en el código, que aparecerán en español o en inglés, según le especifiquemos que el idioma global del sitio es uno u otro. Recuerda que, para alojar este proyecto, debes hacerlo en una máquina Linux. Si es una máquina local tuya, deberás alojarlo colgando de /var/www/html/. Si es un servidor remoto, el proveedor de servicios te asignará la ruta donde puedes colocar tus archivos.

Nuestro proyecto de pruebas, al que llamaremos, por ejemplo, pruebas_idiomas, tendrá la siguiente estructura:

Como puedes ver, hay una serie de archivos PHP, de los que vamos a hablar en este artículo y, dentro de unas carpetas con nombres de idiomas, hay unos archivos con extensión .po y extensión .mo. De estos archivos hablaremos en el siguiente artículo. De momento, basta con saber que son los que contienen las traducciones que PHP empleará para mostrarnos las páginas de nuestro sitio en los idiomas adecuados. En realidad, los archivos con extensión .po se emplean para que grabemos las traducciones. Mediante un programa diseñado para ello (llamado poedit, del que hablaremos en el próximo artículo), podemos crear o editar las traducciones. Una vez que las tenemos a nuestro gusto, guardamos el archivo .po y el programa poedit lo compila a un archivo .mo. que es el que realmente es usado por PHP. El sistema puede parecer un poco rebuscado al principio. Sin embargo, a día de hoy, es la mejor solución para internacionalizar un sitio web de forma eficiente. Tanto es así que, incluso, el propio WordPress que, sin duda, es el mejor CMS del mercado, emplea este sistema.

LOS ARCHIVOS PHP

Vamos a conocer los archivos PHP que componen este mini proyecto de prueba. Han sido concebidos en una estructura tal que, extrapolándola, te permitirá crear cualquier sitio web internacionalizado, por grande y complejo que sea.

EL ARCHIVO includes/config.php

Como es habitual en las aplicaciones en PHP, en este archivo guardamos todas aquellas variables y funciones que son necesarias para la operativa de nuestros scripts y que deben estar disponibles en todos ellos o, al menos, en la mayoría. De este modo, nos basta con incluir este archivo en cada una de nuestras páginas, y tendremos todos los datos de configuración y variables y funciones de uso general disponibles. Además, esto tiene la ventaja de que, si debemos actualizar el valor de una variable, o el código de una función, nos basta con actualizarlo en el archivo config.php y queda todo el sitio actualizado. Funciones de uso general o conexiones a base de datos son los usos más habituales de este tipo de archivos. Además, es muy normal que, por razones organizativas, config.php aparezca dentro de un directorio includes colgando del directorio raíz del sitio.

En nuestro caso, el código de includes/config.php es el siguiente:

Este archivo comprende las siguientes funcionalidades:

En primer lugar fuerza a PHP a emplear la codificación UTF-8. Si normalmente esto es necesario para que aparezcan correctamente textos en español, mucho más si estamos pensando en otros idiomas como el catalán, francés o alemán (por no hablar de idiomas asiáticos o árabes), en los que puede haber todo tipo de caracteres no estándar. Hacemos esto con las siguientes líneas:

header('Content-Type: text/html; charset=UTF-8');
mb_internal_encoding("UTF-8");

Lo siguiente es determinar el idioma del navegador del usuario. En la mayoría de los casos, este será el idioma nativo (o preferido) de dicho usuario, ya que todos solemos poner el navegador de nuestro ordenador en el idioma con el que nos sentimos cómodos. Lo hacemos así:

$idiomaNavegador = explode(",", $_SERVER["HTTP_ACCEPT_LANGUAGE"])[0];

No obstante, tenemos que darle al usuario la posibilidad de seleccionar otro idioma. Podemos hacerlo como en este ejemplo, mediante variables grabadas en el código, o mediante enlaces que le permitan cambiar el idioma, almacenando el código del idioma en variables de sesión o en cookies, etc. Eso ya dependerá de nuestro criterio a la hora de diseñar la operativa de la aplicación. En todo caso, el código del idioma elegido debe seguir la normalización de dos letras minúsculas referidas al idioma, un guión bajo, y dos letras mayúsculas referidas a la región. Así pues, para fijar el idioma como español de España usaremos es_ES; el español de Colombia, por ejemplo, será es_CO; el inglés del Reino Unido es en_GB, mientras que el estadounidense es en_US. De esta forma, podemos emplear todos los idiomas en las distintas regiones donde se hablan. Si tu navegador está en español, y vives en España, el valor de $idiomaNavegador es es_ES.

Con el código del idioma en el que el usuario quiere navegar por el sitio, se establecen las variables de internalización de PHP:

putenv('LC_MESSAGES='.$idioma);
setlocale(LC_ALL, $idioma.'.UTF-8');

Esta es la parte que suele dar problemas en Windows, pero que funciona correctamente en servidores Linux. Siempre que necesitemos emplear estas funciones, probaremos nuestros desarrollos en Linux (y si no, aconsejablemente también).

Lo siguiente que tenemos que hacer es establecer lo que se conoce como dominio de los archivos de traducciones. Para ello debemos indicar el nombre del archivo .mo, que debe ser el mismo que el del archivo .po. El nombre de este archivo es el mismo para todos los idiomas. Sólo cambia, de un idioma a otro, el nombre del directorio que contiene las distintas traducciones, cómo ves en la estructura de directorios y archivos que aparece más arriba. El nombre del archivo de traducciones lo establecemos tal cual, sin extensión (.mo ni .po) ni ruta, así:

textdomain ("idiomas");

También debemos indicar cual es la ruta desde el sitio más alto del proyecto al directorio locale, donde se encuentran los directorios de las distintas traducciones:

$rutaDelDominio = __DIR__ . "/../locale";
bindtextdomain('idiomas', $rutaDelDominio);

Debemos asegurarnos que los datos que procedan del dominio indicado se interpreten correctamente en UTF-8:

bind_textdomain_codeset('idiomas', 'UTF-8');

ACLARACIÓN. Cuando hablamos del dominio todos tendemos a pensar en los nombres de dominio de los sitios web en Internet, y esto puede inducir a confusiones en otros contextos. Para entendernos. Consideramos el término dominio en su acepción más amplia (dentro de lo relativo a la web) que implica cualquier conjunto de ficheros, directorios, u otros recursos que se agrupen bajo un mismo nombre. En el contexto de las internalizaciones, por dominio entendemos el nombre del fichero de traducciones y, por extensión, también aplicamos el término dominio a los directorios donde se engloban las traducciones de los distintos idiomas. nada que ver con los nombres de URL’s, aunque sea este último el contexto donde más estamos acostumbrados a emplear el término dominio.

Lo último que vamos a ver en este fichero es una función que se emplea cuando un mismo texto puede aparecer en singular o en plural. Por ejemplo, un contador de los mensajes que el usuario ha recibido. Puede que haya recibido más de uno, o uno o ninguno. Si no ha recibido ninguno, la frase no se refiere a singular o plural, ya que sería algo así como Usted no tiene mensajes. Sin embargo, si hay mensajes, la frase si puede aparecer en singular o plural (Tiene 1 nuevo mensaje o Tiene 7 nuevos mensajes). El sistema de traducciones basado en ficheros .po y .mo nos permite establecer las dos formas de la frase, de modo que, según el número de mensajes sea 1 o más de uno, se muestra una forma u otra.

Lo que hacemos es definir una función, que a su vez emplea la función ngetttext() de PHP, para determinar cual de las dos formas se le mostrará al usuario, en base a un argumento numérico:

function _p ($singular, $plural, $cantidad) {
    return sprintf(ngettext($singular, $plural, abs($cantidad)), $cantidad);
}

Que la función definida tenga un nombre tan peregrino como _p() no es del todo caprichoso. En el próximo artículo veremos como el programa poedit para la gestión de traducciones nos ayuda con el nombre de esta función. De momento, sólo acepta que se llama así. Esta función recibe dos cadenas de texto (la que corresponde a la forma singular y la de la forma plural), y un parámetro numérico. Según sea este parámetro, elegirá una u otra forma, y la buscará en el correspondiente archivo .mo de traducciones.

EL ARCHIVO index.php

En este archivo parece no haber gran cosa pero, dado que incluye al principio el que hemos visto en el apartado anterior, ya hay bastantes cosas adelantadas. Lo que quizá nos llame la atención son las líneas que muestran textos, como. por ejemplo:

echo _("Esta es mi página");

o bien:

echo _('Ir a la página de clientes');

La función _() es un alias de gettext(). Lo que hace es buscar la cadena que recibe como argumento en el archivo de traducciones correspondiente. Si la encuentra, vuelca en pantalla la traducción. Si no encuentra la traducción, o no encuentra el fichero de traducciones del idioma que esté buscando, vuelca en pantalla el argumento recibido, tal cual.

EL ARCHIVO clientes/saludo.php

Hemos querido añadir otro script PHP al sitio, en un subdirectorio, para que veas que esto funciona correctamente en toda la estructura del sitio. Cómo ves, tenemos sentencias listas para traducir mediante la función _() que ya hemos comentado en el script anterior. También tenemos el caso de sentencias que hacen uso de la función _p() que definimos en el config, para mostrar frases en singular y en plural:

echo _p("Tienes %d mensaje", "Tienes %d mensajes", 1);

o, en plural:

echo _p("Tienes %d mensaje", "Tienes %d mensajes", 25);

El último argumento (el valor numérico) determina si se va a coger la forma singular (si vale 1) o la plural (si vale más de 1). Esto se establece en el archivo de traducciones mediante poedit, como veremos en el siguiente artículo.

El argumento %d hace que, al construir la frase para mostrarla en pantalla, se sustituya dicho argumento por el valor numérico.

LOS ARCHIVOS DE TRADUCCIONES

Los archivos de traducciones se crean y actualizan con poedit. En el próximo articulo veremos como funciona esta herramienta. Lo que debes tener en cuenta es que, a la hora de alojarlos en el servidor se deben colocar en una estructura de subdirectorios, dentro de locale, en la que habra un directorio para cada idioma (en este ejemplo, dos directorios: es_ES y en_GB). Dentro de cada uno, hay, a su vez, otro directorio llamado LC_MESSAGES, donde se alojan los archivos correspondientes a ese idioma.

Para que pueda probar esto, te he dejado el paquete completo para descargar en este enlace. Recuerda crear un directorio para alojarlo, dentro de tu servidor Linux, y podrás probarlo. Verás las páginas en inglés. Si abres el fichero includes/config.php y cambias las líneas 14 y 15 (comentas la 15 y descomentas la 14), recarga el navegador y verás los contenidos en español.

     

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *