1. Introdução
Como parte do meu projeto, tive a tarefa de tornar o site atual da empresa multilíngue. Mais precisamente: para criar a capacidade de traduzir com rapidez e facilidade o site para inglês, polonês, italiano etc.
Uma pesquisa na Internet mostrou que as opções existentes para criar um site multilíngue são extremamente volumosas e ineficientes. A conexão de bibliotecas de terceiros geralmente é problemática, e as dicas para escrever sua solução estão associadas a uma grande quantidade de trabalho do mesmo tipo.
Escrever um método alternativo para alterar a localidade levou apenas algumas horas, e manter a unidade semântica minimiza completamente as alterações ao adicionar novas páginas.
Os arquivos de origem do site de amostra com tradução automática podem ser baixados
no githubAlternativas existentes
Quando a tarefa apareceu no desenvolvimento, o primeiro passo, claro, foi estudar soluções prontas e dicas nos fóruns sobre como implementar de maneira mais fácil e correta a possibilidade de alterar a localidade. Os recursos mais populares da Internet para criar sites multilíngues oferecem as seguintes soluções:
- Criando blocos html duplicados com texto em diferentes idiomas, dos quais apenas um permanece ativo para o usuário e o restante fica oculto (exibição: nenhum).
A desvantagem óbvia desse método é o incrivelmente rápido aumento do código e uma perda instantânea de legibilidade e manutenção do código. Além disso, esta solução é vulnerável a erros no texto e escalabilidade em termos de aumento do número de idiomas (doravante referidos como localidades).
- Conectando um serviço de tradução automática de terceiros (como o google translate) com muitos idiomas internos e alterações mínimas no código-fonte da página.
Quando a tarefa apareceu pela primeira vez na lista de tarefas, usamos esse método como o mais óbvio e conveniente; no entanto, a experiência de trabalhar com falantes nativos dos Estados Unidos e Israel mostrou que a tradução automática comete erros ao alterar o código de idioma, e os usuários do site reagem muito a esse erros de tradução. No final, os parceiros estratégicos aconselharam fortemente a mudar o método de alteração do código do idioma, e esse método teve que ser abandonado.
- Alterando o idioma usando os recursos de js ou bibliotecas / estruturas de terceiros, como jQuery, com base na pesquisa e alteração direta de elementos DOM.
Uma característica dessa abordagem é a busca por um grande número de seletores js, cujo texto deve ser substituído. Essa abordagem pode funcionar bem para pequenos projetos, mas com um aumento no número de páginas, o número de funções de substituição de texto aumenta proporcionalmente, o que leva a uma perda de eficiência em grandes projetos.
Solução alternativa
A base da abordagem que proponho, como alternativa aos métodos existentes, não é, curiosamente, a base escrita por me js code, que geralmente é trivial, e o design de seletores, cujo suporte permite configurar de forma flexível e fácil a tradução de qualquer número de páginas para qualquer idioma sem alterações na base de código e duplicação excessiva de dados.
Ao alterar o código do idioma com uma abordagem alternativa, três jogadores principais se distinguem:
- página html com a regra estabelecida para seletores de bloco com texto
- serviço js geral, cuja principal tarefa é substituir os elementos DOM textContet de acordo com a regra de design dos seletores
- Arquivo JSON de localidade que contém uma estrutura que contém blocos html em todos os idiomas usados ao alterar o código do idioma
A conformidade com as regras de design para seletores de elementos mutáveis elimina a necessidade de alterar o código js do serviço de alteração de localidade, que é uma grande vantagem em termos de escalabilidade do projeto.
A regra para construir seletores
A maioria dos métodos para alterar a localidade da página (entre as alternativas 1.3 e parcialmente 2) sugere que você precisa “marcar” o bloco html a ser alterado de alguma forma, pois é correto alterando o campo da classe. O mesmo mecanismo usa uma opção alternativa.
A primeira etapa no projeto de seletores é dividir a página de origem em blocos de funções de nível superior. Na página da nossa empresa são blocos:
Damos a cada bloco um nome condicional, por exemplo,
Menu
Cartão de visita (casa)
Exemplo de operação de serviço (exemplo)
Parceiros (clientes)
Escopo do serviço (userfulBlock)
Exemplos do serviço (exemplos)
Contatos e feedback (contatos)
Depois disso, dividimos cada bloco em blocos funcionais menores, como é feito com a biblioteca React.
Atribuímos nossos nomes às áreas selecionadas e obtemos uma estrutura do formulário:
cardápio
casa principal, descrição, botões
exemplo estatísticas, título, descrição, botões
botões de clientes
título de userfulBlock, userfulCards, elseBlock
exemplos título, cartões
manchete de contatos , descrição, contatos, formulário
Continuamos esse procedimento até alcançarmos os blocos que contêm o texto de origem.
Como resultado, obtemos uma estrutura de arquivo JSON de localidade pronta, contendo todos os textos necessários para alterar o idioma. Além disso, com base nesse algoritmo, é determinada a regra para a construção de seletores:
Cada seletor começa com a palavra-chave locale e, de acordo com o estilo de maiúsculas e minúsculas, os nomes de todos os blocos pai são adicionados, incluindo o bloco que contém o texto de origem; por exemplo, a descrição do exemplo no primeiro cartão terá o seletor locale-example-cards-description
Um exemplo do arquivo json locale resultante pode ser visto
no githubServiço de alteração de localidade
O serviço de alteração de localidade é um módulo que contém a função de carregar um arquivo de localidade
loadLocale(defLang)
com o parâmetro opcional defLang - o idioma definido após o carregamento do código do idioma (idioma padrão), bem como a principal função de alterar o código do idioma atual
changeLocale(lang)
indicando o idioma necessário.
Função de download de localidade
A função de carregamento do código do idioma usa a solicitação XMLHttpRequest padrão para dados. O uso deste padrão se deve ao desejo de minimizar o número de dependências e a facilidade de uso da solicitação. Depois de receber o arquivo de localidade, uma notificação sobre o recebimento de dados é exibida no console e a função de alterar a localidade para o idioma padrão é chamada se esse idioma foi passado para a função como um parâmetro opcional. Você pode se familiarizar com o código de função aqui:
function loadLocale(defLang) { var xhr = new XMLHttpRequest(); xhr.open("GET", 'http://localhost:3000/locale.json', true); xhr.onreadystatechange = saveLocale.bind(this); xhr.onerror = function () { console.log("no found page"); }; xhr.send(); function saveLocale() { if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) { locale = JSON.parse(xhr.responseText); console.log("locale loaded"); if(defLang) changeLocale(defLang); } } }
Função de mudança de localidade
Tipos de dados
É uma função recursiva cuja principal tarefa é atravessar o objeto que contém o código de idioma da página (usando o algoritmo DFS). O uso da recursão na construção de uma função permite codificar o algoritmo da maneira mais simples e concisa possível; no entanto, muita profundidade de recursão pode levar ao estouro da pilha. Os recursos para solucionar esse problema podem ser encontrados no fórum com o mesmo nome ou lendo os artigos relevantes em habr.com.
A função recursiva é baseada no processamento de 4 tipos de dados:
- campo contendo uma sequência de texto de origem usada para adicionar à página.
Por exemplo:
"main": " "
- campo contendo uma matriz de linhas de texto de origem usada para adicionar a
página. Este campo é necessário para criar listas cujos elementos podem ser alterados.
ordem. Por exemplo:
"menu":["Home","Example","Clients","Info","Contacts"]
- Estrutura de dados aninhada contendo seu próprio conjunto de campos necessários para a construção
arquitetura de página. Por exemplo:
"home": { "main": "selling quest from your video", "description": "for social networks & sites", "buttons": ["try","order"] }
- Uma matriz de estruturas de dados aninhadas, com o mesmo conjunto de campos usado. Tais
matrizes são usadas quando listas de blocos de código idênticos aparecem, por exemplo,
cartões de membros da equipe ou carteira ou tarifas pelos serviços prestados.
Por exemplo:
"usefulCards": [ { "headline": "Marketers and agencies", "statistics": ["convers 26%", "retent 25%"], "button": "ORDER" }, { "headline": "Production studios and TV platforms", "statistics": ["convers 24%", "retent 33%"], "button": "ORDER" }, { "headline": "Conference creators", "statistics": ["convers 65%", "retent 15%"], "button": "ORDER" }, { "headline": "Bloggers and streamers", "statistics": ["convers 24%", "retent 33%"], "button": "ORDER" } ],
Em um site, pode ser assim:
Funções de processamento
O processamento do tipo de dados de origem é realizado por uma função separada
function getText(key, object, name,startIndex)
O nome do campo de entrada do campo de estrutura com o texto de origem, o objeto de localidade atual que contém o texto a ser adicionado e o nome atual do seletor necessário para procurar o elemento DOM.
function getText(key, object, name, startIndex) { var elementKey=0; if(startIndex) elementKey = startIndex; for ( ; elementKey < document.getElementsByClassName(name + "-" + key).length; elementKey++) if (!isNaN(elementKey)) document.getElementsByClassName(name + "-" + key)[elementKey].textContent = object[key]; }
O processamento de uma matriz de seqüências de caracteres com texto de origem também é realizado por uma função separada
function getArrayText(key, object, name,startIndex)
A assinatura e o corpo desta função não são diferentes do passado, exceto que os elementos da matriz são atribuídos aos elementos DOM.
function getArrayText(key, object, name, startIndex) { var elementKey=0; if(startIndex) elementKey = startIndex; for ( ; elementKey < document.getElementsByClassName(name + "-" + key).length; elementKey++) if (!isNaN(elementKey)) document.getElementsByClassName(name + "-" + key)[elementKey].textContent = object[key][elementKey % object[key].length]; }
A principal função recursiva para substituir o texto lida com a classificação do campo local atual em um dos 4 tipos acima e a reação correspondente ao tipo resultante:
function changeText(name, object, startIndex) { for (key in object) if (Array.isArray(object[key]) && typeof object[key] != 'string' && typeof object[key][0] == 'string') getArrayText(key, object, name); else if (typeof object[key] == "object" ){ if(isNaN(key)) changeText(name + "-" + key, object[key]); else changeText(name, object[key],key); } else getText(key, object, name, startIndex); }
Esta função aceita a localidade do idioma atual e o seletor de raiz (nesse caso, “localidade”). Além disso, após a detecção de uma estrutura aninhada ou de uma matriz de estruturas, a função se chamará recursivamente, alterando os parâmetros de entrada de acordo.
A principal vantagem da abordagem alternativa é que o serviço descrito acima não requer nenhuma alteração funcional e é adicionado como um arquivo js usando o arquivo de localidade que você criou.
Conclusão
A essência da abordagem descrita acima é uma regra fixa para descrever seletores e criar um arquivo de localidade. Graças a isso, há uma oportunidade única de traduzir qualquer página imediatamente e reutilizar o material já traduzido.
O algoritmo para construir os seletores descritos acima não é obrigatório e crítico para o serviço. O serviço é flexível para expandir e adicionar novos métodos e algoritmos, bem como para construir nomes de seletores e estruturas json locale. Uma possível vantagem seria salvar o código do idioma no cookie do navegador e alterar o código do idioma, dependendo da localização do usuário do serviço.
Os arquivos de origem do site de amostra com tradução automática podem ser baixados
no github .