Estou desenvolvendo um software multilíngue. No que diz respeito ao código do aplicativo, a localização não é um problema. Podemos usar recursos específicos do idioma e ter todos os tipos de ferramentas que funcionam bem com eles.
Mas qual é a melhor abordagem para definir um esquema de banco de dados multilíngue? Digamos que temos muitas tabelas (100 ou mais) e cada tabela pode ter várias colunas que podem ser localizadas (a maioria das colunas nvarchar deve ser localizável). Por exemplo, uma das tabelas pode conter informações do produto:
CREATE TABLE T_PRODUCT (
NAME NVARCHAR(50),
DESCRIPTION NTEXT,
PRICE NUMBER(18, 2)
)
Posso pensar em três abordagens para oferecer suporte a texto multilíngue nas colunas NAME e DESCRIPTION:
Coluna separada para cada idioma
Quando adicionamos um novo idioma ao sistema, precisamos criar colunas adicionais para armazenar o texto traduzido, assim:
CREATE TABLE T_PRODUCT ( NAME_EN NVARCHAR(50), NAME_DE NVARCHAR(50), NAME_SP NVARCHAR(50), DESCRIPTION_EN NTEXT, DESCRIPTION_DE NTEXT, DESCRIPTION_SP NTEXT, PRICE NUMBER(18,2) )
Tabela de tradução com colunas para cada idioma
Em vez de armazenar o texto traduzido, apenas uma chave estrangeira para a tabela de traduções é armazenada. A tabela de traduções contém uma coluna para cada idioma.
CREATE TABLE T_PRODUCT ( NAME_FK int, DESCRIPTION_FK int, PRICE NUMBER(18, 2) ) CREATE TABLE T_TRANSLATION ( TRANSLATION_ID, TEXT_EN NTEXT, TEXT_DE NTEXT, TEXT_SP NTEXT )
Tabelas de tradução com linhas para cada idioma
Em vez de armazenar o texto traduzido, apenas uma chave estrangeira para a tabela de traduções é armazenada. A tabela de traduções contém apenas uma chave e uma tabela separada contém uma linha para cada tradução para um idioma.
CREATE TABLE T_PRODUCT ( NAME_FK int, DESCRIPTION_FK int, PRICE NUMBER(18, 2) ) CREATE TABLE T_TRANSLATION ( TRANSLATION_ID ) CREATE TABLE T_TRANSLATION_ENTRY ( TRANSLATION_FK, LANGUAGE_FK, TRANSLATED_TEXT NTEXT ) CREATE TABLE T_TRANSLATION_LANGUAGE ( LANGUAGE_ID, LANGUAGE_CODE CHAR(2) )
Há prós e contras em cada solução, e eu gostaria de saber quais são suas experiências com essas abordagens, o que você recomenda e como você desenharia um esquema de banco de dados em vários idiomas.
LANGUAGE_CODE
são chave natural, eviteLANGUAGE_ID
.Respostas:
O que você acha de ter uma tabela de tradução relacionada para cada tabela traduzível?
Dessa forma, se você tiver várias colunas traduzíveis, será necessário apenas uma única associação para obtê-la +, uma vez que você não está gerando uma tradução automaticamente, pode ser mais fácil importar itens juntamente com suas traduções relacionadas.
O lado negativo disso é que, se você possui um mecanismo complexo de fallback de idioma, pode ser necessário implementá-lo para cada tabela de conversão - se você está contando com algum procedimento armazenado para fazer isso. Se você fizer isso no aplicativo, isso provavelmente não será um problema.
Deixe-me saber o que você pensa - também estou prestes a tomar uma decisão sobre isso para a nossa próxima aplicação. Até agora, usamos o seu terceiro tipo.
fonte
T_PRODUCT
tem 1 milhão de linhas,T_PRODUCT_tr
teria 2 milhões. Reduziria muito a eficiência do sql?Esta é uma questão interessante, então vamos necromance.
Vamos começar pelos problemas do método 1:
Problema: você está desnormalizando para economizar velocidade.
No SQL (exceto PostGreSQL com hstore), você não pode passar uma linguagem de parâmetro e dizer:
Então você tem que fazer isso:
O que significa que você deve alterar TODAS as suas consultas se adicionar um novo idioma. Isso naturalmente leva ao uso de "SQL dinâmico", para que você não precise alterar todas as suas consultas.
Isso geralmente resulta em algo assim (e não pode ser usado em exibições ou funções com valor de tabela, a propósito, o que realmente é um problema se você realmente precisar filtrar a data do relatório)
O problema é que:
a formatação da data é muito específica do idioma; portanto, você encontra um problema se não inserir no formato ISO (o que o programador comum de variedades de jardins geralmente não faz e, no caso de um relatório que o usuário com certeza não fará por você, mesmo que seja explicitamente instruído a fazê-lo).
e
b) de forma mais significativa , você perder qualquer tipo de verificação de sintaxe . Se
<insert name of your "favourite" person here>
alterar o esquema, porque de repente os requisitos para alteração de ala e uma nova tabela é criada, a antiga foi deixada, mas o campo de referência foi renomeado, você não recebe nenhum tipo de aviso. Um relatório até funciona quando você o executa sem selecionar o parâmetro wing (==> guid.empty). Mas de repente, quando um usuário real realmente seleciona uma asa ==>boom . Esse método viola completamente qualquer tipo de teste.Método 2:
Em poucas palavras: idéia "ótima" (aviso - sarcasmo), vamos combinar as desvantagens do método 3 (velocidade lenta em muitas entradas) com as horríveis desvantagens do método 1.
A única vantagem desse método é que você mantém toda a tradução em uma tabela e, portanto, simplifica a manutenção. No entanto, o mesmo pode ser alcançado com o método 1 e um procedimento dinâmico armazenado em SQL, além de uma tabela (possivelmente temporária) contendo as traduções e o nome da tabela de destino (e é bastante simples, assumindo que você nomeou todos os seus campos de texto como mesmo).
Método 3:
Uma tabela para todas as traduções: Desvantagem: você precisa armazenar n Chaves estrangeiras na tabela de produtos para os n campos que deseja traduzir. Portanto, você precisa fazer n junções para n campos. Quando a tabela de conversão é global, ela possui muitas entradas e as junções ficam lentas. Além disso, você sempre deve ingressar na tabela T_TRANSLATION n vezes para n campos. Isso é uma sobrecarga. Agora, o que você faz quando precisa acomodar traduções personalizadas por cliente? Você precisará adicionar outras junções 2x n em uma tabela adicional. Se você tiver que entrar, digamos 10 mesas, com 2x2xn = 4n junções adicionais, que confusão! Além disso, esse design torna possível usar a mesma tradução com 2 tabelas. Se eu alterar o nome do item em uma tabela, eu realmente quero alterar uma entrada em outra tabela também A CADA VEZ?
Além disso, você não pode mais excluir e reinserir a tabela, porque agora existem chaves estrangeiras NA (s) TABELA (S) DE PRODUTO (s) ... é claro que você pode omitir a configuração dos FKs e, em seguida,
<insert name of your "favourite" person here>
pode excluir a tabela e reinserir todas as entradas com newid () [ou especificando o ID na inserção, mas com a inserção de identidade desativada ], e isso levaria (e levará) a lixo de dados (e exceções de referência nula) muito em breve.Método 4 (não listado): Armazenando todos os idiomas em um campo XML no banco de dados. por exemplo
Em seguida, você pode obter o valor por XPath-Query no SQL, onde você pode colocar a variável de cadeia em
E você pode atualizar o valor assim:
Onde você pode substituir
/lang/de/...
por'.../' + @in_language + '/...'
Assim como o hstore PostGre, exceto que, devido à sobrecarga de analisar XML (em vez de ler uma entrada de uma matriz associativa no PG hstore), ele fica muito lento, mais a codificação xml torna muito doloroso ser útil.
Método 5 (conforme recomendado pelo SunWuKung, o que você deve escolher): Uma tabela de conversão para cada tabela "Produto". Isso significa uma linha por idioma e vários campos de "texto", portanto, requer apenas UMA (esquerda) junção em N campos. Em seguida, você pode adicionar facilmente um campo padrão na tabela "Produto", excluir e reinserir facilmente a tabela de conversão e criar uma segunda tabela para traduções personalizadas (sob demanda), que também podem ser excluídas. e reinsira), e você ainda terá todas as chaves estrangeiras.
Vamos fazer um exemplo para ver isso FUNCIONA:
Primeiro, crie as tabelas:
Em seguida, preencha os dados
E, em seguida, consulte os dados:
Se você é preguiçoso, também pode usar o ISO-TwoLetterName ('DE', 'EN' etc.) como chave primária da tabela de idiomas, e não precisa procurar o ID do idioma. Mas se você fizer isso, talvez queira usar a tag no idioma IETF , o que é melhor, porque você obtém de-CH e de-DE, que não são realmente os mesmos em termos de ortografia (s duplo em vez de ß em todos os lugares) , embora seja o mesmo idioma base. Isso é apenas um pequeno detalhe que pode ser importante para você, especialmente considerando que en-US e en-GB / en-CA / en-AU ou fr-FR / fr-CA têm problemas semelhantes.
Citação: não precisamos, fazemos apenas nosso software em inglês.
Resposta: Sim - mas qual?
De qualquer forma, se você usa um número inteiro, é flexível e pode alterar seu método posteriormente.
E você deve usar esse número inteiro, porque não há nada mais irritante, destrutivo e problemático do que um design de Db danificado.
Veja também RFC 5646 , ISO 639-2 ,
E, se você ainda está dizendo "nós" apenas fazemos nosso pedido para "apenas uma cultura" (como geralmente nos EUA) - portanto, não preciso desse número inteiro extra, esse seria um bom momento e um lugar para mencionar o Tags de idioma da IANA , não?
Porque eles são assim:
e
(houve uma reforma ortográfica em 1996 ...) Tente encontrar uma palavra em um dicionário se ela estiver incorreta; isso se torna muito importante em aplicativos que lidam com portais legais e de serviço público.
Mais importante, existem regiões que estão mudando de alfabetos cirílico para latino, o que pode ser mais problemático do que o incômodo superficial de alguma reforma obscura da ortografia, e é por isso que isso também pode ser uma consideração importante, dependendo do país em que você vive. De uma forma ou de outra, é melhor ter esse número inteiro lá, por via das dúvidas ...
Edit:
E adicionando
ON DELETE CASCADE
depoisvocê pode simplesmente dizer:
DELETE FROM T_Products
e não obter nenhuma violação de chave estrangeira.Quanto ao agrupamento, eu faria assim:
A) Tenha seu próprio DAL
B) Salve o nome do agrupamento desejado na tabela de idiomas
Você pode colocar os agrupamentos em sua própria tabela, por exemplo:
C) Tenha o nome do agrupamento disponível em suas informações de auth.user.language
D) Escreva seu SQL assim:
E) Em seguida, você pode fazer isso no seu DAL:
O que lhe dará essa consulta SQL perfeitamente composta
fonte
A terceira opção é a melhor, por alguns motivos:
-Adão
fonte
Veja este exemplo:
Eu acho que não há necessidade de explicar, a estrutura se descreve.
fonte
Eu normalmente usaria essa abordagem (não o sql real), isso corresponde à sua última opção.
Porque ter todos os textos traduzíveis em um só lugar facilita muito a manutenção. Às vezes, as traduções são terceirizadas para agências de tradução, dessa forma você pode enviar a eles apenas um grande arquivo de exportação e importá-lo novamente com a mesma facilidade.
fonte
Translation
tabela ou aTranslationItem.translationitemid
coluna?Antes de ir para detalhes e soluções técnicas, você deve parar por um minuto e fazer algumas perguntas sobre os requisitos. As respostas podem ter um enorme impacto na solução técnica. Exemplos de tais perguntas seriam:
- Todos os idiomas serão usados o tempo todo?
- Quem e quando preencherá as colunas com as diferentes versões de idiomas?
- O que acontece quando um usuário precisa de um determinado idioma de texto e não existe nenhum no sistema?
- Apenas os textos devem ser localizados ou também existem outros itens (por exemplo, PRICE pode ser armazenado em $ e € porque podem ser diferentes)
fonte
Eu estava procurando algumas dicas para localização e encontrei este tópico. Eu queria saber por que isso é usado:
Então você obtém algo como user39603 sugere:
Você não pode simplesmente deixar a tabela de fora para obter o seguinte:
fonte
ProductItem
mesa de algo parecidoProductTexts
ouProductL10n
não. Faz mais sentido.Eu concordo com o randomizador. Não vejo por que você precisa de uma "tradução" de tabela.
Eu acho que isso é suficiente:
fonte
A abordagem abaixo seria viável? Digamos que você tenha tabelas em que mais de uma coluna precisa ser traduzida. Portanto, para o produto, você pode ter o nome e a descrição do produto que precisam ser traduzidos. Você poderia fazer o seguinte:
fonte
"Qual é o melhor" é baseado na situação do projeto. O primeiro é fácil de selecionar e manter, e também o melhor desempenho, pois não é necessário ingressar nas tabelas quando a entidade é selecionada. Se você confirmou que seu projeto é compatível apenas com 2 ou 3 idiomas e não aumenta, você pode usá-lo.
O segundo é ok, mas é difícil de entender e manter. E o desempenho é pior que o primeiro.
O último é bom em escalabilidade, mas ruim em desempenho. A tabela T_TRANSLATION_ENTRY se tornará cada vez maior; é terrível quando você deseja recuperar uma lista de entidades de algumas tabelas.
fonte
Este documento descreve as possíveis soluções e as vantagens e desvantagens de cada método. Prefiro a "localização da linha" porque você não precisa modificar o esquema do banco de dados ao adicionar um novo idioma.
fonte