Como o Youtube e o Instagram: internacionalizando e localizando um aplicativo Python

O Python está no centro de aplicativos mundialmente famosos, como Youtube, Instagram e Pinterest. Para avançar no mercado mundial, um aplicativo precisa de localização, ou seja, adaptação às características de um país em particular e internacionalização - tradução de conteúdo. Neste artigo, compartilharemos nossa experiência sobre como acelerar a automação da tradução e resolver alguns problemas típicos nessa área.



1. Introdução


Este é um pequeno guia para internacionalizar aplicativos python (i18n). Este guia será interessante para todos os programadores com experiência em desenvolvimento python. A leitura de um artigo leva 10 a 15 minutos.

Usaremos a ferramenta gettext bem testada incluída na linguagem python.

Para começar, entenderemos o que é internacionalização:

A internacionalização (I18N) é o processo de adaptação de um aplicativo para os idiomas de diferentes países e regiões que não aquele em que foi desenvolvido.

Mas há também um conceito mais amplo:

Localização (L10N) é o processo de adaptar um aplicativo internacionalizado a uma região ou idioma específico, adicionando componentes específicos a um determinado local e traduzindo o texto.

Localização significa tradução:

  • formato de data e hora;
  • formato numérico;
  • fuso horário
  • um calendário
  • representações de moeda;
  • impostos / IVA;
  • temperatura e outras medidas;
  • códigos postais, telefones;
  • formatação de endereço;
  • código do acordo.



A localização vai além da tradução de conteúdo para outro idioma. Existem parâmetros culturais e funcionais que também requerem atenção. Por exemplo, o formato da data na América do Norte é MM / DD / AAAA, mas na maioria dos países asiáticos está escrito como DD / MM / AAAA.



Um exemplo conhecido de um erro de conversão de aplicativo

Outro exemplo diz respeito à exibição de nomes nos aplicativos. Nos Estados Unidos, chamar alguém pelo nome é aceitável e até preferível; o nome do cliente é exibido no cabeçalho assim que o cliente efetua login. No entanto, no Japão, o oposto é verdadeiro: chamar alguém pelo nome é indelicado ou até ofensivo. A localização deve levar isso em conta e evitar o uso de nomes para um público japonês.

Neste artigo, consideraremos apenas a internacionalização, mas os mecanismos de localização são construídos de maneira semelhante. As bibliotecas mencionadas neste artigo oferecem suporte à localização de aplicativos.

Tipos principais


A internacionalização é dividida em:

  1. Tradução de dados diretamente em scripts python.
  2. Tradução de dados em mecanismos de modelo.
  3. Tradução de dados armazenados em um banco de dados.

1. Tradução de dados de script python


Para que nossa internacionalização funcione, precisamos entender a biblioteca babel e o kit de ferramentas distutils para gerenciar a montagem do projeto para venda e além.

Preparação da tradução


Para começar, precisamos criar uma lista de traduções. Para começar, instalamos a biblioteca Babel - essa é uma biblioteca python geralmente reconhecida para localizar e converter datas, moedas, com adições convenientes para a construção do projeto (discutido abaixo).

O Python fornece um kit de ferramentas para multilinguismo - gettext. O GNU gettext é na verdade uma solução de localização universal que fornece suporte para outras linguagens de programação em mensagens multilíngues. O Gettext é usado não apenas em muitas linguagens de programação, mas também na tradução de sistemas operacionais; é um software bem testado e distribuído gratuitamente disponível no github .

Para que as traduções funcionem, é necessário importar o módulo gettext e passar os scripts com as traduções para a entrada. Primeiro, marcamos todas as strings traduzidas com a função especial _ ('some_text'). A chamada para esta função no projeto será parecida com esta:

import gettext import os localedir = os.path.join(os.path.abspath('/path/to/locales'), 'locales') translate = gettext.translation('domain_name', localedir, ['ru']) _ = translate.gettext print(_('some_text')) print(_('some_text_2')) 

Em um pequeno pedaço de código, crie um objeto de internacionalização que use o diretório 'locales' como fonte de frases traduzidas. O diretório 'locales' ainda não foi criado, mas é nele que o aplicativo procurará traduções em tempo de execução.

Por uma questão de brevidade, a função translate.gettext será indicada abaixo como _. Sublinhado é o nome comum para essa função, que é reconhecido pela comunidade Python.

A função _ () marca as linhas a serem traduzidas. O módulo gettext é acompanhado pela ferramenta xgettext, que analisa os marcadores de string _ () pelo código e forma um Modelo de Objeto Portátil (arquivo de pot). Para criar o arquivo pot, vamos voltar à biblioteca Babel instalada, que possui muitos recursos para oferecer suporte à internacionalização. O Babel estende o script de configuração setup.py, que pode ser escrito usando a biblioteca padrão do python distutils ou o pacote setuptools de terceiros de sua escolha. A montagem dos módulos Python está além do escopo de nosso artigo; para obter mais detalhes, consulte a documentação . Tudo o que é necessário é criar um arquivo setup.py com o seguinte conteúdo:

 from babel.messages import frontend as babel from distutils.core import setup setup(name='foo', version='1.0', cmdclass = {'extract_messages': babel.extract_messages, 'init_catalog': babel.init_catalog, 'update_catalog': babel.update_catalog, 'compile_catalog': babel.compile_catalog,} ) 

Assim, criamos instruções para a construção do projeto e adicionamos quatro equipes de internacionalização da biblioteca babel. Considere esses comandos com mais detalhes em ordem de uso.

extract_messages

Este comando é um invólucro da ferramenta GNU xgettext, que analisa as tags traduzíveis _ () em um arquivo pot. Para executar, você precisa de várias configurações para a montagem. Para fazer isso, no diretório raiz, crie o arquivo setup.cfg com o conteúdo:

 [extract_messages] input_dirs = foobar output_file = foobar/locales/messages.pot 


  • input_dirs - o nome do diretório no qual todos os rótulos no código _ () serão selecionados para traduções.
  • output_file - caminho para o arquivo .pot resultante

Para executar o comando, execute no console:

 $ python setup.py extract_messages 


 running extract_messages extracting messages from foobar/__init__.py extracting messages from foobar/core.py ... writing PO template file to foobar/locales/messages.pot 

No arquivo pot, as linhas marcadas são coletadas em uma lista da qual os tradutores podem criar traduções para cada um dos idiomas desejados.

 # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2018-01-28 16:47+0000\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" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: src/main.py:5 msgid "some_text" msgstr "" #: src/main.py:6 msgid "some_text_2" msgstr "" 

Em seguida, você precisa criar traduções para vários idiomas. Para fazer isso, use os seguintes comandos babel.

init_catalog

Este comando é um invólucro da ferramenta msginit GNU, que cria um novo diretório de conversão baseado no arquivo pot.

 $ python setup.py init_catalog -l en -i foobar/locales/messages.pot \ -o foobar/locales/en/LC_MESSAGES/base.po 

 running init_catalog creating catalog 'foobar/locales/en/LC_MESSAGES/messages.po' based on 'foobar/locales/messages.pot' 

Importante! Os arquivos de localização são armazenados de uma maneira específica, de acordo com a convenção:

locales // LC_MESSAGES / .po

- um diretório com traduções para um idioma específico, no nosso caso é o inglês (en). Também pode haver um diretório com traduções não apenas para um idioma específico, mas também levando em consideração recursos adicionais. Por exemplo, uma tradução em inglês para os Estados Unidos é en_US;

- domínio com traduções. Se o nosso aplicativo crescer, as traduções serão divididas em domínios para não sobrecarregar um arquivo.

update_catalog

Este comando é um invólucro da ferramenta GNU msgmerge, que atualiza os diretórios de tradução existentes para arquivos * .po.

Ao adicionar novas traduções, simplesmente executamos o comando:

 $ python setup.py update_catalog -l en -i foobar/locales/messages.pot \ -o foobar/locales/en/LC_MESSAGES/base.po 

 running update_catalog updating catalog 'foobar/locales/en/LC_MESSAGES/base.po' based on 'foobar/locales/messages.pot' 

Também podemos especificar a localização em russo, especificando ru em vez de en.

compile_catalog

O comando final é um wrapper sobre a ferramenta GNU msgfmt. Ele pega mensagens traduzíveis de arquivos * .po e as compila em arquivos * .mo binários para otimizar o desempenho.

 $ python setup.py compile_catalog --directory foobar/locales --domain base 

 running compile_catalog compiling catalog to foobar/locales/en/LC_MESSAGES/base.mo 

--directory - caminho para o diretório com localização,
--domain - um sinalizador para especificar um domínio de tradução, nós o especificamos de acordo com os domínios de aplicativos existentes.

Os scripts Python funcionam apenas com traduções * .mo otimizadas. Portanto, com qualquer alteração, para que apareça no aplicativo, é necessário recompilar os arquivos com a localização. Para trabalhar com arquivos de tradução, você pode usar o aplicativo poedit - ele está disponível para todos os sistemas operacionais e é um software distribuído gratuitamente.



poedit - aplicativo de tradução

Cada tradução é exibida como uma linha separada, e isso é conveniente. Após a conclusão do trabalho com as traduções, ao salvar as alterações, um arquivo binário * .mo com todas as alterações é compilado automaticamente.

Como resultado, a estrutura dos catálogos de tradução ficará assim:

 locales ├── en │ └── LC_MESSAGES │ ├── base.mo │ └── base.po ├── ru │ └── LC_MESSAGES │ ├── base.mo │ └── base.po └── messages.pot 


Convenção sobre nomes de marcadores de tradução

Os arquivos po contêm traduções de texto e são combinados logicamente em um arquivo com um nome comum. Esses grupos são chamados de domínios. No exemplo acima, há apenas um domínio chamado base. Em aplicativos grandes, haverá mais domínios, e as listas de tradução precisam ser escritas levando em consideração a estrutura do aplicativo.

A consistência dos nomes dos marcadores de tradução deve ser mantida para evitar mais confusão nas traduções. Por exemplo, temos um formulário para salvar dados do usuário na página de perfil do usuário:

profile.user_form.component.title: Dados do usuário
profile.user_form.component.save: Salvar
profile.user_form.field.username: nome de usuário
profile.user_form.field.password: Senha


Implantação de aplicativo

Para implantar e implantar o aplicativo na janela de encaixe, você precisa compilar os arquivos de tradução em arquivos binários * .mo usando o seguinte comando:

 $ python setup.py compile_catalog --domain <> 

Recomendamos excluir os arquivos * .mo e * .pot no .gitignore:

# Traduções
* .mo
* .pot

2. Tradução de dados em mecanismos de modelo


Com a localização no modelo, tudo fica um pouco mais fácil. Considere o mecanismo de modelo python mais popular - o jinja. Para esse mecanismo de modelo, o suporte à localização de gettext por meio de complementos já está implementado. Para ativar o complemento, você deve especificar o caminho para o módulo add-in no construtor Environment. Para plataformas multilíngues, é necessário baixar as traduções uma vez e adicionar objetos de tradução ao objeto Environment durante a inicialização do aplicativo:

 translations = get_gettext_translations() env = Environment(extensions=['jinja2.ext.i18n']) env.install_gettext_translations(translations) 

Nos modelos, usamos apenas as construções:

 {{ gettext('some_text') }} {{ gettext('Hello %(name)s!')|format(name='World') }} 

3. Tradução dos dados armazenados no banco de dados


Vamos considerar opções para trabalhar com traduções nos bancos de dados relacionais mais comuns. Deve-se notar que a implementação de traduções e localização para bancos de dados noSQL e newSQL é semelhante.

Nota: não consideraremos o caso quando a tradução para cada idioma for armazenada em uma coluna separada. Essa implementação envolve limitações de escala e outros riscos com suporte adicional a aplicativos.

1) Linhas separadas para cada idioma


Com essa abordagem, para cada idioma, a tradução para um idioma específico nas linhas é baseada no valor da coluna, por exemplo, language_code. Se o valor en estiver nesta coluna, todos os valores traduzidos deverão se referir ao país e à região especificados.



Para o esquema descrito, os dados na tabela devem ficar assim:



Vantagens:

  • Implementação simples e eficiente.
  • Consultas simples ao usar um código de idioma específico.


Desvantagem:
  • Falta de centralização

Traduções para diferentes idiomas podem ser armazenadas em tabelas diferentes. Portanto, você não sabe quantos idiomas seu aplicativo está totalmente traduzido.

Essa solução é adequada para aplicativos que inicialmente não exigem internacionalização total de todos os dados. Mas é possível adicionar traduções para novas regiões à medida que os negócios se expandem.

A solicitação de dados será a seguinte:

 SELECT p.product_name, p.price, p.description FROM product p WHERE p.language_code = @language_code; 

2) Tabelas separadas com traduções


Nesta abordagem, para cada tabela que requer localização, criamos tabelas com traduções.



Prós:

  • Não há necessidade de associar tabelas para dados não traduzidos.
  • As consultas se tornam fáceis, pois existem tabelas separadas para tradução.
  • Não há discrepâncias nos dados.
  • Além das traduções, é possível localizar efetivamente o restante dos dados na tabela de idiomas.

Desvantagem:

  • Em aplicativos grandes, a tabela de conversão está inchada e fica mais lenta. Ao otimizar o aplicativo, será necessário implementar a migração de dados em tabelas separadas.

A solicitação de dados será a seguinte:

 SELECT tp.text, p.price, tc.text, c.contact_name FROM order_line o, product p, customer c, translation tp, translation tc, language l WHERE o.product_id = p.id AND o.customer_id = c.id AND p.name_translation_id = tp.id AND c.name_translation_id = tc.id AND tp.language_id = l.id AND tc.language_id = l.id AND l.name = @language_code AND o.id = ***; 

3) Criando entidades para campos traduzidos e não traduzidos


Nesta solução, as tabelas de entidades que contêm um ou mais campos traduzidos expandem os dados com os não traduzidos.



Prós:

  • Não há necessidade de combinar tabelas de conversão com tabelas que contêm dados que não requerem conversão. Portanto, a amostragem desses dados terá melhor desempenho,
  • É fácil escrever consultas ORM,
  • Uma consulta SQL simples para obter o texto traduzido,
  • É fácil oferecer suporte à tradução de determinados dados em todos os idiomas disponíveis.

Desvantagem:

  • A complexidade relativa da implementação.

Aqui está um exemplo de uma consulta que recuperará o texto traduzido:

 SELECT pt.product_name, pt.description, p.price FROM order_line o, product p, product_translation pt, language l WHERE o.product_id = p.id AND AND p.id = pt.product_non_trans_id AND pt.language_id = l.id AND l.name = @language_code; 

Conclusões


Ao localizar e internacionalizar aplicativos para o mercado internacional, vários métodos podem ser usados, cada um com certos recursos e limitações.

Neste artigo, examinamos os seguintes tipos de internacionalização:

  • no código: usamos traduções ao criar um serviço ou aplicativo com gui;
  • em modelos: usamos no desenvolvimento de um aplicativo da web sem um front-end dinâmico;
  • no banco de dados: use ao armazenar dados do usuário ou gerados dinamicamente.

Esperamos que nosso artigo o ajude a escolher o método mais adequado para o seu projeto.

Source: https://habr.com/ru/post/pt479738/


All Articles