多语言数据库,具有默认回退function

我有一个问题,我知道,已被广泛讨论过,但在我看来,有一个方面仍需要澄清。

我正在创建一个带有多语言数据库的Web应用程序,我已经找到了一些好的练习文章(例如这个 ),这里的答案就像这样堆栈溢出。

所以我决定使用一个带有我的项目ID的主表和另一个带有每个项目翻译的表格,比方说,例如

Content ContentTranslation 

要么

 Category CategoryTranslation 

等等。

现在我在做什么? 我只是从数据库中获取所有翻译的项目,然后我迭代每一个以根据当前用户的本地查找正确的翻译,如果我找到正确的本地我设置到主要对象,该页面的翻译渲染,否则我只是得到被标记为“默认”的翻译。

但是,对于大量对象和翻译,服务器响应时间可能会增长,即使用户可能没有注意到,我也不希望这样。

那么,这个用例有没有好的做法呢? 例如,一些特定的查询说“选择带语言环境的翻译”,但如果你没有找到它只是设置了一个“默认”标志?

现在对于我正在使用Spring MVC与Hibernate和JPA(通过JPARepository)的技术。

我的对象都扩展了我用这种方式创建的基本可翻​​译类

 @MappedSuperclass public abstract class Translatable extends BaseDTO { private static final long serialVersionUID = 562001309781752460L; private String title; @OneToMany(fetch=FetchType.EAGER, orphanRemoval=true, cascade=CascadeType.ALL) private Set translations = new HashSet(); @Transient private T currentLocale; public void addLocale(T translation, boolean edit) { if (!edit) getTranslations().add(translation); } public void remLocale(String locale) { T tr = null; for (T candidate: getTranslations()) { if (candidate.getLocale().equals(locale)) tr = candidate; } getTranslations().remove(tr); } public T getLocaleFromString(String locale) { if (locale == null) return null; for (T trans: translations) { if (trans.getLocale().equals(locale)) return trans; } return null; } public T getDefaultLocale() { for (T tr: translations) { if (tr.isDefaultLocale()) return tr; } return null; } public Set getTranslations() { return translations; } public void setTranslations(Set translations) { this.translations = translations; } public T getCurrentLocale() { return currentLocale; } public void setCurrentLocale(T currentLocale) { this.currentLocale = currentLocale; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } } 

因此,在我的控制器中,我遍历翻译,找到具有正确语言环境的语言并填充“currentLocale”属性,在我的页面中我只是采用它并且用户获得正确的语言。

我希望我一直很清楚,而不是凌乱,但如果你需要更多的信息,我会很高兴告诉你更多。

一些注意事项:

  • 我的回答更多的是我对这个问题的答案的补充,在那里你添加了一个评论,然后导致了这个问题
  • 在我的回答中,我正在使用C#和MS SQL Server(我将省略任何OR映射特定代码)

在我的应用程序中,我使用两种不同的方法来加载多语言数据,具体取决于用例:

行政/ CRUD

在用户输入数据或编辑现有数据(例如带有翻译的产品)的情况下,我使用的方法与您在问题中所示的方法相同,例如:

 public class Product { public int ID {get; set;} public string SKU {get; set;} public IList 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;} } 

即我将让OR-mapper加载产品实例并附上所有翻译。 然后我遍历翻译并挑选所需的翻译。

前端/只读

在这种情况下,主要是前端代码,我通常只向用户显示信息(最好是用户的语言),我使用的是另一种方法:

首先,我使用的是不同的数据模型,它不支持/知道多个翻译的概念。 相反,它只是当前用户使用“最佳”语言表示的产品:

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

要加载此数据,我使用不同的查询(或存储过程)。 例如,使用@Language语言加载ID为@Id的产品,我将使用以下查询:

 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 

如果存在该语言的翻译,则以所请求的语言返回产品的标题和描述。 如果不存在翻译,则将返回默认语言的标题和说明。

对所有表的所有可翻译字段使用公共共享表

在上述方法中,转换表是父表的扩展。 因此,ProductTranslation具有Product的所有可翻译字段。 这是一个简洁快捷的方法,也很好。

但是有一个缺点(不确定它是否可以称为劣势)。 如果有更多表需要可翻译字段,则需要许多新表。 根据我的经验,我们采取了不同的方法。 我们创建了一个用于翻译的通用表和一个链接表,用于将翻译链接到父表的可翻译字段。

所以我将使用前面的Product示例,它有两个字段标题和描述,可以翻译我们的方法。 还要考虑具有字段名称和描述的另一个表ProductCategory,它们也需要翻译。

 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 } 

要加载此数据,我正在使用不同的查询(修改上述内容)。 例如,使用@Language语言加载ID为@Id的产品,我将使用以下查询

 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 

此查询基于Product的各个字段没有默认转换的假设

类似的查询可用于从ProductCategory获取记录