web-dev-qa-db-ja.com

多言語データベース、デフォルトのフォールバック

私は広く議論されてきたと思いますが、私の意見では、まだ明確にする必要がある側面があります。

私は多言語データベースを使用して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」プロパティを設定します。私のページでは、それを使用するだけで、ユーザーは意図したとおりに正しい言語を取得します。

わかりやすく、ごちゃごちゃしていないことを願っていますが、さらに情報が必要な場合は、もう少しお知らせいたします。

19
Luca

事前のメモ:

  • 私の答えは この質問への私の答え への追加の詳細です。ここでコメントを追加し、それがこの質問につながりました
  • 私の答えでは、C#とMS SQL Serverを使用しています(ORマッピング固有のコードは省略します)

私のアプリケーションでは、ユースケースに応じて、多言語データをロードするために2つの異なるアプローチを使用します。

管理/ CRUD

ユーザーがデータを入力したり、既存のデータ(翻訳された製品など)を編集したりする場合は、質問で上記と同じアプローチを使用しています。

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

これは、その言語の翻訳が存在する場合、要求された言語で製品のタイトルと説明を返します。翻訳が存在しない場合、デフォルトの言語のタイトルと説明が返されます。

33
M4N

すべてのテーブルのすべての翻訳可能なフィールドに共通の共有テーブルを使用

上記のアプローチでは、変換テーブルは親テーブルの拡張です。したがって、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からレコードをフェッチできます

4
Francis Mathew