
O que acontece quando seu produto começa a ser vendido em outro país com seu próprio idioma e características culturais? Provavelmente, a localização o espera. Na maioria dos casos, é necessário apenas converter os arquivos de recursos para que os menus e os elementos da interface estejam no idioma familiar do usuário. Mas o que fazer se a base do que você vende são dados, que são muitos, eles vêm constantemente, em um grande volume e exigem tradução regular. E não apenas um idioma, mas vários idiomas ao mesmo tempo.
Abaixo do corte, você encontrará uma história de como esse problema foi resolvido no 2GIS. Vou lhe dizer o exemplo do último caso em Dubai, mas as práticas são aplicáveis a qualquer idioma.
Sobre o árabe
Toda essa história começou com o lançamento do 2GIS em Dubai, onde dois idiomas estão em uso: árabe e inglês.
A alta precisão dos dados é o valor mais importante da empresa. Isso é alcançado através do trabalho manual de cartógrafos e especialistas no local. Em Dubai, onde especialistas locais e usuários finais conhecem inglês, inicialmente os dados foram inseridos apenas nele. No processo de crescimento, eles decidiram não parar por aí e adicionar o idioma árabe.
Fiji
Para o trabalho de cartógrafos, temos nosso próprio software. Essa coisa é chamada Fiji e serve como um sistema mestre para coletar dados do mapa.
Já escrevemos como Fiji ajuda cartógrafos a editar casas e desenhar estradas. Os dados enviados de Fiji após o processamento e a preparação vão para o produto final para agradar aos usuários. No artigo, estou falando exatamente sobre o que implementamos nas Ilhas Fiji para editar / armazenar / exibir dados multilíngues.
Termos
Na equipe, usamos vocabulário específico. Abaixo estão quatro exemplos ↓
O sistema suporta o trabalho com dois tipos de idiomas:
Idioma dos
metadados - o idioma no qual todos os controles do usuário são exibidos: interface do usuário, metadados.
Idioma dos
dados - um idioma em que os valores dos atributos dos objetos geográficos, alguns diretórios e classificadores são exibidos.
Os idiomas estão vinculados aos territórios. Um território pode ter dois tipos de idiomas:
A língua principal do território é a língua adotada oficialmente nesse território.
O idioma adicional do território é o idioma em que queremos lançar o produto. Vem além do principal.
Idiomas e dialetos
Os dialetos adotados em diferentes regiões do país podem variar significativamente. Portanto, em alguns sistemas, o núcleo do idioma (= versão básica) e os dialetos são armazenados separadamente e, em seguida, ao descarregar seus merjats. Para nós, essa abordagem parecia muito complicada, por isso decidimos considerar cada dialeto como um idioma independente.
Nuance relacionada a idiomas e dialetos árabes. Para cada idioma, é necessário inserir um sinalizador de direção do carro com dois valores: da esquerda para a direita e da direita para a esquerda. Por padrão, o carro deve se mover da esquerda para a direita. Se o valor estiver definido da direita para a esquerda, será necessário alterar a direção do carro para todos os campos editáveis e multilíngues. Como isso foi feito nos produtos finais foi escrito
aqui . Nós tivemos que fazer o mesmo.
Ajustar aos territórios
Nosso mundo inteiro está dividido em certos territórios - podem ser países, regiões, regiões. Para cada território, especificamos várias línguas, uma das quais é considerada a principal e as demais - adicionais. A tradução ocorre do idioma principal para outros idiomas.
Por exemplo, no caso de Dubai, deixamos o inglês como idioma principal, porque havia muitos dados. O árabe foi tornado opcional.
Digite e mude o idioma
Para que os cartógrafos trabalhem confortavelmente, redesenhamos nossa interface naqueles lugares onde se entende a entrada multilíngue.

Nesta figura, você pode ver que dividimos os idiomas em abas, onde o mais à esquerda é o idioma principal e existem outros adicionais.
Nas guias de idiomas adicionais, apenas esses campos estão disponíveis para edição que possuem um sinalizador para a necessidade de tradução no banco de dados. Isso serve como uma medida protetora e ajuda a concentrar a atenção do usuário na tradução dos dados necessários. Todo o resto é editado no idioma principal.

De fato, a edição de dados em um idioma adicional pode ser necessária apenas se o próprio cartógrafo conhecer vários idiomas e não desejar recorrer à ajuda de um tradutor. Para todos os outros, existe o CrowdIn.
Crowdin, ou transferência de stream
Assim, permitimos que nossos cartógrafos preenchessem dados em diferentes idiomas. Mas é muito melhor entregar a tarefa de tradução a profissionais.
A primeira coisa que vem à mente ao traduzir o aplicativo é fornecer os arquivos de recursos aos tradutores e após a transferência, faça o download deles de volta.
Nesse caso, a plataforma CrowdIn nos ajudou muito. Ele permite que você redirecione seus arquivos para tradutores profissionais. A única coisa que restou foi integrar os dados traduzidos ao nosso sistema.
A situação é complicada pelo fato de os dados chegarem a nós em um fluxo contínuo; portanto, gostaríamos de receber traduções continuamente.
Otimizamos o sistema da seguinte forma: se forem feitas alterações no idioma principal do território, faremos o upload das alterações para tradução em todos os idiomas adicionais desse território. Abrimos exceções nos casos em que o próprio cartógrafo fez a tradução. Aqui acreditamos que ele entende o que está fazendo e não há necessidade de conectar um intérprete.
Para cada diretório ou objeto de mapa, temos uma versão ponta a ponta, que é incrementada a cada atualização de dados. Para que possamos obter rapidamente todas as alterações de uma versão específica.
O sistema de versão é muito simples e eficiente, mas tem uma desvantagem significativa: na verdade, temos uma fila única e não podemos gerenciá-lo de nenhuma maneira. Nosso máximo é pular a versão. É necessário mudar para uma fila normal, por exemplo, para RabitMQ ou Kafka, mas as mãos ainda não chegaram.
Para atualizar rapidamente o conteúdo, escrevemos um pequeno serviço que funciona em três fluxos.
O primeiro fluxo (Saver) extrai todos os dados que requerem tradução e gera arquivos xml a partir deles.
O segundo (Exportar) os envia ao CrowdIn e os coloca no projeto desejado, que indica o idioma principal do qual estamos traduzindo e uma lista de idiomas para os quais traduzir.
O terceiro (Importar) pesquisa periodicamente a API CrowdIn em busca de arquivos cuja tradução foi feita e instalada 100%, e importa os arquivos finalizados em nosso banco de dados.

Sem um ancinho não poderia fazer. Tropeçamos no nosso sistema de versão de dados.
Quando baixamos a tradução da palavra, a versão dos dados foi atualizada e a palavra novamente caiu na tradução.
Para evitar um ciclo interminável de tradução, começamos a registrar dados. Cada palavra traduzida é marcada, o que elimina o envio repetido para o CrowdIn.
Tutorial
Agora vou contar como acontece o trabalho com o CrowdIn. Existem várias maneiras de trabalhar com a plataforma, por exemplo, você pode fazer upload de arquivos para o repositório Git e o próprio CrowdIn os suga. Mas achamos que trabalhar com a API parece mais conveniente.
CrowdIn tem um
tutorial bastante detalhado, mas abaixo vou escrever como fizemos.
Precisamos obter uma chave de API, que anexaremos a cada uma de nossas solicitações, para que o sistema nos verifique. Vamos para a guia API nas configurações do projeto e examinamos o que está escrito na coluna chave da API.

Essa chave deve ser adicionada no final de cada uma das suas solicitações de plataforma. Por exemplo, assim:
GET:
https://api.crowdin.com/api/project/{myLitleProject}/download/all.zip?key={project-key}
2. Crie uma pasta na qual carregaremos os arquivos dentro do projeto.
var uri = $"project/{_projectName}/add-directory?key={apiKey}"; var content = new MultipartFormDataContent { { new StringContent(crowdInDirectoryPath), "name" } }; return PostAsync(uri, content);
Há um pequeno momento desajeitado. Estamos escrevendo um serviço, seria bom se, a princípio, ele verifique se a pasta de que precisamos está presente antes de tentar criá-lo. Como o CrowdIn não tem uma maneira normal de verificar uma pasta, enviamos uma solicitação de criação. Se não estiver lá, o CrowdIn o criará e retornará o código 200. Se houver uma pasta, ele não criará nada e retornará o código 500.
3. Exporte arquivos. A função add-file possui muitas opções e parâmetros, como ler e onde. Abaixo está um exemplo de como carregamos dados com arquivos xml.
ExemploColocamos todos os dados que vamos traduzir em arquivos xml com a seguinte estrutura.
<LocalizableDocument> <LocalizableValues> <LocalizableValue> … <Attributes> <LocalizableAttributeValue> <AttributeName/> <Value/> </LocalizableAttributeValue> </Attributes> </LocalizableValue> </LocalizableValues> </LocalizableDocument>
Para que o CrowdIn analise quais dados do arquivo precisam ser traduzidos, eles precisam ser especificados. Para fazer isso, você precisa escrever uma matriz de parâmetros translatable_elements com os caminhos para os elementos necessários do documento no conteúdo. No nosso caso, ficou assim:
var uri = $"project/{_projectName}/add-file?key={apiKey}"; var content = new MultipartFormDataContent { { new StringContent("/LocalizableDocument/LocalizableValues/LocalizableValue/Attributes/LocalizableAttributeValue/Value"), "translatable_elements[0]" } }; foreach (var filePath in filePaths) { var fileName = Path.GetFileName(filePath); var fileStream = File.OpenRead(filePath); var fileContent = new StreamContent(fileStream); content.Add(fileContent, $"files[{_crowdInDirectoryPath}/{fileName}]", fileName); } return PostAsync(uri, content);
Observe: a documentação afirma que o CrowdIn pode mastigar no máximo 20 arquivos por vez, enquanto o tamanho de um arquivo não deve exceder 100 MB.
4. Descobrimos quais arquivos foram totalmente traduzidos. Fazemos isso usando um comando para um idioma específico.
var uri = $"project/{_projectName}/language-status?key={apiKey}"; var content = new MultipartFormDataContent {{ new StringContent(langCode), "language" } }; return PostAsync(uri, content);
A plataforma nos retornará algo como isto:
<item> <node_type>directory</node_type> <id>29812</id> <name>Version 1.0</name> <files> <item> <node_type>file</node_type> <id>29827</id> <name>strings.xml</name> <node_type>file</node_type> <phrases>7</phrases> <translated>0</translated> <approved>0</approved> <words>32</words> <words_translated>0</words_translated> <words_approved>0</words_approved> </item> </files> </item>
Aqui estamos interessados nos valores <traduzidos /> e <aprovados />. O primeiro indica a porcentagem de linhas traduzidas nesse arquivo, o segundo mostra a porcentagem de valores aprovados se, além do tradutor, um revisor também participar do fluxo de trabalho. Dependendo do nosso fluxo de trabalho, por exemplo, em 100, consideramos o documento traduzido e aprovado. Agora, esse arquivo pode ser importado de volta para nós.
5. Importe o arquivo de volta para o nosso sistema.
Isso é feito com uma simples solicitação GET.
https://api.crowdin.com/api/project/{_projectName}/export-file?file={_crowdInDirectoryPath}/{fileName}&language={langCode}&key={project-key}
O arquivo resultante é desserializado e os dados são importados para o nosso sistema.
Em vez de uma conclusão
Em termos gerais, é tudo. Obviamente, ainda era necessário refinar a exibição das assinaturas no mapa de Fiji para que elas fossem exibidas no idioma correto, dependendo do território em que o cartógrafo agora governa. Era necessário concordar com outros sistemas como fornecê-los com dados multilíngues, mas isso é outra história.
Como resultado, recebemos o serviço com o espírito de "ligado e esquecido". Os cartógrafos inserem dados, os tradutores traduzem, o sheik está satisfeito, o serviço faz o upload dos dados quando necessário e resolvemos problemas mais urgentes sem pensar em como o nosso sistema funciona em vários idiomas.