Internacionalización con PHP y gettext
en php gettext internacionalización publicado el 19 de febrero de 2008
gettext es una librería para internacionalizar aplicaciones, es decir para construir aplicaciones que soporten varios idiomas. PHP a partir de la versión 4 viene con una extensión para poder utilizarla pero debe de estar habilitada en el servidor en que se vaya a usar. Ejecutando phpinfo() se puede consultar esta información.
Ventajas
- acceso rápido porque los ficheros de mensajes traducidos están compilados
- facilita mucho el trabajo cuando hay varias personas traduciendo los textos
- herramientas que se ejecutan desde la línea de comandos que ahorran mucho tiempo
Inconvenientes
- puede ser un poco complejo al principio
- no es thread safe, lo que significa que si el servidor es multithread, como IIS o Apache sobre Windows, no va a funcionar correctamente ya que gettext depende del proceso global locale. Básicamente, si desde un thread se cambia el locale, afecta a todos los otros, con lo cual se podrían mostrar los mensajes en un idioma que no es el esperado.
Está previsto que en el futuro, posiblemente la versión 0.15, gettext sea thread safe.
Instalación
En esta página hay una lista de servidores ftp donde se puede descargar.
Funcionamiento
getext trabaja con dos tipos de ficheros, los PO, ficheros de texto con las traducciones y los MO, ficheros binarios obtenidos después de compilar los PO que son los utilizados por PHP para mostrar los literales traducidos. Se necesitan tantos ficheros PO como idiomas se vayan a soportar.
Estructura de carpetas
gettext necesita una estructura de carpeta concreta. A partir de una carpeta raíz, que por ejemplo se puede llamar locale, hay que crear tantas carpetas como idiomas se vaya a soportar y dentro de cada una de ellas tiene que existir una carpeta con el nombre LC_MESSAGES.
La carpeta del idioma debe seguir el formato ii_pp donde ii es un código ISO-639 de dos letras que representa el idioma y pp es un código ISO-3166 de dos letras que representa el país. Por ejemplo en_GB significa inglés hablado en el Reino Unido mientras que en_US significa inglés hablado en Norteamérica.
Inicialización
Antes de hacer una llamada a la función gettext de PHP, se debe definir una serie de parámetros para que gettext sepa con que idioma se quiere trabajar. Estos parámetros son el idioma con el que se quiere trabajar, donde están ubicados y como se llaman los ficheros PO. Solamente es necesario ejecutarlo una vez para cada script.
// Define el idioma a castellano
putenv("LANG=es_ES");
setlocale(LC_ALL, "es_ES");
// Define la ubicación de los ficheros de traducción
bindtextdomain("messages", "locale");
textdomain("messages");
Este trozo de código se puede hacer un poco más flexible y definir el idioma en función de una variable. Así que si suponemos que el idioma con el que se tiene que visualizar la página llega por la url con el nombre de idioma y en la web se da soporte a los idiomas castellano, catalán e inglés, quedaría así:
switch ($_GET["idioma"]) {
case 1:
$idioma = 'es_ES';
break;
case 2:
$idioma = 'ca_ES';
break;
case 3:
$idioma = 'en_GB';
break;
}
// Define el idioma
putenv("LANG=$idioma");
setlocale(LC_ALL, $idioma);
// Define la ubicación de los ficheros de traducción
bindtextdomain("messages", "locale");
textdomain("messages");
Mostrar literales internacionalizados
Para mostrar literales internacionalizados una vez que se ha ejecutado la inicialización se usa la función gettext.
<h1><?php echo gettext("Servicios") ?></h1>
En este caso, Servicios actúa como identificador de literal.
Generación del catálogo de identificadores de literales
Para generar el catálogo de identificadores de literales, gettext viene con una herramienta muy útil, xgettext. Recorre una lista de ficheros que se le indica buscando todas las llamadas a la función gettext y crea un fichero de texto con todos los identificadores. Se llama desde la línea de comandos así:
xgettext -f sources.txt -L PHP -o messages.po --from-code=iso-8859-1
que en castellano significa "lee del fichero sources.txt la lista de ficheros que quiero que recorras buscando llamadas a gettext, que por cierto son PHP y están codificados en ISO-8859-1, y genera un fichero que se llame messages.po".
Traducción de literales
La primera vez que se va a hacer las traducciones hay que copiar el catálogo en cada una de las carpetas de idioma que se hayan creado como se ha visto en estructura de carpetas.
Un fichero messages.po se parece a algo así:
msgid "Servicios"
msgstr ""
Dentro de las comillas después de msgstr es donde debe escribirse la traducción. Si es igual que el identificador no hace falta escribir nada así que el messages.po para el castellano no debería tener traducciones a menos que haya algún literal que no corresponde con su identificador.
Compilación de las traducciones
Para generar los ficheros binarios PO, hay que utilizar la herramienta msgfmt desde la línea de comandos de la forma siguiente:
msgfmt -o messages.mo messages.po
En vez de ir carpeta por carpeta ejecutando este comando, es más cómodo tener un fichero batch y que compile todos los mensajes de golpe.
msgfmt -o ca_ES\LC_MESSAGES\messages.mo ca_ES\LC_MESSAGES\messages.po
msgfmt -o en_GB\LC_MESSAGES\messages.mo en_GB\LC_MESSAGES\messages.po
msgfmt -o es_ES\LC_MESSAGES\messages.mo es_ES\LC_MESSAGES\messages.po
Actualización de las traducciones
gettext viene con una herramienta que permite actualizar los ficheros PO con los identificadores de mensaje no encontrados, es decir los creados nuevos desde la última vez que se modificó. Así que después de haber generado el catálogo se debe ejecutar msgmerge que como las demás se ejecuta desde la línea de comandos. En este caso también es más cómodo tener un fichero batch que lo haga para todos los idiomas de golpe.
msgmerge ca_ES\LC_MESSAGES\messages.po ca_ES\LC_MESSAGES\messages.po -U
msgmerge en_GB\LC_MESSAGES\messages.po en_GB\LC_MESSAGES\messages.po -U
msgmerge es_ES\LC_MESSAGES\messages.po es_ES\LC_MESSAGES\messages.po -U
La opción -U le indica a msgmerge que debe actualizar el fichero de traducciones.
Abstracción de la lógica en una clase
Una buena práctica es abstraer qué mecanismo se utiliza para internacionalizar, de esta manera la aplicación no sabe que sistema se está utilizando. La ventaja es que si por alguna razón se decidiese cambiar de sistema, por ejemplo porque finalmente el cliente quiere modificar los mensaje y le es más cómodo hacerlo en un fichero XML o porque hay que cambiar de servidor y en el nuevo no está habilitado gettext, la aplicación no se verá alterada, es decir no habrá que ir sustituyendo todas las llamadas a la función gettext por otras nuevas, simplemente habrá que modificar la lógica de la clase.
Para esto utilizo una clase de instancia única o singleton.
class i18n {
function &_instance() {
static $instance = null;
if (is_null($instance)) {
$instance = new i18n();
}
return $instance;
}
function init() {
switch ($_GET['idioma']) {
case 'es':
$idioma = 'es_ES';
break;
case 'ca':
$idioma = 'ca_ES';
break;
case 'en':
$idioma = 'en_GB';
break;
}
// Define el idioma
putenv("LANG=$idioma");
setlocale(LC_ALL, $idioma);
// Define la ubicación de los ficheros de traducción
bindtextdomain("messages", "locale");
textdomain("messages");
$instance = &i18n::_instance();
return $instance;
}
function getLabel($label) {
return gettext("$label");
}
}
En este caso la inicialización se haría de la siguiente forma:
$i18n = i18n::init();
Y para mostrar los literales internacionalizados:
<h1><?php echo $i18n->getLabel("Servicios") ?></h1>
45 comentarios
-
Kamara
25 de septiembre de 2008
Mira tu a quién me encuentro pro aquí!, vaya sorpresón!!!
uno buscando temas de internacionalización y le sale al colega de la vuelta la esquina!!!
Bueno, mi enhorabuena, esto (y en castellano para su lectura más rapida y placentera) ayuda bastante!
te doy 100 puntos!!! jeje,
1 abrazo y mis mil gracias maestro
manu -
kamara
27 de septiembre de 2008
no se si te peleasete con ello, yo llevo toda la mañana incapaz de pasar del paso de la compilación de los .mo.
he creado mi lista de archivos y la sentencia para crearlos es exactamente la misma, solo que trabajo todo en utf-8, es esta:
$ xgettext -f sources.txt -L PHP -o messages.po --from-code=utf-8
me genera perfectamente el messages.po, pero cuando hago lo siguiente:
$ msgfmt -o messages.mo messages.po
,obtengo el siguiente error:
atención: El conjunto de caracteres "CHARSET" no es un nombre de codificación portátil.
La conversión de mensajes al conjunto de caracteres del usuario podría no funcionar.
He buscado info pero no encontré mucha, y he probado con las combinaciones "utf-8", "UTF-8" "utf8", "UTF8", "iso-8859-1" en la primera sentencia,
te suena de algo esto?...
estoy a un paso... SOLO A UN PASO!!!! pero con un dolor de cabeza terrible.
saludo,
-
kamara
29 de septiembre de 2008
aquí de nuevo, correcto lo de los parámetros (pasa que cuando aparece algún error o warning ya dudas hasta de que madre me trajo al mundo), thanks.
anoche dándome de cabezazos logré hacer funcionar todo, tube más problemillas pero eran referentes ya al entorno que había montado (ubuntu, php5, ...) me faltaba instalar y actualizar los locales del propio sistema operativo (ca_ES.utf8 no estaba en el sistema, había que añadirlo a mano).
y con esto y un bizcocho, me quedé de nuevo ayer hasta las ocho!, ahora estoy que me caigo, pero muy happy de haberlo conseguido.
la verdad que sin tu artículo no sabía ni como meterle mano, me ha servido de bastante ayuda.
gracias maestro!
-
kamara
27 de septiembre de 2008
Bueno, lo logré, modifiqué el charset del php.ini en el mbstring.output y iconv y listo, ahora funciona!
-
kamara
27 de septiembre de 2008
una pregunta tonta, en las lineas:
bindtextdomain("message", "locale");
textdomain("message");
Los primeros parámetros "message" ¿corresponde al nombre del archivo que generamos? message.po y message.mo ? -
kamara
27 de septiembre de 2008
una pregunta tonta, en las lineas:
bindtextdomain("message", "locale");
textdomain("message");
Los primeros parámetros "message" ¿corresponde al nombre del archivo que generamos? message.po y message.mo ? -
Albert Lanchas
29 de septiembre de 2008
Manu,
sí que me pasó lo que comentas del warning. La razón que yo encontré es que cuando generas el catálogo con todos los literales a internacionalizar, aunque le pases el charset por la línea de comando, en la cabecera del po nunca pone el charset. Si lo editas manualmente ya no muestra ese warning.
Sobre los parámetros de las funciones bindtextdomain y textdomain, messages es el nombre de los ficheros po sin la extensión -
Joaquin Leonel Robles
23 de noviembre de 2009
Puede ser que este método sea el que usa Wordpress?
-
Jaume Font
19 de enero de 2010
Lo es, Joaquín, lo es...
-
Joaquin Leonel Robles
11 de febrero de 2010
efectivamente, es el metodo de wordpress...
igualmente, la utilización de un framework como Zend Framework facilita muchisimo la tarea de internacionalizar un sitio... -
Raul
18 de enero de 2011
Buenas,
Muy interesante el articulo. Estoy preparando un CMS multiidiomas y estoy usando el mismo sistema que describes en la web. Hasta que he metido el catalán todo funciona perfecto pero ahora cuando se ve en catalán empiezan problemas de accentos. Deseperante es poco. No encuentro la combninacioin perefcta. ¿Te suena el problema? -
buy phentermine
03 de febrero de 2012
Aloha! buy phentermine
-
cheap phentermine
03 de febrero de 2012
Aloha! cheap phentermine
-
generic phentermine
04 de febrero de 2012
Aloha! generic phentermine
-
adipex
04 de febrero de 2012
Aloha! adipex
-
phentermine no prescription
05 de febrero de 2012
Aloha! phentermine no prescription
-
buy phentermine
01 de febrero de 2012
Aloha! buy phentermine
-
phentermine
02 de febrero de 2012
Aloha! phentermine
-
valium
02 de febrero de 2012
Aloha! valium
-
cheap phentermine
02 de febrero de 2012
Aloha! cheap phentermine
-
phentermine online
02 de febrero de 2012
Aloha! phentermine online
-
valium online
02 de febrero de 2012
Aloha! valium online
-
generic phentermine
02 de febrero de 2012
Aloha! generic phentermine
-
generic valium
31 de enero de 2012
Aloha! generic valium
-
phentermine
31 de enero de 2012
Aloha! phentermine
-
phentermine without prescription
01 de febrero de 2012
-
diazepam
01 de febrero de 2012
Aloha! diazepam
-
phentermine online
01 de febrero de 2012
Aloha! phentermine online
-
phentermine 37.5mg
01 de febrero de 2012
Aloha! phentermine 37.5mg
-
valium without prescription
01 de febrero de 2012
Aloha! valium without prescription
-
phentermine no prescription
31 de enero de 2012
Aloha! phentermine no prescription
-
buy valium
31 de enero de 2012
Aloha! buy valium
-
phentermine 37.5mg
31 de enero de 2012
Aloha! phentermine 37.5mg
-
buy phentermine online
31 de enero de 2012
Aloha! buy phentermine online
-
adipex
30 de enero de 2012
Aloha! adipex
-
buy valium online
30 de enero de 2012
Aloha! buy valium online
-
phentermine without prescription
30 de enero de 2012
-
ugg
31 de enero de 2012
1.31 angry ブランド腕時計 ブランド レプリカ ルイヴィトンバッグ
1.31 angry ブランド腕時計 ブランド レプリカ ルイヴィトンバッグ -
pohippyi
26 de enero de 2012
-
Gundosbsc
28 de enero de 2012
Aloha! qhmhl iufqn umrom zmlah fpyvz liwcf lbgbe dmsra aoxpx bmreb
-
Gundosurd
23 de enero de 2012
Aloha!aayh! http://gfoiqz.com pryon pkumj http://skoieh.com aiqhz xvswn http://boaeqk.com iczxc punkq http://ayewtk.com xwmzk sbbfz http://nkosqd.com kedvt tkmqx
-
Gundoshwf
23 de enero de 2012
Aloha! defow xfwnj amvcp gysna cayjk wybsb dgkwo cnopk ncmzi hdibr
-
viagra
23 de enero de 2012
buy cialis Online
-
viagra
23 de enero de 2012
http://www.viagraonlinewithoutprescriptions.us viagra Online asd viagra Online asda
http://www.ambien-cheap.com Buy ambien asd Buy ambien asda
http://www.insomnia-meds.com ambien asd ambien asda
Viagra Online Without Prescription DTJLK SDFG
Viagra Online Without Prescription DTJLK SDFG
http://www.viagraonlinewithoutprescriptions.com viagra Online asd viagra asda
5
-
programarivm
20 de enero de 2012
Excelente artículo amigos. A continuación adjunto un post que explica cómo gestionar la i18n de forma muy sencilla sin necesidad de gettext. Ánimo y saludos.
http://programarivm.com/2012/01/internacionaliza-i18n-tus-aplicaciones-php-de-tamano-pequeno-o-mediano-de-la-forma-mas-rapida-y-sencilla/