Internacionalização

por Osvaldo Santana Neto <acidbase@bigfoot.com> e Andreia Soares Farias <andreia@conectiva.com.br>

Muitas vezes as pessoas que começam a trabalhar com Software Livre gostariam de saber como podem contribuir com a comunidade e não sabem. Uma coisa é certa, trabalho é o que não falta. Você pode escrever documentação, programar, reportar bugs, convencer pessoas a usarem software livre e... internacionalizar programas.

Internacionalizar (i18n) um programa consiste em adaptar o mesmo para que ele atenda às necessidades de vários pessoas em todo mundo, não importando o idioma, sistema monetário, alfabeto, formato de data e medidas. Internacionalização de programas é um assunto extenso demais para ser tratado em apenas um artigo de uma revista, portanto iremos explicar aqui apenas como se fazer a "localização" de um programa. Ou seja, adaptar o programa para que as mensagens sejam traduzidas para qualquer idioma.

Pessoas que traduzem um programa direto no seu código fonte não estão fazendo esse trabalho de forma correta. Para esse tipo de trabalho a glibc e o pacote gettext fornecem as ferramentas necessárias. A glibc fornece funções para o uso desses recursos em tempo de execução do programa e o pacote gettext fornece as ferramentas para extração, compilação (que será explicado adiante) e merge dos arquivos com as mensagens internacionalizadas. Assumiremos aqui que você já tenha conhecimento em linguagem C.

Acho que a melhor maneira de explicar como fazemos isso é por meio de exemplos, então vamos lá.

Obtendo os fontes do programa

Como exemplo usaremos um pseudo-programa chamado (adivinhem!) Hello World. Pegaremos esse programa de algum lugar e iremos localizar o mesmo.

$ tar zxvf helloworld-0.0.1.tar.bz2

Dica: faça uma cópia desse diretório. Ex.: cp helloworld-0.0.1 helloworld-0.0.1.orig.

Esse programa contém apenas um arquivo hello.c com o conteúdo:

/* hello.c */
#include <stdio.h>
int main()
{
   char *test[] = { "English", "Hello", "Beautiful" };
   printf("%s: %s World!\n%s day!\n", 
          test[0], test[1], test[2]);
}

Compilando e executando esse programa iremos obter a seguinte saída:

$ ./hello
English: Hello World!
Beautiful day!

O mais afoito iria traduzir todos os textos entre aspas para o português. Isso obrigaria ao usuário da espanha fazer o mesmo em todo o programa fonte e o chinês, e o ... Poderíamos de alguma forma tornar esse trabalho útil para todas as pessoas do mundo. Como? Marcando nos fontes do programa todas as mensagens que são traduzíveis. Para fazer essa marcação usaremos o símbolo "_" para textos do programa e "N_" para marcar as strings usadas para iniciar as variáveis. Esses marcadores são os normalmente utilizados na maioria dos trabalhos de localização, portanto não fugiremos à regra.

/* hello.c */
#include <stdio.h>
int main()
{
   char *test[] = { N_("English"), N_("Hello"), N_("Beautiful") };
   printf(_("%s: %s World!\n%s day!\n"),
          test[0], test[1], test[2]);
}

Agora com as strings sinalizadas poderemos posteriormente extraí-las para um arquivo separado, chamado potfile, com o programa xgettext para iniciarmos o processo de tradução.

Mas não é só isso. Agora teremos que modificar o programa para que ele suporte realmente mensagens traduzidas em outros idiomas. Para isso primeiramente deveremos incluir o arquivo de cabeçalho libintl.h e definir os símbolos "N_" e "_". No início do arquivo acrescente:

/* hello.c */
#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) (x)

O arquivo de cabeçalho libintl.h contém a definição da função gettext e de outras que usaremos posteriormente. A função gettext tem a função de substituir, durante a execução do programa o texto da mensagem para o idioma definido no locale da máquina. O locale de uma máquina indica em qual região do mundo e qual idioma falado nessa região. Para ver qual o locale setado na máquina você pode usar o comando locale. As variáveis com os locales serão mostradas no formato xx_YY onde xx contém a sigla do idioma e YY a região onde ele é usado. Os símbolos "N_" não serão substituídos por nada e servem apenas para marcar as mensagens para extraí-las posteriormente.

Como os símbolos "N_" não serão substituidos por nada, precisamos fazer um laço para traduzir o conteúdo do vetor test. Esse laço ficará como:

   :
   char *test[] = { N_("English"), N_("Hello"), N_("Beautiful") };
   int i;
   for (i = 0; i < (sizeof(test)/sizeof(char *)); i++)
      test[i] = _(test[i]);
   :

Isso fará com que todos os elementos do vetor test sejam traduzidos para serem usados posteriormente.

Bom agora precisamos fazer nosso programa ler o locale e setar as configurações para que a função gettext possa funcionar corretamente. No libintl.h existe as definições das funções bindtextdomain e textdomain. Elas funcionam basicamente da seguinte maneira:

   :
   char *test[] = { N_("English"), N_("Hello"), N_("Beautiful") };
   int i;
   bindtextdomain("hello", "/usr/share/locale");
   textdomain("hello");
   for (i = 0; i < (sizeof(test)/sizeof(char *)); i++)
      test[i] = _(test[i]);
   :

A função bindtextdomain recebe como parâmetros o nome do final do programa e o diretório onde ficarão os arquivos com os potfiles compilados pelo comando msgfmt. E a função textdomain recebe como parâmenteo apenas o nome final do programa.

Terminamos o processo de internacionalização do programa. Agora precisamos traduzí-lo. Para fazer isso precisaremos extrair as strings que tinhamos marcado. Para essa tarefa usaremos o comando xgettext. Para extraírmos as mensagens do programa hello.c usaremos o comando:

# xgettext -a --keyword=_ --keyword=N_ hello.c -o hello.pot

Isso irá criar o arquivo hello.pot que conterá o esqueleto para o arquivo de tradução. Copie esse arquivo para outro chamado pt_BR.po que conterá as traduções para o português do Brasil (você poderá ver uma listagem das siglas dos idiomas na infopage do gettext, recomendo fortemente que você leia essa infopage para maiores informações sobre esse assunto). O arquivo pt_BR.po deverá conter um cabeçalho que deverá ser preenchido conforme o solicitado e as mensagens que estão nesse formato (as traduções ficarão em msgstr e o texto original em msgid) já traduzido:

#: teste.c:9
msgid "Hello"
msgstr "Olá"

msgid ""
"%s: %s World!\n"
"%s day!\n"
msgstr ""
"%s: %s Mundo\n"
"Dia %s!\n"

Note que nessa última mensagem nós devemos manter os caracteres de formatação e controle intactos. Note também que invertemos a ordem da palavra "day" e sua respectiva tradução porque no inglês o adjetivo vem sempre antes do substantivo e no português isso nem sempre é verdade. Portanto é necessário uma atenção especial para esses casos. Cuidado também em manter sempre a mesma ordem dos caracteres de formatação (ex.: "%s") pois o printf receberá as variáveis com o conteúdo nessa string numa ordem específica. Existem casos onde xgettext coloca um comentário "#, fuzzy" no arquivo .po que indica que pode existir algum problema na tradução. Verifique isso, corrija se necessário e remova esse comentário.

Agora devemos "compilar" nosso arquivo potfile. Para isso usamos o comando msgfmt conforme abaixo:

# msgfmt -vv pt_BR.po /usr/share/locale/pt_BR/LC_MESSAGES/hello.mo

Note que o arquivo .mo deve ficar dentro da árvore correspondente ao idioma e deve ter o mesmo nome referenciado em bindtextdomain com a extensão .mo. Depois disso é só recompilar e executar o programa para testá-lo.

# make hello
# ./hello
Português: Olá Mundo!
Dia Bonito!

Bom, como eu havia dito, isso é um resumo de como você deve proceder para internacionalizar um programa. Se você quiser contribuir para a comunidade (ou para o autor do programa) você pode criar um patch e enviar para o mesmo. Para fazer isto você deve proceder da seguinte maneira:

# diff -uNr hello-0.0.1.orig hello-0.0.1 > hello-0.0.1-i18n.patch

Antes de começar a internacionalizar um programa eu recomendaria uma leitura da infopage do gettext. E se o programa que você quiser adaptar para o nosso idioma já for internacionalizado o seu trabalho será apenas o de traduzir o potfile.