一部のソフトウェアシステムは、同じ言語を話す限られた数のユーザーによって使用されますが、ほとんどの組織は、世界中の異なる言語を話す人々が使用できるように、アプリケーションを統合および一元化する必要があります。多言語データベースでは、データモデルの設計と実装がさらに困難になります。この記事では、この課題に対処するためのいくつかのアプローチを提案します。
複数の言語で保存する必要がある情報は何ですか?
一見すると、すべての文字列情報は、複数の言語に翻訳するのにもっともらしいと思われるかもしれません。ただし、これは通常は当てはまりません。 CompanyName
などの顧客関連情報 またはAddress
翻訳されるかもしれませんが、それは良い考えではないかもしれません。
「123UpperCastleRoad」にオフィスを構える、「RiversideTrucks」という名前の英国のビジネス顧客を例にとってみましょう。スペイン語を話すユーザーが「123CalleCastilloSuperior」にある「CamionesOrilla」に手紙を印刷して送信することは望ましくありません。 Royal Mail(英国の郵便局)はそれを見つけられません!おそらく、適切な名前ではなく、説明的な情報を含む列だけを翻訳したいと思うでしょう。
翻訳を処理するシステムを設計する場合、どの列が翻訳可能で、どの列が翻訳を必要としないかが事前に正確にわかっているとは限りません。柔軟なアプローチを選択すると、設計と開発にかかる時間を大幅に節約できます。いくつかの例については、「ローカリゼーション対応システムを設計する方法」の記事をご覧ください。
どのようなアプローチを検討しますか?
この記事では、多言語データベース設計への3つのアプローチについて説明します。柔軟性のない最も単純なものから始めて、次に他のオプションを検討し、それぞれの長所と短所を説明します。
この記事で使用されている構文モデルとデータベースモデル(Vertabelo Webベースのデータモデラーで利用可能)はどちらもSQLServer用です。ただし、どのデータベースエンジンにも簡単に適応できます。
アプローチ1:翻訳されたコンテンツを保持するための追加の列を作成する
これは、非常に柔軟なアプローチではありませんが、実装するのに最も簡単なアプローチです。次のVertabelo図に示すように、システムで使用する必要のある列と言語ごとに列を追加することで構成されます。
これは非常に単純な解決策のように見えるかもしれませんが、いくつかの欠点があります。以下に説明します。
短所:コードの複雑度
このアプローチにより、コードがより複雑になります。言語ごとに異なるクエリを作成するか、CASE
を使用する必要があります ユーザー構成に基づいて適切な言語の翻訳を取得するように構築します。たとえば、次のコードを参照してください。
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, ProductCategoryID FROM Product WHERE …
注: この例では、変数@Languageを使用して、使用する言語を保持しています。 SESSION_CONTEXT()(またはOracleのアプリケーションコンテキスト)を使用して、各ユーザーの言語を設定および読み取ることを検討できます。
短所:柔軟性の欠如
このアプローチには柔軟性がありません。新しい言語を実装する必要がある場合は、システム内の翻訳可能な列ごとに新しい言語の列を追加して、データモデルを変更する必要があります。また、テーブルごとに新しい言語クエリを作成する必要があります(または、新しいCASE WHEN
を追加して既存の言語クエリを編集します 翻訳可能な列ごとに新しい言語を使用する句)。
短所:不明な情報の処理における課題
これを想像してみてください。ユーザーは製品を追加しましたが、それを翻訳する方法がわからず、翻訳された列を空のままにします。これらの言語を話すユーザーには、NULL
が表示されます または、必要になる可能性のある列の空白情報。
アプローチ2:翻訳可能な列を別のテーブルに分離する
このアプローチでは、テーブル内の列を翻訳可能な列と翻訳不可能な列にグループ化します。翻訳不可能な列は元のテーブルに残ります。対照的に、翻訳可能なものは別のテーブルにあり、元のテーブルへの外部キーと言語インジケータがあります。以下を参照してください:
この図は、元のテーブル(翻訳可能なデータなし)を白で示しています。翻訳を保持するテーブルは水色で、言語情報を保持するマスターテーブルは黄色で表示されます。
これには、前述のように複数の列を使用する場合と比較して、柔軟性に大きな利点があります。この方法では、新しい言語が必要になったときにデータモデルを変更する必要はありません。また、情報を照会するための構文はより単純です:
SELECT p.ProductID, pt.ProductName, pt.ProductDescription, p.Price, p.Weight, p.ProductCategoryID FROM Product p LEFT JOIN ProductTranslation pt ON pt.ProductID = p.ProductID AND pt.LanguageID = @Language WHERE …
ただし、以下で説明するように、まだいくつかの短所があります。
短所:追加の列を翻訳する必要がある場合の課題
翻訳不可能な列を翻訳可能な列に(またはその逆に)変換する必要がある場合は、データモデルを変更して、列を1つのテーブルから別のテーブルに移動する必要があります。これは通常、システムが実装されて使用されると、コストが高くなることを意味します。
短所:不明な情報の処理における課題
最初のアプローチと同様に、このアプローチには、未知の情報を処理する際の課題があります。繰り返しになりますが、ユーザーが製品を追加したが、それを翻訳する方法がわからず、翻訳された列を空のままにした場合、それらの言語を話すユーザーにはNULL
が表示されます。 または、必要になる可能性のある列の空白情報。また、クエリにはLEFT JOIN
が必要です 現在のユーザーの言語の翻訳がまだ作成されていないため、翻訳できないデータが引き続き表示される場合。
アプローチ3:翻訳サブシステムの追加
変換は、変換を必要とするデータモデルから完全に独立した機能と見なすことができます。理想的なシステムでは、データモデルを変更せずに、任意の列の変換を有効または無効にすることができます。残念ながら、これは口で言うほど簡単ではありません。
既存のデータモデルに影響を与えず、完全に柔軟な方法を紹介します。データのクエリ時に複雑さが増しますが、いくつかのテーブルを除いて、データモデルに追加の変更を加える必要はありません。これは、既存のデータモデルに翻訳を保持する機能を追加する必要がある場合に最適です。
モデルを見て、どのように機能するかを見てみましょう。
最初に気付くのは、元のデータモデルにまったく変更がないことです。また、そのモデルと翻訳サブシステムの間に直接的な関係はありません。
変換サブシステムには、変換が必要なテーブルと列を含む小さなデータディクショナリが含まれています。このデータディクショナリは、データモデルを変更せずに行を追加/削除するだけで変更できます。翻訳は個別のテーブルに保存され、各値は次の3つの列で識別されます。
-
ColumnID
:翻訳する列(およびテーブル)を一意に識別します。 -
KeyID
:翻訳する特定の行のID(主キー)を格納します。 -
LanguageID
:翻訳の言語を識別します。
この設計により、データを元のテーブルに入力して保存し、必要な場合にのみ翻訳を追加できます。翻訳された情報は、データを取得するときに使用され、元のデータ(元の言語)はそのままになります。
上記の例よりも複雑な構文を使用して、データをクエリできます。追加のJOIN
が必要です 以下に示すように、翻訳可能な列ごとに:
SELECT p.ProductID, ISNULL(t1.TranslationValue, p.ProductName) AS ProductName, ISNULL(t2.TranslationValue, p.ProductDescription) AS ProductDescription, p.Price, p.Weight, p.ProductCategoryID FROM Product p LEFT JOIN Translation t1 ON t1.ColumnID = <> AND t1.Key = p.ProductID AND t1.LanguageID = @Language LEFT JOIN Translation t2 ON t2.ColumnID = < > AND t2.Key = p.ProductID AND t2.LanguageID = @Language WHERE …;
注: 「<<ProductName_ColumnID>>
」および「<<ProductDescription_ColumnID>>
」は、 ColumnInformation
に保存されているように変換される列のIDに置き換える必要があります テーブル。エンドユーザーのJOINの複雑さを隠すために、変換が必要なテーブルごとに変換ビューを生成することを検討してください。各ビューを生成するスクリプトを使用して、このステップを自動化することもできます。このスクリプトは、データベースのデータディクショナリにクエリを実行して、テーブルと列を選択し、 ColumnInformation
に存在する列の変換ロジックを追加できます。 テーブル。
追加のヒント#1
構文を単純化することもできます。以下に示すように、各JOINを、変換アスペクトを処理(および非表示)する関数の呼び出しに置き換えます。
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.ProductCategoryID FROM Product p WHERE …;
関数は、コンテキストから目的の言語を読み取る場合もあれば、追加のパラメーターとして追加する場合もあります。この例では、関数はパラメーターとして提供されたテーブル名と列名に加えて、行キー(パラメーターとしても提供されます)を使用して、目的の翻訳を検索して返します。
関数を呼び出すと、SQLと手続き型言語の間でコンテキストが切り替わるため、パフォーマンスに追加の影響があります。ただし、変換されるデータの量が許すデータベースやテーブルの場合は、より簡単なソリューションになる可能性があります。
両方の例(JOINを使用する例と関数を使用する例)では、ISNULL()SQLServer関数を使用しています。したがって、目的の言語への変換が存在しない場合でも、空白またはNULLではなく、ProductName列とProductDescription列に格納されている元の値が表示されます。
一般的な考慮事項
3番目のアプローチは、通常、より大きな設計を実装するのに最適です。これにより、設計時とシステムの使用後の両方で柔軟性が得られます。ただし、他のアプローチを有用にする可能性のある特定の考慮事項があります。選択に関係なく、設計と開発/実装の両方で時間を節約するために、次のことを考慮してください。
抽象化レイヤーを追加する
前述のように、複数の変換列から1つの列を選択したり、特定の行に結合したりするなど、変換ロジックを処理するビューを作成することを検討してください。これにより、特定の実装の詳細がプログラマーから隠されます。翻訳可能な情報を含むテーブルにアクセスする必要があるたびに複雑なSQL文を作成するのではなく、単にこれらのビューを使用します。
コンテキストを使用してデータをフィルタリングする
最初の例で述べたように、ほとんどのデータベースエンジンで使用可能なコンテキスト関数の使用を検討してください。それらを使用して、システムにログインした後のユーザー言語情報を保存し、翻訳を処理するビューで結果を自動的にフィルタリングします。
自動化
最新のシステムには、数百、さらには数千のテーブルがある場合があります。クエリを1つずつ作成するのではなく、時間をかけて翻訳ビューの生成を自動化してください。動作するスクリプトに到達するまでに時間がかかる場合がありますが、その後はいつでも新しいビューを作成したり、既存のビューを1秒以内に再作成したりできます。