267

我正在开发一个多语言软件。就应用程序代码而言,可本地化不是问题。我们可以使用特定于语言的资源,并拥有与它们配合良好的各种工具。

但是定义多语言数据库模式的最佳方法是什么?假设我们有很多表(100 或更多),每个表可以有多个可以本地化的列(大多数 nvarchar 列应该是可本地化的)。例如,其中一张表可能包含产品信息:

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

我可以想到三种方法来支持 NAME 和 DESCRIPTION 列中的多语言文本:

  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. 每种语言都有行的翻译表

    不存储翻译文本,只存储翻译表的外键。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)
    )
    

每种解决方案各有利弊,我想知道您对这些方法有何经验,您有什么建议以及您将如何设计多语言数据库模式。

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

这样,如果您有多个可翻译列,则只需要一个连接即可获得它+,因为您没有自动生成 translateid,因此将项目及其相关翻译一起导入可能会更容易。

不利的一面是,如果您有一个复杂的语言回退机制,您可能需要为每个翻译表实现该机制 - 如果您依赖某些存储过程来执行此操作。如果您从应用程序中执行此操作,这可能不会成为问题。

让我知道你的想法——我也即将为我们的下一次申请做出决定。到目前为止,我们已经使用了您的第 3 种类型。

于 2008-11-27T10:02:17.933 回答
67

这是一个有趣的问题,所以让我们使用死灵法。

让我们从方法 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 的需求突然改变而改变了模式,并创建了一个新表,旧表离开但引用字段重命名,您不会收到任何警告。当您在不选择wing 参数(==> guid.empty)的情况下运行报告时,它甚至可以工作。但是突然间,当实际用户实际选择了一个机翼时 ==>繁荣这种方法完全打破了任何类型的测试。


方法2:
简而言之:“好”的想法(警告-讽刺),让我们将方法3的缺点(许多条目时速度慢)与方法1的相当可怕的缺点结合起来。
这种方法的唯一优点是你保持所有翻译都在一张表中,因此使维护变得简单。但是,使用方法 1 和动态 SQL 存储过程以及包含翻译的(可能是临时的)表以及目标表的名称(假设您将所有文本字段命名为相同的)。


方法3:
一张表,所有翻译:缺点:你必须在products表中为你要翻译的n个字段存储n个外键。因此,您必须对 n 个字段进行 n 次连接。当翻译表是全局的时,它有很多条目,并且连接变得很慢。此外,您总是必须为 n 个字段加入 T_TRANSLATION 表 n 次。这是相当大的开销。现在,当您必须为每位客户提供自定义翻译时,您会怎么做?您必须将另一个 2x n 连接添加到另一个表中。如果你必须加入,比如说 10 个表,还有 2x2xn = 4n 个额外的连接,那真是一团糟!此外,这种设计使得可以对 2 个表使用相同的翻译。如果我更改一个表中的项目名称,我真的想每次都更改另一个表中的条目吗?

另外,您不能再删除并重新插入表,因为现在产品表中有外键...您当然可以省略设置 FK,然后<insert name of your "favourite" person here>可以删除表并重新插入所有带有newid() [或通过在插入中指定 id,但具有标识插入 OFF ] 的条目,这将(并且将)很快导致数据垃圾(和空引用异常)。


方法 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,除了由于解析 XML 的开销(而不是从 PG hstore 中的关联数组中读取条目),它变得太慢了,而且 xml 编码使它变得太痛苦而无法使用。


方法5(根据孙悟空的推荐,你应该选择的那个):每个“产品”表对应一个翻译表。这意味着每种语言一行,以及几个“文本”字段,因此它只需要在 N 个字段上进行 ONE(左)连接。然后您可以轻松地在“产品”表中添加一个默认字段,您可以轻松删除并重新插入翻译表,您可以创建第二个自定义翻译表(按需),您也可以删除并重新插入),您仍然拥有所有外键。

让我们举个例子来看看这个作品:

首先,创建表:

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,这实际上是不一样的正字法(双 s 而不是 ß 到处) ,尽管它是相同的基础语言。这只是一个对您可能很重要的小细节,特别是考虑到 en-US 和 en-GB/en-CA/en-AU 或 fr-FR/fr-CA 有类似的问题。
Quote: 我们不需要它,我们只用英文做我们的软件。
答案:是的 - 但哪一个??

无论如何,如果您使用整数 ID,您会很灵活,并且可以在以后随时更改您的方法。
而且您应该使用该整数,因为没有什么比拙劣的 Db 设计更令人讨厌、破坏性和麻烦的了。

另见RFC 5646ISO 639-2

而且,如果您仍然说“我们”为“仅一种文化”(通常像 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 查询

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

第三种选择是最好的,原因如下:

  • 不需要为新语言更改数据库架构(从而限制代码更改)
  • 对于未实现的语言或特定项目的翻译不需要大量空间
  • 提供最大的灵活性
  • 你不会得到稀疏的表
  • 您不必担心空键并检查您显示的是现有翻译而不是某些空条目。
  • 如果您更改或扩展您的数据库以包含其他可翻译的项目/事物/等,您可以使用相同的表和系统 - 这与其余数据非常分离。

-亚当

于 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'

因为将所有可翻译的文本集中在一个位置使维护变得更加容易。有时翻译会外包给翻译局,这样您就可以只向他们发送一个大的导出文件,然后再轻松地将其导入回来。

于 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'

你不能把表格翻译出来,这样你就可以得到这个:

    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

我同意随机化器。我不明白你为什么需要一个表格“翻译”。

我认为,这就足够了:

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

下面的方法可行吗?假设您有超过 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

文档描述了可能的解决方案以及每种方法的优缺点。我更喜欢“行本地化”,因为您不必在添加新语言时修改数据库架构。

于 2020-03-23T03:50:22.867 回答
1

您需要记住,当您创建多语言数据库时,您会从产品表中删除名称或描述等字段,并将其移动到已翻译资源中。

翻译后的资源可能是另一个表,就像在我的示例中一样,它旨在与 SQL 视图一起使用,以实现查询的简单性和底层应用程序的友好开发

多语言数据库

我将 LabelTranslations 分开,因为这是一个包含网页上字段标签的全局翻译的表格。您可以随意称呼它,它们是无国籍的,不依赖于特定的产品或类别。

ProductTranslations 的 CategoryTranslations 是有状态的,这意味着“名称”的描述符将是实际的产品名称。

与简单的 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种语言,并且不会增加,您可以使用它。

第二个很好,但很难理解和维护。而且性能比第一个差。

最后一个在可扩展性方面很好,但在性能方面很差。T_TRANSLATION_ENTRY 表会越来越大,当您想从某些表中检索实体列表时,这很糟糕。

于 2013-04-03T05:34:02.687 回答