Embora alguns sistemas de software sejam usados por um número limitado de usuários que falam o mesmo idioma, a maioria das organizações precisa unificar e centralizar seus aplicativos para serem usados por pessoas que falam idiomas diferentes em todo o mundo. Bancos de dados multilíngue apresentam um nível adicional de dificuldade em projetar e implementar modelos de dados. Neste artigo, sugerimos algumas abordagens para lidar com esse desafio.
Quais informações precisamos armazenar em vários idiomas?
Na superfície, todas as informações de string podem parecer plausíveis para tradução em vários idiomas. No entanto, este não é geralmente o caso. Informações relacionadas ao cliente, como
CompanyName
ou Address
pode ser traduzido, mas isso pode não ser uma boa ideia. Veja um cliente empresarial no Reino Unido chamado “Riverside Trucks” com um escritório em “123 Upper Castle Road”. Você não quer que um usuário de língua espanhola imprima e envie uma carta para “Camiones Orilla” localizada em “123 Calle Castillo Superior”. O Royal Mail (o serviço postal do Reino Unido) não o encontrará! Você provavelmente quer traduzir apenas as colunas que contêm informações descritivas, não nomes próprios.
Ao projetar um sistema para lidar com traduções, nem sempre se sabe de antemão exatamente quais colunas são traduzíveis e quais não requerem traduções. Escolher uma abordagem flexível economiza muito tempo em design e desenvolvimento. Dê uma olhada no artigo “Como projetar um sistema pronto para localização” para ver alguns exemplos.
Que abordagens consideramos?
Neste artigo, descrevemos três abordagens para o projeto de banco de dados multilíngue. Começamos com a mais simples que não é tão flexível e depois passamos a considerar outras opções, explicando os prós e contras de cada uma.
Tanto a sintaxe quanto os modelos de banco de dados (disponíveis no modelador de dados baseado na Web Vertabelo) usados neste artigo são para SQL Server. No entanto, eles são facilmente adaptados a qualquer mecanismo de banco de dados.
Abordagem 1:criando colunas adicionais para manter o conteúdo traduzido
Esta é a abordagem mais simples de implementar, embora não seja muito flexível. Consiste em adicionar uma coluna para cada coluna e idioma que precisamos usar em nosso sistema, conforme mostrado no diagrama Vertabelo a seguir:
Embora isso possa parecer uma solução muito simples, tem algumas desvantagens. Explicamos abaixo.
Contra:Complexidade do código
Essa abordagem torna o código mais complexo. Requer que escrevamos uma consulta diferente para cada idioma ou usemos um
CASE
construção para recuperar a tradução para o idioma apropriado com base na configuração do usuário. Veja o código a seguir por exemplo:SELECT ProductID, CASE @Language WHEN 'ES' THEN ProductName_ES QUANDO 'DE' THEN ProductName_DE WHEN 'FR' THEN ProductName_FR ELSE ProductName END AS ProductName, CASE @Language QUANDO 'ES' THEN ProductDescription_ES WHEN 'DE' THEN ProductDescription_DE WHEN ' FR' THEN ProductDescription_FR ELSE ProductDescription END AS ProductDescription, Price, Weight, ProductCategoryIDFROM ProductWHERE …
Observação: No exemplo, usamos a variável @Language para manter o idioma que queremos usar. Você pode considerar usar SESSION_CONTEXT() (ou Application Context no Oracle) para definir e ler o idioma de cada usuário.
Contra:falta de flexibilidade
Esta abordagem carece de flexibilidade. Se precisarmos implementar um novo idioma, precisamos modificar nosso modelo de dados adicionando uma coluna para o novo idioma para cada coluna traduzível em nosso sistema. Também precisamos criar uma nova consulta de idioma para cada tabela (ou editar a existente adicionando um novo
CASE WHEN
cláusula que usa o novo idioma para cada coluna traduzível). Con:Desafios ao lidar com informações desconhecidas
Imagine isso:um usuário adiciona um produto mas não sabe como traduzi-lo e deixa as colunas traduzidas vazias. Os usuários que falam esses idiomas veem
NULL
ou informações em branco nas colunas que podem ser necessárias. Abordagem 2:Isolando colunas traduzíveis em uma tabela separada
Essa abordagem agrupa as colunas em uma tabela em colunas traduzíveis e não traduzíveis. As colunas não traduzíveis permanecem na tabela original. Em contraste, os traduzíveis estão em uma tabela separada, com uma chave estrangeira para a tabela original e um indicador de idioma. Ver abaixo:
O diagrama mostra as tabelas originais (sem dados traduzíveis) em branco. As tabelas que contêm as traduções estão em azul claro e a tabela principal que contém as informações do idioma está em amarelo.
Isso tem enormes vantagens de flexibilidade em comparação com o uso de várias colunas, conforme discutido anteriormente. Esse método não requer a alteração do modelo de dados quando um novo idioma é necessário. Além disso, a sintaxe para consultar as informações é mais simples:
SELECT p.ProductID, pt.ProductName, pt.ProductDescription, p.Price, p.Weight, p.ProductCategoryIDFROM Product pLEFT JOIN ProductTranslation pt ON pt.ProductID =p.ProductID AND pt.LanguageID =@LanguageWHERE …
No entanto, ainda existem alguns contras, como discutimos abaixo.
Contra:Desafios quando colunas adicionais precisam ser traduzidas
Se precisarmos converter uma coluna não traduzível em uma traduzível (ou vice-versa), precisamos modificar nosso modelo de dados, movendo a coluna de uma tabela para outra. Isso geralmente implica em maiores custos uma vez que o sistema esteja implementado e em uso.
Con:Desafios ao lidar com informações desconhecidas
Assim como a primeira abordagem, essa abordagem apresenta desafios ao lidar com informações desconhecidas. Novamente, se um usuário adiciona um produto, mas não sabe como traduzi-lo e deixa as colunas traduzidas vazias, os usuários que falam esses idiomas veemNULL
ou informações em branco nas colunas que podem ser necessárias. Além disso, a consulta requer umLEFT JOIN
caso a tradução para o idioma do usuário atual ainda não tenha sido criada para que os dados não traduzíveis ainda sejam mostrados.
Abordagem 3:Adicionando um Subsistema de Tradução
A tradução pode ser considerada como um recurso completamente independente do modelo de dados que requer tradução. Em um sistema ideal, podemos habilitar ou desabilitar a tradução para qualquer coluna sem exigir modificação no modelo de dados. Infelizmente, isso é mais fácil dizer do que fazer.
Apresentamos um método que não tem impacto no modelo de dados existente e é completamente flexível. Embora acrescente complexidade na hora de consultar os dados, não requer nenhuma alteração adicional no modelo de dados, exceto algumas tabelas. Essa pode ser uma ótima opção se você precisar adicionar a capacidade de manter traduções em um modelo de dados existente.
Vamos dar uma olhada no modelo e ver como ele funciona:
A primeira coisa a notar é que o modelo de dados original não tem nenhuma alteração. Além disso, não há relação direta entre esse modelo e o subsistema de tradução.
O subsistema de tradução inclui um pequeno dicionário de dados com as tabelas e colunas que requerem tradução. Este dicionário de dados pode ser modificado apenas adicionando/removendo linhas sem alterar o modelo de dados. As traduções são armazenadas em uma tabela separada, com cada valor identificado pelas 3 colunas a seguir:
ColumnID
:identifica exclusivamente a coluna (e a tabela) que estamos traduzindo.KeyID
:armazena o ID (chave primária) da linha específica que estamos traduzindo.LanguageID
:identifica o idioma da tradução.
Esse design permite que os dados sejam inseridos e armazenados nas tabelas originais, adicionando traduções somente se e quando necessário. As informações traduzidas são usadas na recuperação de dados, mantendo os dados originais (no idioma original) intocados.
Os dados podem ser consultados usando uma sintaxe mais complexa do que os exemplos acima. Requer um
JOIN
adicional para cada coluna traduzível, conforme mostrado abaixo:SELECT p.ProductID, ISNULL(t1.TranslationValue, p.ProductName) AS ProductName, ISNULL(t2.TranslationValue, p.ProductDescription) AS ProductDescription, p.Price, p.Weight, p.ProductCategoryIDFROM Product pLEFT JOIN Tradução t1 ON t1.ColumnID =<> AND t1.Key =p.ProductID AND t1.LanguageID =@LanguageLEFT JOIN Tradução t2 ON t2.ColumnID =< > AND t2.Key =p.ProductID AND t2.LanguageID =@LanguageWHERE …;
Observação: “
<<ProductName_ColumnID>>
” e “<<ProductDescription_ColumnID>>
” deve ser substituído pelos IDs das colunas a serem traduzidas como armazenadas em ColumnInformation
tabela. Considere gerar visualizações de tradução para cada tabela que requer tradução para ocultar a complexidade dos JOINs para os usuários finais. Você pode até automatizar essa etapa com um script que gera cada visualização. Este script pode consultar o dicionário de dados do banco de dados para escolher as tabelas e colunas e adicionar a lógica de tradução para as colunas que existem no ColumnInformation
tabela. Dica Extra nº 1
Você também pode simplificar a sintaxe. Substitua cada JOIN por uma chamada para uma função que trata (e oculta) o aspecto da tradução, conforme mostrado abaixo:
SELECT p.ProductID, ISNULL(fn_translate('Product','ProductName',ProductID), p.ProductName) AS ProductName, ISNULL(fn_translate('Product','ProductDescription',ProductID), p.ProductDescription) AS ProductName, p.Price, p.Weight, p.ProductCategoryIDFROM Product pWHERE …;
A função pode ler o idioma desejado do contexto ou você pode adicioná-lo como um parâmetro adicional. Neste exemplo, a função usa os nomes de tabela e coluna fornecidos como parâmetros mais a chave de linha (também fornecida como parâmetro) para pesquisar e retornar a tradução desejada.
Chamar uma função implica um impacto adicional no desempenho devido à alternância de contexto entre SQL e linguagem procedural. No entanto, pode ser uma solução mais simples para bancos de dados ou tabelas onde a quantidade de dados sendo traduzidos o permita.
Ambos os exemplos – aquele com um JOIN e aquele com uma função – usam a função ISNULL() do SQL Server. Assim, quando a tradução para o idioma desejado não existe, ela ainda exibe o valor original armazenado nas colunas ProductName e ProductDescription em vez de espaços em branco ou NULL.
Considerações Gerais
A terceira abordagem geralmente é a melhor para implementar projetos maiores. Ele permite flexibilidade tanto no projeto quanto uma vez que o sistema está em uso. No entanto, existem considerações específicas que podem tornar as outras abordagens úteis. Independentemente de sua escolha, considere o seguinte para economizar tempo tanto no projeto quanto no desenvolvimento/implementação.
Adicione uma camada de abstração
Como mencionado anteriormente, considere a criação de visualizações que cuidam da lógica de tradução, por exemplo, selecionando uma coluna entre várias colunas de tradução ou juntando-se a linhas específicas. Isso mantém detalhes de implementação específicos ocultos dos programadores. Eles simplesmente usam essas visões em vez de ter que construir sentenças SQL complexas toda vez que precisam acessar uma tabela com informações traduzíveis.
Use o contexto para filtrar dados
Conforme mencionado no primeiro exemplo, considere o uso de funções de contexto disponíveis na maioria dos mecanismos de banco de dados. Use-os para armazenar informações de idioma do usuário uma vez logado no sistema e filtre os resultados automaticamente nas visualizações que cuidam da tradução.
Automatizar
Os sistemas modernos podem ter centenas e até milhares de tabelas. Reserve um tempo para automatizar a geração das visualizações de tradução em vez de escrever as consultas uma a uma. Pode levar algum tempo para você chegar a um script que funcione, mas você sempre pode criar novas visualizações ou recriar as existentes em menos de um segundo!