terça-feira, 29 de janeiro de 2013

Sites com vários idiomas em Django - Rosetta

Já faz algum tempo que entrei em contato com um pacote chamado Rosetta. Mas não mexi com ele. Fiz tudo que precisava no sistema e ficou esquecido lá. Só agora que fui trabalhar com ele de novo. E o achei que interessante.
Como o nome sugere, o Rosetta serve para tradução em vários idiomas. Como ele faz a tradução? Ele gerencia os arquivos de tradução do django. O que deixa tudo muito mais organizado e fácil de traduzir, pois tudo que você vai precisar fazer é preencher os campos para tradução no admin do Rosetta. Bem mais simples que lidar diretamente com os arquivos de idioma do Django, não é mesmo?

Antes de usarmos o Rosetta, precisamos gerar os arquivos de tradução do Django. Isso pode ser feito automaticamente. Primeiro, temos que configurar algumas coisas no settings.py. Pra começar temos que ativar o módulo i18n, que é o módulo de internacionalização do Django. Pelo que notei até na versão 1.4 do Django já vem ativado, caso não, ative-o no seu settings.py:
USE_I18N = True



Depois, defina o seu idioma nativo. No caso, é português brasileiro:
LANGUAGE_CODE = 'pt_BR'

E monte uma lista no seguinte formato para todos os idiomas que serão usados no site, inclusive seu idioma nativo:
LANGUAGES = (
    ('pt_BR', ('Portugues brasileiro')),
    ('en_US', ('English')),
)
Aqui já temos o básico para usar o sistema de internacionalização do Django. Porém, mesmo que utilizemos os comandos para gerar os arquivos de tradução agora, eles não serão gerados. Para que isso aconteça, crie um diretório chamado locale na raiz do seu projeto Django. Crie diretórios dentro dele nomeados com as siglas dos idiomas escolhidos.
Por exemplo, para o caso aqui de inglês americano e português brasileiro:
./locale/pt_BR/
./locale/en_US/

Pronto! Vamos gerar os arquivos de tradução. Instale o gettext para que o comando do Django que vamos usar funcione. Se não ele vai te mandar instalar, assim como me mandou:
sudo apt-get install gettext

Esses arquivos são gerados utilizando o comando makemessages do Django. Aceita vários parâmetros, mas só o usei de duas formas, que já são mais que suficiente. Caso queira saber mais é só usar o ./manage.py help makemessage. De qualquer forma, As duas formas que usei foram estas:
#gera os arquivos de traducao para TODOS os idiomas da lista
./manage.py makemessages -a
#gera os arquivos de traducao so para o idioma passado no comando
./manage.py makemessages -l pt_BR

Confira os diretórios dentro de locale. Dentro de cada um é pra ter surgido um diretório LC_MESSAGES e dentro dele um arquivo chamado django.po. Se você abri-lo com um editor de texto verá que ele apresenta um padrão até que simples, mas daria um trabalho terrível fazer na mão. Por exemplo, note que nas linhas acima das começadas com msgid, tem uma lista de ocorrências do texto. Imagina ter de fazer isso tudo na unha?

Espera um pouco! De onde vem essas referências em arquivos?
Todas essas referências são chamadas de funções ou templates tags de tradução do Django como o ugettext (função) ou o trans (template tag). Ou seja, a medida que você mandar o Django traduzir mais palavras/frases você vai ter que gerar arquivos novos usando o comando makemessages, ou o Django não vai traduzi-las tão bem quanto você queria.

ugettext

O ugettext é para ser usado nas suas views. Importe-o daqui:
from django.utils.translation import ugettext as _

E use-o assim:
..
_('texto a ser traduzido')
..
Obs: Importar essa função como sublinhado (underscore ou o que for) é só pra facilitar pra escrever. Não é necessário.

Template tags: trans e block trans

O trans é um template tag usado da seguinte forma:
{% trans 'texto a ser traduzido' %}
#ou
{% trans 'texto a ser traduzido' as texto %}
#guarda o conteudo na variavel "texto", a qual pode ser usada no restante do template

Já a blocktrans serve para traduzir textos, não só uma string. Veja:
{% blocktrans %}
Todo este texto será traduzido.
Não importa quantas linhas, ou se ha {{variaveis}} no meio dele.
{% endblocktrans %}


Todas as referências a quaisquer um desses métodos vai parar nos arquivos de tradução quando executar o makemessages. Agora, o rosetta finalmente vai começar a trabalhar. Instale-o:
sudo easy_install django-rosetta

Adicionei ao seu projeto Django. No settings.py:
INSTALLED_APPS = (
...
'rosetta',
...
)

E no seu urls.py:
urlpatterns += patterns('',
    url(r'^rosetta/', include('rosetta.urls')),
)

Faça login no seu projeto com seu admin e entre na página de administração do rosetta. Se você estiver rodando seu projeto num servidor de desenvolvimento local vai ser algo do tipo http://localhost:8000/rosetta . Lá você vai ter opções de editar os arquivos de tradução separados por idioma:


Cliquei no primeiro link e vim para esta tela:
Nela o Rosetta está listando todos os termos encontrados e suas respectivas traduções. Veja que o item "Todos" no menu acima da lista está em destaque. Já "Untranslated only" mostra todos os termos que ainda não tem tradução. Aqui é um dos lugares onde você tem de trabalhar. Já o "Fuzzy only" lista os itens que o sistema do Django traduziu automaticamente. Esses termos, ele ignora e é preciso que você verifique-os e vá desmarcando as caixinhas "fuzzy".

Como dá pra ver, estamos traduzindo aí do inglês para o português. No caso do projeto em que estou trabalhando, a base dele é em inglês e usamos o rosetta para gerar os arquivos de tradução para exibi-lo em português. Agora, se a sua base for em português e estiver traduzindo para o inglês, edite o arquivo de tradução de inglês.

Para efetivar as traduções, salve-as e reinicie o servidor do Django. O rosetta tem algumas configurações para reiniciar o Django automaticamente a cada alteração, mas dá muita dor de cabeça configurar por muito pouco resultado. Mais fácil reiniciar o servidor manualmente. ;)

Ok! E agora, como escolho o idioma?

Isso é simples de ser feito, ao contrário do que parece. Temos que configurar nossas urls para terem um parâmetro de idioma, utilizar um middleware para recuperar esse parâmetro e efetivar a escolha.
Bem, vamos supor que seu projeto ainda está em fase inicial e só tem dois padrões de url no seu urls.py:
from django.conf.urls.defaults import *

urlpatterns = patterns('projeto_exemplo.exemplo.views',
    (r'$', 'home', {}, 'home'),
    (r'item/$', 'ver_item', {}, 'ver_item'),
)

O que precisamos fazer é acrescentar os padrões correspondentes para ter um parâmetro para idioma. É pra ficar mais ou menos assim:
from django.conf.urls.defaults import *

urlpatterns = patterns('projeto_exemplo.exemplo.views',
    (r'$', 'home', {}, 'home'),
    (r'item/$', 'ver_item', {}, 'ver_item'),
)

urlpatterns += patterns('projeto_exemplo.exemplo.views',
    (r'(?P.*)/$', 'home', {}, 'home'),
    (r'(?P.*)/item/$', 'ver_item', {}, 'ver_item'),
)

Porém, esse parâmetro não vai ser utilizado na sua view. Por que não? Porque o Django faz a escolha de qual idioma será usado antes dele executar uma view em um middleware. O que é um middleware? Basicamente, é uma série de funções chamadas para pré e pós processar os requisições e respostas do Django. Ou seja, são funções que tratam os objetos request antes de chegarem até uma view e os objetos response gerados pelas views, antes de serem enviados para o seu navegador. Os middlewares padrões são chamados nesta ordem:
Cada middleware pode ter vários métodos. Na imagem acima as barras cinzas são os middlewares padrões do Django e os nomes nas setas são os métodos. Vendo onde as setas começam e onde terminam já dá pra ver quais tratam a entrada e quais tratam a saída, certo?

Bem, o método que vamos editar é o proccess_view. Poderia ser no process_request, mas no proccess_views temos uma lista já processada de todos os parâmetros passados via url. Podeíamos utilizar request.session para mudar o idioma nas views, pois é na session que o idioma fica armazenado, mas precisamos que a escolha seja feita antes da view ser executada. Ou o usuário vai escolher outro idioma, e só vai ver a mudança na próxima visualização de página.

Então, vamos montar nosso middleware. Crie um arquivo qualquer na raiz do seu projeto. Criei o meu como middlewares.py. Achei mais organizado, assim sei bem de que se trata. O implementação do middleware ficou assim:
# -*- coding: utf-8 -*-

from django.utils import translation

class MultiLanguageMiddleware(object):
    """
    Este é um middleware bem simples que verifica o parâmetro idioma
    e decide qual objeto de tradução será usado com base nesse valor.
    Isto permite que um mesmo projeto Django apresente variantes nos
    diversos idiomas que o desenvolvedor disponibilizar.
    """
    
    #este metodo é o último a ser chamado antes da view ser executada
    def process_view(self, request, view_func, view_args, view_kwargs):
        if 'idioma' in view_kwargs:
            if view_kwargs['idioma'] == 'en':
                request.session['django_language'] = 'en_US'
            else:
                request.session['django_language'] = 'pt_BR'
            #apaga o parâmetro para que ele não chegue até a view e cause problemas
            #também é possível não apagá-lo e acrescentá-lo a view. ;)
            del view_kwargs['idioma']
        
        language = translation.get_language_from_request(request)
        translation.activate(language)
        request.LANGUAGE_CODE = translation.get_language()

Agora, acresente esse middleware ao seu settings.py:
MIDDLEWARE_CLASSES = (
    ...
    'projeto_exemplo.middleware.MultiLanguageMiddleware',
    ...
)

Pronto! A mágica já vai começar a acontecer quando você acresentar o parâmetro de idioma em suas urls. ;)


Fontes:
Repositório do rosetta, Django docs - i18n, Django docs - Middlewares e Stackoverflow.




Um comentário: