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:
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]