Schema per un database multilingue
-
11-07-2019 - |
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:
-
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) )
-
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 )
-
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.
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&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 ""I am a ''value ""')
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.