Schema for a multilanguage database
我正在开发一个多语言软件。就应用程序代码而言,本地化不是问题。我们可以使用特定于语言的资源,并拥有各种能够与它们一起工作的工具。
但是,定义多语言数据库模式的最佳方法是什么?假设我们有很多表(100个或更多),并且每个表可以有多个可以本地化的列(nvarchar的大多数列应该是可本地化的)。例如,其中一个表可能包含产品信息:
1 2 3 4 5 | CREATE TABLE T_PRODUCT ( NAME NVARCHAR(50), DESCRIPTION NTEXT, PRICE NUMBER(18, 2) ) |
我可以想到三种方法来支持名称和描述列中的多语言文本:
每种语言单独列
当我们向系统添加新语言时,必须创建额外的列来存储翻译后的文本,如下所示:
1 2 3 4 5 6 7 8 9 | 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) ) |
带有每种语言列的翻译表
不存储翻译文本,只存储翻译表的外键。翻译表包含每种语言的一列。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 ) |
具有每种语言行的翻译表
不存储翻译文本,只存储翻译表的外键。翻译表只包含一个键,而单独的表包含每种语言翻译的行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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) ) |
每个解决方案都有优缺点,我想知道您对这些方法有哪些经验,您推荐什么,以及如何设计多语言数据库模式。
对于每个可翻译表都有一个相关的翻译表,您认为如何?
CREATE TABLE T_PRODUCT (pr_id int, PRICE NUMBER(18, 2))
CREATE TABLE T_PRODUCT_tr (pr_id INT FK, languagecode varchar, pr_name text, pr_descr text)
这样,如果您有多个可翻译列,则只需要一个联接即可获得它+,因为您没有自动生成translationid,因此导入项目及其相关翻译可能更容易。
这的消极方面是,如果您有一个复杂的语言回退机制,那么可能需要为每个翻译表实现这个机制——如果您依赖某个存储过程来实现这个机制的话。如果你从应用程序中做到这一点,这可能不是问题。
让我知道你的想法-我也将为我们的下一个申请做一个决定。到目前为止,我们已经使用了您的第三种类型。
第三种选择是最好的,原因如下:
- 不需要为新语言修改数据库模式(从而限制代码更改)
- 对于未实现的语言或特定项目的翻译不需要很大的空间
- 提供最大的灵活性
- 你不会以稀疏的表格结束
- 您不必担心空键,也不必检查是否显示了一个现有的翻译而不是一些空条目。
- 如果更改或扩展数据库以包含其他可翻译项目/事物/等,则可以使用相同的表和系统-这与其他数据非常不耦合。
-亚当
这是一个有趣的问题,所以让我们来讨论一下。好的。
让我们从方法1的问题开始:
问题:您正在取消规格化以节省速度。
在SQL中(postgresql和hstore除外),不能传递参数语言,并说:好的。
1 | SELECT ['DESCRIPTION_' + @in_language] FROM T_Products |
所以你必须这样做:好的。
1 2 3 4 5 6 7 8 9 | 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",因此您不必更改所有查询。好的。
这通常会导致类似的结果(顺便说一下,它不能用于视图或表值函数中,如果您实际需要筛选报告日期,这确实是一个问题)好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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格式输入(普通的花园品种程序员通常不会这样做,并且在报告的情况下,用户肯定不会为您做,即使明确指示这样做)。< BR>
b)最重要的是,您不必进行任何语法检查。如果
方法2:
简而言之:"好"的想法(警告-讽刺),让我们把方法3的缺点(当有许多条目时速度较慢)和方法1的相当可怕的缺点结合起来。< BR>这种方法的唯一优点是将所有翻译保存在一个表中,从而使维护变得简单。但是,使用方法1和动态SQL存储过程、包含翻译的(可能是临时的)表以及目标表的名称(假设您将所有文本字段命名为相同的名称,这很简单)。好的。
方法3:
所有翻译的一个表:缺点:对于要翻译的n个字段,必须在Products表中存储n个外键。因此,必须对n个字段执行n个联接。当转换表是全局的时,它有许多条目,并且连接变慢。此外,对于n个字段,您总是必须加入t_翻译表n次。这是相当大的开销。现在,当您必须为每个客户提供自定义翻译时,您要做什么?您必须在一个额外的表中添加另一个2XN联接。如果你必须连接,比如说10个表,有2x2xn=4n个附加连接,那真是一团糟!此外,这种设计还可以在两个表中使用相同的翻译。如果更改一个表中的项名称,是否确实要每次都更改另一个表中的项?好的。
另外,您不能再删除和重新插入该表,因为现在产品表中有外键…当然,您可以省略设置fks,然后
方法4(未列出):将所有语言存储在数据库的XML字段中。例如好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | -- 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&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查询获取该值,在该查询中可以将字符串变量放入好的。
1 | filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla |
您可以这样更新值:好的。
1 2 3 | UPDATE YOUR_TABLE SET YOUR_XML_FIELD_NAME.modify('replace value of (/lang/de/text())[1] with""I am a ''value ""') WHERE id = 1 |
你可以用
有点像postgre hstore,但由于分析XML的开销(而不是从pg hstore中的关联数组中读取条目),它变得太慢,加上XML编码会使它变得太痛苦而无法使用。
好的。
方法5(根据孙武功的建议,您应该选择一种方法):每个"产品"表都有一个翻译表。这意味着每种语言有一行,并且有几个"文本"字段,所以在n个字段上只需要一个(左)联接。然后,您可以轻松地在"product"表中添加默认字段,您可以轻松地删除和重新插入翻译表,还可以为自定义翻译(按需)创建第二个表,您还可以删除和重新插入该表,并且您仍然拥有所有外键。好的。
让我们举一个例子来说明这一点:好的。
首先,创建表:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | CREATE TABLE [dbo].[T_Languages]( [Lang_ID] [int] NOT NULL, [Lang_NativeName] [nvarchar](200) NULL, [Lang_EnglishName] [nvarchar](200) NULL, [Lang_ISO_TwoLetterName] [varchar](10) NULL, CONSTRAINT [PK_T_Languages] PRIMARY KEY CLUSTERED ( [Lang_ID] 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].[T_Products]( [PROD_Id] [int] NOT NULL, [PROD_InternalName] [nvarchar](255) NULL, CONSTRAINT [PK_T_Products] PRIMARY KEY CLUSTERED ( [PROD_Id] 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].[T_Products_i18n]( [PROD_i18n_PROD_Id] [int] NOT NULL, [PROD_i18n_Lang_Id] [int] NOT NULL, [PROD_i18n_Text] [nvarchar](200) NULL, CONSTRAINT [PK_T_Products_i18n] PRIMARY KEY CLUSTERED ( [PROD_i18n_PROD_Id] ASC, [PROD_i18n_Lang_Id] 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 -- 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] WITH CHECK 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] WITH CHECK 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_Languages] 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] [nvarchar](200) NULL, CONSTRAINT [PK_T_Products_i18n_Cust] PRIMARY KEY CLUSTERED ( [PROD_i18n_Cust_PROD_Id] ASC, [PROD_i18n_Cust_Lang_Id] 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 ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH CHECK ADD CONSTRAINT [FK_T_Products_i18n_Cust_T_Languages] FOREIGN KEY([PROD_i18n_Cust_Lang_Id]) REFERENCES [dbo].[T_Languages] ([Lang_ID]) GO ALTER TABLE [dbo].[T_Products_i18n_Cust] CHECK CONSTRAINT [FK_T_Products_i18n_Cust_T_Languages] GO --ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH NOCHECK ADD CONSTRAINT [FK_T_Products_i18n_Cust_T_Products] FOREIGN KEY([PROD_i18n_Cust_PROD_Id]) ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH CHECK 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 |
然后填写数据好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 |
然后查询数据:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 |
如果你很懒惰,那么你也可以使用is o twolettername(‘de’,‘en’等)作为语言表的主键,那么你不必查找语言ID。但是如果你这样做了,你可能想使用ietf语言标记,这是更好的,因为你得到了de ch和de de,而这实际上不是相同的或是不同的(double s,而不是f?尽管它是相同的基础语言。这对您来说可能是一个很重要的小细节,特别是考虑到en-US和en-GB/en-CA/en-AU或fr-fr/fr-CA有类似的问题。
报价:我们不需要它,我们只做英文软件。
回答:是-但是哪一个??BR/>好的。
无论如何,如果您使用整数ID,那么您是灵活的,并且可以在以后的任何时候更改您的方法。< BR>你应该使用这个整数,因为没有什么比糟糕的数据库设计更烦人、更具破坏性和更麻烦的了。好的。
另见RFC 5646,ISO 639-2,好的。
而且,如果你仍然说"我们"只是为了"一种文化"(就像我们通常所说的那样),那么我不需要额外的整数,这将是一个提到IANA语言标签的好时机和地点,不是吗?< BR>因为他们是这样的:好的。
1 2 | de-DE-1901 de-DE-1996 |
和好的。
1 2 | de-CH-1901 de-CH-1996 |
(1996年有一项正字法改革…)如果拼写错误,请尝试在字典中查找单词;这在处理法律和公共服务门户的应用程序中非常重要。
更重要的是,有些地区正在从西里尔字母改为拉丁字母,这可能比一些模糊的正字法改革表面上的麻烦更麻烦,这也是为什么这可能也是一个重要的考虑因素,取决于你居住的国家。不管怎样,最好是用那个整数,以防万一…好的。
编辑:
在后面加上EDOCX1[0]好的。
1 | REFERENCES [dbo].[T_Products] ([PROD_Id]) |
您可以简单地说:
至于排序规则,我会这样做:好的。
a)拥有自己的DAL
b)将所需的排序规则名称保存在语言表中
好的。
您可能希望将排序规则放在自己的表中,例如:好的。
1 2 3 | SELECT * FROM sys.fn_helpcollations() WHERE description LIKE '%insensitive%' AND name LIKE '%german%' |
c)在auth.user.language信息中提供排序规则名称好的。
d)这样编写SQL:好的。
1 2 3 4 5 | SELECT COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName FROM T_Groups ORDER BY GroupName COLLATE {#COLLATION} |
e)然后,您可以在DAL中执行此操作:好的。
1 | cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation) |
然后,它将为您提供这个完美组合的SQL查询好的。
1 2 3 4 5 | SELECT COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName FROM T_Groups ORDER BY GroupName COLLATE German_PhoneBook_CI_AI |
好啊。
看看这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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) ) |
我觉得没必要解释,结构本身就是这样描述的。
我通常会使用这种方法(而不是实际的SQL),这与您的最后一个选项相对应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 2 3 | CREATE TABLE T_TRANSLATION ( TRANSLATION_ID ) |
所以你会得到用户39603的建议:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 2 3 4 5 6 7 8 9 10 | 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' |
在讨论技术细节和解决方案之前,您应该停下来问一些关于需求的问题。答案会对技术解决方案产生巨大的影响。此类问题的例子如下:-所有语言都会一直使用吗?-谁和什么时候用不同的语言版本填充列?-当用户需要某种语言的文本而系统中没有文本时会发生什么?-只有文本要本地化,或者还有其他项目(例如,价格可以以美元和&euro;存储,因为它们可能不同)
下面的方法可行吗?假设您有多于一列需要转换的表。因此,对于产品,您可以同时拥有需要翻译的产品名称和产品描述。你能做一下吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 2 3 | TA_product: ProductID, ProductPrice TA_Language: LanguageID, Language TA_Productname: ProductnameID, ProductID, LanguageID, ProductName |
"哪一个最好"是根据项目情况而定的。第一种方法易于选择和维护,而且性能最好,因为在选择实体时不需要联接表。如果您确认您的poject只支持2或3种语言,并且不会增加,那么您可以使用它。
第二个是好的,但很难理解和保持。而且性能比第一个差。
最后一个在可伸缩性方面很好,但在性能方面却很差。t_translation_条目表将变得越来越大,当您想要从一些表中检索实体列表时,这是非常糟糕的。