12

質問

データベース バックエンドをアプリケーションに合わせてローカライズするという課題に直面したことがある方は多いと思います。そうでない場合は、将来そうしなければならない可能性が非常に高いと自信を持って言えます。私は、データベース エンティティのテキストの複数の翻訳 (通貨などについても同じことが言えます) を格納することについて話しているのです。

たとえば、従来の「Category」テーブルには、グローバル化する必要がある Name 列と Description 列がある場合があります。1 つの方法は、エンティティごとに「テキスト」テーブルを作成し、結合を実行して、提供された言語に基づいて値を取得することです。

これにより、ローカライズするエンティティごとに 1 つずつ、多くの「テキスト」テーブルが残り、TextType が追加されて、格納されるさまざまなテキストが区別されます。

この種のサポートを SQL Server 2005/2008 データベースに実装するための文書化されたベスト プラクティス/設計パターンがあるかどうかに興味があります (サポートされているキーワードなどが含まれている可能性があるため、RDBMS について具体的に説明しています。実装で)?

XML アプローチに関する考え

私が (これまで頭の中でのみ) 考えていたアイデアの 1 つは、SQL Server 2005 で導入された XML データ型を活用することでした。 )。XML には、ローカライズされた文字列と、関連付けられている言語コード/カルチャが含まれます。

の線に沿った何か

Product
ID (int, identity)
Name (XML ...)
Description (XML ...)

次に、XMLとして次のようなものがあります

<localization>
  <text culture="sv-SE">Detta är ett namn</text>
  <text culture="en-EN">This is a name</text>
</localization>

その後、次のことができます (これは製品コードではないため、* を使用します)

SELECT *
From Product
Where Product.ID = 10

そして、ローカライズされたすべてのテキストを含む製品が返されます。つまり、クライアント側で抽出を行う必要があります。ここでの最大の問題は、明らかに、各クエリで返さなければならない余分なデータの量です。利点は、ルックアップ テーブルや結合などのないクリーンな設計になることです。

ところで、私が自分の設計で使用する方法が何であれ、Linq To SQL (.NET Platform) を使用してデータベースにクエリを実行します (XML アプローチは、クライアントと解釈できる XElement を返すため、問題になるはずです)。側)

そのため、データベースのローカリゼーションの設計パターンに関する提案と、XML の考え方に関するコメントをいただければ幸いです。

4

10 に答える 10

3

よりクリーンなデザインを可能にする XML に固執できると思います。さらに進んで、この用途向けに設計されxml:langた属性を利用します。

<l10n>
  <text xml:lang="sv-SE">Detta är ett namn</text>
  <text xml:lang="en-EN">This is a name</text>
</l10n>

さらに一歩進んで、クライアント側の処理を回避するために、XPath クエリを介して(コメントで提案されているように)クエリでローカライズされたリソースを選択できます。これにより、次のような結果が得られます(テストされていません):

SELECT Name.value('(l10n/text[lang()="en"])[1]', 'NVARCHAR(MAX)')
  FROM Product
  WHERE Product.ID=10;

このソリューションはエレガントですが、別のテーブルのソリューションよりも効率が悪いことに注意してください。一部のアプリケーションではこれで問題ない場合があります。

于 2008-11-04T14:06:39.157 に答える
2

これが私がそれをした方法です。クエリが複雑すぎて動的に構築されており、これはクエリの抜粋にすぎないため、これにはLINQまたはSPを使用しません。

私は製品テーブルを持っています:

* id
* price
* stocklevel
* active
* name
* shortdescription
* longdescription

およびproducts_globalizationテーブル:

* id
* products_id
* name
* shortdescription
* longdescription

ご覧のとおり、products-tableにはすべてのグローバリゼーション列も含まれています。これらの列にはデフォルト言語が含まれています(したがって、デフォルトカルチャを要求するときに結合の実行をスキップできます-しかし、これが問題に値するかどうかはわかりません。つまり、2つのテーブル間の結合はインデックスベースです。 ..-これについてのフィードバックをください)。

特定の状況では、MATCH(name、shortdescription、longdescription)AGAINST( 'ここに何か')。

通常のシナリオでは、一部の製品の翻訳が欠落している可能性がありますが、それでもすべての製品(翻訳された製品だけでなく)を表示したいと思います。したがって、結合を行うだけでは不十分です。実際には、products-tableに基づいて左結合を行う必要があります。

擬似:

string query = "";
if(string.IsNullOrEmpty(culture)) {
   // No culture specified, no join needed.
   query = "SELECT p.price, p.name, p.shortdescription FROM products p WHERE p.price > ?Price";
} else {
   query = "SELECT p.price, case when pg.name is null then p.name else pg.name end as 'name', case when pg.shortdescription is null then p.shortdescription else pg.shortdescription end as 'shortdescription' FROM products p"
   + " LEFT JOIN products_globalization pg ON pg.products_id = p.id AND pg.culture = ?Culture"
   + " WHERE p.price > ?Price";
}

私はCASEELSEの代わりにCOALESCEを使いますが、それは重要なことではありません。

まあ、それは私の見解です。私の提案を自由に批判してください...

よろしく、リチャード

于 2010-11-09T02:52:57.687 に答える
1

答えには「依存する」ものがたくさんあるので、答えるのが難しい質問の1つです:-)

答えは、データベース内のローカライズされたアイテムの量、展開シナリオ、キャッシュの問題、アクセスパターンなどによって異なります。アプリケーションの大きさ、同時ユーザーの数、およびアプリケーションのデプロイ方法に関するデータを提供していただければ、非常に役立ちます。

一般的に、私は通常、次の2つのアプローチのいずれかを使用します。

  1. ローカライズされたアイテムを実行可能ファイルの近くに保存します(ローカライズされたリソースdll)
  2. ローカライズされたアイテムをDBに格納し、ローカライズされたアイテムを含むテーブルにlocaleID列を導入します。

最初の方法の利点は、優れたVisualStudioサポートです。2番目の利点は、一元化された展開です。

于 2008-11-03T14:09:37.950 に答える
1

ローカライズされた値を格納するために XML 列を使用する利点はありません。それがあなたにとって価値がある場合は、1つのアイテムのすべてのローカライズされたバージョンを「1か所に」持っていることを除いて.

ローカライズ可能な項目を持つ各テーブルで cultureID-column を使用することを提案します。そうすれば、XML 処理はまったく必要ありません。すでにリレーショナル スキーマにデータがあるのに、リレーショナル スキーマが問題を完全に処理できるのに、別の複雑なレイヤーを導入する必要はありません。

「sv-SE」の cultureID = 1 と「en-EN」のカルチャー ID が 2 だとします。

次に、クエリは次のように変更されます

SELECT *
From Product
Where Product.ID = 10 AND Product.cultureID = 1

スウェーデンのお客様へ。

このソリューションは、ローカライズされたデータベースで頻繁に見られます。文化の数とデータレコードの数の両方にうまく対応します。XML の解析と処理を回避し、実装が簡単です。

もう 1 つのポイント: XML ソリューションは、必要のない柔軟性を提供します。たとえば、"Name" 列から "sv-SE" 値を取得し、"説明」欄。ただし、クライアントは一度に 1 つのカルチャのみを要求するため、これは必要ありません。通常、柔軟性にはコストがかかります。この場合、すべての列を個別に解析する必要がありますが、cultureID ソリューションでは、要求されたカルチャに適したすべての値を含むレコード全体を取得します。

于 2008-11-04T13:30:37.747 に答える
1

複数のテキスト テーブルが必要な理由がわかりません。「グローバルに」一意のテキスト ID を持つ単一のテキスト テーブルで十分です。テーブルには ID、言語、テキストの列があり、提示する必要がある言語のテキストのみを取得します (または、テキストをまったく取得しない可能性があります)。(ID、言語) の組み合わせが主キーであるため、結合はかなり効率的です。

于 2008-11-03T13:58:01.733 に答える
0

ここにリック・シュトラールのブログのいくつかの考えがあります:

データベースの ローカリゼーションJavaScriptのローカリゼーション

私はUserSettingテーブルで単一のスイッチを使用することを好みます。これはストアドプロシージャを呼び出すことによって使用されます...ここにいくつかのコードがあります

CREATE TABLE [dbo].[Lang_en_US_Msg](
    [MsgId] [int] IDENTITY(1,1) NOT NULL,
    [MsgKey] [varchar](200) NOT NULL,
    [MsgTxt] [varchar](2000) NOT NULL,
    [MsgDescription] [varchar](2000) NOT NULL,
 CONSTRAINT [PK_Lang_US-us__Msg] PRIMARY KEY CLUSTERED 
(
    [MsgId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[User](
    [UserId] [int] IDENTITY(1,1) NOT NULL,
    [FirstName] [varchar](50) NOT NULL,
    [MiddleName] [varchar](50) NULL,
    [LastName] [varchar](50) NULL,
    [DomainName] [varchar](50) NULL,
 CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
(
    [UserId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[UserSetting](
    [UserSettingId] [int] IDENTITY(1,1) NOT NULL,
    [UserId] [int] NOT NULL,
    [CultureInfo] [varchar](50) NOT NULL,
    [GuiLanguage] [varchar](10) NOT NULL,
 CONSTRAINT [PK_UserSetting] PRIMARY KEY CLUSTERED 
(
    [UserSettingId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

行く

 ALTER TABLE [dbo].[UserSetting] ADD  CONSTRAINT [DF_UserSetting_CultureInfo]  DEFAULT ('fi-FI') FOR [CultureInfo]
 GO

 CREATE TABLE [dbo].[Lang_fi_FI_Msg](
    [MsgId] [int] IDENTITY(1,1) NOT NULL,
    [MsgKey] [varchar](200) NOT NULL,
    [MsgTxt] [varchar](2000) NOT NULL,
    [MsgDescription] [varchar](2000) NOT NULL,
    [DbSysNameForExpansion] [varchar](50) NULL,
 CONSTRAINT [PK_Lang_Fi-fi__Msg] PRIMARY KEY CLUSTERED 
(
    [MsgId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE PROCEDURE [dbo].[procGui_GetPageMsgs]
@domainUser varchar(50) ,           -- the domain_user performing the action  
@msgOut varchar(4000) OUT,        -- the (error) msg to be shown to the user   
@debugMsgOut varchar(4000) OUT ,   -- this variable holds the debug msg to be shown if debug level is enabled   
@ret int OUT                  -- the variable indicating success or failure 

AS                            
BEGIN -- proc start                            
 SET NOCOUNT ON;                            

declare @procedureName varchar(200)        
declare @procStep varchar(4000)  


set @procedureName = ( SELECT OBJECT_NAME(@@PROCID))        
set @msgOut = ' '     
set @debugMsgOut = ' '     
set @procStep = ' '     


BEGIN TRY        --begin try                  
set @ret = 1 --assume false from the beginning                  

--===============================================================
 --debug   set @procStep=@procStep + 'GETTING THE GUI LANGUAGE FOR THIS USER '
--===============================================================

declare @guiLanguage nvarchar(10)




if ( @domainUser is null)
    set @guiLanguage = (select Val from AppSetting where Name='guiLanguage')
else 
    set @guiLanguage = (select GuiLanguage from UserSetting us join [User] u on u.UserId = us.UserId where u.DomainName=@domainUser)

set @guiLanguage = REPLACE ( @guiLanguage , '-' , '_' ) ;


--===============================================================
set @procStep=@procStep + ' BUILDING THE SQL QUERY '
--===============================================================

DECLARE @sqlQuery AS nvarchar(2000)
SET @sqlQuery = 'SELECT  MsgKey , MsgTxt FROM dbo.lang_' + @guiLanguage + '_Msg'


--===============================================================
set @procStep=@procStep + 'EXECUTING THE SQL QUERY'
--===============================================================
print @sqlQuery

    exec sp_executesql @sqlQuery

    set @debugMsgOut = @procStep
    set @ret = @@ERROR                  


END TRY        --end try                  

BEGIN CATCH                        
 PRINT 'In CATCH block.                         
 Error number: ' + CAST(ERROR_NUMBER() AS varchar(10)) + '                        
 Error message: ' + ERROR_MESSAGE() + '                        
 Error severity: ' + CAST(ERROR_SEVERITY() AS varchar(10)) + '                        
 Error state: ' + CAST(ERROR_STATE() AS varchar(10)) + '                        
 XACT_STATE: ' + CAST(XACT_STATE() AS varchar(10));                        

set @msgOut = 'Failed to execute ' + @sqlQuery             
set @debugMsgOut = ' Error number: ' + CAST(ERROR_NUMBER() AS varchar(10)) +               
 'Error message: ' + ERROR_MESSAGE() + 'Error severity: ' + CAST(ERROR_SEVERITY() AS varchar(10)) +               
 'Error state: ' + CAST(ERROR_STATE() AS varchar(10)) + 'XACT_STATE: ' + CAST(XACT_STATE() AS varchar(10))                        

--record the error in the database                        
--debug    
 --EXEC [dbo].[procUtils_DebugDb]
    --  @DomainUser = @domainUser,
    --  @debugmsg = @debugMsgOut,
    --  @ret = 1,
    --  @procedureName = @procedureName ,
    --  @procedureStep = @procStep

 -- set @ret = 1                       

END CATCH                        


return  @ret                                   
END --procedure end                             
于 2009-05-14T19:07:34.180 に答える
0

考慮すべきもう 1 つのアプローチ: コンテンツをデータベースに保存せず、データベース レコードをサポートする「アプリケーション」と「コンテンツ」を別々のエンティティとして保持します。

e コマース Web サイト用に複数のテーマを作成するときに、これと同様のアプローチを使用しました。一部の製品には、ウェブサイトのテーマと一致する必要があるメーカーのロゴが付いています。テーマに対する実際のデータベース サポートがないため、問題が発生しました。私が思いついた解決策は、画像の URL (テーマによって異なります) を保存するのではなく、データベース内のトークンを使用して画像の ClientID を識別することでした。

同じアプローチに従って、データベースを製品の名前と説明の保存から、リソースを識別する名前トークンと説明トークンの保存に変更できます (resx ファイルまたは Rick Strahl のアプローチを使用したデータベース内)。コンテンツ。.NET の組み込み機能は、データベースで言語の切り替えを試みるのではなく、言語の切り替えを処理します (ビジネス ロジックをデータベースに配置することはほとんど良い考えではありません)。その後、クライアントでトークンを使用して正しいリソースを検索できます。

Label1.Text = GetLocalResourceObject("TokenStoredInDatabase").ToString()

このアプローチの欠点は、明らかにデータベース トークンとリソース トークンの同期を維持することです (説明なしで製品を追加できるため) が、Rick Strahl が作成したようなリソース プロバイダーを使用すると、より簡単に実行できる可能性があります。頻繁に変更される製品がある場合、このアプローチはうまくいかないかもしれませんが、一部の人にとってはうまくいくかもしれません。

利点は、データベースからクライアントに転送するデータが少量であり、コンテンツがデータベースから明確に分離され、データベースを現在よりも複雑にする必要がないことです。

余談ですが、e コマース ストアを運営していて、実際にローカライズされたページをインデックスに登録したい場合は、Microsoft が作成した一見自然な方法から少し逸脱する必要があります。実用的で論理的なデザイン フローと、 Google が推奨する SEOとの間には明らかに相違があります。実際、ブラウザの文化によって URL が変わっても、検索エンジンは 1 つの URL を 1 回しか索引付けしないため、「デフォルト」の文化以外のページは検索エンジンによって索引付けされていないと不満を述べる We​​b マスターもいます。

幸いなことに、これを回避する簡単な方法があります。ページにリンクを配置して、クエリ文字列パラメーターに基づいて他の言語に翻訳します。この例を見つけることができます (おっと、別のリンクを投稿させてくれません!!) 確認すると、ページの各カルチャは Google と Yahoo の両方によってインデックス化されています (ただし、Bing ではありません)。より高度なアプローチでは、URL 書き換えをいくつかの派手な正規表現と組み合わせて使用​​して、単一のローカライズされたページに複数のディレクトリがあるように見せますが、実際には代わりにクエリ文字列パラメーターをページに渡します。

于 2009-09-29T11:50:00.343 に答える
0

インデックス作成が問題になります。xml にインデックスを付けることはできないと思います。もちろん、すべての文字列が<localization> <text culture="...">.

于 2010-08-05T20:34:41.990 に答える
0

たとえば、外部結合を行わない限り、スウェーデン語の翻訳がない場合 (cultureID = 1)、個別テーブル ソリューションは結果を返さないため、XML アプローチが気に入っています。それでも、英語に戻ることはできません。XML アプローチを使用すると、簡単に英語に戻ることができます。生産的な環境での XML アプローチに関するニュースはありますか?

于 2008-12-17T17:27:23.630 に答える