Multi language database, with default fallback

后端 未结 2 703
春和景丽
春和景丽 2021-01-29 19:47

I have a question that, I know, has been widely discussed about, but in my opinion, there is one aspect that still needs clarification.

I am creating a web-application w

相关标签:
2条回答
  • 2021-01-29 20:27

    Some notes upfront:

    • my answer is more of an addition to my answer to this question, where you added a comment which then led to this question
    • in my answer I'm using C# and MS SQL Server (and I'll leave out any OR-mapping specific code)

    In my applications, I use two different approaches for loading multilingual data, depending on the use case:

    Administration / CRUD

    In the case where the user is entering data or editing existing data (e.g. a product with its translations) I'm using the same approach as you have shown above in your question, e.g:

    public class Product
    {
        public int ID {get; set;}
        public string SKU {get; set;}
        public IList<ProductTranslation> Translations {get; set;}
    }
    public class ProductTranslation
    {
        public string Language {get; set;}
        public bool IsDefaultLanguage {get; set;}
        public string Title {get; set;}
        public string Description {get; set;}
    }
    

    I.e. I'll let the OR-mapper load the product instance(s) with all their translations attached. I then iterate through the translations and pick the ones needed.

    Front-end / read-only

    In this case, which is mainly front-end code, where I usually just display information to the user (preferably in the user's language), I'm using a different approach:

    First of all, I'm using a different data model which doesn't support/know the notion of multiple translations. Instead it is just the representation of a product in the "best" language for the current user:

    public class Product
    {
        public int ID {get; set;}
        public string SKU {get; set;}
    
        // language-specific properties
        public string Title {get; set;}
        public string Description {get; set;}
    }
    

    To load this data, I'm using different queries (or stored procedures). E.g. to load a product with ID @Id in the language @Language, I'd use the following query:

    SELECT
        p.ID,
        p.SKU,
        -- get title, description from the requested translation,
        -- or fall back to the default if not found:
        ISNULL(tr.Title, def.Title) Title,
        ISNULL(tr.Description, def.Description) Description
      FROM Products p
      -- join requested translation, if available:
      LEFT OUTER JOIN ProductTranslations tr
        ON p.ID = tr.ProductId AND tr.Language = @Language
      -- join default language of the product:
      LEFT OUTER JOIN ProductTranslations def
        ON p.ID = def.ProductId AND def.IsDefaultLanguage = 1
      WHERE p.ID = @Id
    

    This returns the product's title and description in the requested language if a translation for that language exists. If no translation exists, the title and description from the default language will be returned.

    0 讨论(0)
  • 2021-01-29 20:35

    Using common shared table for all translatable fields of all tables

    In the above approach the translation table is an extension of the parent table. Hence ProductTranslation has all the translatable fields of Product. It is a neat and quick approach and nice one as well.

    But there is one disadvantage (not sure if it can be called disadvantage). If many more tables require translate-able fields, that many new tables are required. From my experience we took a different approach. We created a generic table for translation and a link table to link translations to the translate-able fields of the parent table.

    So I'm going to use the previous example of Product which has two fields title and description that are translate-able to explain our approach. Also consider another table ProductCategory with fields name and description that also require translations.

    Product
    (
       ID: Integer
       SKU: String
       titleID: Integer // ID of LocalizableText record corresponding title
       descriptionID: Integer // ID of LocalizableText record corresponding description
    )
    
    ProductCategory
    (
       ID: Integer
       nameID: Integer // ID of LocalizableText record corresponding name
       descriptionID: Integer // ID of LocalizableText record corresponding description
    )
    
    LocalizableText // This is nothing but a link table
    {
        ID: Integer
    }
    
    Translations //This is where all translations are stored.
    {
        ID: Integer
        localizableTextID: Integer
        language: String
        text: String
    }
    

    To load this data, I'm using different queries (modified the above). E.g. to load a product with ID @Id in the language @Language, I'd use the following query

    SELECT
        p.ID,
        p.SKU,
        -- get title, description from the requested translation,
        -- or fall back to the default if not found:
        Title.text Title,
        description.text Description
      FROM Products p
      -- join requested translation for title, if available:
      LEFT OUTER JOIN Translations title
        ON p.titleID = title.localizableTextID
           AND title.Language = @Language
      -- join requested translation for description, if available:
      LEFT OUTER JOIN Translations description
        ON p.descriptionID = description.localizableTextID
           AND description.Language = @Language
      WHERE p.ID = @Id
    

    This query is based on the assumption that individual fields of Product does not have a default translation

    Similar query can be used to fetch records from ProductCategory

    0 讨论(0)
提交回复
热议问题