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:

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

War es hilfreich?

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

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top