web-dev-qa-db-ja.com

多言語データベースのスキーマ

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

しかし、多言語データベーススキーマを定義する最良の方法は何ですか?多数のテーブル(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. 各言語の列を含む翻訳テーブル

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

    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. 各言語の行を含む翻訳テーブル

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

    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)
    )
    

各ソリューションには長所と短所がありますが、これらのアプローチの経験、推奨するもの、多言語データベーススキーマの設計方法について知りたいと思います。

221
qbeuek

翻訳可能な各テーブルに関連する変換テーブルがあるとどう思いますか?

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テキスト)

この方法では、複数の翻訳可能な列がある場合、それを取得するために単一の結合のみが必要になります。

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

あなたの考えをお聞かせください-私はまた、次のアプリケーションのためにこれについて決定をしようとしています。これまで、3番目のタイプを使用しました。

109
SunWuKung

これは興味深い問題なので、壊死しましょう。

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

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)。しかし、突然、実際のユーザーが実際に翼を選択すると==> boomになります。 この方法は、あらゆる種類のテストを完全に中断します。


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


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

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


-- 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で値を取得できます。この場合、string-variableを

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エンコードが非常に苦痛になります。


このWORKSを見るための例を作りましょう:

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

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-language tag を使用することをお勧めします。これは、de-CHとde-DEを取得するためです。どこでもßの代わりにdouble s)、それは同じベース言語ですが。特に、en-USとen-GB/en-CA/en-AUまたはfr-FR/fr-CAに同様の問題があることを考えると、あなたにとって重要なほんの少しの詳細です。
引用:私たちはそれを必要としません。私たちはソフトウェアを英語でしか行いません。
回答:はい-しかし、どれですか?

とにかく、整数IDを使用する場合、柔軟性があり、後でメソッドを変更できます。

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

そして、まだ「we」onlyと言っている場合は、「only one culture」(通常en-USなど)のアプリケーションを作成します。したがって、余分な整数は必要ありません。これは IANA言語タグ に言及するのに良い時間と場所でしょう。
次のようになるためです。

de-DE-1901
de-DE-1996

そして

de-CH-1901
de-CH-1996

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

編集:
そして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
49
Stefan Steiger

3番目のオプションは、いくつかの理由により最適です。

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

-アダム

47
Adam Davis

この例を見てください:

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)
)

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

9
bamburik

私は通常、このアプローチ(実際の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つの大きなエクスポートファイルを送信し、同じように簡単にインポートして戻すことができます。

8
user39603

ローカライズのヒントを探していたところ、このトピックを見つけました。私はこれがなぜ使用されるのか疑問に思っていました:

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'

あなたはこれを得るためにテーブル翻訳をそのままにしておくことはできません:

    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'
3
randomizer

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

3
Aleris

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

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)
    )   
1
davey

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

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

TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName
1
Bart VW

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

2つ目はオーケーですが、理解して維持するのは困難です。そして、パフォーマンスは最初のものよりも悪いです。

最後の1つは拡張性は優れていますが、パフォーマンスは劣ります。 T_TRANSLATION_ENTRYテーブルはますます大きくなり、いくつかのテーブルからエンティティのリストを取得する場合はひどいものになります。

0
studyzy