Tema XV Internacionalización (I18N) (Revision : 1,4) Herramientas de Programación. 19 de noviembre de 2004 Resumen Internacionalización. Localización. Catalogos de mensajes. Dpto. Lenguajes y Sistemas Informáticos Universidad de Alicante DLSI H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Preliminares: [I] Entendemos por internacionalización al proceso por el que un programa o conjunto de programas de un proyecto es capaz de soportar diversos lenguajes. Generalización. I18n Entendemos por localización al proceso por el que un conjunto de programas ya ‘internacionalizados’ son capaces de adaptar su entrada y salida a un determinado idioma y hábitos culturales. Particularización. L10n DLSI 1 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Preliminares: [II] A la descripción formal del conjunto de hábitos culturales de un paı́s junto con las traducciones de los mensajes a un lenguaje nativo, se le llama locale, por localización para un determinado paı́s e idioma. Al conjunto de internacionalización y localización se le llama Soporte de Lenguaje Nativo: NLS A lo largo del tema nos ocuparemos de gettext como herramienta de ayuda en la internacionalización . DLSI 2 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext : [I] La idea que hay detrás de gettext es muy sencilla. . . Cualquier cadena del programa/librerı́a que pueda leer el usuario no se ‘imprime’ directamente. . . Previamente se procesa con una función de la librerı́a gettext y el resultado es lo que se muestra al usuario. DLSI 3 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext : [II] Y el proceso serı́a el siguiente Se extraen todas las cadenas del código fuente a unos ficheros especiales. xgettext . Hay un fichero de éstos por cada idioma, y en él se acompaña cada cadena en el idioma original por la cadena traducida al idioma destino. DLSI 4 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext : [III] Se procesan estos ficheros especiales —cada uno de un idioma— para optimizar su lectura. msgfmt . Se modifica el fuente de manera que cada cadena que aparecı́a en él, ahora es el resultado de llamar a ‘gettext (cadena)’. Se compila y enlaza el programa con la librerı́a gettext . DLSI 5 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext : [IV] En realidad es —sólo— un poco más complicado: Original C Sources ---> PO mode ---> Marked C Sources ---. | .---------<--- GNU gettext Library | .--- make <---+ | | ‘---------<--------------------+-----------’ | | | .-----<--- PACKAGE.pot <--- xgettext <---’ .---<--- PO Compendium | | | ^ | | ‘---. | | ‘---. +---> PO mode ---. | +----> msgmerge ------> LANG.pox --->--------’ | | .---’ | | | | | ‘-------------<---------------. | | +--- LANG.po <--- New LANG.pox <----’ | .--- LANG.gmo <--- msgfmt <---’ | | | ‘---> install ---> /.../LANG/PACKAGE.mo ---. | +---> "Hello world!" ‘-------> install ---> /.../bin/PROGRAM -------’ DLSI 6 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Archivos especiales: [I] Los archivos especiales que hemos comentado antes usan la extensión .po por Portable Object. Son de texto, con un formato especial. Emacs dispone de un modo de edición especı́fico para ellos. msgfmt los transforma en archivos binarios, con extensión .mo por Machine Object. DLSI 7 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Archivos especiales: [II] Ejemplo de archivo .po: # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Free Software Foundation, Inc. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "POT-Creation-Date: 2001-07-16 18:43+0200\n" "PO-Revision-Date: 2001-07-16 19:00+0200\n" "Last-Translator: Alfredo <[email protected]>\n" "Language-Team: english <[email protected]>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" #: ../src/hola.c:31 msgid "Hola Mundo." "\n" msgstr "Hello World." "\n" DLSI 8 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [I] Comenzaremos preparando el código fuente. . . Para ello definiremos de este modo las siguientes macros del preprocesador: #define #define #define #define DLSI _(String) (String) N_(String) (String) textdomain(Domain) bindtextdomain(Package, Directory) 9 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [II] Y marcaremos las cadenas del código que se vayan a traducir con la macro: (cadena): cout << (‘‘hola’’) << endl. La macro N (cadena) sirve para marcar cadenas usadas en la iniciación de vectores: const char *strs[] = { N ("Cadena de iniciación") }; cout << (strs[0]) << endl; DLSI 10 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [III] Más tarde, cuando ya estemos listos para soportar completamente gettext , lo haremos ası́: #include <libintl.h> #define _(String) gettext (String) #define gettext_noop(String) (String) #define N_(String) gettext_noop (String) Y al inicio del programa principal pondremos: setlocale(LC_ALL,""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); DLSI 11 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [IV] En ocasiones puede ser necesario reemplazar la llamada a setlocale(LC ALL,) por estas otras: setlocale (LC_CTYPE, ""); setlocale (LC_MESSAGES, ""); DLSI 12 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [V] PACKAGE es una macro con el nombre de nuestro programa. LOCALEDIR es una macro que contiene el subdirectorio de inicio a partir del cual se encuentra la ‘estructura’ de locales para los distintos idiomas. DLSI 13 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [VI] Suponiendo que LOCALEDIR sea igual a locale, a partir de él hay una estructura de directorios ası́: locale/es/LC MESSAGES/aplicacion.mo. locale----+ +----emacs.mo | +----gvim.mo +---es/LC_MESSAGES---+----xterm.mo | | +----emacs.mo +---en/LC_MESSAGES---+----xterm.mo | +---ca/LC_MESSAGES---......... DLSI 14 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [VII] A continuación generaremos los ficheros .po a partir de los fuentes usando xgettext ası́: xgettext -k -kN hola.c -o hola.pot La extensión .pot indica que es un ‘patrón’ de un fichero .po. Duplicaremos este fichero por cada idioma al que queramos traducir la aplicación: en.po , ca.po , etc. . . y haremos las traducciones sobre ellos. Finalmente los transformaremos a ficheros .mo usando msgfmt ası́: msgfmt en.po -o en.mo. DLSI 15 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [VIII] ¿Qué ocurre al ir modificando los fuentes en los que se basan los ficheros .po que ya contienen traducciones?. Van quedando desfasados. . . pero se pueden actualizar automáticamente. Eso lo hace msgmerge def.po ref.po. Donde def.po es un fichero de traducciones ya existente y ref.po es el último fichero extraido de los fuentes con xgettext. DLSI 16 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [IX] En las cadenas de formato de printf puede alterarse el orden de los parámetros en la traducción. Por ejemplo, en la traducción al alemán de: printf (gettext ("String ‘ %s’ has %d characters"), s, strlen (s)); podrı́a obtenerse esta cadena: " %d Zeichen lang ist die Zeichenkette ‘ %s’". Lo cual producirı́a. . . un error de ejecución . msgfmt lo soluciona con la notación: " %2$d Zeichen lang ist die Zeichenkette ‘ %1$s’". DLSI 17 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 Uso de gettext : [X] Finalmente queda enlazar1 con la librerı́a libintl.a. Dar el valor del idioma elegido a una de estas variables de entorno que, por prioridad decreciente, son: LANGUAGE, LC ALL , LC xxx —donde ‘xxx’ es el valor del locale elegido— y LANG . Y poner en marcha el programa. Por ejemplo: LANG=en GB hola 1 Sólo si fuera necesario, versiones recientes de la librerı́a gnu-libc no lo necesitan pues ya incluyen las funciones comentadas. DLSI 18 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext y autotools : [I] automake y autoconf proporcionan soporte para el uso de gettext . Estando en el directorio raı́z del proyecto, basta con seguir estos pasos: • Ejecutamos el programa gettextize, el cual crea: ABOUT-NLS , po/ e intl/ . • El subdirectorio po contiene inicialmente el fichero Makefile.in.in —doble sufijo—. • En el subdirectorio po creamos el fichero POTFILES.in que contiene una lista de los ficheros de código fuente con cadenas para traducir. DLSI 19 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext y autotools : [II] Ejemplo de fichero POTFILES.in : # List of source files containing translatable strings. # Copyright (C) 1995 Free Software Foundation, Inc. # Common library files lib/my_error.c lib/scan_text.c # Package source files src/process.c src/main.c DLSI 20 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext y autotools : [III] En configure.ac añadimos las siguientes lı́neas: ALL LINGUAS="de fr". AM GNU GETTEXT. AC OUTPUT([a~ nadimos... intl/Makefile po/Makefile.in]). DLSI 21 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext y autotools : [IV] Copiamos al directorio raı́z del proyecto los ficheros: config.guess y config.sub. Se pueden obtener de ftp://ftp.gnu.org/pub/gnu/config/, o de la distribución de automake o libtool que tengamos, pero —probablemente— menos actualizados. DLSI 22 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext y autotools : [V] Añadimos al Makefile.in del directorio raı́z: PACKAGE = @PACKAGE@ y VERSION = @VERSION@. Añadimos ABOUT-NLS a la variable DISTFILES. La variable SUBDIRS, valdrá algo parecido a : SUBDIRS = doc intl lib src @POSUB@. DLSI 23 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext y autotools : [VI] Añadimos al Makefile.in del directorio con el código fuente: PACKAGE = @PACKAGE@, VERSION = @VERSION@ y top srcdir = @top srcdir@. Añadimos datadir = @datadir@ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=’’$(localedir)’’ @DEFS@ LIBS = @INTLLIBS@ @LIBS@ . DLSI 24 H.P.: Tema XV: Internacionalización (I18N) - Revision : 1,4 gettext y autotools : [VII] El resto de acciones, tales como: • La extracción de cadenas • Traducción a diversos lenguajes de las mismas la realizaremos tal y como ya sabemos. DLSI 25