私は広く議論されてきたと思いますが、私の意見では、まだ明確にする必要がある側面があります。
私は多言語データベースを使用してWebアプリケーションを作成しています。すでに this などのいくつかの優れた実践記事を見つけ、ここで this のようなスタックオーバーフローで回答します。
たとえば、アイテムのIDを含むメインテーブルと、各アイテムの翻訳を含む別のテーブルを使用することにしました。たとえば、
Content
ContentTranslation
または
Category
CategoryTranslation
等々。
今私がやっていることは?すべての翻訳を含むデータベースからアイテムを取得し、現在のユーザーのローカルに基づいて正しい翻訳を探すためにそれぞれを繰り返し、正しいローカルが見つかった場合は、ページのその翻訳にメインオブジェクトを設定しましたレンダリングする場合、それ以外の場合は「デフォルト」のフラグが付けられた変換を取得します。
ただし、大量のオブジェクトと翻訳があると、サーバーの応答時間が長くなる可能性があり、ユーザーが気付かない場合でも、これは望ましくありません。
それで、このユースケースにも良い習慣がありますか?たとえば、「ロケール "it"を使用して翻訳を選択しますが、見つからない場合は、 "default"フラグが設定されたクエリを取得する特定のクエリの例を示します。
ここで、テクノロジーのために、Spring MVCをHibernateおよびJPA(JPARepositoryを使用)とともに使用しています。
私のオブジェクトはすべて、この方法で作成した基本的な翻訳可能なクラスを拡張します
@MappedSuperclass
public abstract class Translatable<T extends Translation> extends BaseDTO {
private static final long serialVersionUID = 562001309781752460L;
private String title;
@OneToMany(fetch=FetchType.EAGER, orphanRemoval=true, cascade=CascadeType.ALL)
private Set<T> translations = new HashSet<T>();
@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<T> getTranslations() {
return translations;
}
public void setTranslations(Set<T> 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」プロパティを設定します。私のページでは、それを使用するだけで、ユーザーは意図したとおりに正しい言語を取得します。
わかりやすく、ごちゃごちゃしていないことを願っていますが、さらに情報が必要な場合は、もう少しお知らせいたします。
事前のメモ:
私のアプリケーションでは、ユースケースに応じて、多言語データをロードするために2つの異なるアプローチを使用します。
ユーザーがデータを入力したり、既存のデータ(翻訳された製品など)を編集したりする場合は、質問で上記と同じアプローチを使用しています。
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;}
}
つまりORマッパーに、すべての翻訳が添付された製品インスタンスをロードさせます。次に、翻訳を繰り返し、必要なものを選択します。
この場合、これは主にフロントエンドコードであり、通常はユーザーに情報を(できればユーザーの言語で)表示するだけですが、別の方法を使用しています。
最初に、私は複数の翻訳の概念をサポート/認識しない別のデータモデルを使用しています。代わりに、現在のユーザーにとって「最良の」言語での製品の表現にすぎません。
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;}
}
このデータを読み込むために、さまざまなクエリ(またはストアドプロシージャ)を使用しています。例えば。言語@Id
でID @Language
の商品をロードするには、次のクエリを使用します。
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のすべての翻訳可能なフィールドがあります。それはきちんとした迅速なアプローチであり、ニースのものでもあります。
しかし、欠点が1つあります(欠点と言えるかどうかはわかりません)。さらに多くのテーブルが翻訳可能なフィールドを必要とする場合、その多くの新しいテーブルが必要です。私の経験から、私たちは別のアプローチをとりました。翻訳用の汎用テーブルと、翻訳を親テーブルの翻訳可能なフィールドにリンクするリンクテーブルを作成しました。
したがって、私たちのアプローチを説明するために翻訳可能なタイトルと説明の2つのフィールドを持つ前の製品の例を使用します。また、翻訳が必要なフィールド名と説明を持つ別のテーブル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
このクエリは、製品の個々のフィールドにデフォルトの翻訳がないことを前提としています
同様のクエリを使用して、ProductCategoryからレコードをフェッチできます