Internet no conoce de idiomas, o mejor dicho, conoce todos los idiomas. A nadie escapa que ofrecer una web multi-idioma derriba todas las barreras idiomáticas que podrían impedir a un visitante acceder a nuestra web y multiplica nuestras posibilidades de establecer lazos con clientes de todo el mundo.
Cualquier proyecto web en el que se embarque debería contemplar desde el mismo principio la internacionalización de los contenidos, incluso en el caso de que no vayan a ser localizados desde un principio. De este manera, cuando llegue el momento, evitará tener que tirar todo el trabajo a la basura y empezar de cero.
Existen dos conceptos básicos internacionalización y localización que aunque similares no tienen el mismo significado:
- La internacionalización es la capacidad de una web de poder ser traducida.
- La localización es el proceso de traducción de una página.
Para que una página web pueda ser localizable es indispensable que presente una estructura modular en la que el contenido sea independiente de la presentación.
A la hora de representar los diferentes idiomas lo más lógico es utilizar la codificación ISO639 que identifica de manera inequívoca todos los lenguajes reconocidos con códigos de 2 y 3 caracteres. Así por ejemplo, el idioma español se representa como "ES", el francés como "FR", el alemán "DE", etc. En la siguiente página tiene a su disposición todos los códigos ISO.
Tenga en cuenta que esa nomenclatura sólo es válida para realizar localizar idiomáticamente una web. Si además de los textos, quisiéramos localizar unidades monetarias, fechas, sistema de pesos y medidas... lo que se define como cultura, el convenio RFC3066 nos indica que utilicemos un código compuesto por el código de idioma ISO698 seguido de un guión y el código de país ISO3166. Por ejemplo podríamos tener para tres países en los que se habla español pero tienen diferente cultura los siguientes códigos: es-ES(España), es-AR (Argentina), es-UY(Uruguay).
¿Tal vez se haya preguntado de dónde vienen las abreviaturas i18n y l10n? La respuesta es sencilla a la par que curiosa pues se trata de una especie de jeroglífico: Internacionalizació(18 letras)N y Localizació(10 letras)N
Nota:
Para una mejor comprensión del artículo le recomiendo que se descargue el código de ejemplo que he preparado y que instale en su equipo la aplicación poedit.
Cómo saber cuál es el idioma de mi visitante
Una manera sencilla y bastante certera es comprobar cual es el idioma en el que nuestro visitante tiene configurado su navegador. Esto lo podemos conocer a través de la variable de servidor $_SERVER['HTTP_ACCEPT_LANGUAGE'] que nos devuelve en forma de cadena de texto la información contenida en la cabecera Accept-Language de la petición HTTP de nuestro visitante. Sin embargo, no es un método infalible, pues podría darse el caso de que el idioma del navegador no se corresponde con su idioma deseado.
Otra opción un poco más complicada es intentar determinar el país de origen a través de una geolocalización de la IP de nuestro visitante. Esto es factible pues existe una gran BD donde se incluyen todas las IP de los distintos ISPs y el país en el que se encuentra. Encontrará muchas referencias sobre geolocalización en google si le pregunta.
Utilicemos un método u otro, siempre sera indispensable permitir al usuario que elija el idioma en el que quiere visualizar la página. La elección del usuario debe ser recordada durante el resto de la navegación e incluso la siguiente vez que vuelva a la página. Esto lo podemos conseguir almacenando el lenguaje elegido en una variable se sesión o en una Cookie.
Localizando con el paquete PHP-gettext
En PHP existen diversas maneras de localizar una web. Podemos recurrir a constantes, variables, arrays... la mecánica es básicamente la misma y se resume en sacar las cadenas de texto a un fichero y acceder a ellas en el momento de ejecución de la página. Estos mecanismos de localización pueden resultar bastante engorrosos para el desarrollador, poco eficientes para el usuario y enormemente incómodos para el traductor, por lo tanto mi recomendación es que sólo los utilice para webs muy simples.
El mecanismo de localización que ofrece PHP de manera la nativa es a través de la función gettext(). Esta función presenta algunos problemas derivados de su dependencia respecto al soporte del idioma que este instalado en el servidor web. Por otra parte, su inicialización requiere una serie de llamadas a funciones que pueden parecen un tanto caprichosas. Por este motivo voy a mostrarle el que a mi juicio es el mecanismo de localización más recomendable hoy en día PHP-gettext.
El paquete PHP-gettext permite utilizar ficheros .mo para traducir cadenas y funciona de manera independiente a la función gettext nativa de PHP. Además Ofrece la posibilidad de de cachear en memoria las cadenas lo que agiliza de manera importante las búsquedas. Otro aspecto importante es que las traducciones de pueden gestionar cómodamente con aplicaciones como poedit.
PHP-gettext se ha convertido por su sencillez, flexibilidad y eficiencia en el estándar que utilizan muchos desarrolladores, de hecho tiene el aval de ser el mecanismo de internacionalización que utilizan aplicaciones tan extendidas como Wordpress.
Para poder comenzar a utilizar este paquete basta con descargarlo en nuestro entorno de desarrollo e incluir en nuestra página los archivos que contienen todas las clases y funciones que necesitamos:
include "php-gettext-1.0.7/streams.php";
include "php-gettext-1.0.7/gettext.php";
Deberemos indicar también el charset correspondiente al lenguaje. En la mayor parte de lenguajes occidentales la elección suele ser UTF-8.
header("Content-type: text/html; charset=UTF-8");
La codificación UTF-8 presenta algunas ventajas que merece la pena resaltar:
- La práctica totalidad de navegadores soporta UTF-8.
- UTF-8 nos evita tener que reemplazar caracteres típicos por sus entidades html.
- Se simplifica la publicación de nuestros contenidos en otros formatos como RSS, clientes CML-RPC de escritorio, etc...
- La migración de una base de datos en UTF-8 a otro sistema es más transparente.
Como ya comentamos antes podemos identificar y manejar el idioma de nuestros visitantes utilizando variables de sesión. En primer lugar recogeremos con el método GET la variable language para saber si contiene el idioma elegido por el usuario. Si no existe esta variable, se comprueba si la variable de sesión language contiene ya el idioma del usuario. En el caso de que la página se haya cargado por primera vez y la variable de sesión no contenga información alguna, se inicializaron un idioma por defecto que en este caso será el español (es_es). El codigo sería similar a este:
session_start(); //inicialización de las variables de sesión
if ($_GET['language']) $_SESSION['language'] = $_GET['language']; //Recogemos y guardamos el lenguaje de la query si procede
if (!array_key_exists('language',$_SESSION))$_SESSION['language'] = 'es_es'; //Inicializamos la sesión con el lenguaje por defecto
Para cargar en memoria el fichero con los textos correspondiente al idioma basta comprobar si existe el fichero y cargarlo en la cache. En el siguiente punto veremos con un poco más de detalle los ficheros .mo
if (file_exists("locale/".$_SESSION["language"]."/LC_MESSAGES/messages.mo")) {
$gettext_cache = new gettext_reader( new CachedFileReader("locale/".$_SESSION["language"]."/LC_MESSAGES/messages.mo"));}
A la hora de manejar las traducciones podemos utilizar directamente las funciones translate y ngettext que nos ofrece el paquete, pero lo más cómodo es crear un par de alias de la siguiente manera:
//Alias para manejar traducciones
function __($my_text) {
global $gettext_cache;
if (is_null($gettext_cache)) return $my_text;
else return $gettext_cache->translate($my_text);}
//Alias para manejar traducciones con plural
function _ngettext($my_text1,$my_text2,$n) {
global $gettext_cache;
if (is_null($gettext_cache)) if ($n>1) return $my_text2; else return $my_text1;
else return $gettext_cache->ngettext($my_text1,$my_text2,$n);}
Nota: Es necesario configurar poedit para que pueda reconocer correctamente los alias que hemos creado. En Catálogo>Opciones>en la pestaña palabras clave incluya estas dos entradas:
El siguiente paso para poder internacionalizar una web es identificar en el código fuente todas las cadenas que son localizables utilizando las funciones alias que se han creado:
Por ejemplo un código php sin localizar podría ser así:
El mismo código localizado presentaría el siguiente aspecto::
Los archivos .mo
Los archivos con los textos correspondientes a cada idioma tienen la extensión .mo. Las utilidades de traducción tipo Poedit automatizan el proceso para generar y editar un fichero .mo a partir de una web internacionalizable. A nivel interno el proceso que se desencadena es el siguiente:
- Se ejecuta la función xgettext en el código fuente de una web para detectar las cadenas que requieren de traducción y las incluyen dentro de un fichero plantilla .pot.
- A continuación a partir de la plantilla se crea un fichero .po por cada idioma que puede deben ser editados para realizar las traducciones.
- Finalmente se compila cada archivos .po con msgfmt y se genera un fichero binario .mo con toda la información necesaria para traducir la web.
Los ficheros se pueden ubicar en la carpeta que prefiramos aunque si queremos seguir la pauta general la estructura de carpetas sería la siguiente:
\locale\(ISO698)_(ISO3166)\lc_messages\messages.mo
\locale\es_es\lc_messages\messages.mo
\locale\es_ar\lc_messages\messages.mo
\locale\es_uy\lc_messages\messages.mo
nota: En Poedit es necesario configurar las rutas correspondientes a los archivos .mo. Le recomiendo la lectura del siguiente manual donde se explican de manera muy clara todos los pasos para configurar y utilizar la aplicación.
Problemas y soluciones en las formas plurales
Una vez que comencemos a traducir nuestra web, nos enfrentaremos a diversos problemas relacionados con la diversidad y disparidad de idiomas que existen. Un caso es la forma es la que se crean las formas plurales en cada idioma. Un ejemplo podría ser la palabra rebanada que como sabemos su plural en castellano se construye añadiendo una s:
Hay 0 rebanadas de pan
Hay 1 rebanada de pan
Hay 2 rebanadas de pan
En inglés el plural de la palabra rebanada, loaf, no se construye añadiendo una s, si no que es necesario sustituir la f por v y añadir el sufijo es, leaves:
There are 0 bread loaves
There is 1 bread loaf
There are 2 bread loaves
En francés en cambio se considera que cero cosas no es plural. por lo tanto tendríamos lo siguiente:
l y a 0 baguette
Il y a 1 baguette
Il y a 2 baguettes
Para facilitar el manejo de las particularidades de las formas plurales en cada lenguaje podemos utilizar la función ngettext($single, $plural, $number) que nos devuelve una cadena en singular o plural en función del valor de tercer parámetro.
printf(_ngettext("There is %d bird in the sky","There are %d birds in the sky", $number_birds),$number_birds);?>
Nota: Es necesario configurar poedit para que pueda trabajar correctamente con las formas plurales y el alias _ngettex:
- En la pestaña opciones deberá configurar la formas plurales que se corresponde con el lenguaje, por ejemplo para el lenguaje español tendríamos la siguiente forma de plural: nplurals=2; plural=n == 1 ? 0 : 1; Puede ver todas las formas de plurales en la siguiente página.
En la siguiente página puede leer más información sobre cómo configurar poedit.
Un ejemplo completo
Tal vez todo esto le haya resultado un poco complejo de digerir, en realidad una vez entendida la dinámica de la localización, el desarrollo de una web no es mucho más complejo que trabajar sin ella. En cualquier caso para facilitarle la comprensión de cómo localizar una web en php he preparado un ejemplo funcional de una página localizada en tres idiomas.
He incluido diferentes tipos de cadena para que pueda conocer las diferentes problemáticas que podrá encontrarse en un caso real. También he incluido un ejemplo de localización de imágenes.
Como siempre puede descargarse el código, analizarlo, trocearlo y utilizarlo libremente en sus proyectos.
A la memoría de Jon García Sáinz.
Fuente:hellogoogle.com