Domanda

Sto sviluppando un software multilingue. Per quanto riguarda il codice dell'applicazione, la localizzazione non è un problema. Siamo in grado di utilizzare risorse specifiche della lingua e avere tutti i tipi di strumenti che funzionano bene con loro.

Ma qual è l'approccio migliore per definire uno schema di database multilingue? Diciamo che abbiamo molte tabelle (100 o più) e ogni tabella può avere più colonne che possono essere localizzate (la maggior parte delle colonne nvarchar dovrebbe essere localizzabile). Ad esempio, una delle tabelle potrebbe contenere informazioni sul prodotto:

CREATE TABLE T_PRODUCT (
  NAME        NVARCHAR(50),
  DESCRIPTION NTEXT,
  PRICE       NUMBER(18, 2)
)

Mi vengono in mente tre approcci per supportare il testo multilingue nelle colonne NOME e DESCRIZIONE:

  1. Colonna separata per ogni lingua

    Quando aggiungiamo una nuova lingua al sistema, dobbiamo creare colonne aggiuntive per memorizzare il testo tradotto, in questo modo:

    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. Tabella di traduzione con colonne per ogni lingua

    Invece di memorizzare il testo tradotto, viene memorizzata solo una chiave esterna nella tabella delle traduzioni. La tabella delle traduzioni contiene una colonna per ogni lingua.

    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. Tabelle di traduzione con righe per ogni lingua

    Invece di memorizzare il testo tradotto, viene memorizzata solo una chiave esterna nella tabella delle traduzioni. La tabella delle traduzioni contiene solo una chiave e una tabella separata contiene una riga per ogni traduzione in una lingua.

    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)
    )
    

Ci sono pro e contro in ogni soluzione e vorrei sapere quali sono le tue esperienze con questi approcci, cosa consigli e come potresti progettare uno schema di database multilingue.

È stato utile?

Soluzione

Cosa pensi di avere una tabella di traduzione correlata per ogni tabella traducibile?

  

CREA TABELLA T_PRODUCT (pr_id int, NUMERO DI PREZZO (18, 2))

     

CREATE TABLE T_PRODUCT_tr (pr_id INT FK, languagecode varchar, pr_name text, pr_descr text)

In questo modo, se si dispone di più colonne traducibili, per ottenerlo è necessario un solo join + poiché non si sta generando automaticamente un traduttore, potrebbe essere più semplice importare elementi insieme alle loro traduzioni correlate.

Il lato negativo di questo è che se si dispone di un meccanismo di fallback del linguaggio complesso, potrebbe essere necessario implementarlo per ogni tabella di traduzione, se si fa affidamento su una procedura memorizzata per farlo. Se lo fai dall'app, questo probabilmente non sarà un problema.

Fammi sapere cosa ne pensi - Sto anche per prendere una decisione su questo per la nostra prossima applicazione. Finora abbiamo usato il tuo terzo tipo.

Altri suggerimenti

Questo è un problema interessante, quindi andiamo a negromanzia.

Cominciamo dai problemi del metodo 1:
Problema: stai denormalizzando per risparmiare velocità.
In SQL (tranne PostGreSQL con hstore), non puoi passare un linguaggio di parametri e dire:

SELECT ['DESCRIPTION_' + @in_language]  FROM T_Products

Quindi devi farlo:

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 

Il che significa che devi modificare TUTTE le tue query se aggiungi una nuova lingua. Questo porta naturalmente all'utilizzo di & Quot; SQL dinamico & Quot ;, quindi non è necessario modificare tutte le query.

Questo di solito si traduce in qualcosa del genere (e non può essere utilizzato nelle viste o nelle funzioni con valori di tabella a proposito, il che è davvero un problema se devi effettivamente filtrare la data del rapporto)

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

Il problema con questo è
a) La formattazione della data è molto specifica della lingua, quindi c'è un problema lì, se non si inserisce in formato ISO (cosa che di solito non fa il programmatore medio di varietà da giardino, e in caso di una segnalazione l'utente è sicuro come l'inferno non farà per te, anche se esplicitamente incaricato di farlo).
e
b) nel modo più significativo , perdi qualsiasi tipo di controllo della sintassi . Se <insert name of your "favourite" person here> altera lo schema perché all'improvviso cambiano i requisiti per l'ala e viene creata una nuova tabella, quella vecchia rimane ma il campo di riferimento viene rinominato, non viene visualizzato alcun tipo di avviso. Un report funziona anche quando lo esegui senza selezionare il parametro wing (== & Gt; guid.empty). Ma improvvisamente, quando un utente reale seleziona effettivamente un'ala == & Gt; braccio . Questo metodo interrompe completamente qualsiasi tipo di test.


Metodo 2:
In breve: & Quot; Great & Quot; idea (attenzione - sarcasmo), combiniamo gli svantaggi del metodo 3 (bassa velocità quando molte voci) con gli svantaggi piuttosto orribili del metodo 1.
L'unico vantaggio di questo metodo è di mantenere tutte le traduzioni in una tabella e quindi semplificare la manutenzione. Tuttavia, la stessa cosa può essere raggiunta con il metodo 1 e una stored procedure SQL dinamica e una tabella (possibilmente temporanea) contenente le traduzioni e il nome della tabella di destinazione (ed è abbastanza semplice supponendo che tu abbia chiamato tutti i tuoi campi di testo il stessa).


Metodo 3:
Una tabella per tutte le traduzioni: Svantaggio: Devi memorizzare n chiavi esterne nella tabella dei prodotti per n campi che vuoi tradurre. Pertanto, devi fare n join per n campi. Quando la tabella di traduzione è globale, ha molte voci e i join diventano lenti. Inoltre, devi sempre unirti alla tabella T_TRANSLATION n volte per n campi. Questo è piuttosto un sovraccarico. Ora, cosa fai quando devi inserire traduzioni personalizzate per cliente? Dovrai aggiungere altri 2x n join su una tabella aggiuntiva. Se devi unirti, diciamo 10 tavoli, con 2x2xn = 4n join aggiuntivi, che casino! Inoltre, questo design consente di utilizzare la stessa traduzione con 2 tabelle. Se cambio il nome dell'articolo in una tabella, voglio davvero cambiare anche una voce in un'altra tabella OGNI SINGOLO?

Inoltre non puoi più eliminare e reinserire la tabella, perché ora ci sono chiavi esterne nella TABELLA (E) DEL PRODOTTO ... puoi ovviamente omettere di impostare gli FK, e quindi /lang/de/... puoi eliminare il tabella e reinserire tutte le voci con newid () [o specificando l'id nell'inserimento, ma avendo inserimento identità disattivato ], e ciò sarebbe (e sarà ) porta a dati-garbage (e eccezioni riferimento null) molto presto.


Metodo 4 (non elencato): Memorizzare tutte le lingue in un campo XML nel database. per esempio

-- 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 

Quindi puoi ottenere il valore da XPath-Query in SQL, dove puoi inserire la variabile stringa in

filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla

E puoi aggiornare il valore in questo modo:

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 

Dove puoi sostituire '.../' + @in_language + '/...' con ON DELETE CASCADE

Un po 'come l'hstore di PostGre, tranne che a causa del sovraccarico di analisi XML (invece di leggere una voce da un array associativo in PG hstore) diventa troppo lento e la codifica XML rende troppo doloroso essere utile.


Metodo 5 (come raccomandato da SunWuKung, quello che dovresti scegliere): Una tabella di traduzione per ogni & Quot; Prodotto & Quot; tavolo. Ciò significa una riga per lingua e diversi & Quot; text & Quot; campi, quindi richiede solo UN (a sinistra) join su N campi. Quindi puoi facilmente aggiungere un campo predefinito nella tabella & Quot; Prodotto & Quot; puoi facilmente cancellare e reinserire la tabella di traduzione, e puoi creare una seconda tabella per traduzioni personalizzate (su domanda), che puoi anche eliminare e reinserire) e hai ancora tutte le chiavi esterne.

Facciamo un esempio per vedere queste OPERE:

Innanzitutto, crea le tabelle:

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

Quindi inserire i dati

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 poi interroga i dati:

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 sei pigro, puoi anche usare ISO-TwoLetterName ('DE', 'EN', ecc.) come chiave primaria della tabella delle lingue, quindi non devi cercare l'ID lingua . Ma se lo fai, potresti voler usare il tag in lingua IETF invece, il che è meglio, perché ottieni de-CH e de-DE, che in realtà non è la stessa ortografia (doppi invece di & # 223; ovunque), sebbene sia lo stesso linguaggio di base. Questo è solo un piccolo dettaglio che può essere importante per te, specialmente considerando che en-US / en-GB / en-CA / en-AU o fr-FR / fr-CA hanno problemi simili.
Citazione: non ne abbiamo bisogno, facciamo il nostro software solo in inglese.
Risposta: Sì, ma quale ??

Comunque, se usi un ID intero, sei flessibile e puoi cambiare il tuo metodo in qualsiasi momento successivo.
E dovresti usare quell'intero, perché non c'è niente di più fastidioso, distruttivo e problematico di un design Db pasticciato.

Vedi anche RFC 5646 , ISO 639-2 ,

E, se stai ancora dicendo " we " solo presenta la nostra domanda per " solo una cultura " (come di solito en-US) - quindi non ho bisogno di quel numero intero in più, questo sarebbe un buon momento e un posto per menzionare tag lingua IANA , no?
Perché vanno così:

de-DE-1901
de-DE-1996

e

de-CH-1901
de-CH-1996

(c'è stata una riforma dell'ortografia nel 1996 ...) Prova a trovare una parola in un dizionario se è errata; questo diventa molto importante nelle applicazioni che si occupano di portali legali e di servizio pubblico.
Ancora più importante, ci sono regioni che stanno cambiando da alfabeti cirillici a latini, che potrebbero essere solo più fastidiosi del fastidio superficiale di qualche oscura riforma dell'ortografia, motivo per cui questa potrebbe essere anche una considerazione importante, a seconda del paese in cui vivi. In un modo o nell'altro, è meglio avere quell'intero lì dentro, nel caso ...

Modifica
E aggiungendo DELETE FROM T_Products dopo

REFERENCES dbo.T_Products( PROD_Id )

puoi semplicemente dire: <=> e non ottenere alcuna violazione di chiave esterna.

Per quanto riguarda le regole di confronto, lo farei in questo modo:

A) Avere il tuo DAL
B) Salvare il nome della fascicolazione desiderata nella tabella delle lingue

Potresti voler mettere le regole di confronto nella propria tabella, ad esempio:

SELECT * FROM sys.fn_helpcollations() 
WHERE description LIKE '%insensitive%'
AND name LIKE '%german%' 

C) Rendi disponibile il nome della collation nelle tue informazioni auth.user.language

D) Scrivi il tuo SQL in questo modo:

SELECT 
    COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName 
FROM T_Groups 

ORDER BY GroupName COLLATE {#COLLATION}

E) Quindi, puoi farlo nel DAL:

cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)

Che ti darà quindi questa query SQL perfettamente composta

SELECT 
    COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName 
FROM T_Groups 

ORDER BY GroupName COLLATE German_PhoneBook_CI_AI

La terza opzione è la migliore, per alcuni motivi:

  • Non richiede la modifica dello schema del database per le nuove lingue (e quindi la limitazione delle modifiche al codice)
  • Non richiede molto spazio per le lingue non implementate o le traduzioni di un particolare elemento
  • Offre la massima flessibilità
  • Non si finisce con tabelle sparse
  • Non devi preoccuparti delle chiavi null e verificare che stai visualizzando una traduzione esistente anziché una voce null.
  • Se modifichi o espandi il tuo database per includere altri oggetti / cose traducibili / ecc. puoi usare le stesse tabelle e lo stesso sistema - questo è molto disaccoppiato dal resto dei dati.

-Adam

Dai un'occhiata a questo esempio:

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)
)

Penso che non sia necessario spiegare, la struttura si descrive da sola.

Di solito preferirei questo approccio (non l'attuale sql), questo corrisponde alla tua ultima opzione.

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'

Perché avere tutti i testi traducibili in un unico posto rende la manutenzione molto più semplice. A volte le traduzioni vengono esternalizzate agli uffici di traduzione, in questo modo è possibile inviare loro solo un grosso file di esportazione e importarlo altrettanto facilmente.

Prima di andare ai dettagli tecnici e alle soluzioni, dovresti fermarti un minuto e porre alcune domande sui requisiti. Le risposte possono avere un impatto enorme sulla soluzione tecnica. Esempi di tali domande sarebbero:
  - Saranno utilizzate tutte le lingue in qualsiasi momento?
  - Chi e quando riempirà le colonne con le diverse versioni linguistiche?
  - Cosa succede quando un utente avrà bisogno di una determinata lingua di un testo e non ce n'è nel sistema?
  - Solo i testi devono essere localizzati o ci sono anche altri elementi (ad esempio PREZZO può essere memorizzato in $ e & # 8364; perché potrebbero essere diversi)

Stavo cercando alcuni suggerimenti per la localizzazione e ho trovato questo argomento. Mi chiedevo perché questo è usato:

CREATE TABLE T_TRANSLATION (
   TRANSLATION_ID
)

Quindi ottieni qualcosa come suggerisce user39603:

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'

Non puoi semplicemente lasciare fuori la traduzione del tavolo in modo da ottenere questo:

    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'

Sono d'accordo con randomizer. Non vedo perché hai bisogno di una tabella & Quot; traduzione & Quot ;.

Penso che sia abbastanza:

TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName

L'approccio di seguito sarebbe praticabile? Supponi di avere tabelle in cui è necessario tradurre più di 1 colonna. Quindi per il prodotto potresti avere entrambi i nomi di prodotto & Amp; descrizione del prodotto che deve essere tradotta. Potresti fare quanto segue:

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 è il migliore " si basa sulla situazione del progetto. Il primo è facile da selezionare e mantenere, e anche le prestazioni sono le migliori poiché non è necessario unire le tabelle quando si seleziona un'entità. Se hai confermato che il tuo supporto supporta solo 2 o 3 lingue e che non aumenterà, puoi usarlo.

Il secondo è okey ma è difficile da capire e mantenere. E la performance è peggiore della prima.

L'ultimo è buono per la scalabilità ma cattivo per le prestazioni. La tabella T_TRANSLATION_ENTRY diventerà sempre più grande, è terribile quando vuoi recuperare un elenco di entità da alcune tabelle.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top