Gettext izar en "C"

De Gruvi

Tabla de contenidos

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:

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...)

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