Alors que certains systèmes logiciels sont utilisés par un nombre limité d'utilisateurs parlant la même langue, la plupart des organisations doivent unifier et centraliser leurs applications pour qu'elles soient utilisées par des personnes parlant différentes langues partout dans le monde. Les bases de données multilingues présentent un niveau supplémentaire de difficulté dans la conception et la mise en œuvre des modèles de données. Dans cet article, nous proposons quelques approches pour relever ce défi.
De quelles informations devons-nous stocker dans plusieurs langues ?
À première vue, toutes les informations de chaîne peuvent sembler plausibles pour une traduction dans plusieurs langues. Cependant, ce n'est généralement pas le cas. Informations relatives au client telles que CompanyName
ou Address
peut être traduit, mais ce n'est peut-être pas une bonne idée.
Prenez un client professionnel au Royaume-Uni nommé « Riverside Trucks » avec un bureau au « 123 Upper Castle Road ». Vous ne voulez pas qu'un utilisateur hispanophone imprime et envoie une lettre à "Camiones Orilla" situé au "123 Calle Castillo Superior". Royal Mail (le service postal du Royaume-Uni) ne le trouvera pas ! Vous souhaitez probablement traduire uniquement les colonnes contenant des informations descriptives, pas les noms propres.
Lors de la conception d'un système pour gérer les traductions, on ne sait pas toujours à l'avance exactement quelles colonnes sont traduisibles et lesquelles ne nécessitent pas de traductions. Le choix d'une approche flexible permet de gagner beaucoup de temps dans la conception et le développement. Consultez l'article "Comment concevoir un système prêt pour la localisation" pour voir quelques exemples.
Quelles approches envisageons-nous ?
Dans cet article, nous décrivons trois approches de conception de bases de données multilingues. Nous commençons par la plus simple qui n'est pas aussi flexible, puis nous passons à d'autres options, en expliquant les avantages et les inconvénients de chacune.
La syntaxe et les modèles de base de données (disponibles sur le modélisateur de données Web Vertabelo) utilisés dans cet article sont destinés à SQL Server. Cependant, ils s'adaptent facilement à n'importe quel moteur de base de données.
Approche 1 :Créer des colonnes supplémentaires pour contenir le contenu traduit
C'est l'approche la plus simple à mettre en œuvre, même si elle n'est pas très flexible. Il consiste à ajouter une colonne pour chaque colonne et langue que nous devons utiliser dans notre système, comme indiqué dans le diagramme Vertabelo suivant :
Bien que cela puisse sembler être une solution très simple, elle présente certains inconvénients. Nous vous expliquons ci-dessous.
Inconvénient :complexité du code
Cette approche rend le code plus complexe. Cela nous oblige soit à écrire une requête différente pour chaque langue, soit à utiliser un CASE
construire pour récupérer la traduction pour la langue appropriée en fonction de la configuration de l'utilisateur. Voir le code suivant par exemple :
SELECT ProductID, CASE @Language WHEN 'ES' THEN ProductName_ES WHEN 'DE' THEN ProductName_DE WHEN 'FR' THEN ProductName_FR ELSE ProductName END AS ProductName, CASE @Language WHEN 'ES' THEN ProductDescription_ES WHEN 'DE' THEN ProductDescription_DE WHEN ' FR' THEN ProductDescription_FR ELSE ProductDescription END AS ProductDescription, Price, Weight, ProductCategoryIDFROM ProductWHERE …
Remarque : Dans l'exemple, nous utilisons la variable @Language pour conserver la langue que nous voulons utiliser. Vous pouvez envisager d'utiliser SESSION_CONTEXT() (ou Application Context dans Oracle) pour définir et lire la langue de chaque utilisateur.
Inconvénient :Manque de flexibilité
Cette approche manque de souplesse. Si nous devons implémenter une nouvelle langue, nous devons modifier notre modèle de données en ajoutant une colonne pour la nouvelle langue pour chaque colonne traduisible dans notre système. Nous devons également créer une nouvelle requête de langage pour chaque table (ou modifier celle existante en ajoutant un nouveau CASE WHEN
clause qui utilise la nouvelle langue pour chaque colonne traduisible).
Inconvénient :difficultés à gérer les informations inconnues
Imaginez ceci :un utilisateur ajoute un produit mais ne sait pas comment le traduire et laisse les colonnes traduites vides. Les utilisateurs parlant ces langues voient NULL
ou des informations vides dans les colonnes qui peuvent être requises.
Approche 2 :Isoler les colonnes traduisibles dans un tableau séparé
Cette approche regroupe les colonnes d'une table en colonnes traduisibles et non traduisibles. Les colonnes non traduisibles restent dans la table d'origine. En revanche, ceux traduisibles sont dans une table séparée, avec une clé étrangère vers la table d'origine et un indicateur de langue. Voir ci-dessous :
Le diagramme montre les tables d'origine (sans données traduisibles) en blanc. Les tables contenant les traductions sont en bleu clair et la table principale contenant les informations linguistiques est en jaune.
Cela présente d'énormes avantages de flexibilité par rapport à l'utilisation de plusieurs colonnes, comme indiqué précédemment. Cette méthode ne nécessite pas de modifier le modèle de données lorsqu'un nouveau langage est nécessaire. De plus, la syntaxe pour interroger les informations est plus simple :
SELECT p.ProductID, pt.ProductName, pt.ProductDescription, p.Price, p.Weight, p.ProductCategoryIDFROM Product pLEFT JOIN ProductTranslation pt ON pt.ProductID =p.ProductID AND pt.LanguageID =@LanguageWHERE …Cependant, il y a encore quelques inconvénients, comme nous en discutons ci-dessous.
Inconvénient :difficultés lorsque des colonnes supplémentaires doivent être traduites
Si nous devons convertir une colonne non traduisible en une colonne traduisible (ou vice versa), nous devons modifier notre modèle de données, en déplaçant la colonne d'une table à l'autre. Cela implique généralement des coûts plus élevés une fois le système mis en œuvre et utilisé.
Inconvénient :difficultés à gérer les informations inconnues
Comme la première approche, cette approche présente des défis lorsqu'il s'agit d'informations inconnues. Encore une fois, si un utilisateur ajoute un produit mais ne sait pas comment le traduire et laisse les colonnes traduites vides, les utilisateurs parlant ces langues voient
NULL
ou des informations vides dans les colonnes qui peuvent être nécessaires. De plus, la requête nécessite unLEFT JOIN
au cas où la traduction pour la langue de l'utilisateur actuel n'a pas encore été créée afin que les données non traduisibles soient toujours affichées.Approche 3 :Ajouter un sous-système de traduction
La traduction peut être considérée comme une fonctionnalité totalement indépendante du modèle de données nécessitant une traduction. Dans un système idéal, nous pouvons activer ou désactiver la traduction pour n'importe quelle colonne sans nécessiter de modification du modèle de données. Malheureusement, c'est plus facile à dire qu'à faire.
Nous présentons une méthode qui n'a aucun impact sur le modèle de données existant et qui est complètement flexible. Bien que cela ajoute de la complexité au moment de l'interrogation des données, cela ne nécessite aucune modification supplémentaire du modèle de données, à l'exception de quelques tables. Cela peut être un excellent choix si vous avez besoin d'ajouter la capacité de conserver les traductions d'un modèle de données existant.
Examinons le modèle et voyons comment cela fonctionne :
La première chose à remarquer est que le modèle de données d'origine n'a subi aucun changement. De plus, il n'y a pas de relation directe entre ce modèle et le sous-système de traduction.
Le sous-système de traduction comprend un petit dictionnaire de données avec les tables et les colonnes nécessitant une traduction. Ce dictionnaire de données peut être modifié en ajoutant/supprimant simplement des lignes sans altérer le modèle de données. Les traductions sont stockées dans une table séparée, chaque valeur étant identifiée par les 3 colonnes suivantes :
ColumnID
:identifie de manière unique la colonne (et le tableau) que nous traduisons.KeyID
:Stocke l'ID (clé primaire) de la ligne spécifique que nous traduisons.LanguageID
:Identifie la langue de la traduction.
Cette conception permet de saisir et de stocker des données dans les tables d'origine, en ajoutant des traductions uniquement si et quand cela est nécessaire. Les informations traduites sont utilisées lors de la récupération des données, en gardant les données d'origine (dans la langue d'origine) intactes.
Les données peuvent être interrogées en utilisant une syntaxe plus complexe que les exemples ci-dessus. Il nécessite un JOIN
supplémentaire pour chaque colonne traduisible comme indiqué ci-dessous :
SELECT p.ProductID, ISNULL(t1.TranslationValue, p.ProductName) AS ProductName, ISNULL(t2.TranslationValue, p.ProductDescription) AS ProductDescription, p.Price, p.Weight, p.ProductCategoryIDFROM Produit pLEFT JOIN Translation t1 ON t1.ColumnID =<> AND t1.Key =p.ProductID AND t1.LanguageID =@LanguageLEFT JOIN Translation t2 ON t2.ColumnID =< > AND t2.Key =p.ProductID AND t2.LanguageID =@LanguageWHERE …;
Remarque : "<<ProductName_ColumnID>>
” et “<<ProductDescription_ColumnID>>
” doit être remplacé par les ID des colonnes à traduire telles qu'elles sont stockées dans ColumnInformation
table. Envisagez de générer des vues de traduction pour chaque table nécessitant une traduction afin de masquer la complexité des JOIN pour les utilisateurs finaux. Vous pouvez même automatiser cette étape avec un script qui génère chaque vue. Ce script peut interroger le dictionnaire de données de la base de données pour sélectionner les tables et les colonnes et ajouter la logique de traduction pour les colonnes qui existent dans ColumnInformation
tableau.
Astuce supplémentaire n° 1
Vous pouvez également simplifier la syntaxe. Remplacez chaque JOIN par un appel à une fonction qui gère (et masque) l'aspect traduction, comme indiqué ci-dessous :
SELECT p.ProductID, ISNULL(fn_translate('Product','ProductName',ProductID), p.ProductName) AS ProductName, ISNULL(fn_translate('Product','ProductDescription',ProductID), p.ProductDescription) AS ProductName, p.Price, p.Weight, p.ProductCategoryIDFROM Product pWHERE… ;
La fonction peut lire la langue souhaitée à partir du contexte, ou vous pouvez l'ajouter en tant que paramètre supplémentaire. Dans cet exemple, la fonction utilise les noms de table et de colonne fournis en tant que paramètres ainsi que la clé de ligne (également fournie en tant que paramètre) pour rechercher et renvoyer la traduction souhaitée.
L'appel d'une fonction implique un impact supplémentaire sur les performances en raison du changement de contexte entre SQL et le langage procédural. Cependant, cela peut être une solution plus simple pour les bases de données ou les tables où la quantité de données traduites le permet.
Les deux exemples - celui avec un JOIN et celui avec une fonction - utilisent la fonction ISNULL() SQL Server. Ainsi, lorsque la traduction dans la langue souhaitée n'existe pas, elle affiche toujours la valeur d'origine stockée dans les colonnes ProductName et ProductDescription au lieu d'espaces ou de NULL.
Considérations générales
La troisième approche est généralement la meilleure pour mettre en œuvre des conceptions plus grandes. Il permet une flexibilité à la fois lors de la conception et une fois que le système est utilisé. Cependant, il existe des considérations spécifiques qui peuvent rendre les autres approches utiles. Quel que soit votre choix, tenez compte des éléments suivants pour gagner du temps à la fois lors de la conception et lors du développement/de la mise en œuvre.
Ajouter une couche d'abstraction
Comme mentionné précédemment, envisagez de créer des vues qui prennent en charge la logique de traduction, par exemple, en sélectionnant une colonne parmi plusieurs colonnes de traduction ou en se joignant à des lignes spécifiques. Cela garde les détails d'implémentation spécifiques cachés aux programmeurs. Ils utilisent simplement ces vues plutôt que d'avoir à construire des phrases SQL complexes chaque fois qu'ils ont besoin d'accéder à une table contenant des informations traduisibles.
Utiliser le contexte pour filtrer les données
Comme mentionné dans le premier exemple, envisagez d'utiliser les fonctions de contexte disponibles dans la plupart des moteurs de base de données. Utilisez-les pour stocker les informations sur la langue de l'utilisateur une fois connecté au système, puis filtrez automatiquement les résultats dans les vues qui s'occupent de la traduction.
Automatiser
Les systèmes modernes peuvent avoir des centaines voire des milliers de tables. Prenez le temps d'automatiser la génération des vues de traduction plutôt que d'écrire les requêtes une par une. Il vous faudra peut-être un certain temps pour arriver à un script qui fonctionne, mais vous pourrez toujours créer de nouvelles vues ou recréer des vues existantes en moins d'une seconde !