Schéma pour une firebase database multilingue

Je développe un logiciel multilingue. En ce qui concerne le code de l’application, la localisation n’est pas un problème. Nous pouvons utiliser des ressources spécifiques à une langue et disposer de toutes sortes d’outils qui fonctionnent bien avec eux.

Mais quelle est la meilleure approche pour définir un schéma de firebase database multilingue? Disons 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 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 colonnes pour chaque langue

    Au lieu de stocker le texte traduit, seule une clé étrangère de la table de traductions est stockée. La table de 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 de traductions est stockée. La table de traductions ne contient qu’une clé et une table distincte 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 allez-vous concevoir un schéma de firebase database multilingue.

Que pensez-vous d’une table de traduction associée à chaque tableau traduisible?

CREATE TABLE T_PRODUCT (pr_id int, NOMBRE DE PRIX (18, 2))

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

De cette façon, si vous avez plusieurs colonnes traduisibles, il vous suffira d’une seule jointure pour l’obtenir, car vous ne générez pas automatiquement une traduction. Il peut être plus facile d’importer des éléments avec leurs traductions associées.

Le côté négatif de ceci est que si vous avez 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 pour cela. Si vous faites cela depuis l’application, cela ne posera probablement pas de problème.

Faites-moi savoir ce que vous pensez – je suis également sur le sharepoint prendre une décision à ce sujet pour notre prochaine demande. Jusqu’à présent, nous avons utilisé votre 3ème type.

La troisième option est la meilleure pour quelques raisons:

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

-Adam

C’est une question intéressante, alors nécromancions.

Commençons par les problèmes de la méthode 1:
Problème: Vous êtes en train de dénormaliser pour économiser de la vitesse.
En SQL (sauf PostGreSQL avec hstore), vous ne pouvez pas passer un langage de parameters et dire:

 SELECT ['DESCRIPTION_' + @in_language] FROM T_Products 

Donc, vous devez 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 “SQL dynamic”, vous n’avez donc pas à modifier toutes vos requêtes.

Cela se traduit généralement par quelque chose comme ceci (et il ne peut pas être utilisé dans les vues ou les fonctions de la table, ce qui est vraiment un problème si vous devez filtrer la date du 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 date est très spécifique au langage, donc vous rencontrez un problème, si vous n’entrez pas au format ISO (ce que le programmeur de jardin ne fait généralement pas, et en cas de rapport l’utilisateur est sûr) comme l’enfer ne fera pas pour vous, même si explicitement chargé de le faire).
et
b) plus important encore , vous perdez tout type de vérification de la syntaxe . Si modifie le schéma parce que soudainement les exigences pour l’aile changent, et qu’une nouvelle table est créée, l’ancienne rest mais le champ de référence renommé, vous ne recevez aucun avertissement. Un rapport fonctionne même lorsque vous l’exécutez sans sélectionner le paramètre wing (==> guid.empty). Mais soudain, quand un utilisateur réel sélectionne réellement une aile ==> boom . Cette méthode brise complètement tout type de test.


Méthode 2:
En résumé: “Grande” idée (avertissement – sarcasme), combinons les inconvénients de la méthode 3 (vitesse lente lorsque de nombreuses 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 dynamic, ainsi qu’une table (éventuellement temporaire) contenant les traductions et le nom de la table cible (et simple si vous nommez tous vos champs de texte). même).


Méthode 3:
Une table pour toutes les traductions: Inconvénient: Vous devez stocker n clés étrangères dans la table des produits pour n champs que vous souhaitez traduire. Par conséquent, vous devez faire n jointures pour n champs. Lorsque la table de traduction est globale, elle comporte de nombreuses entrées et les jointures deviennent lentes. De plus, vous devez toujours joindre la table T_TRANSLATION n fois pour n champs. C’est un peu une surcharge. Maintenant, que faites-vous lorsque vous devez adapter les traductions personnalisées par client? Vous devrez append un autre 2x jointures sur une table supplémentaire. Si vous devez rejoindre, disons 10 tables, avec 2x2xn = 4n jointures supplémentaires, quel bordel! En outre, cette conception permet d’utiliser la même traduction avec 2 tableaux. Si je change le nom de l’article dans une table, est-ce que je veux vraiment changer une entrée dans une autre table aussi CHAQUE HEURE UNIQUE?

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 DE PRODUITS … vous pouvez bien sûr ne pas définir les FK, puis peut supprimer la table, et réinsérer toutes les entrées avec newid () [ou en spécifiant l’id dans l’insert, mais en ayant OFF-insert OFF ], et cela (et conduira) à data-garbage (et null -référence exceptions) très bientôt.


Méthode 4 (non répertoriée): Stockage de toutes les langues dans un champ XML de la firebase database. 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'Hello', 2) --,CONVERT(XML, N'Hello', 2) ,CONVERT(XML , N'  Deutsch Français Ital&iano English  ' , 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 de XPath-Query en SQL, où vous pouvez placer la variable de 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 ""I am a ''value ""') WHERE id = 1 

Où vous pouvez remplacer /lang/de/... par '.../' + @in_language + '/...'

Un peu comme le PostGre hstore, sauf qu’en raison de la surcharge de l’parsing syntaxique (au lieu de lire une entrée d’un tableau associatif dans PG hstore), cela devient beaucoup trop lent et l’encodage XML rend son utilisation trop pénible.


Méthode 5 (recommandée par SunWuKung, celle que vous devriez choisir): Une table de traduction pour chaque table “Produit”. Cela signifie une ligne par langue et plusieurs champs “texte”, de sorte qu’il ne nécessite qu’une seule jointure (à gauche) sur N champs. Ensuite, vous pouvez facilement append 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 demande), que vous pouvez également supprimer. et réinsérez), et vous avez toujours toutes les clés étrangères.

Faisons un exemple pour voir ceci:

Tout d’abord, créez les tables:

 CREATE TABLE [dbo].[T_Languages]( [Lang_ID] [int] NOT NULL, [Lang_NativeName] [nvarchar](200) NULL, [Lang_EnglishName] [nvarchar](200) NULL, [Lang_ISO_TwoLetterName] [varchar](10) NULL, CONSTRAINT [PK_T_Languages] PRIMARY KEY CLUSTERED ( [Lang_ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE TABLE [dbo].[T_Products]( [PROD_Id] [int] NOT NULL, [PROD_InternalName] [nvarchar](255) NULL, CONSTRAINT [PK_T_Products] PRIMARY KEY CLUSTERED ( [PROD_Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE TABLE [dbo].[T_Products_i18n]( [PROD_i18n_PROD_Id] [int] NOT NULL, [PROD_i18n_Lang_Id] [int] NOT NULL, [PROD_i18n_Text] [nvarchar](200) NULL, CONSTRAINT [PK_T_Products_i18n] PRIMARY KEY CLUSTERED ( [PROD_i18n_PROD_Id] ASC, [PROD_i18n_Lang_Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] 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] WITH CHECK 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] WITH CHECK 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_Languages] 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] [nvarchar](200) NULL, CONSTRAINT [PK_T_Products_i18n_Cust] PRIMARY KEY CLUSTERED ( [PROD_i18n_Cust_PROD_Id] ASC, [PROD_i18n_Cust_Lang_Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH CHECK ADD CONSTRAINT [FK_T_Products_i18n_Cust_T_Languages] FOREIGN KEY([PROD_i18n_Cust_Lang_Id]) REFERENCES [dbo].[T_Languages] ([Lang_ID]) GO ALTER TABLE [dbo].[T_Products_i18n_Cust] CHECK CONSTRAINT [FK_T_Products_i18n_Cust_T_Languages] GO --ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH NOCHECK ADD CONSTRAINT [FK_T_Products_i18n_Cust_T_Products] FOREIGN KEY([PROD_i18n_Cust_PROD_Id]) ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH CHECK 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 ensuite interroger 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 (eg 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 le paramètre ISO-TwoLetterName (‘DE’, ‘EN’, etc.) en tant que clé primaire de la table des langues, vous n’avez donc pas à rechercher l’identifiant de langue. Mais si vous le faites, vous voudrez peut-être plutôt utiliser le tag IETF-language , ce qui est mieux, car vous obtenez de-CH et de-DE, ce qui n’est pas le même en orthographe (double s au lieu de ß partout) , bien que ce soit la même langue de base. C’est juste un tout petit détail qui peut être important pour vous, surtout si l’on considère qu’en-US et en-GB / en-CA / en-AU ou fr-FR / fr-CA ont des problèmes similaires.
Quote: nous n’en avons pas besoin, nous ne faisons 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’y a rien de plus ennuyeux, destructeur et gênant qu’une conception Db bâclée.

Voir également RFC 5646 , ISO 639-2 ,

Et si vous continuez à dire “nous” ne faisons que notre application pour ” une seule culture” (comme en-US en général) – donc je n’ai pas besoin de cet entier supplémentaire, ce serait un bon moment et un lieu pour mentionner le Balises 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 avait une réforme orthographique 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 juridiques et de services publics.
Plus important encore, il y a des régions qui passent de l’alphabet cyrillique à l’alphabet latin, ce qui peut être plus gênant que la nuisance superficielle de certaines réformes obscures de l’orthographe, raison pour laquelle cela peut être important, selon le pays où vous vivez. D’une manière ou d’une autre, il vaut mieux avoir cet entier là, juste au cas où …

Modifier:
Et en ajoutant ON DELETE CASCADE après

 REFERENCES [dbo].[T_Products] ([PROD_Id]) 

vous pouvez simplement dire: DELETE FROM T_Products , et ne pas avoir de 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 langue

Vous voudrez peut-être placer les classements dans leur propre table, par exemple:

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

C) Avoir le nom de 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) 

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 

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

J’irais généralement pour cette approche (pas réelle sql), 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 disposer de tous les textes traduisibles en un seul endroit facilite la maintenance. Parfois, les traductions sont sous-traitées à des bureaux de traduction, vous pouvez ainsi leur envoyer un seul fichier d’exportation important et les importer tout aussi facilement.

Avant de passer aux détails techniques et aux solutions, vous devez vous arrêter une minute et poser quelques questions sur les exigences. 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 rempliront 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 et qu’il n’y en a pas dans le système?
– Seuls les textes doivent être localisés ou il y a aussi d’autres éléments (par exemple, PRICE peut être stocké dans $ et € car ils peuvent être différents)

Je cherchais des conseils pour la localisation et j’ai trouvé ce sujet. Je me demandais pourquoi cela est utilisé:

 CREATE TABLE T_TRANSLATION ( TRANSLATION_ID ) 

Donc, vous obtenez 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 laisser la table Translation out pour obtenir 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 randomizer. Je ne vois pas pourquoi vous avez besoin d’une table “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? Disons que vous avez des tables où plus d’une colonne doit être traduite. Donc, pour le produit, vous pouvez avoir à la fois le nom du produit et la 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) ) 

“Lequel est le meilleur” est basé sur la situation du projet. Le premier est facile à sélectionner et à gérer, et les performances sont optimales, car il n’est pas nécessaire de joindre des tables lorsque vous sélectionnez une entité. Si vous confirmez que votre projet ne supporte que 2 ou 3 langues, et que cela n’augmentera pas, vous pouvez l’utiliser.

Le second est correct mais difficile à comprendre et à maintenir. Et la performance est pire que la première.

Le dernier est bon en matière d’évolutivité mais mauvais en performance. La table T_TRANSLATION_ENTRY deviendra de plus en plus grande, c’est terrible lorsque vous voulez récupérer une liste d’entités de certaines tables.