Schema für eine mehrsprachige Datenbank
-
11-07-2019 - |
Frage
Ich entwickle eine mehrsprachige Software.Was den Anwendungscode betrifft, ist die Lokalisierbarkeit kein Problem.Wir können sprachspezifische Ressourcen nutzen und verfügen über alle Arten von Tools, die gut damit funktionieren.
Aber was ist der beste Ansatz zur Definition eines mehrsprachigen Datenbankschemas?Nehmen wir an, wir haben viele Tabellen (100 oder mehr) und jede Tabelle kann mehrere Spalten haben, die lokalisiert werden können (die meisten Nvarchar-Spalten sollten lokalisierbar sein).Eine der Tabellen könnte beispielsweise Produktinformationen enthalten:
CREATE TABLE T_PRODUCT (
NAME NVARCHAR(50),
DESCRIPTION NTEXT,
PRICE NUMBER(18, 2)
)
Ich kann mir drei Ansätze vorstellen, um mehrsprachigen Text in den Spalten NAME und BESCHREIBUNG zu unterstützen:
Separate Spalte für jede Sprache
Wenn wir dem System eine neue Sprache hinzufügen, müssen wir zusätzliche Spalten erstellen, um den übersetzten Text zu speichern, wie folgt:
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) )
Übersetzungstabelle mit Spalten für jede Sprache
Anstatt übersetzten Text zu speichern, wird nur ein Fremdschlüssel für die Übersetzungstabelle gespeichert.Die Übersetzungstabelle enthält eine Spalte für jede Sprache.
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 )
Übersetzungstabellen mit Zeilen für jede Sprache
Anstatt übersetzten Text zu speichern, wird nur ein Fremdschlüssel für die Übersetzungstabelle gespeichert.Die Übersetzungstabelle enthält nur einen Schlüssel und eine separate Tabelle enthält eine Zeile für jede Übersetzung in eine Sprache.
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) )
Jede Lösung hat Vor- und Nachteile, und ich würde gerne wissen, welche Erfahrungen Sie mit diesen Ansätzen gemacht haben, was Sie empfehlen und wie Sie beim Entwurf eines mehrsprachigen Datenbankschemas vorgehen würden.
Lösung
Was denken Sie über ein verwandtes Übersetzungstabelle für jede übersetzbar Tabelle mit?
CREATE TABLE T_PRODUCT (pr_id int, PRICE NUMBER (18, 2))
TABLE T_PRODUCT_tr CREATE (pr_id INT FK, Sprachcode varchar, pr_name Text, pr_descr Text)
Auf diese Weise, wenn Sie mehrere übersetzbar Spalte wäre es nur eine einzige beitreten erfordern es zu bekommen +, da Sie kein translationid sind autogenerating kann es einfacher sein, Elemente zu importieren sowie die dazugehörigen Übersetzungen.
Die negative Seite davon ist, dass, wenn Sie einen komplexen Sprache Ausweichmechanismus haben, können Sie, dass für jede Übersetzungstabelle implementieren müssen - wenn Sie auf einiger gespeicherte Prozedur setzen, das zu tun. Wenn Sie, dass aus der App tun dies wahrscheinlich kein Problem sein.
Lassen Sie mich wissen, was Sie denken - ich bin auch über eine Entscheidung über das für unsere nächste Anwendung zu machen. Bisher haben wir Ihren dritten Typen verwendet wird.
Andere Tipps
Das ist ein interessantes Thema, also lasst uns Nekromantie betreiben.
Beginnen wir mit den Problemen von Methode 1:
Problem:Sie denormalisieren, um Geschwindigkeit zu sparen.
In SQL (außer PostGreSQL mit hstore) können Sie keine Parametersprache übergeben und sagen:
SELECT ['DESCRIPTION_' + @in_language] FROM T_Products
Sie müssen also Folgendes tun:
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
Das bedeutet, dass Sie ALLE Ihre Abfragen ändern müssen, wenn Sie eine neue Sprache hinzufügen.Dies führt natürlich dazu, dass „dynamisches SQL“ verwendet wird, sodass Sie nicht alle Ihre Abfragen ändern müssen.
Dies führt normalerweise zu so etwas (und es kann übrigens nicht in Ansichten oder Tabellenwertfunktionen verwendet werden, was wirklich ein Problem darstellt, wenn Sie tatsächlich das Berichtsdatum filtern müssen)
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
Das Problem dabei ist
a) Die Datumsformatierung ist sehr sprachspezifisch, sodass Sie dort ein Problem bekommen, wenn Sie nicht im ISO-Format eingeben (was der durchschnittliche Gartenprogrammierer normalerweise nicht tut, und im Falle eines Berichts der Benutzer sicher). das geht verdammt noch mal nicht für dich, selbst wenn du ausdrücklich dazu aufgefordert wirst).
Und
B) am deutlichsten, Du jegliche Art der Syntaxprüfung verlieren.Wenn <insert name of your "favourite" person here>
Ändert das Schema, weil sich plötzlich die Anforderungen für Wing ändern und eine neue Tabelle erstellt wird, die alte übrig bleibt, aber das Referenzfeld umbenannt wird, erhalten Sie keinerlei Warnung.Ein Bericht funktioniert sogar wenn Sie es ausführen, ohne den Flügelparameter auszuwählen (==> guid.empty).Aber plötzlich, wenn ein tatsächlicher Benutzer tatsächlich einen Flügel auswählt ==> Boom. Diese Methode macht jegliche Art von Tests völlig zunichte.
Methode 2:
Kurzgesagt:„Tolle“ Idee (Achtung – Sarkasmus), kombinieren wir die Nachteile von Methode 3 (langsame Geschwindigkeit bei vielen Einträgen) mit den ziemlich schrecklichen Nachteilen von Methode 1.
Der einzige Vorteil dieser Methode besteht darin, dass Sie alle Übersetzungen in einer Tabelle speichern und somit die Wartung vereinfachen.Dasselbe kann jedoch mit Methode 1 und einer dynamischen gespeicherten SQL-Prozedur und einer (möglicherweise temporären) Tabelle erreicht werden, die die Übersetzungen und den Namen der Zieltabelle enthält (und das ist ganz einfach, vorausgesetzt, Sie haben alle Ihre Textfelder benannt). Dasselbe).
Methode 3:
Eine Tabelle für alle Übersetzungen:Nachteil:Sie müssen n Fremdschlüssel in der Produkttabelle für n Felder speichern, die Sie übersetzen möchten.Daher müssen Sie n Verknüpfungen für n Felder durchführen.Wenn die Übersetzungstabelle global ist, enthält sie viele Einträge und Verknüpfungen werden langsam.Außerdem müssen Sie die T_TRANSLATION-Tabelle immer n-mal für n Felder verknüpfen.Das ist ein ziemlicher Aufwand.Was tun Sie nun, wenn Sie individuelle Übersetzungen pro Kunde berücksichtigen müssen?Sie müssen einer zusätzlichen Tabelle weitere 2x n Joins hinzufügen.Wenn Sie beispielsweise 10 Tabellen mit 2x2xn = 4n zusätzlichen Verknüpfungen verbinden müssen, was für ein Durcheinander!Außerdem ermöglicht dieses Design die Verwendung derselben Übersetzung mit zwei Tabellen.Wenn ich den Elementnamen in einer Tabelle ändere, möchte ich dann wirklich JEDES MAL auch einen Eintrag in einer anderen Tabelle ändern?
Außerdem können Sie die Tabelle nicht mehr löschen und erneut einfügen, da sich jetzt Fremdschlüssel in den Produkttabellen befinden ...Sie können das Setzen der FKs natürlich weglassen, und dann <insert name of your "favourite" person here>
Mit können Sie die Tabelle löschen und alle Einträge wieder einfügen newid() [oder durch Angabe der ID in der Einfügung, aber mit Identitätseinfügung AUS], und das würde (und wird) sehr bald zu Datenmüll (und Null-Referenz-Ausnahmen) führen.
Methode 4 (nicht aufgeführt):Speichern aller Sprachen in einem XML-Feld in der Datenbank.z.B
-- 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
Dann können Sie den Wert per XPath-Query in SQL abrufen, wo Sie die String-Variable einfügen können
filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla
Und Sie können den Wert wie folgt aktualisieren:
UPDATE YOUR_TABLE
SET YOUR_XML_FIELD_NAME.modify('replace value of (/lang/de/text())[1] with ""I am a ''value ""')
WHERE id = 1
Wo Sie ersetzen können /lang/de/...
mit '.../' + @in_language + '/...'
Ein bisschen wie der PostGre-Hstore, nur dass er aufgrund des Mehraufwands beim Parsen von XML (anstatt einen Eintrag aus einem assoziativen Array im PG-Hstore zu lesen) viel zu langsam wird und die XML-Codierung es zu aufwändig macht, um nützlich zu sein.
Methode 5 (wie von SunWuKung empfohlen, die Sie wählen sollten):Eine Übersetzungstabelle für jede „Produkt“-Tabelle.Das bedeutet eine Zeile pro Sprache und mehrere „Text“-Felder, sodass nur EINE (linke) Verknüpfung für N Felder erforderlich ist.Dann können Sie ganz einfach ein Standardfeld in der „Produkt“-Tabelle hinzufügen, Sie können die Übersetzungstabelle einfach löschen und wieder einfügen und Sie können eine zweite Tabelle für benutzerdefinierte Übersetzungen (auf Anfrage) erstellen, die Sie auch löschen können und erneut einfügen), und Sie haben immer noch alle Fremdschlüssel.
Lassen Sie uns ein Beispiel erstellen, um zu sehen, dass dies funktioniert:
Erstellen Sie zunächst die Tabellen:
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
Geben Sie dann die Daten ein
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
Und dann die Daten abfragen:
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
Wenn Sie faul sind, können Sie auch den ISO-TwoLetterName ('DE', 'EN' usw.) als Primärschlüssel der Sprachtabelle verwenden, dann müssen Sie die Sprach-ID nicht nachschlagen.Aber wenn Sie dies tun, möchten Sie vielleicht das verwenden IETF-Sprach-Tag Stattdessen ist das besser, weil man de-CH und de-DE erhält, was ortographisch gesehen wirklich nicht dasselbe ist (doppeltes s statt ß überall), obwohl es sich um die gleiche Basissprache handelt.Das ist nur ein kleines Detail, das für Sie wichtig sein könnte, insbesondere wenn man bedenkt, dass es bei en-US und en-GB/en-CA/en-AU oder fr-FR/fr-CA ähnliche Probleme gibt.
Zitat:Wir brauchen es nicht, wir machen unsere Software nur auf Englisch.
Antwort:Ja – aber welches??
Wenn Sie jedoch eine Ganzzahl-ID verwenden, sind Sie flexibel und können Ihre Methode jederzeit später ändern.
Und Sie sollten diese Ganzzahl verwenden, denn nichts ist ärgerlicher, destruktiver und problematischer als ein verpfuschtes DB-Design.
Siehe auch RFC 5646, ISO 639-2,
Und wenn Sie immer noch „wir“ sagen nur Machen Sie unsere Bewerbung für „nur“. eins Kultur“ (wie normalerweise in den USA) – daher benötige ich diese zusätzliche Ganzzahl nicht, dies wäre ein guter Zeitpunkt und Ort, um das zu erwähnen IANA-Sprach-Tags, nicht wahr?
Denn sie gehen so:
de-DE-1901
de-DE-1996
Und
de-CH-1901
de-CH-1996
Dies wird bei Anwendungen, die sich mit juristischen und öffentlichen Dienstleistungsportalen befassen, sehr wichtig.
Noch wichtiger ist, dass es Regionen gibt, in denen von kyrillischen auf lateinische Alphabete umgestellt wird, was möglicherweise problematischer ist als das oberflächliche Ärgernis einer obskuren Rechtschreibreform, weshalb dies je nach Land, in dem Sie leben, ebenfalls eine wichtige Überlegung sein kann.Auf die eine oder andere Weise ist es besser, diese Ganzzahl dort zu haben, nur für den Fall ...
Bearbeiten:
Und indem man hinzufügt ON DELETE CASCADE
nach
REFERENCES dbo.T_Products( PROD_Id )
man kann einfach sagen: DELETE FROM T_Products
, und erhalten Sie keine Fremdschlüsselverletzung.
Was die Sortierung angeht, würde ich es so machen:
A) Haben Sie Ihr eigenes DAL
B) Speichern Sie den gewünschten Sortiernamen in der Sprachtabelle
Möglicherweise möchten Sie die Sortierungen in einer eigenen Tabelle ablegen, z. B.:
SELECT * FROM sys.fn_helpcollations()
WHERE description LIKE '%insensitive%'
AND name LIKE '%german%'
C) Halten Sie den Kollationsnamen in Ihren auth.user.Language-Informationen bereit
D) Schreiben Sie Ihr SQL wie folgt:
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE {#COLLATION}
E) Dann können Sie dies in Ihrem DAL tun:
cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)
Damit erhalten Sie dann diese perfekt komponierte SQL-Abfrage
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE German_PhoneBook_CI_AI
Die dritte Option ist die beste, für ein paar Gründe:
- Ist es erforderlich, nicht das Datenbankschema für neue Sprachen zu verändern (und somit Codeänderungen zu begrenzen)
- Erfordert nicht viel Platz für unimplemented Sprachen oder Übersetzungen eines ein bestimmtes Element
- bietet die Flexibilität
- Sie enden nicht mit spärlichen Tabellen bis
- Sie müssen nicht über null Tasten sorgen und prüfen, ob Sie eine vorhandene Übersetzung anstatt einige Nulleintrag sind angezeigt werden.
- Wenn Sie Ihre Datenbank ändern oder erweitern, um andere übersetzbar Artikel / Dinge umfassen / etc Sie die gleichen Tabellen und System verwenden können -. Das ist sehr abgekoppelt vom Rest der Daten
-Adam
Werfen Sie einen Blick für dieses Beispiel:
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)
)
Ich denke, es gibt keine Notwendigkeit zu erklären, die Struktur beschreibt sich selbst.
Ich würde in der Regel für diesen Ansatz gehen (nicht tatsächlich SQL), dies entspricht dem letzten Option.
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'
Weil an einem Ort alle übersetzbaren Texte mit Wartung macht so viel einfacher. Manchmal sind die Übersetzungen zu Übersetzungsbüros ausgelagert, auf diese Weise können Sie sie nur eine große Exportdatei senden können, und importieren Sie es wieder genauso einfach.
Vor den technischen Details und Lösungen, sollten Sie für eine Minute stoppen und ein paar Fragen zu den Anforderungen stellen. Die Antworten können einen großen Einfluss auf die technische Lösung. Beispiele für solche Fragen wären:
- Werden alle Sprachen die ganze Zeit verwendet werden
?
- Wer und wann werden die Spalten mit den verschiedenen Sprachversionen füllen
?
- Was passiert, wenn ein Benutzer eine bestimmte Sprache eines Textes benötigt, und es gibt keine im System
?
- Nur die Texte lokalisierten oder gibt es auch andere Elemente (zB PREIS in $ gespeichert werden kann und €, weil sie könnte anders sein)
Ich war auf der Suche nach ein paar Tipps für die Lokalisierung und dieses Thema gefunden. Ich frage mich, warum diese genutzt werden:
CREATE TABLE T_TRANSLATION (
TRANSLATION_ID
)
So erhalten Sie so etwas wie user39603 schlägt vor:
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'
Sie können nicht nur die Tabelle übersetzen lassen, so dass Sie diese:
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'
Ich bin mit randomizer. Ich sehe nicht, warum Sie eine Tabelle „Übersetzung“ benötigen.
Ich denke, das ist genug:
TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName
Würde der unten Ansatz lebensfähig sein? Sagen Sie Tabellen, wo mehr als 1 Spalte übersetzen muss. So zum Produkt könnten Sie beide Produktnamen und Produktbeschreibung, die übersetzen müssen. Könnten Sie Folgendes tun:
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)
)
„Welches ist am besten“ auf der Projektsituation basiert. Die erste ist einfach zu wählen und zu pflegen, und auch die Leistung ist am besten, da es Tabellen beitreten muß nicht, wann Objekt wählen. Wenn Sie bestätigt, dass Ihre poject nur gerade 2 oder 3 Sprachen unterstützen, und es wird nicht erhöhen, können Sie es verwenden.
Das zweite ist okey aber ist schwer zu verstehen und zu pflegen. Und die Leistung schlechter ist als erstes.
Der letzte ist gut Skalierbarkeit aber schlecht Leistung. Die T_TRANSLATION_ENTRY Tabelle wird größer und größer, es ist schrecklich, wenn Sie eine Liste von Personen aus einigen Tabellen abgerufen werden sollen.