Artículo para la revista Linux Actual número 18: Internacionalización de programas en GNU/Linux

Javier Fernández-Sanguino Peña

10 junio 2001


La internacionalización es el proceso que permite que un programa ofrezca a un usuario un entorno de computación adaptado a su propio país: lenguaje, símbolos monetarios, formatos de fecha y hora... En éste artículo se verán los distintos esfuerzos que se están llevando a cabo para hacer al SO GNU/Linux internacional, centrándose en la internacionalización de programas.

1. El inglés como idioma universal

El inglés es el idioma oficial por regla general en la práctica totalidad de proyectos de software libre en la actualidad. Este idioma se ha convertido en este siglo en el idioma internacional por excelencia, y es una realidad que afecta, no sólo a los usuarios dentro del mundo GNU/Linux sino a cualquier usuario de los sistemas de la información. Es por esto que el inglés es el idioma en el que habitualmente los programas presentan sus mensajes, y la mayoría de la documentación está realizada en inglés.

No cabe duda que el uso de un único lenguaje facilita la coordinación del trabajo de los desarrolladores, a la hora de intercambiar ideas, y la difusión de un proyecto, pero los usuarios desean más, desean que los entornos en los que trabajen estén adaptados a sus entornos específicos.

Esta adaptación no es exclusivamente, aunque sí es la primera evidencia, la traducción de todos y cada uno de los mensajes, menús, botones, etc. que muestra el programa al usuario. También incluye la adaptación del programa para que al utilizar otras características eminentemente locales, se "adapte" al entorno. Por ejemplo, al utilizar unidades monetarias muestre preferentemente las unidades locales con las expresiones habituales de separación de decimales, nomenclatura, etc. o que la representación de fechas y horas se haga en la manera acostumbrada al usuario (día-mes-año en lugar de mes-día-año por ejemplo), o incluso que las unidades métricas utilizadas para la representación de distancias, pesos, etc. sean las utilizadas por el usuario.

La modificación de un software para que sea capaz de ofrecer estas funciones es lo que se conoce como internacionalización (muchas veces se reduce al acrónimo 'i18n'). Mientras que la modificación de estas funciones para adaptar un programa a un determinado entorno se conoce como localización (muchas veces reducido a 'l10n'). Explicándolo en forma breve, los programadores internacionalizan y los traductores localizan.

2. Internacionalización de mensajes: gettext

Dentro de la internacionalización de programas, uno de los aspectos más críticos es la posibilidad de traducir los mensajes que presenta el programa al usuario. No sólo los mensajes que puedan aparecer en el interfaz gráfico, por ejemplo, sino todo tipo de mensajes de error que genere el programa, ayuda en las opciones al ejecutarse, etc.

La herramienta GNU para la internacionalización de mensajes en programas es Gettext. Esta herramienta, desarrollada entre 1994 y 1995 por un grupo variado de programadores, facilita la creación de programas que pueden distribuirse con múltiples catálogos de mensajes en distintos idiomas. Posteriormente, en entornos localizados, los programas pueden presentar los mensajes correspondientes al entorno declarado por el usuario.

Esta herramienta es relativamente transparente al programador, ya que sólo tiene que marcar los mensajes que cree que deben traducirse. Asimismo, la modificación del código fuente y reubicación de los mensajes es relativamente transparente al traductor, que sólo tiene que mantener actualizado un listado de traducciones de los mensajes. Las herramientas de gettext se encargan, por debajo, de homogeneizar los catálogos y modificar éstos cuando las fuentes cambian, pero preservando las traducciones ya realizadas.

De esta forma, el trabajo de traducción de los mensajes de un programa se reduce a una traducción inicial de todos los mensajes y al mantenimiento de los pequeños (o grandes) cambios en el código que puedan suponer la introducción (o desaparición) de mensajes. Y el trabajo del programador se limita a incorporar las funciones necesarias en su programa. Una vez hecho esto, el trabajo de ambos grupos puede proceder por separado, lo cual facilita el desarrollo en ambos sentidos. Es decir, un traductor no tiene que depender del programador para incorporar un nuevo idioma y un programador no depende del esfuerzo de traducción para la generación de nuevas versiones de su programa.

3. Preparar un programa para internacionalización

Lo primero que debe hacer un programador para internacionalizar su software es detectar en qué puntos de éste se introducen mensajes que van destinados al usuario (bien porque sean mensajes que aparezcan en pantalla durante el uso del programa o porque se envíen en determinadas circunstancias de error). Estos mensajes (cadenas de caracteres) tienen que ser marcados de forma que pueda extraerse esta información.

Algunos de los cambios que son necesarios hacer a las fuentes del programa las realiza el programa gettextize. Este programa, invocado directamente en el directorio raíz de las fuentes realiza las siguientes funciones:

Sin embargo, aunque se hayan realizado estos cambios a las fuentes, el desarrollador tiene que realizar otros de forma manual para asegurar que la internacionalización pueda llevarse fácilmente a cabo:

Con estos cambios, el desarrollador se asegura que, al configurar el programa se detecte el soporte (o no) de internacionalización del sistema y que, tras esto, se compilen las fuentes de la manera adecuada y se instalen con soporte de internacionalización (incluyendo todos los ficheros de las traducciones) si el sistema no carece de soporte para éste.

Una vez realizado estos cambios es necesario cambiar las fuentes C en sí del programa, para marcar los mensajes que se deban traducir. La búsqueda de estos mensajes puede ser algo tediosa, aunque dependerá del programa en sí. Se puede seguir la máxima de "todo aquello que se presenta al usuario debe ser internacionalizado" y así buscar mensajes que se presenten a este (mediante funciones de salida como printf) o cadenas que se utilicen para generar salidas (como botones o menús). Una vez localizadas, estos mensajes deben rodearse con una llamada a gettext. Así, una línea de código del estilo de

printf("Hola, mundo\n");
se convertirá a
printf(gettext("Hola, mundo\n"));
También es común utilizar la forma abreviada _() con un #DEFINE previo, en lugar de la llamada a gettext. En la mayoría de estos casos estos cambios no afectan mucho al código así, sin embargo habrá casos en los que sea necesario modificar ligeramente el código. Por ejemplo, cuando la cadena de caracteres a traducir se había supuesto de un tamaño fijo de caracteres. No olvidemos que lo que hará la función gettext en tiempo de ejecución será sustituir esta cadena por el equivalente traducido dentro de la ejecución del programa.

En muchos casos, esta modificación de las fuentes, cuando los mensajes de salida están definidos claramente, se puede hacer incluso de forma directa mediante la sustitución de expresiones regulares con un sencillo programa en PERL o awk.

En cualquier caso, la documentación de gettext incluye precisas explicaciones de cómo modificar convenientemente las fuentes para que tengan soporte de internacionalización.

En el listado 1 se muestra un ejemplo de la internacionalización de un programa. En este caso se trata del programa hello, un programa de demostración de GNU que muestra el consabido "Hello, world!" por pantalla al ejecutarse. Como puede verse (con '+' se indican las líneas añadidas y con '-' las eliminadas) se han modificado, rodeándolos con una llamada a la función gettext todos los mensajes que se envían por pantalla. También se han añadido las directivas de compilador necesarias para incluir las funciones de internacionalización.

El resultado de ejecutar xgettext hello.c se muestra en el listado 2. Como puede verse aquí, todas las cadenas marcadas con gettext() han sido extraídas y el fichero resultante es una plantilla que puede utilizarse para traducir directamente éstas.

4. Traducción de los mensajes

Una vez que se han modificado de forma adecuada las fuentes, se utilizará la herramienta xgettext para extraer de éstas las cadenas que pueden ser traducidas. Estas cadenas se extraen todas juntas a un sólo fichero po/messages.pot. Este fichero será el que se pueda utilizar como plantilla posteriormente por los traductores para generar los ficheros LENGUAJE.po.

Este proceso se muestra de forma completa en la figura 1. Como puede verse allí éste fichero se obtiene a través de las fuentes ya preparadas para internacionalizarse. Si se dispone de una traducción de una versión previa del programa, podrá utilizarse el programa msgmerge para utilizar las traducciones ya realizadas de mensajes en la generación del fichero a traducir. Msgmerge tiene la ventaja de que es capaz de juntar ficheros .po de forma difusa. Es decir, aunque las fuentes hayan modificado la localización de los mensajes a internacionalizar, es capaz de encontrar dónde debería incluir un texto ya traducido.

Con el fichero .po generado, con o sin traducciones previas, éste puede ser ya distribuido a los responsables de las traducciones para que incorporen a éste la traducción de los mensajes existentes. Si nos encontramos ante un programa que se va a internacionalizar en este momento a un nuevo idioma, el fichero .po tendrá un aspecto similar al del Listado 2. Sin embargo, si el programa ya ha sido internacionalizado previamente, el fichero tendrá cadenas traducidas y cadenas sin traducir, o incluso cadenas que han sido modificadas en la última versión del programa y necesitan ser revisadas.

5. La evolución en ficheros po

¿Cómo evolucionan los ficheros .po? Uno podría pensar que una vez realizada la traducción de los mensajes a un determinado idioma para un programa no es necesario realizar nada más. Sin embargo esto no es así, los programas son entes vivos, cambian, evolucionan, incorporan nuevas funcionalidades, etc. Esto trae consigo, como uno pudiera esperar, que los mensajes que se ofrecen al usuario, los botones, los menús, etc. cambien también con el tiempo. Aunque el grueso de los mensajes pueda no variar, es necesario revisar, con cada nueva versión que se distribuye de un programa los mensajes internacionalizados.

Pero sin embargo, y he aquí una de las maravillas de gettext, esta revisión no influye para nada la tarea del programador o programadores encargados de mejorar y distribuir las nuevas versiones del programa. Es decir, el hecho de que no haya una persona encargada de una determinada traducción no tiene por qué interrumpir la distribución de un programa. Sí, los mensajes no estarán todos traducidos, pero ésto "sólo" se traducirá en que un usuario verá los mensajes en dos idiomas. Los traducidos, en su idioma nativo, y los no traducidos en el idioma original.

Evidentemente, esto es mucho mejor que no disponer de una nueva versión del programa por no poder contactar con los traductores cuando a lo mejor se está hablando de un programa internacionalizado a más de diez idiomas. También es mejor que ver los mensajes en el idioma original cuando no se ha podido llegar a traducir el 100% de los mensajes.

Asisten en esta tarea las herramientas, msgmerge y msgcmp. La segunda permite determinar si se han traducido todos los mensajes con respecto a la última versión disponible del programa, y la primera permite distribuir una nueva versión de un fichero de mensajes "mezclando" los mensajes ya traducidos con los nuevos a traducir.

6. ¿Quién traduce los programas?

Como se puede ver en listado 2, la traducción de mensajes es muy sencilla, y cualquier usuario puede coger un fichero .po traducido parcialmente y rellenar los "huecos" que falten. Este hecho, en el mundo GNU lleno de personas de muchas capacidades distintas dispuestas a colaborar, garantiza la posibilidad de colaborar y de formar parte de un equipo de traducción a usuarios que no tengan ningún tipo de conocimiento de programación. Para mantener un fichero .po al día sólo es necesario conocer tanto el idioma original como el idioma final de la traducción.

Así pues, uno de los objetivos logrados de la librería gettext es que cualquier persona, con unos mínimos conocimientos, pueda colaborar en la internacionalización de los programas derivados del software libre. No siendo necesario ser un desarrollador de programas, ni una persona experta en programación para llevar estas tareas a cabo.

En el proyecto GNU se dan soporte a distintos grupos de internacionalización que son las personas responsables de la traducción de los programas. Aún así no es necesario una dedicación permanente a estos grupos para internacionalizar un programa, como ya se ha visto, la internacionalización puede ser un esfuerzo puntual y concreto. La existencia de los grupos garantiza, sin embargo, la correcta revisión de estos trabajos puntuales que pueden realizar los usuarios, la elaboración de glosarios que den uniformidad a los programas traducidos y la actualización de las traduccioens en vistas de nuevas versiones de programas.

7. Distribución de los binarios

Con las traducciones ya realizadas de los ficheros .po de los distintos lenguajes disponibles, ya sólo queda generar el formato necesario para su distribución. En la distribución de mensajes traducidos, se utilizan ficheros .mo que genera de forma automática la herramienta msgftm. Estos ficheros binarios se distribuyen de forma conjunta con el código fuente, instalándose en ubicaciones predefinidas.

La librería gettext cargará dichos binarios cuando el entorno del usuario indique un idioma del que está disponible la correspondiente versión traducida.

Esta ubicación suele ser /usr/share/locale/LENGUAJE/LC_MESSAGES, lo que permite que, el usuario que no desee mensajes en otros idiomas distintos del suyo, pueda eliminar aquellos que no considere importantes. En cualquier caso, los binarios del programa se distribuyen habitualmente con todos los mensajes traducidos a todos los idiomas, y tendrá que ser el sistema de instalación del sistema operativo del usuario el que le de la opción de eliminar dichos idiomas.

8. Herramientas de gettext

Ya se han visto, en la descripción de las tareas de internacionalización, algunos de los programas que incorpora la librería de GNU gettext para ayudar a la tarea de internacionalización. La librería incorpora, en total, los siguientes programas:

9. Conclusiones

En este artículo se ha visto cómo se internacionaliza un programa, y qué herramientas se pueden utilizar para ello, particularizando a la librería más utilizada del mundo de software libre, la librería Gettext. Aunque éste artículo se ha centrado en la internacionalización de fuentes en C/C++, también es posible internacionalizar fuentes escritas en otros lenguajes de programación. Por ejemplo, PERL cuenta con una librería de adaptación a gettext llamada Locale::gettext que permite la internacionalización de programas interpretados en este lenguaje.

El usuario avanzado, que desee encontrar más información, puede acudir a http://www.gnu.org/software/gettext/gettext.html (y gettext.gnu.org). Si tiene instalado el software de gettext, también dispondrá de la ayuda en línea, que podrá consultar ejecutando info gettext

Si desea ayudar al esfuerzo de internacionalización de programas de GNU, contacte con su equipo local de trabajo. Puede contribuir sus trabajos a través del Proyecto de Traducciones Libres (Free Translation Proyect, n. del a.) en http://www.iro.umontreal.ca/contrib/po/. Si desea ver el estado de las traducciones existe una base de datos de traducciones y traductores que puede consultar en http://www2.iro.umontreal.ca/ pinard/po/registry.cgi?team=es. En cualquier caso, algunos programas internacionalizados pueden no haberse incluido en el proyecto GNU, para hacerse una idea de la internacionalización de programas en un sistema operativo GNU/Linux, pruebe a consultar, por ejemplo, el Monitor de traducciones de Debian en http://www.debian.org/intl/l10n/po-es

10. Sumarios

El inglés es el idioma oficial de la mayoría de los proyectos de software libre.

Los usuarios desean que sus entornos estén adaptados a ellos.

La modificación del software es parte de la internacionalización.

La librería gettext es el estándar para internacionalizar programas.

Para preparar un programa hay que detectar dónde se ofrecen mensajes al usuario.

Algunos cambios para la internacionalización se pueden hacer de forma automática.

El soporte de internacionalización se puede detectar al compilar el programa.

Es necesario marcar los mensajes en las fuentes en C.

La plantilla de mensajes está en el fichero messages.pot

Los mensajes se traducen en los ficheros .po

Los ficheros .po se pueden utilizar aunque estén parcialmente traducidos.

Gettext incorpora una variedad de herramientas para ayudar a la internacionalización.

La traducción de mensajes es tan sencilla que cualquier usuario puede colaborar.

PERL también incorpora una librería de adaptación a gettext.

11. Listados

LISTADO 1

$ diff -ur hello/hello-1.3/hello.c ./hello-int/hello-1.3/hello.c
--- hello/hello-1.3/hello.c     Sun Jun 10 18:30:47 2001
+++ ./hello.c   Tue May 16 21:49:28 2000
@@ -64,6 +64,12 @@
 #endif /* HAVE_ALLOCA_H.  */
 #endif /* GCC.  */
 
+#include >libintl.h<
+#define _(String) gettext (String)
+
+#include "config.h"
 
 #define the (1)
 
@@ -79,7 +85,7 @@
 
 extern char version[];
 
-char usage[] = "Usage: %s [-htvm] [--help] [--traditional] [--version] [--mail]\n";
+char usage[] = gettext("Usage: %s [-htvm] [--help] [--traditional] [--version] [--mail]\n");
 
 static char *progname;
 
@@ -91,6 +97,14 @@
   int optc;
   int h = 0, v = 0, t = 0, m = 0, lose = 0, z = 0;
 
+#ifdef ENABLE_NLS
+   setlocale (LC_ALL, "");
+   bindtextdomain (PACKAGE, LOCALEDIR);
+   textdomain (PACKAGE);
+#endif
+
+
+
   progname = argv[0];
 
 #define king
@@ -136,15 +150,15 @@
   if (h)
     {
       /* Print help info and exit.  */
-      fputs ("This is GNU Hello, THE greeting printing program.\n",
+      fputs (gettext("This is GNU Hello, THE greeting printing program.\n"),
             stderr);
       fprintf (stderr, usage, progname);
-      fputs ("  -h, --help\t\t\tPrint a summary of the options\n", stderr);
-      fputs ("  -t, --traditional\t\tUse traditional greeting format\n",
+      fputs (gettext("  -h, --help\t\t\tPrint a summary of the options\n"), stderr);
+      fputs (gettext("  -t, --traditional\t\tUse traditional greeting format\n"),
             
             stderr);
-      fputs ("  -v, --version\t\t\tPrint the version number\n", stderr);
-      fputs ("  -m, --mail\t\t\tPrint your mail\n", stderr);
+      fputs (gettext("  -v, --version\t\t\tPrint the version number\n"), stderr);
+      fputs (gettext("  -m, --mail\t\t\tPrint your mail\n"), stderr);
       exit (0);
     }
 
@@ -177,7 +191,7 @@
              struct passwd *pwd = getpwuid (getuid ());
              if (! pwd)
                {
-                 fprintf (stderr, "%s: Who are you?\n", progname);
+                 fprintf (stderr, gettext("%s: Who are you?\n"), progname);
                  exit (1);
                }
              user = pwd->pw_name;
@@ -239,13 +253,13 @@
        }
     }
   else if (z)
-    puts ("Nothing happens here.");
+    puts (gettext("Nothing happens here."));
   else
     {
       if (t)
-        printf ("hello, world\n");
+        printf (gettext("hello, world\n"));
       else
-        puts ("Hello, world!");
+        puts (gettext("Hello, world!"));
     }
 
   exit (0);
@@ -260,7 +274,7 @@
   char *ptr = malloc (size);
   if (! ptr)
     {
-      fprintf (stderr, "%s: virtual memory exhausted\n", progname);
+      fprintf (stderr, gettext("%s: virtual memory exhausted\n"), progname);
       exit (1);
     }
   return ptr;
PIE LISTADO 1: Modificaciones realizadas al código de hello.c para internacionalización

LISTADO 2

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Free Software Foundation, Inc.
# FIRST AUTHOR >EMAIL@ADDRESS<, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2001-06-10 19:29+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME >EMAIL@ADDRESS<\n"
"Language-Team: LANGUAGE >LL@li.org<\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"

#: hello.c:88
#, c-format
msgid "Usage: %s [-htvm] [--help] [--traditional] [--version] [--mail]\n"
msgstr ""

#: hello.c:153
msgid "This is GNU Hello, THE greeting printing program.\n"
msgstr ""

#: hello.c:156
msgid "  -h, --help\t\t\tPrint a summary of the options\n"
msgstr ""

#: hello.c:157
msgid "  -t, --traditional\t\tUse traditional greeting format\n"
msgstr ""

#: hello.c:160
msgid "  -v, --version\t\t\tPrint the version number\n"
msgstr ""

#: hello.c:161
msgid "  -m, --mail\t\t\tPrint your mail\n"
msgstr ""

#: hello.c:194
#, c-format
msgid "%s: Who are you?\n"
msgstr ""

#: hello.c:256
msgid "Nothing happens here."
msgstr ""

#: hello.c:260
msgid "hello, world\n"
msgstr ""

#: hello.c:262
msgid "Hello, world!"
msgstr ""

#: hello.c:277
#, c-format
msgid "%s: virtual memory exhausted\n"
msgstr ""

PIE LISTADO 2: Fichero po de hello.c

12. Capturas

Figura 1: El proceso completo de internacionalización con gettext

13. Notas de maquetación

14. Notas de coordinación