Gettext izar en "C"
De Gruvi
Gettex_izar aplicaciones en "C"
Un agradecimiento especial a Martin Vazquez (GLUG) y a Cesar Mauri (eViacam) que casi al unísono detectaron una chapuza mia que lleva retrasando el resultado durante más de una semana
General
Este proceso es lo que se define como "i18n" o "internacionalización" (según la Wikipedia):
La internacionalización es el proceso de diseñar software de manera tal que pueda adaptarse a diferentes idiomas y regiones sin la necesidad de realizar cambios de ingeniería ni en el código.
Preparación/adaptación de los ficheros
Esta preparación o adaptación deberemos realizarla en todos los ficheros que contengan cadenas de texto traducibles, considerando como tales aquellas que la aplicación utiliza como elemento de comunicación o información con el (al) usuario. No se consideran cadenas traducibles aquellas que pertenecen a notas del desarrollador, comentarios internos, etc...
Utilizando gettext("cadena_de_texto_original")
/* includes para localizar con gettext */
#include <libintl.h>
#include <locale.h>
// Definición de ficheros y rutas para gettext
trans_text(void)
{
setlocale( LC_ALL, "" );
bindtextdomain( "nombre_ejecutable", "/usr/share/locale" );
bind_textdomain_codeset ( "nombre_ejecutable", "UTF-8" );
textdomain( "nombre_ejecutable" );
}
Original:
void create_file_selection(void)
{
// file_selection_box = gtk_file_selection_new("Select MIXER application");
file_selection_box = gtk_file_chooser_dialog_new("Select MIXER application", NULL,
...
Modificado:
void create_file_selection(void)
{
// file_selection_box = gtk_file_selection_new("Select MIXER application");
file_selection_box = gtk_file_chooser_dialog_new(gettext("Select MIXER application"), NULL,
...
Utilizando _("cadena_de_texto_original")
/* includes y define para localizar con gettext */
#include <libintl.h>
#include <locale.h>
#define _(String) gettext(String) // cadenas traducibles
#define N_(String) (String) // cadenas no traducibles
// Definición de ficheros y rutas para gettext
trans_text(void)
{
setlocale( LC_ALL, "" );
bindtextdomain( "nombre_ejecutable", "/usr/share/locale" );
bind_textdomain_codeset ("nombre_ejecutable", "UTF-8");
textdomain( "nombre_ejecutable" );
}
Original:
void create_file_selection(void)
{
// file_selection_box = gtk_file_selection_new("Select MIXER application");
file_selection_box = gtk_file_chooser_dialog_new("Select MIXER application", NULL,
...
Modificado:
void create_file_selection(void)
{
// file_selection_box = gtk_file_selection_new("Select MIXER application");
file_selection_box = gtk_file_chooser_dialog_new(_("Select MIXER application"), NULL,
...
NOTAS IMPORTANTES:
Los "include" de bibliotecas que se indican son los específicos para utilizar gettext, deben añadirse a los que tenga la aplicación. Así mismo necesita de otras bibliotecas que suelen formar parte de las aplicaciones, en todo caso no está de más comprobar que estén ya "incluidas" y si no lo están incluirlas.
#include <stdio.h> #include <stdlib.h>
Usar LC_ALL en "setlocale" podría no ser apropiado. LC_ALL incluye todas las categorias de localización (locale), especialmente LC_CTYPE, responsable de determinar las clases de carácteres 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, "");
En el mismo fichero, debemos buscar la llamada a "main()"
main (int argc, char *argv[])
y al principio hacer una llamada a "trans_text()"
main (int argc, char *argv[])
{
trans_text();
...
Tambien deberemos tener en cuenta que algunas aplicaciones, al ser compiladas, se nos instalan en "/usr/locale/bin". En este caso deberemos tener en cuenta que la ruta que indiquemos en "bindtextdomain debe ser a "usr/local/share/locale" ya que será aquí donde busque de forma predeterminada.
bindtextdomain( "absvolume", "/usr/local/share/locale" );
Creación de los ficheros de traducción
Extracción de textos a una plantilla .pot "nombre_ejecutable.pot"
- Utilizando gettext("cadena_de_texto_original")
xgettext -d nombre_ejecutable -s -o po/nombre_ejecutable.pot src/main.c
- Utilizando _("cadena_de_texto_original") debe añadirse la opción -k_
xgettext -k_ -kN_ -d nombre_ejecutable -s -o po/nombre_ejecutable.pot src/main.c
Si hay que extraer las cadenas de más de un fichero, lo primero será crear un fichero nombre_ejecutable.pot
touch po/nombre_ejecutable.pot
ahora extraemos las cadenas con la opción "-j" (--join-existing)
xgettext -k_ -kN_ -d nombre_ejecutable -s -o po/nombre_ejecutable.pot -j src/*.c
De esta forma extraemos las cadenas de todos los ficheros ".c" en "./src", existen más opciones, como excluir ficheros etc...
man xgettext
...
Operation mode:
-j, --join-existing
join messages with existing file
Generación de fichero de traducción "nombre_ejecutable.po"
Nos desplazamos a la carpeta "po" ( $ cd po )
En el mismo "locale" (idioma) de nuestro sistema
msginit -o po/es.po -i po/nombre_ejecutable.pot
Nos devuelve el siguiente mensaje:
El nuevo catálogo de mensajes debería contener su dirección de correo electrónico, de tal forma que los usuarios puedan retroalimentarlo sobre las traducciones, y el personal de mantenimiento pueda contactarlo en caso de tener problemas técnicos inesperados. Is the following your email address? manolito@maquina Please confirm by pressing Return, or enter your email address.
Introducimos nuestro correo-e y nos puede devolver algo como esto:
If you want to create a new translation team for es, please visit http://www.iro.umontreal.ca/contrib/po/HTML/teams.html http://www.iro.umontreal.ca/contrib/po/HTML/leaders.html http://www.iro.umontreal.ca/contrib/po/HTML/index.html
En otro "locale" (idioma) distinto al de nuestro sistema
msginit -l gl -o gl.po -i nombre_ejecutable.pot
Si nos fijamos, la diferencia está en la opción "-l" y en el parámetro del idioma, tener en cuenta que es costumbre que el fichero ".po" resultante lleve por nombre el código del idioma al que corresponde ya que de esta forma, cualquiera que lo vea sabrá qué es.
Creando (compilando) el fichero binario ".mo" que leerá el ejecutable
msgfmt -c -v -o po/nombre_ejecutable.mo po/es.po
Normalmente el fichero ".mo" lleva el mismo nombre que la aplicación, siempre deberá ser el mismo que definamos en las líneas
bindtextdomain( "nombre_ejecutable", "/usr/share/locale" ); textdomain( "nombre_ejecutable" );
sinó el ejecutable no sabrá como buscarlo.
Casos especiales
Añadir comentarios para los traductores
Colocar comentarios antes de las cadenas para dar instrucciones o ayudas a los traductores
/// TRANSLATORS: Please leave %s as it is, because it is needed by the program.
/// Thank you for contributing to this project.
printf(_("My name is %s.\n"), my_name);
En este caso se inicia el comentario con /// y debe tenerse en cuenta a la hora de construir la plantilla .pot a fin de que pueda extraer estos comentarios.
xgettext --add-comments=///
El fichero .pot tendrá este aspecto
#: TRANSLATORS: Please leave %s as it is, because it is needed by the program. #: Thank you for contributing to this project. #: src/name.c:36 msgid "My name is %s.\n" msgstr ""
Modificar el orden de las variables
Este es un error muy frecuente en las traducciones
Frase ejemplo
printf (gettext ("Written on `%s' at %s\n"), s, strlen (s));
En la plantilla lo veremos así:
msgid "Written on `%s' at %s\n"
Lo traducimos literalmente por
msgstr "Escrito el «%s» a las %s\n"
Como nos gusta más decir "Escrito a las %s del día «%s»" lo traducimos así y el resultado será algo como:
Escrito a las 25/7/2009 del dia 7:30
Para solucionarlo deberemos tener en cuenta el orden de las variables y modificarlo añadiendole un valor "ordinal" 1$, 2$, etc... antes del tipo de dato
msgstr "Escrito a las %2$s del día «%1$s»\n"
Generación/modificación del procedimiento general de compilación
para que compile toda la aplicación y ubique correctamente los ficheros ".mo"
Dependencias
Logicamente necesitamos tener instalado "gettext" y para poder compilar necesitaremos tener instaldos automake gcc y las cabeceras (header) -En Debian/Ubuntu son "build-essentials"-
apt-get install gettext automake build-essentials
Y según el tipo de aplicación deberemos tener instaladas las bibliotecas de las que dependa.
Ejemplos de avisos al trabajar con GRandR
Cuando no existe "automake"
**Error**: You must have `autoconf' installed. Download the appropriate package for your distribution, or get the source tarball at ftp://ftp.gnu.org/pub/gnu/ **Error**: You must have `automake' installed.
Cuando no existe gcc, etc...
checking for gcc... gcc checking for C compiler default output file name... configure: error: C compiler cannot create executables See `config.log' for more details.
Cuando nos faltan librerias particulares de la aplicación
No package 'gtk+-2.0' found No package 'gconf-2.0' found No package 'xrandr' found
sudo apt-get install libgtk2.0-dev libgconf2-dev libxrandr-dev
Procedimiento
Procedimiento "gettextize"
Siempre dentro del directorio/carpeta de la aplicación
cd /Ruta/a/la/carpeta/del/proyecto
Aplicamos la orden
gettextize
Invocando esta orden no se copia el directorio ./intl por lo que deberemos invocarlo en el fichero "configure.ac" (ó en configure.in) con la siguiente línea
AM_GNU_GETTEXT([external])
Recomiendo que se incluya el directorio ./intl a fin de que en las fuentes ya van incluidos los "cabeceras" para la compilación, además para prevenir cualquier fallo tonto, es preferible aplicar la orden completa
gettextize --copy --force --intl
Nos devuelve el siguiente mensaje:
Please use AM_GNU_GETTEXT([external]) in order to cause autoconfiguration to look for an external libintl. Please create po/Makevars from the template in po/Makevars.template. You can then remove po/Makevars.template. Please fill po/POTFILES.in as described in the documentation. Please run 'aclocal -I m4' to regenerate the aclocal.m4 file. You need aclocal from GNU automake 1.9 (or newer) to do this. Then run 'autoconf' to regenerate the configure file. You will also need config.guess and config.sub, which you can get from the CVS of the 'config' project at http://savannah.gnu.org/. The commands to fetch them are $ wget 'http://savannah.gnu.org/cgi-bin/viewcvs/*checkout*/config/config/config.guess' $ wget 'http://savannah.gnu.org/cgi-bin/viewcvs/*checkout*/config/config/config.sub' You might also want to copy the convenience header file gettext.h from the /usr/share/gettext directory into your package. It is a wrapper around <libintl.h> that implements the configure --disable-nls option. Press Return to acknowledge the previous 6 paragraphs.
Abrimos otra terminal y vamos haciendo cada uno de los pasos que se nos indican
cp po/Makevars.template po/Makevars
nano po/POTFILES.in
añadimos lo que sea preciso en función de la aplicación:
# List of source files containing translable strings. src/main.c src/interface.c src/callbacks.c src/otro_fichero src/ ...
Situar sólo los ficheros que tienen cadenas traducibles por gettext
Aunque no nos lo dice en el mensaje de salida de "gettextize", ahora deberemos crear el Fichero "LINGUAS" que es un listado de los idiomas que va a procesar durante la compilación
nano po/LINGUAS
y en el añadimos los idiomas separados por un espacio o, si nos gusta más, uno en cada línea
es gl
ó
es gl
Seguimos con lo que nos indica gettextize en el mensaje y ejecutamos:
aclocal -I m4
autoconf
wget http://savannah.gnu.org/cgi-bin/viewcvs/*checkout*/config/config/config.guess wget http://savannah.gnu.org/cgi-bin/viewcvs/*checkout*/config/config/config.sub
./autogen.sh
Opcionalmente
cp /usr/share/gettext/gettext.h .
cp -r /usr/share/gettext/intl/ .
- Arbol de ficheros
./ ./
|----aplicación |----aplicación
| |
|----src/ |----src/
| | | |
| |---- ... | |---- ...
| |---- ... | |---- ...
| |
|----po/ |----po/
| | | |
| |----aplicación.pot | |----aplicación.pot
| |----es.po | |----es.po
| |----gl.po | |----gl.po
| |---- ... | |---- ...
| |
|---- ... |----intl/
|---- ... | |
| |---- ...
|
|---- ...
Compilación final
./configure
make all install
Construir un ".deb"
Como esto es tema de otro manual y hay bastantes y buenos en la red, dejo los enlaces:
- http://women.debian.org/wiki/Spanish/PackagingTutorial
- http://women.debian.org/wiki/Spanish/BuildingTutorial
- http://soleup.eup.uva.es/mario/post/1/446
- http://www.musix.org.ar/wiki/index.php/Como_hacer_un_paquete_deb
Un metodo sencillo que permite controlar algunas variables es "checkinstall"
Y aunque con pocas posibilidades de control es muy sencillo utilizar el script "autodeb"
Como crear paquetes ".deb" con "checkinstall"
Como crear paquetes ".deb" con AutoDeb
Descargamos el script
y le damos los permisos necesarios:
chmod +x autodeb.sh
ejecutamos el script:
# (o sudo) ./autodeb.sh
Si ejecutamos
# (o sudo) ./autodeb.sh --gnome
Autodeb se ejecuta en modo gráfico y trabaja sobre el tarball (tgz, tra.gz, etc...)
- http://www.tuxi.com.ar/2007/12/05/autodeb-compilar-programas-y-generar-archivos-deb-automaticamente/
Corrección final
En las aplicaciones que de forma predeterminada se nos instalan en "/usr/local/bin/XXXX" y los ficheros de idioma ".mo" en "/usr/local/share/locale" pero queremos forzar la instalación en "/usr/bin/XXXX" y los ficheros de idioma en "/usr/share/local", tendremos que hacer la configuración con la instrucción:
./configure --prefix=/usr
Asegurandonos de antemano que la ruta en "bindtextdomain" es:
bindtextdomain( "absvolume", "/usr/share/locale" );
Referencias
http://www.gnu.org/software/gettext/manual/gettext.html
http://www.alu.ua.es/p/psp4/Documentacion/Febrero_2002/gettext2.html
http://www.escomposlinux.org/lfs-es/lfs-es-6.3/chapter06/gettext.html
http://www.alu.ua.es/p/psp4/Documentacion/Febrero_2002/gettext.html

