Artículo para la revista Linux Actual número 18: Interna­ cionalizació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. 11.. EEll iinnggllééss ccoommoo iiddiioommaa uunniivveerrssaall 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. 22.. IInntteerrnnaacciioonnaalliizzaacciióónn ddee mmeennssaajjeess:: ggeetttteexxtt 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. 33.. PPrreeppaarraarr uunn pprrooggrraammaa ppaarraa iinntteerrnnaacciioonnaalliizzaacciióónn 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 _g_e_t_t_e_x_t_i_z_e. Este programa, invocado directamente en el directorio raíz de las fuentes realiza las siguientes funciones: · copia el fichero ABOUT-NLS que describe el entorno de internacionalización utilizado. · crea el directorio intl/ y pone allí todos los ficheros que se distribuyen con _g_e_t_t_e_x_t, estos ficheros son los ficheros de cabecera de la librería _l_i_b_i_n_t_l y deben ser idénticos entre distintos proyectos de software internacionalizados. · crea el directorio po/ y el fichero Makefile.in.in dentro de éste. Este directorio será el repositorio de la traducción de mensajes que se incluirá con la distribución del programa. 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: · crear el fichero po/POTFILES.in e introducir en él una lista (un fichero por línea) con los ficheros que contienen mensajes marcados para traducción. · modificar el fichero configure.in (del sistema de auto configuración) para: · introducir los cambios necesarios para que el sistema de auto- configuración busque las librerías de internacionalización (con la macro AM_GNU_GETTEXT). · indicar los idiomas a los que está traducido el programa (ficheros .po con traducciones) con la definición de ALL_LINGUAS. · declarar el nombre del paquete y versión. Generalmente se hará con las siguientes líneas, y esto permitirá a _g_e_t_t_e_x_t el hacer un seguimiento de las fuentes de código frente a las traducciones de mensajes. Como se verá más adelante podrán variar de forma independiente: PACKAGE=programa VERSION=X.Y.Z AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE") AC_DEFINE_UNQUOTED(VERSION, "$VERSION") AC_SUBST(PACKAGE) AC_SUBST(VERSION) · incorporar nuevos ficheros a los que se modifican con el guión _c_o_n_f_i_g_u_r_e, cambiando la macro AC_OUTPUT, para incluir a intl/Makefile y po/Makefile.in. · modificar los ficheros acconfig.h y aclocal.m4 (si existen) para incluir las funciones necesarias para que se detecte el soporte de internacionalización al configurar las fuentes. · modificar el fichero Makefile.in en el directorio raíz y en los de las fuentes de forma que se incluyan las librerías de internacionalización al compilar (tanto al generar los ficheros objeto como al enlazar dinámicamente), y para que se instalen los ficheros po en el sistema al ejecutar a _m_a_k_e _i_n_s_t_a_l_l. 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 pre­ vio, 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 _g_e_t_t_e_x_t 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 _g_e_t_t_e_x_t 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 _h_e_l_l_o, 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 _g_e_t_t_e_x_t 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 _x_g_e_t_t_e_x_t _h_e_l_l_o_._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. 44.. TTrraadduucccciióónn ddee llooss mmeennssaajjeess Una vez que se han modificado de forma adecuada las fuentes, se utilizará la herramienta _x_g_e_t_t_e_x_t para extraer de éstas las cadenas que pueden ser traducidas. Estas cadenas se extraen todas juntas a un sólo fichero _p_o_/_m_e_s_s_a_g_e_s_._p_o_t. Este fichero será el que se pueda utilizar como plantilla posteriormente por los traductores para generar los ficheros _L_E_N_G_U_A_J_E_._p_o. 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 _m_s_g_m_e_r_g_e para utilizar las traducciones ya realizadas de mensajes en la generación del fichero a traducir. _M_s_g_m_e_r_g_e tiene la ventaja de que es capaz de juntar ficheros .po de forma _d_i_f_u_s_a. 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. 55.. LLaa eevvoolluucciióónn eenn ffiicchheerrooss ppoo ¿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, _m_s_g_m_e_r_g_e y _m_s_g_c_m_p. 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. 66.. ¿¿QQuuiiéénn ttrraadduuccee llooss pprrooggrraammaass?? 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. 77.. DDiissttrriibbuucciióónn ddee llooss bbiinnaarriiooss 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 _m_s_g_f_t_m. 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. 88.. HHeerrrraammiieennttaass ddee ggeetttteexxtt 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: · gettextize. Añade los ficheros necesarios al árbol de fuentes de un programa para su internacionalización. Estos ficheros son comunes a todos los programas con ésta característica. · xgettext. Extrae los mensajes a traducir de unas fuentes internacionalizadas, generando el fichero messages.po correspondiente. · msgmerge. De una serie de mensajes ya traducidos, dentro de un catálogo, incorpora las traducciones a los correspondientes ficheros de un programa internacionalizado. · msgfmt. Genera los ficheros binarios con los mensajes traducidos para su distribución, es decir, los ficheros .mo · msgunfmt. Realiza la operación inversa al programa anterior, es decir, de un fichero .mo genera su equivalente .po · msgcomm. Busca mensajes comunes entre dos o más ficheros .po. · msgcmp. Compara dos ficheros .po para comprobar que ambos contienen el mismo número de cadenas traducidas, con lo que permite verificar si se han traducido todos y cada uno de los mensajes del programa. 99.. CCoonncclluussiioonneess 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 (_F_r_e_e _T_r_a_n_s_l_a_t_i_o_n _P_r_o_y_e_c_t, 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 1100.. SSuummaarriiooss 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. 1111.. LLiissttaaddooss 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 1122.. CCaappttuurraass Figura 1: El proceso completo de internacionalización con gettext 1133.. NNoottaass ddee mmaaqquueettaacciióónn 1144.. NNoottaass ddee ccoooorrddiinnaacciióónn