Esquema para um banco de dados multilíngüe
-
11-07-2019 - |
Pergunta
Estou desenvolvendo um software multilíngüe. Quanto ao código do aplicativo vai, localizability não é um problema. Podemos usar recursos específicos do idioma e ter todos os tipos de ferramentas que funcionam bem com eles.
Mas o que é a melhor abordagem na definição de um esquema de banco de dados de vários idiomas? Vamos dizer que temos um monte de mesas (100 ou mais), e cada tabela pode ter várias colunas que podem ser localizados (na 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)
)
Não consigo pensar em três abordagens para apoiar texto multilíngüe em nome e descrição colunas:
-
coluna separada para cada idioma
Quando adicionar um novo idioma para o sistema, devemos criar colunas adicionais para armazenar o texto traduzido, como este:
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 texto traduzido, apenas uma chave estrangeira para a tabela de traduções são armazenados. A tabela 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 texto traduzido, apenas uma chave estrangeira para a tabela de traduções são armazenados. A tabela traduções contém apenas uma chave e uma tabela separada contém uma linha para cada tradução de uma língua.
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 de cada solução, e eu gostaria de saber quais são suas experiências com estas abordagens, o que você recomendaria e como você iria sobre a criação de um esquema de banco de dados de vários idiomas.
Solução
O que você acha sobre ter uma tabela de conversão relacionada para cada tabela traduzível?
CREATE TABLE T_PRODUCT (int pr_id, NÚMERO PREÇO (18, 2))
CREATE TABLE T_PRODUCT_tr (pr_id INT FK, varchar languagecode, texto pr_name, texto pr_descr)
Desta forma, se você tem coluna traduzível múltipla seria apenas exigem um único juntar-se para obtê-lo + desde que você não está autogenerating um translationid pode ser mais fácil para itens de importação junto com suas traduções relacionados.
O lado negativo disso é que se você tem um mecanismo de idioma de fallback complexo pode ser necessário implementar que para cada tabela de tradução - se você está confiando em algum procedimento armazenado para fazer isso. Se você fizer isso a partir do app isso não irá provavelmente ser um problema.
Deixe-me saber o que você pensa - eu também sou prestes a tomar uma decisão sobre isso para a nossa próxima aplicação. Até agora temos usado o seu tipo 3.
Outras dicas
Esta é uma questão interessante, para Necromance do let.
Vamos começar pelos problemas do método 1:
Problema: Você está desnormalizar para salvar velocidade
.
Em SQL (exceto PostgreSQL com o hstore), você não pode passar uma linguagem de parâmetro, e dizer:
SELECT ['DESCRIPTION_' + @in_language] FROM T_Products
Assim que você tem para fazer isso:
SELECT
Product_UID
,
CASE @in_language
WHEN 'DE' THEN DESCRIPTION_DE
WHEN 'SP' THEN DESCRIPTION_SP
ELSE DESCRIPTION_EN
END AS Text
FROM T_Products
O que significa que você tem que alterar todas as suas dúvidas se você adicionar um novo idioma. Isto naturalmente leva ao uso de "SQL dinâmico", para que você não tem que alterar todas as suas consultas.
Isso geralmente resulta em algo como isto (e não pode ser usado em vistas ou funções com valor de tabela pelo caminho, o que realmente é um problema se você realmente precisa para filtrar a data de relatório)
CREATE PROCEDURE [dbo].[sp_RPT_DATA_BadExample]
@in_mandant varchar(3)
,@in_language varchar(2)
,@in_building varchar(36)
,@in_wing varchar(36)
,@in_reportingdate varchar(50)
AS
BEGIN
DECLARE @sql varchar(MAX), @reportingdate datetime
-- Abrunden des Eingabedatums auf 00:00:00 Uhr
SET @reportingdate = CONVERT( datetime, @in_reportingdate)
SET @reportingdate = CAST(FLOOR(CAST(@reportingdate AS float)) AS datetime)
SET @in_reportingdate = CONVERT(varchar(50), @reportingdate)
SET NOCOUNT ON;
SET @sql='SELECT
Building_Nr AS RPT_Building_Number
,Building_Name AS RPT_Building_Name
,FloorType_Lang_' + @in_language + ' AS RPT_FloorType
,Wing_No AS RPT_Wing_Number
,Wing_Name AS RPT_Wing_Name
,Room_No AS RPT_Room_Number
,Room_Name AS RPT_Room_Name
FROM V_Whatever
WHERE SO_MDT_ID = ''' + @in_mandant + '''
AND
(
''' + @in_reportingdate + ''' BETWEEN CAST(FLOOR(CAST(Room_DateFrom AS float)) AS datetime) AND Room_DateTo
OR Room_DateFrom IS NULL
OR Room_DateTo IS NULL
)
'
IF @in_building <> '00000000-0000-0000-0000-000000000000' SET @sql=@sql + 'AND (Building_UID = ''' + @in_building + ''') '
IF @in_wing <> '00000000-0000-0000-0000-000000000000' SET @sql=@sql + 'AND (Wing_UID = ''' + @in_wing + ''') '
EXECUTE (@sql)
END
GO
O problema com isto é
a) Data-formatação é específico do idioma muito, de modo a obter um problema lá, se você não faz entrada no formato ISO (que o programador médio jardim-variedade não costuma fazer, e no caso de um relatório que o usuário certo como o inferno não vai fazer por você, mesmo se explicitamente instruído a fazê-lo).
e
b) mais significativamente , você perder qualquer tipo de verificação de sintaxe . Se altera <insert name of your "favourite" person here>
o esquema porque de repente os requisitos para a mudança asa, e um uma nova tabela é criada, a antiga esquerda, mas o campo de referência renomeado, você não obter qualquer tipo de aviso. Um relatório funciona até mesmo quando você executá-lo sem selecionar a asa parâmetro (==> guid.empty). Mas, de repente, quando um usuário real, na verdade, seleciona uma asa ==> crescimento . Este método breakes completamente qualquer tipo de teste.
Método 2:
Em poucas palavras: "Grande" idéia (aviso - sarcasmo), vamos combinar as desvantagens do método 3 (velocidade lenta quando muitas entradas) com as desvantagens bastante horríveis de método 1.
A única vantagem deste método é que você mantenha tudo de tradução em uma tabela, e, portanto, fazer a manutenção simples. No entanto, a mesma coisa pode ser alcançado com o método 1 e um procedimento armazenado SQL dinâmico, e uma mesa (possivelmente temporária) contendo as traduções, eo nome da tabela de destino (e é bastante simples supondo que você nomeou todo o seu texto-campos do mesmo).
Método 3:
Uma mesa para todas as traduções:
Desvantagem:
Você tem que Foreign Keys loja n na tabela de produtos para n campos que você deseja traduzir.
Portanto, você tem que fazer n se junta para n campos.
Quando a tabela de tradução é global, tem muitas entradas, e junta-se ficar lento.
Além disso, você sempre tem que unir a tabela T_TRANSLATION n vezes para n campos.
Este é um grande sobrecarga.
Agora, o que você faz quando você deve acomodar traduções personalizados por cliente?
Você vai ter que adicionar outro 2x n se junta em uma tabela adicional.
Se você tem que juntar, digamos, 10 mesas, com junta 2x2xn = 4n adicional, que confusão!
Além disso, este projeto faz com que seja possível usar a mesma tradução com 2 mesas.
Se eu mudar o nome do item em uma tabela, eu realmente quero mudar uma entrada em outra tabela, bem Toda vez?
Além disso, você não pode excluir e re-inserir a tabela mais, porque agora existem chaves estrangeiras na tabela produto (s) ... você pode, naturalmente omitir definir os FKs, e depois <insert name of your "favourite" person here>
pode excluir a tabela, e re-inserção todas as entradas com newid () [ou especificando o id na inserção, mas tendo identidade-insert OFF ], e que (e vai) lead seria a data-lixo (e exceções nulo de referência) muito em breve.
Método 4 (não listados): Armazenar todas as línguas em um campo XML no banco de dados. por exemplo
-- CREATE TABLE MyTable(myfilename nvarchar(100) NULL, filemeta xml NULL )
;WITH CTE AS
(
-- INSERT INTO MyTable(myfilename, filemeta)
SELECT
'test.mp3' AS myfilename
--,CONVERT(XML, N'<?xml version="1.0" encoding="utf-16" standalone="yes"?><body>Hello</body>', 2)
--,CONVERT(XML, N'<?xml version="1.0" encoding="utf-16" standalone="yes"?><body><de>Hello</de></body>', 2)
,CONVERT(XML
, N'<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<lang>
<de>Deutsch</de>
<fr>Français</fr>
<it>Ital&iano</it>
<en>English</en>
</lang>
'
, 2
) AS filemeta
)
SELECT
myfilename
,filemeta
--,filemeta.value('body', 'nvarchar')
--, filemeta.value('.', 'nvarchar(MAX)')
,filemeta.value('(/lang//de/node())[1]', 'nvarchar(MAX)') AS DE
,filemeta.value('(/lang//fr/node())[1]', 'nvarchar(MAX)') AS FR
,filemeta.value('(/lang//it/node())[1]', 'nvarchar(MAX)') AS IT
,filemeta.value('(/lang//en/node())[1]', 'nvarchar(MAX)') AS EN
FROM CTE
Em seguida, você pode obter o valor de XPath-consulta em SQL, onde você pode colocar a string variável em
filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla
E você pode atualizar o valor como este:
UPDATE YOUR_TABLE
SET YOUR_XML_FIELD_NAME.modify('replace value of (/lang/de/text())[1] with ""I am a ''value ""')
WHERE id = 1
Onde você pode substituir /lang/de/...
com '.../' + @in_language + '/...'
Como o tipo de PostGre hstore, exceto que, devido à sobrecarga de analisar XML (em vez de ler uma entrada de uma matriz associativa em PG hstore) torna-se demasiado lento além da codificação XML torna doloroso demais para ser útil.
Método 5 (como recomendado pelo SunWuKung, o que você deve escolher): Uma tabela de conversão para cada tabela "Produto". Isso significa que uma linha por idioma, e vários campos "texto", por isso requer apenas um (esquerda) juntar-se em campos N. Então você pode facilmente adicionar um default-campo no "Produto" -table, você pode facilmente apagar e re-inserção a tabela de tradução, e você pode criar uma segunda tabela para Custom-traduções (on demand), que você também pode excluir e re-inserção), e você ainda tem todas as chaves estrangeiras.
Vamos fazer um exemplo para ver isso funciona:
Em primeiro lugar, criar as tabelas:
CREATE TABLE dbo.T_Languages
(
Lang_ID int NOT NULL
,Lang_NativeName national character varying(200) NULL
,Lang_EnglishName national character varying(200) NULL
,Lang_ISO_TwoLetterName character varying(10) NULL
,CONSTRAINT PK_T_Languages PRIMARY KEY ( Lang_ID )
);
GO
CREATE TABLE dbo.T_Products
(
PROD_Id int NOT NULL
,PROD_InternalName national character varying(255) NULL
,CONSTRAINT PK_T_Products PRIMARY KEY ( PROD_Id )
);
GO
CREATE TABLE dbo.T_Products_i18n
(
PROD_i18n_PROD_Id int NOT NULL
,PROD_i18n_Lang_Id int NOT NULL
,PROD_i18n_Text national character varying(200) NULL
,CONSTRAINT PK_T_Products_i18n PRIMARY KEY (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id)
);
GO
-- ALTER TABLE dbo.T_Products_i18n WITH NOCHECK ADD CONSTRAINT FK_T_Products_i18n_T_Products FOREIGN KEY(PROD_i18n_PROD_Id)
ALTER TABLE dbo.T_Products_i18n
ADD CONSTRAINT FK_T_Products_i18n_T_Products
FOREIGN KEY(PROD_i18n_PROD_Id)
REFERENCES dbo.T_Products (PROD_Id)
ON DELETE CASCADE
GO
ALTER TABLE dbo.T_Products_i18n CHECK CONSTRAINT FK_T_Products_i18n_T_Products
GO
ALTER TABLE dbo.T_Products_i18n
ADD CONSTRAINT FK_T_Products_i18n_T_Languages
FOREIGN KEY( PROD_i18n_Lang_Id )
REFERENCES dbo.T_Languages( Lang_ID )
ON DELETE CASCADE
GO
ALTER TABLE dbo.T_Products_i18n CHECK CONSTRAINT FK_T_Products_i18n_T_Products
GO
CREATE TABLE dbo.T_Products_i18n_Cust
(
PROD_i18n_Cust_PROD_Id int NOT NULL
,PROD_i18n_Cust_Lang_Id int NOT NULL
,PROD_i18n_Cust_Text national character varying(200) NULL
,CONSTRAINT PK_T_Products_i18n_Cust PRIMARY KEY ( PROD_i18n_Cust_PROD_Id, PROD_i18n_Cust_Lang_Id )
);
GO
ALTER TABLE dbo.T_Products_i18n_Cust
ADD CONSTRAINT FK_T_Products_i18n_Cust_T_Languages
FOREIGN KEY(PROD_i18n_Cust_Lang_Id)
REFERENCES dbo.T_Languages (Lang_ID)
ALTER TABLE dbo.T_Products_i18n_Cust CHECK CONSTRAINT FK_T_Products_i18n_Cust_T_Languages
GO
ALTER TABLE dbo.T_Products_i18n_Cust
ADD CONSTRAINT FK_T_Products_i18n_Cust_T_Products
FOREIGN KEY(PROD_i18n_Cust_PROD_Id)
REFERENCES dbo.T_Products (PROD_Id)
GO
ALTER TABLE dbo.T_Products_i18n_Cust CHECK CONSTRAINT FK_T_Products_i18n_Cust_T_Products
GO
Em seguida, preencha os dados
DELETE FROM T_Languages;
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (1, N'English', N'English', N'EN');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (2, N'Deutsch', N'German', N'DE');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (3, N'Français', N'French', N'FR');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (4, N'Italiano', N'Italian', N'IT');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (5, N'Russki', N'Russian', N'RU');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (6, N'Zhungwen', N'Chinese', N'ZH');
DELETE FROM T_Products;
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (1, N'Orange Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (2, N'Apple Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (3, N'Banana Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (4, N'Tomato Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (5, N'Generic Fruit Juice');
DELETE FROM T_Products_i18n;
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 1, N'Orange Juice');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 2, N'Orangensaft');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 3, N'Jus d''Orange');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 4, N'Succo d''arancia');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (2, 1, N'Apple Juice');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (2, 2, N'Apfelsaft');
DELETE FROM T_Products_i18n_Cust;
INSERT INTO T_Products_i18n_Cust (PROD_i18n_Cust_PROD_Id, PROD_i18n_Cust_Lang_Id, PROD_i18n_Cust_Text) VALUES (1, 2, N'Orangäsaft'); -- Swiss German, if you wonder
e depois consultar os dados:
DECLARE @__in_lang_id int
SET @__in_lang_id = (
SELECT Lang_ID
FROM T_Languages
WHERE Lang_ISO_TwoLetterName = 'DE'
)
SELECT
PROD_Id
,PROD_InternalName -- Default Fallback field (internal name/one language only setup), just in ResultSet for demo-purposes
,PROD_i18n_Text -- Translation text, just in ResultSet for demo-purposes
,PROD_i18n_Cust_Text -- Custom Translations (e.g. per customer) Just in ResultSet for demo-purposes
,COALESCE(PROD_i18n_Cust_Text, PROD_i18n_Text, PROD_InternalName) AS DisplayText -- What we actually want to show
FROM T_Products
LEFT JOIN T_Products_i18n
ON PROD_i18n_PROD_Id = T_Products.PROD_Id
AND PROD_i18n_Lang_Id = @__in_lang_id
LEFT JOIN T_Products_i18n_Cust
ON PROD_i18n_Cust_PROD_Id = T_Products.PROD_Id
AND PROD_i18n_Cust_Lang_Id = @__in_lang_id
Se você é preguiçoso, então você também pode usar o ISO-TwoLetterName ( 'DE', 'en', etc.) como de chave primária da tabela de linguagem, então você não tem que procurar o ID de idioma . Mas se você fizer isso, você talvez queira usar o IETF-language tag em vez disso, o que é melhor, porque você começa de-CH e de-dE, que não os mesmos ortografia-wise é realmente (s duplas em vez de ß em todos os lugares), embora seja a mesma base de língua. Que, assim como 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 tem problemas semelhantes.
Citação: não precisamos dele, nós só fazemos o nosso software em Inglês.
Resposta: Sim - mas qual deles ??
De qualquer forma, se você usar um ID de inteiro, você é flexível e pode mudar seu método em qualquer momento posterior.
E você deve usar esse inteiro, porque não há nada mais irritante, destrutivo e problemático do que um projeto Db remendada.
Veja também RFC 5646 , ISO 639-2 ,
E, se você ainda está dizendo "nós" única tornar o nosso pedido de "única um cultura" (como en-US geralmente) - portanto, I don' t necessidade que inteiro extra, isso seria um bom tempo e lugar para mencionar a tags de idioma IANA , não é?
Porque eles vão como este:
de-DE-1901
de-DE-1996
e
de-CH-1901
de-CH-1996
(houve uma reforma ortográfica em 1996 ...)
Tente encontrar uma palavra em um dicionário se ele está mal escrito; Isso se torna muito importante em aplicações que lidam com portais legais e de serviço público.
Mais importante, há regiões que estão mudando do cirílico para o alfabeto latino, que pode apenas ser mais problemático do que o incômodo superficial de alguma reforma ortografia obscura, que é por que isso pode ser uma consideração importante também, dependendo de qual país você vive dentro. uma forma ou de outra, é melhor ter esse inteiro lá, apenas no caso ...
Editar:
E adicionando ON DELETE CASCADE
depois
REFERENCES dbo.T_Products( PROD_Id )
você pode simplesmente dizer:. DELETE FROM T_Products
, e obter nenhuma violação de chave estrangeira
Como para agrupamento, eu faria assim:
A) Ter o seu próprio DAL
B) Guardar o nome de agrupamento desejado na tabela de linguagem
Você pode querer colocar os agrupamentos em sua própria mesa, por exemplo:.
SELECT * FROM sys.fn_helpcollations()
WHERE description LIKE '%insensitive%'
AND name LIKE '%german%'
C) Ter o nome de agrupamento disponíveis no seu informações auth.user.language
D) Escreva o seu SQL como esta:
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE {#COLLATION}
E) Então, você pode fazer isso no seu DAL:
cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)
O que vai te dar isso perfeitamente composta SQL-consulta ??p>
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE German_PhoneBook_CI_AI
A terceira opção é a melhor, por alguns motivos:
- Não necessita de alterar o esquema do banco de dados para novas línguas (e limitando assim as mudanças de código)
- Não requer muito espaço para as línguas não implementadas ou traduções de um determinado item
- Fornece mais flexibilidade
- Você não acabar com tabelas esparsas
- Você não precisa se preocupar com as chaves nulos e verificar se você está exibindo uma tradução existente, em vez de alguma entrada nula.
- Se você alterar ou expandir seu banco de dados para abranger outros itens traduzíveis / coisas / etc você pode usar as mesmas tabelas e sistema -. Isto é muito desacoplada do resto dos dados
-Adam
Dê uma olhada para este exemplo:
PRODUCTS (
id
price
created_at
)
LANGUAGES (
id
title
)
TRANSLATIONS (
id (// id of translation, UNIQUE)
language_id (// id of desired language)
table_name (// any table, in this case PRODUCTS)
item_id (// id of item in PRODUCTS)
field_name (// fields to be translated)
translation (// translation text goes here)
)
Eu acho que não há necessidade de explicar, a estrutura descreve a si mesma.
Normalmente, eu iria para essa abordagem (sql não real), isto corresponde com a sua última opção.
table Product
productid INT PK, price DECIMAL, translationid INT FK
table Translation
translationid INT PK
table TranslationItem
translationitemid INT PK, translationid INT FK, text VARCHAR, languagecode CHAR(2)
view ProductView
select * from Product
inner join Translation
inner join TranslationItem
where languagecode='en'
Porque ter todos os textos traduzíveis em um só lugar torna a manutenção muito mais fácil. Às vezes traduções são terceirizados para agências de tradução, desta forma você pode enviar-lhes apenas um grande arquivo de exportação e importá-lo novamente com a mesma facilidade.
Antes de ir para os detalhes técnicos e soluções, você deve parar por um minuto e fazer algumas perguntas sobre os requisitos. As respostas podem ter um enorme impacto sobre a solução técnica. Exemplos de tais perguntas seria:
- Será que todos os idiomas ser usado todo o tempo
?
- Quem e quando irá preencher as colunas com as diferentes versões linguísticas
?
- O que acontece quando um usuário vai precisar de um determinado idioma de um texto e não há ninguém no sistema
?
- Apenas os textos estão a ser localizada ou há também outros itens (por exemplo, o preço pode ser armazenado em $ e €, pois eles podem ser diferentes)
Eu estava procurando por algumas dicas para localização e encontrei este tópico. Eu queria saber por que isso é usado:
CREATE TABLE T_TRANSLATION (
TRANSLATION_ID
)
Assim que você começa algo como user39603 sugere:
table Product
productid INT PK, price DECIMAL, translationid INT FK
table Translation
translationid INT PK
table TranslationItem
translationitemid INT PK, translationid INT FK, text VARCHAR, languagecode CHAR(2)
view ProductView
select * from Product
inner join Translation
inner join TranslationItem
where languagecode='en'
Você não pode simplesmente deixar o Tradução mesa fora assim que você começar este:
table Product
productid INT PK, price DECIMAL
table ProductItem
productitemid INT PK, productid INT FK, text VARCHAR, languagecode CHAR(2)
view ProductView
select * from Product
inner join ProductItem
where languagecode='en'
Eu concordo com randomizer. Eu não vejo por que você precisa de uma "tradução" da tabela.
Eu acho, isso é suficiente:
TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName
O relator abaixo abordagem ser viável? Digamos que você tenha mesas onde mais de 1 necessidades coluna traduzindo. Assim, para o produto que você poderia ter tanto nome do produto e descrição do produto que precisam de tradução. você poderia fazer o seguinte:
CREATE TABLE translation_entry (
translation_id int,
language_id int,
table_name nvarchar(200),
table_column_name nvarchar(200),
table_row_id bigint,
translated_text ntext
)
CREATE TABLE translation_language (
id int,
language_code CHAR(2)
)
"Qual é o melhor" é baseada na situação do projeto. A primeira é fácil de selecionar e manter, e também o desempenho é melhor, uma vez que não precisa se juntar tabelas quando entidade select. Se você confirmou que o seu poject é apenas suportam apenas 2 ou 3 línguas, e não vai aumentar, você pode usá-lo.
O segundo é ok, mas é difícil de entender e manter. E o desempenho é pior do que primeiro.
A última é bom em escalabilidade, mas ruim no desempenho. A tabela T_TRANSLATION_ENTRY vai se tornar cada vez maior, é terrível quando você quiser recuperar uma lista de entidades de algumas tabelas.