Gettext izar en “C”

Gettex_izar aplicativos en “C”

Este COMO en castellano

Un agradecemento especial a Martin Vazquez (GLUG) e a Cesar Mauri (eViacam) que case ao mesmo tempo detectaron unha trapallada miña que levaba retrasando o resultado durante máis dunha semana

Xeral

Este proceso é o que se define como “i18n” ou “internacionalización” (segundo a Wikipedia):

A internacionalización é o proceso de deseñar software de xeito tal que poida adaptarse a diferentes idiomas e rexións sen a necesidade de realizar cambios de enxeñería nin no código.

Preparación/adaptación dos ficheiros

Esta preparación ou adaptación deberemos realizala en todos os ficheiros que conteñan cadeas de texto traducibles, considerando como tales aquelas que o aplicativo utiliza como elemento de comunicación ou información co/ao usuario. Non se consideran cadeas traducibles aquelas que pertencen a notas do desenvolvedor, comentarios internos, etc…

Utilizando gettext(“cadea_de_texto_orixinal”)

/* includes para localizar con gettext */
#include <libintl.h>
#include <locale.h>
// Definición de ficheiros e rutas para gettext
trans_text(void)
{
    setlocale( LC_ALL, "" );
    bindtextdomain( "nome_executable", "/usr/share/locale" );
    bind_textdomain_codeset ( "nome_executable", "UTF-8" );
    textdomain( "nome_executable" );
}

Orixinal:

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 _(“cadea_de_texto_orixinal”)

/* includes e define para localizar con gettext */
#include <libintl.h>
#include <locale.h>
#define _(String) gettext(String)    // cadeas traducibles
#define N_(String) (String)          // cadeas non traducibles
// Definición de ficheiros e rutas para gettext
trans_text(void)
{
    setlocale( LC_ALL, "" );
    bindtextdomain( "nome_executable", "/usr/share/locale" );
    bind_textdomain_codeset ("nome_executable", "UTF-8");
    textdomain( "nome_executable" );
}

Orixinal:

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:

Os “include” de bibliotecas que se indican son os específicos para utilizar gettext, deben engadirse aos que teña o aplicativo. Así mesmo necesita doutras bibliotecas que adoitan formar parte dos aplicativos, en todo caso non está de máis comprobar que estean xa “incluídas” e se non o están incluílas.

#include <stdio.h>
#include <stdlib.h>

Usar LC_ALL en “setlocale” podería non ser apropiado. LC_ALL inclúe todas as categorias de localización (locale), especialmente LC_CTYPE, responsable de determinar as clases de carácteres nunha serie de funcións de “ctype.h” o cal podería producir unha saída incorrecta. Ademais, algúns sistemas teñen problemas procesando números usando as funcións scanf cando se utiliza LC_ALL. É por iso que normalmente é necesario substituír a liña que fai referencia a LC_ALL por unha secuencia de liñas setlocale:

setlocale(LC_TIME, "");
setlocale(LC_MESSAGES, "");

No mesmo ficheiro, debemos buscar a chamada a “main()”

main (int argc, char *argv[])

e ao principio facer unha chamada a “trans_text()”

main (int argc, char *argv[])
{
  trans_text();
...

Tamén deberemos ter en conta que algúns aplicativoss, ao seren compilados, instálansenos en “/usr/locale/bin”. Neste caso deberemos ter en conta que a ruta que indiquemos en “bindtextdomain”debe ser a “usr/local/share/locale” xa que será aquí onde busque de forma predeterminada.

    bindtextdomain( "absvolume", "/usr/local/share/locale" );

Creación dos ficheiros de tradución

Extracción de textos a unha plantilla .pot “nome_executable.pot”

Utilizando gettext(“cadea_de_texto_orixinal”)
xgettext -d nome_executable -s -o po/nome_executable.pot src/main.c
Utilizando _(“cadea_de_texto_orixinal”) debe engadirse a opción -k_
xgettext -k_ -kN_ -d nome_ejecutable -s -o po/nome_executable.pot src/main.c

Se hai que extraer as cadeas de máis dun ficheiro, o primeiro será crear un ficheiro nome_executable.pot

touch po/nome_executable.pot

agora extraemos as cadeas coa opción “-j” (–join-existing)

xgettext -k_ -kN_ -d grandr -s -o po/nome_executable.pot -j src/*.c

Desta forma extraemos as cadeas de todos os ficheiros “.c” en “./src”, existen máis opcións, como excluír ficheiros etc…

man xgettext
 ...
   Operation mode:
      -j, --join-existing
             join messages with existing file

Xeración de ficheiro de tradución “nome_executable.po”

Desprazámonos ao cartafol “po” ( $ cd po )

No mesmo “locale” (idioma) do noso sistema
msginit -o po/es.po -i po/nome_executable.pot

Devólvenos a seguinte mensaxe:

O novo catálogo de mensaxes debería conter a súa dirección de correo 
electrónico, de tal forma que os usuarios poidan retroalimentalo sobre
as traducións, e o persoal de mantemento poida contactalo 
en caso de ter problemas técnicos inesperados.
Is the following your email address?
  manolito@maquina 
Please confirm by pressing Return, or enter your email address.

Introducimos o noso correo-e e pódenos devolver algo como isto:

If you want to create a new translation team for gl, 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
Noutro “locale” (idioma) distinto ao do noso sistema
msginit -l gl -o gl.po -i nome_executable.pot

Se nos fixamos, a diferenza está na opción “-l” e no parámetro do idioma, ter en conta que é costume que o ficheiro “.po” resultante leve por nome o código do idioma ao que corresponde xa que desta forma, calquera que o vexa saberá que é.

Creando (compilando) o ficheiro binario “.mo” que lerá o executable

msgfmt -c -v -o po/nome_executable.mo po/es.po

Normalmente o ficheiro “.mo” leva o mesmo nome que o aplicativo, sempre deberá ser o mesmo que definamos nas liñas

   bindtextdomain( "nome_executable", "/usr/share/locale" );
   textdomain( "nome_executable" );

senon o executable non saberá como buscalo.

Casos especiais

Engadir comentarios para os tradutores

Colocar comentarios antes das cadeas para dar instrucións ou axudas aos tradutores

/// 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);

Neste caso iníciase o comentario con /// e debe terse en conta á hora de construír a plantilla .pot a fin de que poida extraer estes comentarios.

xgettext --add-comments=///

O ficheiro .pot terá 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 a orde das variables

Este é un erro moi frecuente nas traducións

Frase exemplo

printf (gettext ("Written on `%s' at %s\n"), s, strlen (s));

Na plantilla verémolo así:

msgid "Written on `%s' at %s\n"

Traducímolo literalmente por msgstr

"Escrito o «%s» ás %s\n"

Como nos gusta máis dicir “Escrito ás %s do día «%s»” traducímolo así e o resultado será algo como:

Escrito ás  25/7/2009 do dia 7:30

Para solucionalo deberemos ter en conta a orde das variables e modificalo engadindolle un valor “ordinal” 1, 2, etc… antes do tipo de dato

msgstr "Escrito ás %$2s do día «%$1s»\n"

Xeración/modificación do procedemento xeral de compilación

para que compile todo o aplicativo e sitúe correctamente os ficheiros “.mo”

Dependencias

Loxicamente necesitamos ter instalado “gettext” e para poder compilar necesitaremos ter instaldos automake gcc e as cabeceiras (header) -En Ubuntu é “build-essentials”-

apt-get install gettext automake build-essentials

E segundo o tipo de aplicativo deberemos ter instaladas as bibliotecas das que dependa.

Exemplos de avisos ao traballar con GRandR

Cando non 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.

Cando non existe gcc, etc…

checking for gcc... gcc
checking for C compiler default output file name... 
configure: erro: C compiler cannot create executables
See `config.log' for more details.

Cando nos faltan librerias particulares do aplicativo

Non package 'gtk+-2.0' found
Non package 'gconf-2.0' found
Non package 'xrandr' found
sudo apt-get install libgtk2.0-dev libgconf2-dev libxrandr-dev

Procedemento

Procedemento “gettextize”

Sempre dentro do directorio/cartafol do aplicativo

cd /Ruta/a/o/cartafol/do/proxecto

Aplicamos a orde

gettextize

Invocando esta orde non se copia o directorio ./intl polo que deberemos invocalo no ficheiro “configure.ac” (ou en configure.in) coa seguinte liña

AM_GNU_GETTEXT([external])

Recomendo que se inclúa o directorio ./intl a fin de que nas fontes xa vaian incluídos os “cabeceiras” para a compilación, ademais para previr calquera fallo bobo, é preferible aplicar a orde completa

gettextize --copy --force --intl

Devólvenos a seguinte mensaxe:

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 outra terminal e imos facendo cada un dos pasos que se nos indican

cp po/Makevars.template po/Makevars
nano po/POTFILES.in

engadimos o que sexa preciso en función do aplicativo:

# List of source files containing translable strings.
src/main.c
src/interface.c
src/callbacks.c
src/outro_ficheiro
src/ ...

Situar só os ficheiros que teñen cadeas traducibles por gettext

Aínda que non nolo di na mensaxe de saída de “gettextize”, agora deberemos crear o Ficheiro “LINGUAS” que é unha listaxe dos idiomas que vai procesar durante a compilación

nano po/LINGUAS

e nel engadimos os idiomas separados por un espazo ou, se nos gusta máis, un en cada liña

es gl

ou

es
gl

Seguimos co que nos indica gettextize na mensaxe e executamos:

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
cp /usr/share/gettext/gettext.h .

Opcionalmente

cp -r /usr/share/gettext/intl/ .
  • Árbore de ficheiros
./                                ./
 |----aplicativon                   |----aplicativo
      |                                 |
      |----src/                         |----src/
      |    |                            |    | 
      |    |---- ...                    |    |---- ...
      |    |---- ...                    |    |---- ...
      |                                 |
      |----po/                          |----po/
      |    |                            |    | 
      |    |----aplicativo.pot          |    |----aplicativo.pot
      |    |----es.po                   |    |----es.po
      |    |----gl.po                   |    |----gl.po      
      |    |---- ...                    |    |---- ...
      |                                 |
      |---- ...                         |----intl/
      |---- ...                         |    | 
                                        |    |---- ... 
                                        |
                                        |---- ...

Compilación final

./configure
make
make install

Construír un “.deb”

Como isto é tema doutro manual e hai bastantes e bos na rede, deixo as ligazóns:

  • 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_db

Un metodo sinxelo que permite controlar algunhas variables é “checkinstall”

E aínda que con poucas posibilidades de control é moi sinxelo utilizar o script “autodeb”

Como crear paquetes “.deb” con “checkinstall”
  • http://www.glug.es/node/2447
  • http://www.linuxfocus.org/Castellano/December2004/article360.shtml
Como crear paquetes “.deb” con AutoDeb

Descargamos o script

  • http://apt.byethost14.com/autodeb.sh

e dámoslle os permisos necesarios:

chmod +x autodeb.sh

executamos o script:

# (ou sudo) ./autodeb.sh

Se executamos

# (ou sudo) ./autodeb.sh --gnome

Autodeb execútase en modo gráfico e traballa sobre o ficheiro comprimido -tarball- (tgz, tar.gz, etc…)

  • http://www.tuxi.com.ar/2007/12/05/autodeb-compilar-programas-y-generar-archivos-deb-automaticamente/

Corrección final

Nos aplicativos que de forma predeterminada instálansenos en “/usr/local/bin/XXXX” e os ficheiros de idioma “.mo” en “/usr/local/share/locale” pero queremos forzar a instalación en “/usr/bin/XXXX” e os ficheiros de idioma en “/usr/share/local”, teremos que facer a configuración coa instrución:

./configure --prefix=/usr

Asegurandonos de antemán que a ruta en “bindtextdomain” é:

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

Sidebar