internationalization-cover.png
Nicolle Cysneiros

Nicolle Cysneiros

12 Ago 2020 9 min read

Por que Internacionalização e Localização importam?

Se você trabalha desenvolvendo um produto voltado ao mercado nacional, já deve estar habituado ou habituada a escrever todas as strings em Português, pelo menos aquelas vistas pelos usuários. O que aconteceria se o seu cliente quisesse expandir o mercado e passar a vender o produto em outro país? O quão trabalhoso seria fazer com que uma pessoa mexicana, por exemplo, utilizasse sua aplicação de forma correta e com uma boa experiência de usuário?

Este artigo irá discutir as definições de internacionalização e localização e porque esses conceitos são importantes para o seu projeto. Em seguida, iremos apresentar algumas ferramentas de internacionalização que as pessoas desenvolvedoras têm à disposição para usar em suas aplicações Python e Django. Finalmente, veremos como o nosso processo de desenvolvimento foi adaptado para incorporar o passo de tradução de termos.

Localização vs Internacionalização

Localização (l10n1) é o processo de adaptar uma aplicação, produto ou até mesmo um documento para ser mais agradável para clientes de países e culturas diferentes.

Por outro lado, internacionalização (i18n) é o processo de tornar a localização do produto possível. Implementar o software para que este possa saber quando e como mostrar conteúdos diferentes dependendo da localidade da pessoa usuária.

Como a documentação do Django resume perfeitamente: localização é feita por pessoas tradutoras, enquanto internacionalização é feita por pessoas desenvolvedoras2.

Contudo, essa definição resumida de internacionalização e localização pode dar a impressão errada de que se trata apenas de uma tradução de idiomas. Na verdade, esse processo envolve várias outras adaptações necessárias para fazer com que pessoas de diferentes culturas se sintam mais confortáveis usando o produto, como por exemplo:

  • Formatação de datas e moedas
  • Conversão de câmbio
  • Conversão de unidade de medidas
  • Caracteres especiais e texto bidirecional (ver exemplo abaixo)
  • Fuso horário e feriados

Página inicial do Wikipedia em Inglês

Página inicial do Wikipedia em Árabe

Com essas adaptações, podemos prover uma experiência melhor para os clientes que utilizam a aplicação.

Como podemos fazer isso em Python?

GNU gettext

Existem algumas ferramentas que permitem localizar a sua aplicação Python. A primeira delas é o pacote GNU gettext, que faz parte do Translation Project3. Esse pacote oferece:

  • Uma biblioteca runtime que provê suporte para obtenção de mensagens traduzidas.
  • Um conjunto de convenções sobre como programas devem ser implementados para que dêem suporte à catálogos de mensagens.
  • Uma biblioteca que permite a análise e criação de arquivos contendo mensagens traduzidas.

O trecho de código a seguir é apenas um “Hello, World”, um arquivo python app.py que utiliza o módulo gettext para criar um objeto gettext.translation, passando como parâmetro o domínio (geralmente o nome da aplicação), uma pasta para a localidade e a linguagem para qual queremos traduzir nossas strings. Em seguida, associamos a função gettext ao caractere sublinhado (uma prática comum para diminuir a sobrecarga de digitar gettext para cada string a ser traduzida) e, finalmente, marcamos a string “Hello, World!”.

import gettext

gettext.bindtextdomain("app", "/locale")
gettext.textdomain("app")
t = gettext.translation("app", localedir="locale", languages=['en_US'])
t.install()
_ = t.gettext

greeting = _("Hello, world!")
print(greeting)

Depois de marcar no código todas as strings que serão traduzidas, podemos coletá-las usando a ferramenta de linha de comando xgettext do pacote GNU gettext. Essa ferramenta gera um arquivo PO contendo todas as strings que foram marcadas.

xgettext -d app app.py

O arquivo PO contém uma lista de entradas que têm a seguinte estrutura básica:

#  translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string

No arquivo PO podemos adicionar comentários para os tradutores e referências para como a string é usada no código. Cada entrada no arquivo tem um ID (msgid), que é igual à string original marcada no código, e uma string (msgstr), que representa a versão traduzida da string original.

Quando rodamos o comando xgettext no terminal, passando o arquivo app.py como argumento, esse é o arquivo PO gerado:

"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-03 13:23-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: app.py:7
msgid "Hello, world!"
msgstr ""

No início do arquivo, vemos alguns metadados sobre o arquivo PO, o projeto e o processo de tradução. Em seguida, temos o texto original “Hello, world!” como o ID da entrada e uma string vazia como a string da entrada. Se nenhuma string traduzida for colocada no msgstr, o ID da entrada será usada na versão traduzida dessa string.

Uma vez que o arquivo PO é gerado, podemos começar a traduzir os termos para diferentes linguagens. É importante observar que a biblioteca GNU gettext procura pelos arquivos PO traduzidos em uma estrutura de pastas específica (<localedir>/<language_code>/LC_MESSAGES/<domain>.po) e deve haver um arquivo PO para cada linguagem que você deseja dar suporte na sua aplicação.

|-- app.py
|-- locale
   |-- en_US
   |   |-- LC_MESSAGES
   |       |-- app.po
   |-- pt_BR
       |-- LC_MESSAGES
       |   |-- app.po

A seguir, temos um exemplo de um arquivo PO traduzido para o Português:

"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-03 13:23-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: app.py:7
msgid "Hello, world!"
msgstr "Olá, mundo!"

Para utilizar as strings traduzidas no código, precisamos compilar o arquivo PO em um arquivo MO, usando o comando msgfmt.

msgfmt -o app.mo app.po

Com o arquivo MO devidamente compilado, podemos agora alterar a linguagem da nossa aplicação para Português, passando 'pt_BR' como parâmetro para a função translation. Quando rodamos o código a seguir, teremos como saída no console o texto “Olá, mundo!”:

import gettext

gettext.bindtextdomain("app", "/locale")
gettext.textdomain("app")
t = gettext.translation("app", localedir="locale", languages=['pt_BR'])
t.install()
_ = t.gettext

greeting = _("Hello, world!")
print(greeting)

Locale Module

Este módulo tem acesso ao banco de dados de localidades do POSIX e é especialmente útil para lidar com formatação de datas, números e moedas. O trecho de código a seguir mostra como a biblioteca Locale pode ser usada:

import datetime
import locale

locale.setlocale(locale.LC_ALL, locale='en_US')
local_conv = locale.localeconv()
now = datetime.datetime.now()
some_price = 1234567.89
formatted_price = locale.format('%1.2f', some_price, grouping=True)
currency_symbol = local_conv['currency_symbol']

print(now.strftime('%x'))
print(f'{currency_symbol}{formatted_price}')

Nesse exemplo, depois de importar o Módulo Locale, mudamos todas as configurações de localidade para os EUA e recuperamos as convenções dessa localidade. Com o método locale.format, podemos formatar o número passado como parâmetro sem nos preocupar com o caractere que separa a casa dos milhares e as casas decimais. Já com a diretiva %x, podemos formatar a data com a ordem correta de dia, mês e ano de acordo com a localidade. A partir das convenções local_conv podemos obter o símbolo da moeda do país.

TEsse é o output do código Python mostrado acima. Podemos ver que a data é formatada seguindo a ordem Mês/Dia/Ano, o separador das casas decimais do número é um ponto enquanto o separador das casas dos milhares é uma vírgula, e temos o cifrão como símbolo do dólar americano.

$ python format_example.py
05/03/2019
$1,234,567.89

Agora, usando o mesmo código, alterando apenas a localidade para Brasil, temos um resultado diferente de acordo com as convenções brasileiras: a data segue o formato Dia/Mês/Ano, temos vírgula como separador de casas decimais, pontos como separador das casas dos milhares e o símbolo R$ é usado para representar a moeda Real.

import datetime
import locale

locale.setlocale(locale.LC_ALL, locale='pt_BR')
local_conv = locale.localeconv()
now = datetime.datetime.now()
some_price = 1234567.89
formatted_price = locale.format('%1.2f', some_price, grouping=True)
currency_symbol = local_conv['currency_symbol']

print(now.strftime('%x'))
print(f'{currency_symbol}{formatted_price}')
$ python format_example.py
03/05/2019
R$1.234.567,89

Fica mais fácil com Django?

Tradução e Formatação

Internacionalização é habilitada por padrão quando criamos um projeto Django. O módulo de tradução do framework encapsula a biblioteca GNU e disponibiliza a função gettext já com a tradução configurada, baseando-se na linguagem recebida no header Accept-Language que é passado na requisição pelo navegador. Como todo aquele código Python que vimos anteriormente é encapsulado no módulo de tradução do Django, podemos já usar diretamente a função gettext na nossa view:

from django.http import HttpResponse
from django.utils.translation import gettext as _

def my_view(request):
    greetings = _('Hello, World!')
    return HttpResponse(greetings)

Para traduzir termos, podemos marcar strings a serem traduzidas tanto no código Python como no template, usando as tags de internacionalização. A template tag trans marca uma única string, enquanto a tag blocktrans é capaz de marcar para tradução um bloco de strings, incluindo conteúdo variável:

<p>{% trans "Hello, World!" %}</p>
<p>{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %}</p>

Além da função gettext padrão, podemos também ter traduções lazy em Django: a string marcada só será traduzida quando seu valor for acessado em um contexto visto pelo usuário, como na renderização do template. Essa tradução lazy é especialmente útil para traduzir os atributos help_text e verbose_name dos modelos Django.

Com relação às ferramentas de linha de comando do pacote GNU, a ferramenta django-admin disponibiliza comandos equivalentes aos mais utilizados durante o processo de desenvolvimento. Para coletar todas os termos marcados para tradução no código, você só precisa rodar o comando django-admin makemessages para cada localidade que você deseja dar suporte na sua aplicação. Uma vez criada a pasta da localidade na pasta raíz do projeto, esse comando já vai criar a estrutura de diretórios necessária para os arquivos PO de cada linguagem.

Para compilar os arquivos PO, você só precisa rodar o comando django-admin compilemessages. Se você deseja compilar o arquivo PO para apenas uma localidade, você pode passar ele como argumento django-admin compilemessages --locale=pt_BR. Para entender melhor como traduções funcionam em Django, você pode encontrar mais detalhes na documentação4.

Django também utiliza o header Accept-Language para determinar a localidade do usuário e formatar datas, horas e números corretamente. No exemplo a seguir, temos um formulário com um campo de data (DateField) e um campo de número decimal (DecimalField). Para indicar que queremos receber esses parâmetros no formato dentro do padrão da localidade do usuário, só precisamos passar o parâmetro localize como True na instanciação do campo do formulário.

from django import forms

class DatePriceForm(forms.Form):
    date = forms.DateField(localize=True)
    price = forms.DecimalField(max_digits=10, decimal_places=2, localize=True)

Como isso altera o processo de desenvolvimento?

Depois de internacionalizar a aplicação, o processo de implementação precisa ser adaptado para incluir o passo de tradução dos termos. Em um dos nossos projetos, começamos a enviar qualquer novo termo para os tradutores assim que o termo estava disponível no ambiente de validação. O deploy para produção só seria aprovado quando todos os termos estivessem traduzidos e os arquivos PO estivessem compilados.

Outra mudança importante no nosso processo foi a inclusão de novos testes de integração para diferentes localidades durante a etapa de QA. O time de teste precisa simular as diferentes localidades que a aplicação suporta e checar se todos os textos estão traduzidos, assim como se todas as moedas e unidades de medidas foram convertidas corretamente.

Nosso principal aprendizado no processo de internacionalizar uma aplicação é que isso deveria ter sido feito como uma etapa de design, lá no começo do projeto. Parar tudo para implementar a internacionalização não é a melhor abordagem. Se o seu projeto não está mais nessa fase inicial de concepção, recomendo seguir a Regra do Escoteiro5: comece marcando as strings que precisam ser traduzidas sempre que estiver implementando uma nova feature ou corrigindo um bug não-urgente. Dessa forma você ainda poderá entregar novas funcionalidades enquanto internacionaliza a aplicação.

Notas


  1. [1] l10n and i18n are numeronyms, which means that there are 10 letters between the letter “l” and the last letter “n” in “localization”, and the same for “internationalization”. 

  2. [2] Internationalization and localization / Definitions - Django docs 

  3. [3] GCC and the Translation Project 

  4. [4] Internationalization and localization - Django docs 

  5. [5] https://deviq.com/boy-scout-rule/