GNU gettext I (nociones básicas)
Este documento pretende ser un pequeño resumen en español de la documentación
de las herramientas
GNU gettext. En el mismo se explicarán brevemente los aspectos teóricos básicos.
Introducción
GNU gettext es una herramienta importante a utilizar en el proyecto de traducción de
la GNU. Este paquete ofrece un conjunto de herramientas y documentación bien integrados
para producir mensajes en múltiples lenguajes en nuestros programas.Estas herramientas
incluyen una serie de convenciones acerca de como los programas deberían ser escritos,
como deben ser llamados y organizados los ficheros y los directorios, una librería,
y una serie de programas independientes (en los que se centra el siguiente documento) para
manejar de diversas formas las cadenas traducibles de un programa.
Definiciones previas
Por internacionalización (internationalization o i18n) entendemos el proceso mediante
el que un programa o conjunto de programas de un paquete son preparados para el soporte de múltiples
lenguajes. Es un proceso de generalización.
Por localización (localization o l18n) entendemos la operación mediante la
cual se le proporciona a un programa o conjunto de programas previamente internacionalizados
toda la información necesaria para que puedan adaptarse a manejar entradas y salidas de una
forma que sea correcta para ciertas lenguas. Es un proceso de particularización.
Al conjunto formado por la descripción formal de un conjunto específico de los hábitos
culturales de algún país, junto con todas las traducciones asociadas a dicho país
se le denomina locale para esa lengua o país. Los usuarios consiguen la localización
de sus programas mediante la asignación de los valores correctos a unas variables de entorno
especiales, antes de la ejecución de dichos programas, identificando que locale
debería ser usado.
Conocemos por Soporte de Lenguaje Nativo (Native Language Support o NLS) a la actividad
que engloba la internacionalización y la localización.
La internacionalización es llevada a cabo por los programadores, mientras que la
localización es llevada a cabo por traductores.
Con lo visto anteriormente queda claro que la traducción es solo un aspecto del soporte a
una determinada lengua. Quedan otros aspectos, como el formato de los números, las fechas,
el dinero, etc, que de momento no es tenido en cuenta por gettext (y es bastante improbable que se
tenga en cuenta en el futuro).
Descripción del proceso
Durante el proceso se utilizan dos tipos especiales de archivo:
- Los archivos PO hacen referencia a Portable Object, y son legibles y editables
por humanos. Asocian cada cadena original y traducible de un determinado paquete con su traducción
en una lengua determinada. Es decir, un fichero PO individual está dedicado a la traducción
de un único lenguaje.
- Los archivos MO hacen referencia a Machine Object, son datos leidos por los
programas, y tienen formato binario. El formato es no-portable, lo cual quiere decir que deberemos
crear un fichero MO por cada plataforma donde pensemos utilizar la aplicación.
Como programador, el primer paso será identificar, dentro de las fuentes, aquellas
cadenas que sean susceptibles de ser traducidas. Si se está escribiendo un nuevo programa,
estas cadenas pueden ser marcadas conforme dicho programa se va escribiendo. Si usamos C (aunque más
tarde se explicará el proceso para PHP), podemos incluir estas lineas al principio
de cada fichero o en un fichero de cabecera central:
#define _(String) (String)
#define N_(String) (String)
#define textdomain(Domain)
#define bindtextdomain(Package, Directory)
Más tarde, cuando nos sintamos preparados para usar la librería gettext,
simplemente eliminaremos estas definiciones, incluiremos el fichero libintl.h y
enlazaremos con libintl.a. Eso será todo lo que se tenga que cambiar.
Una vez las fuentes han sido modificadas, el programa xgettext es usado para encontrar
y extraer todas las cadenas traducibles, y crear un archivo PO inicial, con el nombre paquete.pot,
conteniendo dichas cadenas, así como una serie de punteros a la línea
donde se encuentran en los fuentes (todas las traducciones estarán
vacías). La letra t en la extensión .pot hace referencia a que dicho
fichero es una plantilla (template), que todavía no está orientado a ningún
lenguaje en concreto. Tras esto viene la traducción de los mensajes y la creación
de un fichero PO.
Los programas son dinámicos por naturaleza, lo cual quiere decir que nuevas cadenas
serán añadidas con el tiempo. Lo único de lo que se debe preocupar el
encargado de mantener el programa es de marcar las nuevas cadenas traducibles, y olvidarse totalmente de la traducción.
Es importante entender que el proceso de traducción es un proceso contínuo
en la vida de un paquete. Después del esfuerzo inicial para la creación de la primera
traducción de un paquete, será necesario realizar alguna intervención de
vez en cuando (nuevas cadenas a traducir aparecen, y otras quedan obsoletas).
El programa msgmerge tiene el propósito de actualizar un fichero PO existente, comparando
con un nuevo fichero .pot, extraido mediante xgettext de unas fuentes recientes.
Cuando el archivo PO está completado, el programa msgfmt es usado para traducirlo
en un archivo MO orientado a la máquina. En el caso del lenguaje C, las fuentes deberán
ser compiladas y enlazadas con la librería gettext, instalar el ejecutable en algún
lugar donde los usuarios puedan acceder a él, e intalar también en una localización
correcta los archivos MO.
Tras todo este proceso, y suponiendo que las variables de entorno apropiadas tengan el valor
correcto, el programa será capaz de localizarse a sí mismo de forma automática,
cada vez que se ejecute.
Archivos PO
Un archivo PO se compone de un conjunto de entradas, cada una relacionando la cadena
original sin traducir con su correspondiente traducción (estando todas las traducciones
expresadas en una misma lengua). Cada entrada tiene la siguiente estructura:
espacio en blanco
# comentarios del traductor
#. comentarios automáticos
#: referencia
#, flags
msgid cadena sin traducir
msgstr cadena traducida
Cuando el archivo PO se genera utilizando las herramientas gettext, existe exactamente
un espacio en blanco entre cada entrada. Con respecto a los comentarios, existen de dos
tipos: aquellos en los que el símbolo # está seguido inmediatamente de
un espacio en blanco (creados y mantenidos exclusivamente por el traductor), y aquellos
en los que el símbolo # está seguido de algún carácter
que no sea un espacio en blanco, que son creados y mantenidos automáticamente
por gettext.
Después tendremos dos cadenas, correspondientes la primera a la cadena original que aparece
en el código fuente, y la segunda a la traducción de esta cadena. La cadena
original es la que sigue a msgid, mientras que la traducción es la que sigue a
msgstr. Las primeras son creadas y mantenidas por gettext, y las segundas son
de las que se tiene que hacer cargo el traductor.
Tanto las cadenas sin traducir como las traducidas respetan la sintaxis de C para las cadenas
de caracteres, incluyendo las comillas y las secuencias de escape. En el caso de
querer tener cadenas en múltiples líneas, la sintaxis debería ser la siguiente:
msgid ""
"Esto es un ejemplo de como podría continuar una cadena larga"
"en el caso de que esta cadena represente una salida en múltiples líneas"
En este ejemplo, msgid está seguido de tres cadenas, que serán
concatenadas. Concatenar la cadena vacía no tendrá ningún efecto,
pero es la forma de asegurarnos de que se cumple la necesidad de que msgid
vaya seguido de una cadena en la misma línea. De hecho, esta cadena vacía
podría haber sido omitida, pero solo si la que empieza por "Esto" hubiera sido
desplazada a la primera línea.
Preparando las fuentes
A partir de este momento tomaremos el lenguaje C como referencia. Cada módulo en C
que quisieramos que dispusiera de cadenas traducidas debería contener la siguiente
línea:
#include <libintl.h>
La inicialización de los datos locales debería ser realizada más o menos
de la misma forma en todos los programas, tal como se muestra aquí:
int main (char **argv, int argc)
{
setlocale(LC_ALL,"");
bindtextdomain(PACKAGE, LOCALE_DIR);
textdomain(PACKAGE);
}
El valor de las constantes PACKAGE y LOCALE_DIR debería ser proporcionado
por un archivo del tipo 'config.h' o por el correspondiente Makefile. Usar LC_ALL podría
no llegar a ser apropiado. LC_ALL incluye todas las categorias de localización (locale),
especialmente LC_CTYPE, responsable de determinar las clases de caracteres en una serie
de funciones de 'ctype.h' lo cual podría producir una salida incorrecta. Además,
algunos sistemas tienen problemas procesando números usando las funciones scanf cuando
se utiliza LC_ALL. Es por ello que normalmente es necesario reemplazar la línea que
hace referencia a LC_ALL por una secuencia de líneas setlocale:
setlocale(LC_TIME, "");
setlocale(LC_MESSAGES, "");
Otras constantes que pueden ser usadas junto a setlocale son LC_CTYPE, LC_COLLATE,
LC_MONETARY, LC_NUMERIC y LC_TIME.
Con respecto a las cadenas traducibles en las fuentes, todas ellas deberán aparecer
marcadas, como si fueran el argumento de alguna función o macro del preprocesdor. El objetivo
de esto es ayudar a xgettext a extraer todas las cadenas traducibles y producir el archivo
PO plantilla. Para ello hacemos uso de la función gettext. Algunos paquetes
utilizan '_' como marca, escribiéndose '_("cadena traducible")' en lugar de 'gettext("cadena traducible")'.
Aunque GNU gettext utiliza esta convención internamente, no la ofrece de forma
oficial, así que aquellos que deseen utilizar '_' en lugar de 'gettext' deberán
realizar las siguientes declaraciones al principio del código:
#include <libintl.h>
#define _(String) gettext(String)
El mantenimiento del código fuente es extremadamente sencillo. Como programador, tan
solo deberemos preguntarnos si la nueva cadena o una cadena modificada requiere una traducción, e incluirla
entre '_()' en caso afirmativo. La cadena "%s:%d" es un ejemplo de cadena que no requiere
traducción.
En los programas en C normalmente las cadenas son utilizadas como parámetro de la
familia de funciones printf. Estas cadenas suelen contener especificadores de formato
indicados mediante el carácter '%'. Si tenemos el siguiente código:
printf(gettext("La cadena '%s' tiene %d caracteres\n"), s, strlen(s));
Una posible traducción en alemán sería:
"%d Zeichen lang ist die Zeichenkette '%s'"
Como se observa, el orden en el que se encuentran los especificadores de formato
ha cambiado, mientras que el resto de parámetros de printf seguirín
en la misma posición. Para evitar errores de este tipo la herramienta msgfmt
puede comprobar estáticamente si los argumentos de la cadena original y de la
cadena traducida coinciden en tipo y en número. Si este no es el caso se mostrará
un mensaje de aviso. El problema anterior se podría resolver utilizando
el siguiente formato:
"%2$d Zeichen lang ist die Zeichenkette '%1$s'"
Las rutinas en msgfmt conocen esta notación especial.
No todas las cadenas de un programa tienen que ser cadenas formateadas como la anterior, por lo que no
es muy eficiente que msgfmt compruebe todas las cadenas existentes en un archivo PO para
mostrar avisos. Es por ello que xgettext añade un flag especial a aquellos mensajes
que piensa que pueden ser una cadena formateada, mediante métodos heurísticos.
En el archivo PO la entrada correspondiente sería marcada usando el flag
c-format.
Por último, consideramos aquellos casos en los que no es posible marcar las cadenas
traducibles mediante gettext, como en el siguiente ejemplo:
{
static const char *mensajes[] = {
"Algún mensaje interesante",
"otro mensaje"
};
const char *string;
...
string = index > 1 ? "un mensaje por defecto":mensajes[index];
fputs(string)
...
}
Mientras que no hay ningún problema en marcar la cadena "un mensaje por
defecto", no es posible hacer lo propio con las cadenas de inicialización
de mensajes. Debemos hacer dos cosas: por una parte deberemos marcar las cadenas
para que el programa xgettext las pueda encontrar, y por otra parte
deberemos traducir esas mismas cadenas en tiempo de ejecución antes de que
sean mostradas.
La parte de detección de las cadenas por parte de xgettext puede ser
realizada mediante el uso de una nueva palabra reservada: gettext_noop. Para la
parte de traducción en tiempo de ejecución marcamos todos los accesos
a las cadenas desde el array:
...
static const char *mensajes[] = {
gettext_noop("Algún mensaje interesante"),
gettext_noop("otro mensaje")
};
...
string = index > 1 ? gettext("un mensaje por defecto"):gettext(mensajes[index]);
[VOLVER]