267

多言語ソフトウェアを開発しています。アプリケーションコードに関する限り、ローカライズ可能性は問題ではありません。言語固有のリソースを使用して、それらとうまく機能するあらゆる種類のツールを用意できます。

しかし、多言語データベーススキーマを定義するための最良のアプローチは何ですか?多数のテーブル(100以上)があり、各テーブルにローカライズ可能な複数の列を含めることができるとします(nvarchar列のほとんどはローカライズ可能である必要があります)。たとえば、テーブルの1つに製品情報が含まれている場合があります。

CREATE TABLE T_PRODUCT (
  NAME        NVARCHAR(50),
  DESCRIPTION NTEXT,
  PRICE       NUMBER(18, 2)
)

NAME列とDESCRIPTION列で多言語テキストをサポートするための3つのアプローチを考えることができます。

  1. 言語ごとに個別の列

    システムに新しい言語を追加するときは、次のように、翻訳されたテキストを保存するための追加の列を作成する必要があります。

    CREATE TABLE T_PRODUCT (
      NAME_EN        NVARCHAR(50),
      NAME_DE        NVARCHAR(50),
      NAME_SP        NVARCHAR(50),
      DESCRIPTION_EN NTEXT,
      DESCRIPTION_DE NTEXT,
      DESCRIPTION_SP NTEXT,
      PRICE          NUMBER(18,2)
    )
    
  2. 各言語の列を含む翻訳テーブル

    翻訳されたテキストを保存する代わりに、翻訳テーブルへの外部キーのみが保存されます。翻訳テーブルには、各言語の列が含まれています。

    CREATE TABLE T_PRODUCT (
      NAME_FK        int,
      DESCRIPTION_FK int,
      PRICE          NUMBER(18, 2)
    )
    
    CREATE TABLE T_TRANSLATION (
      TRANSLATION_ID,
      TEXT_EN NTEXT,
      TEXT_DE NTEXT,
      TEXT_SP NTEXT
    )
    
  3. 各言語の行を含む翻訳テーブル

    翻訳されたテキストを保存する代わりに、翻訳テーブルへの外部キーのみが保存されます。翻訳テーブルにはキーのみが含まれ、別のテーブルには言語への各翻訳の行が含まれます。

    CREATE TABLE T_PRODUCT (
      NAME_FK        int,
      DESCRIPTION_FK int,
      PRICE          NUMBER(18, 2)
    )
    
    CREATE TABLE T_TRANSLATION (
      TRANSLATION_ID
    )
    
    CREATE TABLE T_TRANSLATION_ENTRY (
      TRANSLATION_FK,
      LANGUAGE_FK,
      TRANSLATED_TEXT NTEXT
    )
    
    CREATE TABLE T_TRANSLATION_LANGUAGE (
      LANGUAGE_ID,
      LANGUAGE_CODE CHAR(2)
    )
    

各ソリューションには長所と短所があります。これらのアプローチでの経験、推奨事項、および多言語データベーススキーマの設計について教えてください。

4

12 に答える 12

128

翻訳可能なテーブルごとに関連する翻訳テーブルを持つことについてどう思いますか?

CREATE TABLE T_PRODUCT (pr_id int, PRICE NUMBER(18, 2))

CREATE TABLE T_PRODUCT_tr (pr_id INT FK、言語コード varchar、pr_name テキスト、pr_descr テキスト)

このように、複数の翻訳可能な列がある場合、それを取得するために 1 つの結合のみが必要になります + translationid を自動生成しないため、アイテムを関連する翻訳と一緒にインポートする方が簡単な場合があります。

これのマイナス面は、複雑な言語フォールバック メカニズムがある場合、変換テーブルごとにそれを実装する必要がある場合があることです。これを行うためにストアド プロシージャに依存している場合です。アプリからそれを行う場合、これはおそらく問題にはなりません。

ご意見をお聞かせください - 私も次の申請のためにこれについて決定を下そうとしています. これまでのところ、3 番目のタイプを使用しています。

于 2008-11-27T10:02:17.933 に答える
67

これは興味深い問題なので、ネクロマンスしましょう。

方法 1 の問題から始めましょう:
問題: 速度を節約するために非正規化しています。
SQL (hstore を使用する PostGreSQL を除く) では、パラメーター language を渡して次のように言うことはできません。

SELECT ['DESCRIPTION_' + @in_language]  FROM T_Products

だからあなたはこれをしなければなりません:

SELECT 
    Product_UID 
    ,
    CASE @in_language 
        WHEN 'DE' THEN DESCRIPTION_DE 
        WHEN 'SP' THEN DESCRIPTION_SP 
        ELSE DESCRIPTION_EN 
    END AS Text 
FROM T_Products 

つまり、新しい言語を追加する場合、すべてのクエリを変更する必要があります。これは当然「動的 SQL」の使用につながるため、すべてのクエリを変更する必要はありません。

これは通常、次のような結果になります (ちなみに、ビューやテーブル値関数では使用できません。実際にレポートの日付をフィルター処理する必要がある場合、これは本当に問題になります)

CREATE PROCEDURE [dbo].[sp_RPT_DATA_BadExample]
     @in_mandant varchar(3) 
    ,@in_language varchar(2) 
    ,@in_building varchar(36) 
    ,@in_wing varchar(36) 
    ,@in_reportingdate varchar(50) 
AS
BEGIN
    DECLARE @sql varchar(MAX), @reportingdate datetime
    
    -- Abrunden des Eingabedatums auf 00:00:00 Uhr
    SET @reportingdate = CONVERT( datetime, @in_reportingdate) 
    SET @reportingdate = CAST(FLOOR(CAST(@reportingdate AS float)) AS datetime)
    SET @in_reportingdate = CONVERT(varchar(50), @reportingdate) 
    
    SET NOCOUNT ON;


    SET @sql='SELECT 
         Building_Nr AS RPT_Building_Number 
        ,Building_Name AS RPT_Building_Name 
        ,FloorType_Lang_' + @in_language + ' AS RPT_FloorType 
        ,Wing_No AS RPT_Wing_Number 
        ,Wing_Name AS RPT_Wing_Name 
        ,Room_No AS RPT_Room_Number 
        ,Room_Name AS RPT_Room_Name 
    FROM V_Whatever 
    WHERE SO_MDT_ID = ''' + @in_mandant + ''' 
    
    AND 
    ( 
        ''' + @in_reportingdate + ''' BETWEEN CAST(FLOOR(CAST(Room_DateFrom AS float)) AS datetime) AND Room_DateTo 
        OR Room_DateFrom IS NULL 
        OR Room_DateTo IS NULL 
    ) 
    '
    
    IF @in_building    <> '00000000-0000-0000-0000-000000000000' SET @sql=@sql + 'AND (Building_UID  = ''' + @in_building + ''') '
    IF @in_wing    <> '00000000-0000-0000-0000-000000000000' SET @sql=@sql + 'AND (Wing_UID  = ''' + @in_wing + ''') '
    
    EXECUTE (@sql) 
    
END


GO

これに関する問題は
次のとおりです。a) 日付の書式設定は非常に言語固有であるため、ISO 形式で入力しないと問題が発生します (一般的な一般的なプログラマーは通常これを行いません。たとえ明示的にそうするように指示されたとしても、ユーザーがあなたのために絶対にしないレポート)。
そして
b)最も重要なのは、あらゆる種類の構文チェックを失うことです。翼の要件が突然変更され、新しいテーブルが作成され、古いテーブルは残されているが参照フィールドの名前が変更されたためにスキーマを変更した場合<insert name of your "favourite" person here>、警告は表示されません。wing パラメーター(==> guid.empty)を選択せず​​にレポートを実行しても、レポートは機能します。しかし突然、実際のユーザーが実際に翼を選択すると ==>ブームこの方法は、あらゆる種類のテストを完全に破ります。


方法 2:
一言で言えば: 「素晴らしい」アイデア (警告 - 皮肉)、方法 3 の欠点 (エントリが多いと速度が遅い) と方法 1 のかなり恐ろしい欠点を組み合わせましょう。
この方法の唯一の利点は、すべての変換が 1 つのテーブルにまとめられるため、メンテナンスが簡単になります。ただし、同じことは、方法 1 と動的 SQL ストアド プロシージャ、および翻訳を含む (おそらく一時的な) テーブルと、ターゲット テーブルの名前を使用して実現できます (すべてのテキスト フィールドに同じ)。


方法 3:
すべての翻訳に対して 1 つのテーブル: 欠点: 翻訳する n フィールドの製品テーブルに n 外部キーを格納する必要があります。したがって、n 個のフィールドに対して n 個の結合を行う必要があります。変換テーブルがグローバルの場合、多くのエントリがあり、結合が遅くなります。また、常に T_TRANSLATION テーブルを n フィールドに対して n 回結合する必要があります。これはかなりのオーバーヘッドです。では、顧客ごとのカスタム翻訳に対応する必要がある場合はどうすればよいでしょうか? 追加のテーブルにさらに 2x n 結合を追加する必要があります。結合する必要がある場合、たとえば 10 個のテーブルに 2x2xn = 4n の追加結合が必要な場合は、なんと面倒なことでしょう。また、この設計により、2 つのテーブルで同じ翻訳を使用することが可能になります。あるテーブルのアイテム名を変更すると、別のテーブルのエントリも毎回変更したいですか?

さらに、製品テーブルに外部キーがあるため、テーブルを削除して再挿入することはできません...もちろん、FKの設定を省略して<insert name of your "favourite" person here>、テーブルを削除して再挿入できますnewid()を使用したすべてのエントリ[または挿入で id を指定するが、identity-insert OFFを使用する]、そしてそれはすぐにデータガベージ (および null 参照例外) につながります (そしてそうなるでしょう)。


方法 4 (一覧にはありません): データベースの XML フィールドにすべての言語を格納します。例えば
-- CREATE TABLE MyTable(myfilename nvarchar(100) NULL, filemeta xml NULL )


;WITH CTE AS 
(
      -- INSERT INTO MyTable(myfilename, filemeta) 
      SELECT 
             'test.mp3' AS myfilename 
            --,CONVERT(XML, N'<?xml version="1.0" encoding="utf-16" standalone="yes"?><body>Hello</body>', 2) 
            --,CONVERT(XML, N'<?xml version="1.0" encoding="utf-16" standalone="yes"?><body><de>Hello</de></body>', 2) 
            ,CONVERT(XML
            , N'<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<lang>
      <de>Deutsch</de>
      <fr>Français</fr>
      <it>Ital&amp;iano</it>
      <en>English</en>
</lang>
            ' 
            , 2 
            ) AS filemeta 
) 

SELECT 
       myfilename
      ,filemeta
      --,filemeta.value('body', 'nvarchar') 
      --, filemeta.value('.', 'nvarchar(MAX)') 

      ,filemeta.value('(/lang//de/node())[1]', 'nvarchar(MAX)') AS DE
      ,filemeta.value('(/lang//fr/node())[1]', 'nvarchar(MAX)') AS FR
      ,filemeta.value('(/lang//it/node())[1]', 'nvarchar(MAX)') AS IT
      ,filemeta.value('(/lang//en/node())[1]', 'nvarchar(MAX)') AS EN
FROM CTE 

次に、SQL の XPath-Query で値を取得できます。ここで、文字列変数を配置できます

filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla

そして、次のように値を更新できます。

UPDATE YOUR_TABLE
SET YOUR_XML_FIELD_NAME.modify('replace value of (/lang/de/text())[1] with "&quot;I am a ''value &quot;"')
WHERE id = 1 

/lang/de/...交換できる場所'.../' + @in_language + '/...'

PostGre hstore に似ていますが、(PG hstore の連想配列からエントリを読み取るのではなく) XML を解析するオーバーヘッドが原因で、処理が非常に遅くなり、xml エンコーディングによって使い物にならなくなる点が異なります。


方法 5 (SunWuKung が推奨する方法で、選択する必要があります): 各「製品」テーブルに対して 1 つの変換テーブル。これは、言語ごとに 1 つの行と複数の「テキスト」フィールドを意味するため、N フィールドに対して 1 つの (左) 結合のみが必要です。次に、「製品」テーブルにデフォルト フィールドを簡単に追加し、変換テーブルを簡単に削除して再挿入し、カスタム翻訳用の 2 つ目のテーブルを (オンデマンドで) 作成して、これを削除することもできます。再挿入)、すべての外部キーがまだあります。

これが機能することを確認する例を作成しましょう。

まず、テーブルを作成します。

CREATE TABLE dbo.T_Languages
(
     Lang_ID int NOT NULL
    ,Lang_NativeName national character varying(200) NULL
    ,Lang_EnglishName national character varying(200) NULL
    ,Lang_ISO_TwoLetterName character varying(10) NULL
    ,CONSTRAINT PK_T_Languages PRIMARY KEY ( Lang_ID )
);

GO




CREATE TABLE dbo.T_Products
(
     PROD_Id int NOT NULL
    ,PROD_InternalName national character varying(255) NULL
    ,CONSTRAINT PK_T_Products PRIMARY KEY ( PROD_Id )
); 

GO



CREATE TABLE dbo.T_Products_i18n
(
     PROD_i18n_PROD_Id int NOT NULL
    ,PROD_i18n_Lang_Id int NOT NULL
    ,PROD_i18n_Text national character varying(200) NULL
    ,CONSTRAINT PK_T_Products_i18n PRIMARY KEY (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id)
);

GO

-- ALTER TABLE dbo.T_Products_i18n  WITH NOCHECK ADD  CONSTRAINT FK_T_Products_i18n_T_Products FOREIGN KEY(PROD_i18n_PROD_Id)
ALTER TABLE dbo.T_Products_i18n  
    ADD CONSTRAINT FK_T_Products_i18n_T_Products 
    FOREIGN KEY(PROD_i18n_PROD_Id)
    REFERENCES dbo.T_Products (PROD_Id)
ON DELETE CASCADE 
GO

ALTER TABLE dbo.T_Products_i18n CHECK CONSTRAINT FK_T_Products_i18n_T_Products
GO

ALTER TABLE dbo.T_Products_i18n 
    ADD  CONSTRAINT FK_T_Products_i18n_T_Languages 
    FOREIGN KEY( PROD_i18n_Lang_Id )
    REFERENCES dbo.T_Languages( Lang_ID )
ON DELETE CASCADE 
GO

ALTER TABLE dbo.T_Products_i18n CHECK CONSTRAINT FK_T_Products_i18n_T_Products
GO


    
CREATE TABLE dbo.T_Products_i18n_Cust
(
     PROD_i18n_Cust_PROD_Id int NOT NULL
    ,PROD_i18n_Cust_Lang_Id int NOT NULL
    ,PROD_i18n_Cust_Text national character varying(200) NULL
    ,CONSTRAINT PK_T_Products_i18n_Cust PRIMARY KEY ( PROD_i18n_Cust_PROD_Id, PROD_i18n_Cust_Lang_Id )
);

GO

ALTER TABLE dbo.T_Products_i18n_Cust  
    ADD CONSTRAINT FK_T_Products_i18n_Cust_T_Languages 
    FOREIGN KEY(PROD_i18n_Cust_Lang_Id)
    REFERENCES dbo.T_Languages (Lang_ID)

ALTER TABLE dbo.T_Products_i18n_Cust CHECK CONSTRAINT FK_T_Products_i18n_Cust_T_Languages

GO



ALTER TABLE dbo.T_Products_i18n_Cust  
    ADD CONSTRAINT FK_T_Products_i18n_Cust_T_Products 
    FOREIGN KEY(PROD_i18n_Cust_PROD_Id)
REFERENCES dbo.T_Products (PROD_Id)
GO

ALTER TABLE dbo.T_Products_i18n_Cust CHECK CONSTRAINT FK_T_Products_i18n_Cust_T_Products
GO

次に、データを入力します

DELETE FROM T_Languages;
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (1, N'English', N'English', N'EN');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (2, N'Deutsch', N'German', N'DE');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (3, N'Français', N'French', N'FR');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (4, N'Italiano', N'Italian', N'IT');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (5, N'Russki', N'Russian', N'RU');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (6, N'Zhungwen', N'Chinese', N'ZH');

DELETE FROM T_Products;
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (1, N'Orange Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (2, N'Apple Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (3, N'Banana Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (4, N'Tomato Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (5, N'Generic Fruit Juice');

DELETE FROM T_Products_i18n;
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 1, N'Orange Juice');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 2, N'Orangensaft');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 3, N'Jus d''Orange');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 4, N'Succo d''arancia');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (2, 1, N'Apple Juice');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (2, 2, N'Apfelsaft');

DELETE FROM T_Products_i18n_Cust;
INSERT INTO T_Products_i18n_Cust (PROD_i18n_Cust_PROD_Id, PROD_i18n_Cust_Lang_Id, PROD_i18n_Cust_Text) VALUES (1, 2, N'Orangäsaft'); -- Swiss German, if you wonder

次に、データをクエリします。

DECLARE @__in_lang_id int
SET @__in_lang_id = (
    SELECT Lang_ID
    FROM T_Languages
    WHERE Lang_ISO_TwoLetterName = 'DE'
)

SELECT 
     PROD_Id 
    ,PROD_InternalName -- Default Fallback field (internal name/one language only setup), just in ResultSet for demo-purposes
    ,PROD_i18n_Text  -- Translation text, just in ResultSet for demo-purposes
    ,PROD_i18n_Cust_Text  -- Custom Translations (e.g. per customer) Just in ResultSet for demo-purposes
    ,COALESCE(PROD_i18n_Cust_Text, PROD_i18n_Text, PROD_InternalName) AS DisplayText -- What we actually want to show 
FROM T_Products 

LEFT JOIN T_Products_i18n 
    ON PROD_i18n_PROD_Id = T_Products.PROD_Id 
    AND PROD_i18n_Lang_Id = @__in_lang_id 
    
LEFT JOIN T_Products_i18n_Cust 
    ON PROD_i18n_Cust_PROD_Id = T_Products.PROD_Id
    AND PROD_i18n_Cust_Lang_Id = @__in_lang_id
    

怠け者の場合は、言語テーブルの主キーとして ISO-TwoLetterName ('DE'、'EN' など) を使用することもできます。その場合、言語 ID を検索する必要はありません。しかし、そうする場合は、代わりにIETF 言語タグを使用することをお勧めします。これは、de-CH と de-DE を取得するため、正書法の観点からは実際には同じではない (どこでも ß の代わりに 2 つの s がある) ためです。 、同じ基本言語ですが。特に en-US と en-GB/en-CA/en-AU または fr-FR/fr-CA が同様の問題を抱えていることを考えると、それはあなたにとって重要かもしれない小さな詳細です。
引用: 私たちはそれを必要としません。私たちはソフトウェアを英語でのみ行っています。
回答: はい - しかしどれですか??

いずれにせよ、整数 ID を使用する場合は柔軟であり、後でいつでもメソッドを変更できます。
そして、その整数を使用する必要があります。なぜなら、失敗した Db 設計ほど厄介で、破壊的で、厄介なものはないからです。

RFC 5646ISO 639-2も参照してください。

また、「私たち」は「1 つのカルチャのみ」(通常は en-US など) に対してのみアプリケーションを作成しているという場合は、余分な整数は必要ありません。IANA 言語タグですね。 彼らは次のようになるからです:

de-DE-1901
de-DE-1996

de-CH-1901
de-CH-1996

(1996 年に正書法の改正がありました...) 単語のつづりが間違っている場合は、辞書で単語を見つけてみてください。これは、法律および公共サービス ポータルを扱うアプリケーションで非常に重要になります。
さらに重要なことに、キリル文字からラテン文字に変更されている地域があり、あいまいな正書法改革の表面的な煩わしさよりも厄介かもしれません.いずれにせよ、念のため、その整数をそこに入れておく方が良いです...

編集:
そしてON DELETE CASCADE 後に追加することによって

REFERENCES dbo.T_Products( PROD_Id )

あなたは単に言うことができます:DELETE FROM T_Products、そして外部キー違反はありません。

照合に関しては、次のようにします。

A) 独自の DAL を
用意する B) 必要な照合名を言語テーブルに保存する

照合順序を独自のテーブルに配置したい場合があります。たとえば、次のようになります。

SELECT * FROM sys.fn_helpcollations() 
WHERE description LIKE '%insensitive%'
AND name LIKE '%german%' 

C) auth.user.language 情報で照合名を使用できるようにする

D) SQL を次のように記述します。

SELECT 
    COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName 
FROM T_Groups 

ORDER BY GroupName COLLATE {#COLLATION}

E) 次に、DAL でこれを行うことができます。

cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)

これにより、この完全に構成された SQL-Query が得られます

SELECT 
    COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName 
FROM T_Groups 

ORDER BY GroupName COLLATE German_PhoneBook_CI_AI
于 2014-09-26T09:28:08.043 に答える
49

いくつかの理由から、3番目のオプションが最適です。

  • 新しい言語のデータベーススキーマを変更する必要はありません(したがって、コードの変更を制限します)
  • 実装されていない言語や特定のアイテムの翻訳のために多くのスペースを必要としません
  • 最も柔軟性があります
  • あなたはまばらなテーブルで終わらない
  • nullキーについて心配する必要はなく、nullエントリの代わりに既存の翻訳を表示していることを確認する必要はありません。
  • データベースを変更または拡張して他の翻訳可能なアイテム/モノなどを含める場合は、同じテーブルとシステムを使用できます。これは、残りのデータから非常に切り離されています。

-アダム

于 2008-11-25T09:32:42.470 に答える
11

次の例を見てください。

PRODUCTS (
    id   
    price
    created_at
)

LANGUAGES (
    id   
    title
)

TRANSLATIONS (
    id           (// id of translation, UNIQUE)
    language_id  (// id of desired language)
    table_name   (// any table, in this case PRODUCTS)
    item_id      (// id of item in PRODUCTS)
    field_name   (// fields to be translated)
    translation  (// translation text goes here)
)

説明する必要はないと思いますが、構造はそれ自体を説明しています。

于 2013-08-12T07:22:08.547 に答える
8

私は通常、このアプローチ(実際のSQLではありません)を使用しますが、これは最後のオプションに対応しています。

table Product
productid INT PK, price DECIMAL, translationid INT FK

table Translation
translationid INT PK

table TranslationItem
translationitemid INT PK, translationid INT FK, text VARCHAR, languagecode CHAR(2)

view ProductView
select * from Product
inner join Translation
inner join TranslationItem
where languagecode='en'

翻訳可能なすべてのテキストを 1 か所にまとめることで、メンテナンスが非常に簡単になるからです。翻訳は翻訳会社にアウトソーシングされることもあります。この方法では、大きなエクスポート ファイルを 1 つ送信するだけで、同じように簡単にインポートして戻すことができます。

于 2008-11-25T09:37:47.047 に答える
4

技術的な詳細と解決策に進む前に、少し立ち止まって、要件についていくつか質問してください。その答えは、技術的なソリューションに大きな影響を与える可能性があります。そのような質問の例は次のとおりです。
- すべての言語が常に使用されますか?
- 誰が、いつ、異なる言語バージョンで列を埋めますか?
- ユーザーがテキストの特定の言語を必要とする場合、システムに何もない場合はどうなりますか?
- テキストのみをローカライズするか、他の項目もあります (たとえば、PRICE は異なる可能性があるため、$ と € に格納できます)。

于 2008-11-25T09:59:32.013 に答える
3

ローカリゼーションのヒントを探していて、このトピックを見つけました。なぜこれが使用されているのか疑問に思っていました:

CREATE TABLE T_TRANSLATION (
   TRANSLATION_ID
)

したがって、user39603 が示唆するようなものが得られます。

table Product
productid INT PK, price DECIMAL, translationid INT FK

table Translation
translationid INT PK

table TranslationItem
translationitemid INT PK, translationid INT FK, text VARCHAR, languagecode CHAR(2)

view ProductView
select * from Product
inner join Translation
inner join TranslationItem
where languagecode='en'

これを取得するために、テーブル Translation をそのままにしておくことはできません:

    table Product
    productid INT PK, price DECIMAL

    table ProductItem
    productitemid INT PK, productid INT FK, text VARCHAR, languagecode CHAR(2)

    view ProductView
    select * from Product
    inner join ProductItem
    where languagecode='en'
于 2012-08-06T18:00:39.017 に答える
1

以下のアプローチは実行可能でしょうか?複数の列を変換する必要があるテーブルがあるとします。したがって、製品の場合、翻訳が必要な製品名と製品の説明の両方を持つことができます。次のことをしていただけませんか。

CREATE TABLE translation_entry (
      translation_id        int,
      language_id           int,
      table_name            nvarchar(200),
      table_column_name     nvarchar(200),
      table_row_id          bigint,
      translated_text       ntext
    )

    CREATE TABLE translation_language (
      id int,
      language_code CHAR(2)
    )   
于 2012-12-13T16:03:31.027 に答える
1

私はランダマイザーに同意します。テーブルの「翻訳」が必要な理由がわかりません。

私は、これで十分だと思います:

TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName
于 2012-08-21T20:35:08.347 に答える
1

多言語データベースを作成するときは、製品テーブルから名前や説明などのフィールドを切り取り、翻訳されたリソースに移動していることを覚えておく必要があります。

変換されたリソースは別のテーブルである場合があります。この例では、SQL ビューで動作するように設計されているため、クエリを簡素化し、基になるアプリを簡単に開発できます。

多言語データベース

これは Web ページ上のフィールド ラベルのグローバルな翻訳を含むテーブルであるため、LabelTranslations を分離しました。呼び方は自由ですが、ステートレスで、特定の製品やカテゴリに依存しません。

ProductTranslations の CategoryTranslations はステートフルなものです。これは、「name」の記述子が実際の製品名になることを意味します。

マテリアライズド ビューを使用すると、単純な SQL ビューよりも優れたパフォーマンスを得ることができます (ストレージ スペースのコストと、それらを更新するための基盤となるアプリ開発の手間がかかります)。必要に応じて、より重い SQL ビューを使用してください。

Postgresでカテゴリ マテリアライズド ビューを作成するには:

CREATE MATERIALIZED VIEW VCategories AS (
    SELECT cat.id, lng.iso_639_1_code, ct.descriptor, ct.value
    FROM Categories cat
    JOIN CategoryTranslations ct ON ct.category_id = cat.id
    JOIN Languages lng ON lng.id = ct.language_id
);

ID 120 のカテゴリのすべての翻訳を照会するには

SELECT * FROM VCategories WHERE id = 120 AND iso_639_1_code = 'en'

アプリケーションのコードを操作しているときに便利だと思います。翻訳をクエリしてレコードを検索するための非常に単純なコードを書くことができます。

于 2020-09-07T21:38:14.267 に答える
0

「どれがいいか」はプロジェクトの状況による。最初のものは、選択と保守が簡単で、エンティティを選択するときにテーブルを結合する必要がないため、パフォーマンスも最適です。あなたのプロジェクトが2つか3つの言語しかサポートしておらず、それが増加しないことが確認された場合は、使用できます。

2 つ目は問題ありませんが、理解と維持が困難です。そして、パフォーマンスは最初のものよりも悪いです。

後者はスケーラビリティには優れていますが、パフォーマンスは劣っています。T_TRANSLATION_ENTRY テーブルはどんどん大きくなっていきます。いくつかのテーブルからエンティティのリストを取得したい場合は大変です。

于 2013-04-03T05:34:02.687 に答える