Internacionalizando programas em Python

locale-1_abertura

Quando escrevi a versão em Python d’O desafio dos monstros, acabei optando por utilizar as mensagens originais do jogo em inglês. Também citei ser possível traduzi-lo facilmente para o português, ou seja, procurá-las dentro do arquivo “monster_wrestling.py” e substituir cada uma delas.

Porém existe uma forma bem mais elegante e utilizando o suporte a internacionalização disponível no Python para tornar o programa facilmente adaptável para qualquer idioma e sem a necessidade de modificações extras no código.

Antes de começar…

Para facilitar as coisas. As alterações já foram aplicadas no repositório do GitHub, então basta cloná-lo:

$ git clone https://github.com/plainspooky/monster_wrestling.git
Cloning into 'monster_wrestling'...
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 43 (delta 4), reused 14 (delta 4), pack-reused 27
Unpacking objects: 100% (43/43), done.

Ou usar git pull, no caso de já existir localmente, para atualizá-lo.

Carregando os módulos

É necessário utilizar dois módulos da biblioteca padrão do Python:

  • gettext — Que provê os serviços de Internacionalização (I18N) e Localização (L10N) do GNU Gettext e
  • locale — Para o acesso à base de dados e as funcionalidades de localização do padrão POSIX — em tese é opcional mas isto automatiza a configuração do idioma.

Que precisam ser carregados no começo do código:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
...
import gettext
import locale
from os.path import join as path_join
...

Já que a função os.path.join() só será usada uma única vez, importo somente ela do módulo os sob o nome de path_join(). A função dela é compor o caminho do diretório utilizando o separador padrão do sistema operacional — que pode ser “/”, “\”, “:”, “.” etc.

Carregando a tradução

Não basta apenas carregar os módulos, é preciso inicializá-lo:

def init_locale():
    locale.setlocale(locale.LC_ALL, )
    (loc, enc) = locale.getlocale()

    filename = path_join('lang', '{}.{}',
                         'LC_MESSAGES/messages.mo').format(loc, enc)
    try:
        trans = gettext.GNUTranslations(open(filename, "rb"))
    except IOError:
        trans = gettext.NullTranslations()

    trans.install()

O que esta função faz é usar o locale.getlocale() para recuperar as configurações de localização do sistema operacional e assim conhecer idioma (“pt”), variação regional (“BR”) e codificação (“UTF-8”) está em uso; verificar a existência da tradução correspondente no catálogo (“pt_BR.UTF-8”)¹ e carregá-lo com a classe gettext.GNUTranslations ou, caso contrário, trabalhar com o idioma padrão (“C”) do programa usando a classe gettext.NullTranslations. Por último usa-se o método install() para iniciar o gettext.

Inicializando a tradução

A função init_locale() deverá ser chamada no começo do programa:

if __name__ == "__main__":
    init_locale()
    screen = curses.initscr()
    main()
    curses.endwin()

Criando o catálogo de traduções

É preciso criar a estrutura do catálogo de traduções:

 $ mkdir -p ./lang/pt_BR.UTF-8/LC_MESSAGES

(¹) Os valores correntes da minha configuração atual.

Definindo o que pode ser traduzido

Obviamente que (i) não são todas as strings que serão traduzidas e que (ii) isto não é um processo automático. É preciso assinalar dentro do programa quais são as strings traduzíveis envolvendo-as com um “_( … )”.

Então, por exemplo, o trecho:

...
if panic_counter == 3:
    screen.addstr(10, 2, ' * * * You are seeing stars * * * ',
                         curses.color_pair(PANIC[1]))
...

Ficará:

...
if panic_counter == 3:
    screen.addstr(10, 2, _(' * * * You are seeing stars * * * '),
                         curses.color_pair(PANIC[1]))
...

Terminado o processo, salve o arquivo, saia e execute:

$ pygettext monster_wrestling.py --outfile=./lang/messages.pot

Ele verificará as strings marcadas com “_( … )” no programa e criará um novo arquivo, o “messages.pot” no diretório “./lang”. Este arquivo é o modelo que será utilizado para qualquer nova tradução.

Não edite diretamente este arquivo, copie-o mudando a extensão para “.po” antes de iniciar cada nova tradução!

Traduzindo o programa

Resolvi aproveitar as mensagens já existentes na versão do meu livro, o Programas de Jogos de Terror, na tradução.

$ cd ./lang
$ cp messages.pot messages_pt_BR.UTF-8.po

Abra o arquivo “messages_pt_BR.UTF-8.po” em qualquer editor de textos e preencha cada msgid com sua respectiva tradução:

#: monster_wrestling.py:174
msgid " * * * You are seeing stars * * * "
msgstr " * * * Está vendo estrelas * * * "

Em caso de dúvida, há sempre o comentário contendo a referência ao número de linha do programa onde a string se encontra para que se possa verificar o contexto da mensagem.

Se preferir, existem ferramentas próprias para a edição destes arquivos como é o caso do Gtranslator:

locale-1_gtranslator

Terminada a tradução, salve o arquivo e compile-o² com:

$ msgfmt messages_pt_BR.UTF8.po \
  --output-file=pt_BR.UTF-8/LC_MESSAGES/messages.mo

E está pronto!

(²) Se você não definir o arquivo de saída ele criará com o nome de “messages.mo” no diretório padrão.

Testando…

locale-1_testando

E com tudo no lugar certo, basta executar o programa:

$ python3 ./monster_wrestling.py

Ele cuidará de selecionar e exibir a tradução correta mas desejando forçar uma tradução específica é possível testar com:

$ LC_ALL="es_ES.UTF-8" python3 ./monster_wrestling.py

Acontece que também fiz uma tradução para o espanhol! Ela é baseada no livro Juegos de Ordenador — Misterio, publicado na Espanha pela Ediciones Generales Anaya no ano de 1985.

Conclusão

Lembrando que para cada modificação feita nas mensagens do programa será necessário gerar um novo modelo e, consequentemente, cuidar de atualizar a tradução. A tradução corrente não será invalidada, apenas a nova mensagem que aparecerá em seu idioma original.

Anúncios