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:

  1. 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)
    )
    
  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
    )
    
  3. 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.

Foi útil?

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&amp;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 "&quot;I am a ''value &quot;"')
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

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top