TraduzindoSeuPrograma

Traduzindo Seu Programa usando gettext

Gettext é um conjunto de bibliotecas e ferramentas muito utilizado no meio software livre para implementar a tradução de programas. Esta ferramenta é multiplataforma e multilinguagem, isto é, roda em vários sistemas operacionais e também em várias linguagens, como C, Ruby, Python, Java, ...

http://www.gnu.org/software/gettext/gettext.html

Introdução ao Gettext

Seu uso é fácil: você "instala" um catálogo de traduções para seu programa e utiliza a chamada gettext(), muito comumente renomeada para _() para as mensagens a serem traduzidas. Esta função tem o seguinte comportamento: Quando o texto passado estiver no catálogo de tradução requerido, esta tradução será utilizada, caso contrário o texto original será retornado.

Depois você deverá rodar o utilitário xgettext em todos os arquivos python que contém mensagem a ser traduzida, isto gerará um arquivo .pot (Portable Object Template), o qual é um arquivo texto puro (plain/text) o qual deverá ser copiado para a língua que se quer a tradução, e então traduzí-lo. Para auxiliar na tradução existem utilitários como o kbabel o qual oferece uma interface amigável e várias facilidades, como a conferência em um Vocabulário Padrão (http://br.tldp.org/ferramentas/vp/vp-kbabel-info.html).

Após traduzido, este arquivo deverá ser compilado em um arquivo .mo (Machine Object) o qual será usado por nosso programa. Para isso utilizamos o utilitário msgfmt.

Toda vez que modificamos alguma mensagem de texto em nosso programa, precisaremos gerar o .pot novamente e também modificar o arquivo .po com a tradução, para isso utilizamos a ferramente msgmerge. Lembre-se de compilar o arquivo traduzido novamente!

Para que o arquivo .mo seja encontrado por nosso programa, ele precisará estar em algum lugar específico como o /usr/share/locale/$LANG/LC_MESSAGES/$PROGRAM.mo, no caso dos Unix. Você pode especificar um caminho alternativo no seu programa ao instalar o catálogo. Note que por $LANG entendemos o código da linguagem, como pt_BR para o Português do Brasil e por $PROGRAM o nome do programa e consequentemente o nome do catálogo.

Para facilitar o processo de muda-gera-mistura-compila eu utilizo o seguinte bash script:

PROGRAM="pytvgrab"                 # my program name
SRC_DIR="./lib"                    # where the .py files are
DST_DIR="./share/locale"           # where to store translation files
DST_FILE="$DST_DIR/${PROGRAM}.pot" # template file

function regen_pot()
{
    echo -e "Regenerate template file: \c"
    find "$SRC_DIR" -name "*.*py" | xargs xgettext -L Python -o "$DST_FILE"
    echo -e "done."
    echo
}

function compile()
{
    echo -e "Compile message catalog to new format:"
    for file in `find "$DST_DIR" -name "${PROGRAM}.po"`; do
        out=`echo "$file" | sed 's/\.po$/.mo/'`
        outn=${out/$DST_DIR\//}
        echo -e "   Generating: $outn"
        msgfmt -o "$out" "$file" 2> /dev/null
    done
    echo
}

function merge()
{
    echo -e "Merge new strings into translations:"
    for file in `find "$DST_DIR" -name "${PROGRAM}.po"`; do
        filen=${file/$DST_DIR\//}
        echo -e "   Merging: $filen\c"
        msgmerge --update --backup=off -i -F "$file" "$DST_FILE"
    done
    echo
}

function check()
{
    echo -e "Checking translations:"
    for file in `find "$DST_DIR" -name "${PROGRAM}.po"`; do
        filen=${file/$DST_DIR\//}
        filen=${filen/\/${PROGRAM}.po/}
        echo -e "   Check: $filen: \c"
        msgfmt -o /dev/null --statistics -v -c "$file"
    done
    echo
}

case $1 in
    check | -C | --check )
        shift
        check $@
        ;;

    compile | -c | --compile )
        shift
        compile $@
        ;;

    merge | -m | --merge )
        shift
        merge $@
        ;;

    regen_pot | -r | --regen_pot | --regen-pot | regen | --regen )
        shift
        regen_pot $@
        ;;

    all | -a | --all | -A | --ALL | --All )
        shift
        regen_pot $@
        merge $@
        compile $@
        check $@
        ;;

    *)
        cat <<EOF
Usage:
   $0 [OPTION]

OPTION is one of:
   -r, --regen    regenerates the po template (.pot)
   -m, --merge    merge new strings from .pot into existing .po files
   -c, --compile  compile .po files in .mo
   -C, --check    check translation files
   -a, --all      do in order: regen, merge, compile and check.
EOF
        ;;
esac

Adaptando seu Programa

Para que seu programa faça uso do gettext você terá que fazer algumas modificações:

  1. Importar o módulo gettext e instalar o catálogo
  2. Envolver as mensagens a serem traduzidas com _()

Eu costumo fazer a primeira parte em um arquivo em separado chamado "i18n.py" o qual replico abaixo:

PROGRAM="pytvgrab" # my program name

import locale

# lê variáveis de ambiente LC_ALL, LANG, ...
LC_ALL = locale.setlocale(locale.LC_ALL, '') 

try:
    import gettext
    from gettext import gettext as _
    # Install internationalization stuff and define _()
    gettext.install( PROGRAM, unicode=True )
except ImportError:
    import sys
    print >> sys.stderr, ( "You don't have gettext module, no " \
                           "internationalization will be used." )
    # define _() so program will not fail
    import __builtin__
    __builtin__.__dict__[ "_" ] = lambda x: x

importe este arquivo no seu código e você terá acesso à função _() e um catálogo instalado.

Um exemplo de programa que use isso é:

import i18n
print _( "Hello World!" )

Como Distribuir?

Podemos utilizar o DistUtils para distribuir nosso programa, para isso teremos que acrescentar alguns parâmetros à chamada setup() do setup.py e algumas linhas no MANIFEST.in:

MANIFEST.in

Inclua os arquivos .pot, .po e .mo nos pacotes:

recursive-include share/locale *.pot
recursive-include share/locale *.po
recursive-include share/locale *.mo

setup.py

   1 # ... code ...
   2 
   3 # Find locale files:
   4 setup_args[ "data_files" ] = []
   5 def mo_find( result, dirname, fnames ):
   6   files = []
   7   for f in fnames:
   8     p = os.path.join( dirname, f )
   9     if os.path.isfile( p ) and f.endswith( ".mo" ):
  10       files.append( p )
  11 
  12   if files:
  13     result.append( ( dirname, files ) )
  14 # po_find()
  15 os.path.walk( os.path.join( "share", "locale" ),
  16               mo_find,
  17               setup_args[ "data_files" ]
  18 )
  19 
  20 setup( **setup_args )

Isto é, passe como parâmetro para setup() data_files e uma lista de tuplas com as tuplas tendo o primeiro argumento o diretório destino e o segundo uma lista de arquivos a serem colocados neste diretório. Por exemplo:

   1 data_files=[ 
   2    ( 'share/locale/pt_BR/LC_MESSAGES', 
   3       [ 'share/locale/pt_BR/LC_MESSAGES/pytvgrab.mo', 
   4         'share/locale/pt_BR/LC_MESSAGES/pytvgrab2.mo' ] ),
   5 
   6    ( 'share/locale/es_ES/LC_MESSAGES', 
   7       [ 'share/locale/es_ES/LC_MESSAGES/pytvgrab.mo', 
   8         'share/locale/es_ES/LC_MESSAGES/pytvgrab2.mo' ],
   9     ) ]

TraduzindoSeuPrograma (editada pela última vez em 2008-09-26 14:06:23 por localhost)