20 projetos, 20 idiomas, prazo de ontem. Parte 3

Artigo final sobre integração Serge + Smartcat . Neste artigo, mostrarei como dimensionamos Serge para toda a empresa, consideramos 4 integrações fora do padrão e, como bônus, falamos sobre dois recursos que podem simplificar sua vida.

Artigos anteriores:

20 projetos, 20 idiomas, prazo ontem
20 projetos, 20 idiomas, prazo de ontem. Parte 2

Escalabilidade


Em um artigo anterior, falei sobre como configurar o Serge para um único repositório. Em nossa empresa, temos várias dezenas de repositórios que precisam de traduções; portanto, um servidor separado para localização foi alocado. A estrutura e o ambiente do arquivo são completamente idênticos ao descrito no artigo anterior. Cada repositório usa sua própria instância do Serge. Para não executar comandos manualmente, cada instância possui uma coroa, que executa sucessivamente os comandos Serge: recebendo novas linhas do repositório, recebendo novas traduções, analisando, enviando novas linhas para o Smartcat e enviando novas traduções para o Gitlab.

Opções de integração


Dois conjuntos de idiomas em um repositório


Vamos começar com o caso mais simples. Imagine que seu repositório possui vários conjuntos de arquivos de recursos. Por exemplo, cadeias de clientes e APIs de aplicativos são armazenadas no mesmo repositório, mas em diretórios diferentes. O cliente é traduzido para 20 idiomas, a API para 6.

Objetivo : organizar um fornecimento independente de traduções em cada um dos diretórios.
Solução :

  1. Configure 2 projetos no Smartcat: em 6 idiomas e em 20.
  2. Configure 2 projetos no servidor de localização.
  3. No primeiro projeto no arquivo project1.cfg, adicione a linha our $ unmerged_branch_mask = '^ (translateAPI-)'; # processa ramificações não imersas que correspondem a essa máscara , onde " translateAPI- " é o prefixo do nome da ramificação. O prefixo indicará a Serge que esse ramo precisa de traduções no diretório da API.
  4. No arquivo project1.serge.tmpl , especifique o caminho para os arquivos de recursos no diretório API no parâmetro source_dir .
  5. Da mesma forma, para o segundo projeto no arquivo project2.cfg, adicione a linha our $ unmerged_branch_mask = '^ (translateCLIENT-)'; # processa ramificações não imersas que correspondem a essa máscara , onde " translateCLIENT " é o prefixo das ramificações deste projeto. O prefixo indicará a Serge que esse ramo precisa de traduções no diretório do cliente.
  6. No arquivo project2.serge.tmpl , especifique o caminho para os arquivos de recursos no diretório CLIENT no parâmetro source_dir .

Observe que os prefixos devem ser exclusivos entre todos os projetos configurados para um repositório.

No total, temos 2 projetos no Smartcat e 2 projetos correspondentes no servidor de localização. Ambos os projetos olham para o mesmo repositório no Gitlab, mas em diretórios diferentes. Serge, usando o prefixo da ramificação, entende quais linhas ele precisa enviar para tradução. Para calcular o diff, a mesma ramificação de conversão de base é usada.

Localização Swagger


Em nossa empresa, todos os produtos, incluindo a documentação, estão localizados. Agora, estamos introduzindo a geração automática de documentação a partir do swagger, e somos confrontados com a necessidade de localizá-la.
Tarefa : localizar arrogância com o mínimo esforço.

Solução : no arquivo myproject.tmpl.serge , inclua o objeto de dados no objeto do analisador e liste os campos cujo valor deve ser extraído e enviado para conversão:

parser { plugin parse_json data { path_matches \/(summary|description)$ } } 

Tarefa semelhante : é necessário traduzir textos de um arquivo, mas não todos, mas apenas os legais. Outros textos são fornecidos por uma equipe de marketing. Para não complicar a estrutura e não criar um arquivo adicional para textos legais, as chaves de todas as linhas legais receberam o prefixo “legal”:

 parser { plugin parse_json data { path_matches ^\/legal\..* } } 

Sutilezas das traduções legais


Outro caso interessante. Temos um documento legal, cujos termos variam de país para país. Mas, no entanto, esse é um aplicativo e os arquivos de recursos estão no mesmo diretório.

Objetivo : no âmbito de um projeto para traduzir vários documentos, cada documento deve ser traduzido para um idioma específico.

O que foi feito :

  1. Um diretório apropriado foi criado para cada país, dentro do qual havia um arquivo de origem em inglês relevante para esse país.
  2. O caminho para a variável source_dir é especificado no diretório compartilhado com arquivos de recursos.
  3. Ativamos a pesquisa de arquivos de recursos em todos os subdiretórios : source_process_subdirs YES
  4. Adicionamos um novo plug-in à lista de plug-ins chamados, que permite enviar cada arquivo de recurso específico para o idioma desejado. Como um guia, use o nome do diretório em que se encontra:

 callback_plugins { :feature_branch { plugin feature_branch data { master_job job.base-translate } } :limit_languages { plugin limit_languages data { # all rules are processed top to bottom; each rule can add or remove languages # so the most priority rules are placed at the bottom if { # by default, don't localize file_matches . then { exclude_all_languages YES } } if { file_matches de-au\/ then { include_languages de-AT } } if { file_matches li-LI\/ then { include_languages li } } if { file_matches pt\/ then { include_languages pt-BR } } if { file_matches zh-Hans\/ then { include_languages zh-Hans } } # and so on.. } } 

Localização ao armazenar linhas no banco de dados


Nosso sistema possui uma parte do código que armazena traduções no banco de dados e, por vários motivos, não pode ser movido para arquivos de recursos no repositório. No entanto, precisamos entregar traduções de forma rápida e automática.

Tarefa : Organize um processo de localização contínua se as linhas não estiverem armazenadas no repositório, mas no banco de dados.

Solução :

  1. Crie um repositório, colete e agrupe todas as linhas do banco de dados de acordo com o princípio que é conveniente para nós (pelo número de idiomas de tradução ou por produtos).
  2. Crie um projeto no Smartcat.
  3. Inicie o ciclo padrão de localização contínua.
  4. Mesclar ramificações de tradução na ramificação de conversão base.
  5. Por coroa, verifique o valor do hash do último commit na conversão base. Se o hash foi alterado, ou seja, novas traduções foram geradas, analise o diff entre o hash antigo e o atual e envie linhas novas / alteradas ao banco de dados.

Recursos de bônus


Alertas


Os alertas básicos do Smartcat não eram adequados para nós, pois cada equipe deseja receber notificações apenas sobre suas ramificações e apenas sobre a disponibilidade completa de traduções em todos os arquivos de recursos do produto.

Decidiu-se aproveitar a disponibilidade de todas as traduções no repositório e, se estiverem completamente prontas, enviar notificações ao messenger corporativo, no nosso caso, o Google Chat.

Tarefa : organizar alertas no repositório, onde oito equipes podem confirmar, duplicar todos os alertas no canal do departamento de documentação técnica.

Solução :

  1. Concorde com cada equipe que o nome das filiais deve conter o nome da equipe. Ainda use o prefixo translate- para indicar os ramos que precisam ser traduzidos.
  2. Crie um pipeline que seja executado apenas para ramificações prefixadas com translate-.
  3. No pipeline, determine a qual comando a ramificação pertence, verifique a presença de linhas com um valor vazio e, se não houver, envie notificações de prontidão para o canal apropriado. Como o código é bastante volumoso, coloquei-o em um script.

Ci


 check-translations: stage: check-translations image: node:8.14.0 tags: - devops script: - chmod +x ./notification.sh - ./notification.sh only: - base-translate - /^translate.*$/ when: always 

Script de alerta


 #!/bin/bash hangouts(){ curl -X POST --max-time 180 -H "Content-Type: application/json; charset=UTF-8" --data "{ \"cards\": [{\"header\": {\"title\": \"LOCALIZATION IS READY\",\"subtitle\": \"REPOSITORY NAME\",\"imageUrl\": \"https://avatanplus.com/files/resources/mid/5775880ee27f8155a31b7a50.png\"},\"sections\": [{\"widgets\": [{\"keyValue\": {\"topLabel\": \"Translation is finished in the branch\",\"content\": \"$1\"}}]},{\"widgets\": [{\"buttons\": [{\"textButton\": {\"text\": \"SEE COMMIT\",\"onClick\": {\"openLink\": {\"url\": \"https://gitlab.loc/common/publisher-client/commit/$2\"}}}}]}]}]}]}" "$3" || true } cd app/translations if echo "$CI_COMMIT_REF_NAME" | grep "commandname1"; then grep -rl '\:\s\"\"' *.json >> result.file if [ -s network.file ]; then echo "Translations are not ready"; cat result.file else hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_COMMAND_NAME_1 hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_DOC fi fi if echo "$CI_COMMIT_REF_NAME" | grep "commandname2"; then grep -rl '\:\s\"\"' *.json >> result.file if [ -s result.file ]; then echo "Translations are not ready"; cat result.file else hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_COMMAND_NAME_2 hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_DOC fi fi ... if echo "$CI_COMMIT_REF_NAME" | grep "commandname8"; then grep -rl '\:\s\"\"' *.json >> result.file if [ -s result.file ]; then echo "Translations are not ready"; cat result.file else hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_COMMAND_NAME_8 hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_DOC fi fi 

Atribuições do tradutor via API Smartcat




É assim que nosso gerente de localização se parece quando chega a hora de atribuir todas as ramificações para tradução.

Em média, temos mais de 10 filiais em nosso trabalho todos os dias. No Smartcat, cada par de idiomas é um documento separado e os tradutores devem ser atribuídos a cada um desses documentos. Manualmente. Imagine: 40 a 60 compromissos todos os dias. Para simplificar esse processo, marcamos uma consulta por meio da API e também a colocamos no pipeline. Este trabalho é iniciado pelo botão. Uma pergunta razoável: por que não tornar as atribuições automáticas ao enviar transferências e não fazer uma chamada de método no plug-in Smartcat, e não no pipeline?

Existem várias razões para esta decisão:

  1. O fator humano. Apesar de criarmos processos e tentarmos aderir a eles, linhas não lidas ou sem contexto entram regularmente no Smartcat. A atribuição automática nesse caso significaria despesas adicionais para nós, pois algumas linhas seriam enviadas para tradução duas vezes: antes e depois da edição.
  2. Distribuição de papéis. Os projetos são configurados e gerenciados no nível do servidor de localização pelo engenheiro de localização ou gravador técnico do projeto. Compromissos e comunicação com tradutores são tratados pelo gerente de localização. Portanto, as atribuições devem ser gerenciáveis, transparentes e acessíveis através da GUI.

Solução: quando o gerente de localização considera que as linhas neste ramo estão prontas para tradução, ele pressiona um botão no Gitlab. Toda a equipe de tradutores é atribuída a esse ramo. A tarefa é realizada pelo tradutor que respondeu primeiro.

Ci


 assignee: stage: assignee image: node:8.14.0 tags: - devops script: - chmod +x ./assignee.sh - ./assignee.sh only: - base-translate - /^translate.*$/ - assignee when: manual 

Script de atribuição


 #!/bin/bash if echo "$CI_COMMIT_REF_NAME" | grep "translate-"; then node -pe "JSON.parse(process.argv[1]).documents.forEach(function(elem){ if(elem.name.indexOf(\"$CI_COMMIT_REF_NAME\") !== -1) { console.log(elem.id) } });" "$(curl -XGET -H "Authorization: Basic $SMARTCAT_API_KEY" -H "Content-type: application/json" "https://smartcat.ai/api/integration/v1/project/$SMARTCAT_PROJECT_ID")" >> documents fi sed '$d' documents > documents.list while read LINE; do bash -c "curl -XPOST -H 'Authorization: Basic $SMARTCAT_API_KEY' -H "Content-type:application/json" -d '{"documentIds":[\""$LINE"\"],"stageNumber": 1}' 'https://smartcat.ai/api/integration/v1/document/assignFromMyTeam'";done < documents.list 

Isso conclui minha série de artigos sobre integração e configuração de localizações contínuas. Terei todo o gosto em responder a todas as suas perguntas.

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


All Articles