Question

Je développe un logiciel multilingue. En ce qui concerne le code de l'application, la possibilité de localisation ne pose pas de problème. Nous pouvons utiliser des ressources spécifiques aux langues et disposer de toutes sortes d’outils qui fonctionnent bien avec elles.

Mais quelle est la meilleure approche pour définir un schéma de base de données multilingue? Supposons que nous avons beaucoup de tables (100 ou plus) et que chaque table peut avoir plusieurs colonnes pouvant être localisées (la plupart des colonnes de nvarchar doivent être localisables). Par exemple, l’une des tables peut contenir des informations sur le produit:

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

Je peux penser à trois approches pour prendre en charge le texte multilingue dans les colonnes NAME et DESCRIPTION:

  1. Colonne séparée pour chaque langue

    Lorsque nous ajoutons une nouvelle langue au système, nous devons créer des colonnes supplémentaires pour stocker le texte traduit, comme ceci:

    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. Table de traduction avec des colonnes pour chaque langue

    Au lieu de stocker le texte traduit, seule une clé étrangère de la table des traductions est stockée. Le tableau des traductions contient une colonne pour chaque langue.

    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. Tables de traduction avec des lignes pour chaque langue

    Au lieu de stocker le texte traduit, seule une clé étrangère de la table des traductions est stockée. La table des traductions contient uniquement une clé et une table séparée contient une ligne pour chaque traduction dans une langue.

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

Chaque solution présente des avantages et des inconvénients, et j'aimerais savoir quelles sont vos expériences avec ces approches, que recommandez-vous et comment procéderiez-vous pour concevoir un schéma de base de données multilingue.

Était-ce utile?

La solution

Que pensez-vous de la création d'une table de traduction associée pour chaque table à traduire?

  

CREATE TABLE T_PRODUCT (pr_id int, PRICE NUMBER (18, 2))

     

CREATE TABLE T_PRODUCT_tr (pr_id INT FK, code de langue varchar, texte pr_name, pr_descr texte)

De cette façon, si vous avez plusieurs colonnes à traduire, une seule jointure suffit pour l'obtenir +, car vous ne générez pas automatiquement un ID de traduction, il sera peut-être plus facile d'importer des éléments avec leurs traductions associées.

Le problème est que, si vous disposez d'un mécanisme de secours de langage complexe, vous devrez peut-être l'implémenter pour chaque table de traduction, si vous comptez sur une procédure stockée. Si vous le faites depuis l'application, ce ne sera probablement pas un problème.

Faites-moi savoir ce que vous pensez - je suis également sur le point de prendre une décision à ce sujet lors de notre prochaine application. Jusqu'à présent, nous avons utilisé votre troisième type.

Autres conseils

C’est une question intéressante, alors faisons de la nécromance.

Commençons par les problèmes de la méthode 1:
Problème: vous dénormalisez pour gagner de la vitesse.
En SQL (sauf PostGreSQL avec hstore), vous ne pouvez pas passer de langage de paramètre et dire:

SELECT ['DESCRIPTION_' + @in_language]  FROM T_Products

Vous devez donc faire ceci:

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 

Ce qui signifie que vous devez modifier TOUTES vos requêtes si vous ajoutez une nouvelle langue. Cela conduit naturellement à utiliser & Quot; SQL dynamique & Quot ;, afin que vous n'ayez pas à modifier toutes vos requêtes.

Cela aboutit généralement à quelque chose comme ceci (et cela ne peut pas être utilisé dans les vues ou les fonctions de table, d'ailleurs, ce qui pose vraiment un problème si vous avez réellement besoin de filtrer la date de rapport)

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

Le problème avec ceci est
a) Le formatage de la date est très spécifique à la langue. Vous rencontrez donc un problème si vous ne saisissez pas au format ISO (ce que le programmeur ordinaire ne fait généralement pas, et dans le cas d'un rapport, l'utilisateur sûr que diable ne fera pas pour vous, même si explicitement demandé de le faire).
et
b) plus précisément , vous perdez tout type de vérification de la syntaxe . Si <insert name of your "favourite" person here> modifie le schéma parce que les conditions requises pour l'aile changent soudainement et qu'une nouvelle table est créée, l'ancienne table étant laissée mais le champ de référence renommé, vous ne recevez aucun type d'avertissement. Un rapport fonctionne même lorsque vous l'exécutez sans sélectionner le paramètre d'aile (== & Gt; guid.empty). Mais tout à coup, lorsqu'un utilisateur sélectionne réellement une aile == & Gt; boum . Cette méthode annule complètement tout type de test.

Méthode 2:
En bref: & "Grand &"; idée (warning - sarcasme), combinons les inconvénients de la méthode 3 (vitesse lente lorsque plusieurs entrées) et les inconvénients plutôt horribles de la méthode 1.
Le seul avantage de cette méthode est que vous conservez toutes les traductions dans une seule table, ce qui simplifie la maintenance. Cependant, la même chose peut être obtenue avec la méthode 1 et une procédure stockée SQL dynamique, ainsi qu’une table (éventuellement temporaire) contenant les traductions, ainsi que le nom de la table cible (il est assez simple de supposer que vous avez nommé tous vos champs de texte le même).

Méthode 3:
Une table pour toutes les traductions: Désavantage: Vous devez stocker n clés étrangères dans la table des produits pour n champs à traduire. Par conséquent, vous devez faire n jointure pour n champs. Lorsque la table de traduction est globale, elle comporte de nombreuses entrées et les jointures deviennent lentes. En outre, vous devez toujours joindre la table T_TRANSLATION n fois pour n champs. Ceci est tout à fait une surcharge. Maintenant, que faites-vous lorsque vous devez adapter des traductions personnalisées par client? Vous devrez ajouter 2x n autres jointures sur une table supplémentaire. Si vous devez rejoindre, disons 10 tables, avec 2x2xn = 4n jointures supplémentaires, quel gâchis! De plus, cette conception permet d’utiliser la même traduction avec 2 tables. Si je change le nom de l’élément dans une table, est-ce que je veux vraiment changer une entrée dans une autre table? CHAQUE FOIS?

De plus, vous ne pouvez plus supprimer et réinsérer la table, car il existe maintenant des clés étrangères DANS LES TABLEAUX DES PRODUITS ... vous pouvez bien sûr omettre de configurer les FK, puis /lang/de/... pouvez supprimer les table et réinsérez toutes les entrées avec newid () [ou en spécifiant l'ID dans l'insert, mais en ayant identité-insert OFF ], et cela ) conduisent à des données-garbage (et des exceptions null-reference) très bientôt.


Méthode 4 (non répertoriée): Stocker toutes les langues dans un champ XML de la base de données. par exemple

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

Ensuite, vous pouvez obtenir la valeur par XPath-Query en SQL, où vous pouvez placer la variable chaîne dans

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

Et vous pouvez mettre à jour la valeur comme ceci:

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 

Où vous pouvez remplacer '.../' + @in_language + '/...' par ON DELETE CASCADE

Un peu comme le PostStore hstore, sauf qu'en raison de la surcharge de l'analyse XML (au lieu de lire une entrée d'un tableau associatif in PG hstore), il devient beaucoup trop lent et le codage xml le rend trop pénible pour être utile.


Méthode 5 (recommandée par SunWuKung, celle que vous devriez choisir): Une table de traduction pour chaque & Quot; Produit & Quot; table. Cela signifie une ligne par langue et plusieurs & Quot; text & Quot; champs, donc il ne nécessite que UN (gauche) rejoindre sur N champs. Ensuite, vous pouvez facilement ajouter un champ par défaut dans la table & "Produit &"; Vous pouvez facilement supprimer et réinsérer la table de traduction et vous pouvez créer une seconde table pour les traductions personnalisées (à la page suivante). demande), que vous pouvez également supprimer et réinsérer), et vous avez toujours toutes les clés étrangères.

Faisons un exemple pour voir cet OEUVRE:

Créez d'abord les tables:

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

Remplissez ensuite les données

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

Et interrogez ensuite les données:

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

Si vous êtes paresseux, vous pouvez également utiliser ISO-TwoLetterName ('DE', 'EN', etc.) comme clé primaire de la table des langues, vous n'avez pas besoin de rechercher l'ID de langue. . Mais si vous le faites, vous voudrez peut-être utiliser le balise IETF-language . au lieu de cela, ce qui est mieux, car vous obtenez de-CH et de-DE, ce qui n’est vraiment pas la même ortographie (double s au lieu de & # 223; partout), bien que ce soit le même langage de base. Cela n’est qu’un petit détail qui peut être important pour vous, d’autant plus en particulier qu’en-US et en-GB / en-CA / en-AU ou fr-FR / fr-CA ont des problèmes similaires.
Citation: nous n'en avons pas besoin, nous n'utilisons que notre logiciel en anglais.
Réponse: Oui - mais lequel ??

Quoi qu'il en soit, si vous utilisez un ID entier, vous êtes flexible et pouvez modifier votre méthode à tout moment.
Et vous devriez utiliser cet entier car il n’ya rien de plus ennuyant, de destructeur et de gênant qu’une conception Db bâclée.

Voir aussi RFC 5646 , ISO 639-2 ,

Et si vous dites toujours & "nous &"; uniquement soumettons notre candidature à & "seule une culture > &"; (comme en-US d'habitude) - donc je n'ai pas besoin de cet entier supplémentaire, ce serait un bon moment pour mentionner le Tags de langue IANA , n'est-ce pas?
Parce qu'ils vont comme ça:

de-DE-1901
de-DE-1996

et

de-CH-1901
de-CH-1996

(il y a eu une réforme de l'orthographe en 1996 ...) Essayez de trouver un mot dans un dictionnaire s'il est mal orthographié. cela devient très important dans les applications traitant de portails de services publics et légaux.
Plus important encore, il existe des régions qui passent de l'alphabet latin à l'alphabet cyrillique, ce qui risque d'être plus gênant que la nuisance superficielle d'une obscure réforme de l'orthographe. C'est pourquoi il pourrait s'agir également d'une considération importante, selon le pays dans lequel vous vivez. D'une manière ou d'une autre, il vaut mieux avoir cet entier, juste au cas où ...

Modifier:
Et en ajoutant DELETE FROM T_Products après

REFERENCES dbo.T_Products( PROD_Id )

vous pouvez simplement dire: <=> et n'obtenez aucune violation de clé étrangère.

En ce qui concerne la collation, je le ferais comme ceci:

A) Avoir votre propre DAL
B) Enregistrez le nom de classement souhaité dans la table de langues

Vous pouvez mettre les classements dans leur propre tableau, par exemple:

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

C) Ayez le nom du classement disponible dans vos informations auth.user.language

D) Écrivez votre code SQL comme ceci:

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

ORDER BY GroupName COLLATE {#COLLATION}

E) Ensuite, vous pouvez le faire dans votre DAL:

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

Ce qui vous donnera alors cette requête SQL parfaitement composée

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 troisième option est la meilleure, pour plusieurs raisons:

  • Ne nécessite pas de modifier le schéma de base de données pour les nouvelles langues (et donc limiter les modifications de code)
  • Ne nécessite pas beaucoup d'espace pour les langues non implémentées ou les traductions d'un élément particulier
  • offre le plus de flexibilité
  • Vous ne vous retrouvez pas avec des tables clairsemées
  • Vous n'avez pas à vous soucier des clés nulles et à vérifier que vous affichez une traduction existante au lieu d'une entrée nulle.
  • Si vous modifiez ou développez votre base de données pour englober d'autres éléments / éléments à traduire / etc, vous pouvez utiliser les mêmes tables et le même système, ce qui est très dissocié du reste des données.

-Adam

Jetez un coup d'œil à cet exemple:

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

Je pense qu'il n'y a pas besoin d'expliquer, la structure se décrit elle-même.

Je choisirais généralement cette approche (pas le sql actuel), cela correspond à votre dernière 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'

Parce que la présence de tous les textes traduisibles au même endroit facilite grandement la maintenance. Parfois, les traductions sont sous-traitées à des bureaux de traduction. Vous pouvez ainsi leur envoyer un gros fichier d'exportation et le réimporter tout aussi facilement.

Avant d’entrer dans les détails techniques et les solutions, arrêtez-vous une minute et posez quelques questions sur les conditions requises. Les réponses peuvent avoir un impact énorme sur la solution technique. Des exemples de telles questions seraient:
  - Toutes les langues seront-elles utilisées tout le temps?
  - Qui et quand remplira les colonnes avec les différentes versions linguistiques?
  - Que se passe-t-il lorsqu'un utilisateur a besoin d'une certaine langue d'un texte alors qu'il n'y en a pas dans le système?
  - Seuls les textes doivent être localisés ou il existe également d'autres éléments (par exemple, PRICE peut être stocké dans $ et & # 8364; car ils pourraient être différents)

Je recherchais des astuces pour la localisation et trouvai ce sujet. Je me demandais pourquoi cela est utilisé:

CREATE TABLE T_TRANSLATION (
   TRANSLATION_ID
)

Vous obtenez donc quelque chose comme User39603 suggère:

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'

Ne pouvez-vous pas simplement quitter la table Traduction afin que vous obteniez ceci:

    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'

Je suis d'accord avec le randomiseur. Je ne vois pas pourquoi vous avez besoin d'un tableau & "; Traduction &";

Je pense que cela suffit:

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

L’approche ci-dessous serait-elle viable? Supposons que vous ayez des tables dans lesquelles plus d'une colonne doit être traduite. Donc, pour le produit, vous pouvez avoir à la fois le nom du produit & Amp; description du produit à traduire. Pourriez-vous faire ce qui suit:

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

& "Quel est le meilleur &"; est basé sur la situation du projet. Le premier est facile à sélectionner et à gérer, et les performances sont meilleures car il n’est pas nécessaire de joindre des tables lorsqu’on sélectionne une entité. Si vous avez confirmé que votre projet ne prend en charge que 2 ou 3 langues et qu'il n'augmentera pas, vous pouvez l'utiliser.

Le second est correct mais difficile à comprendre et à maintenir. Et la performance est pire que le premier.

Le dernier est bon pour l'évolutivité mais mauvais pour la performance. La table T_TRANSLATION_ENTRY deviendra de plus en plus grande. C'est terrible de vouloir récupérer une liste d'entités à partir de certaines tables.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top