Lược đồ cho cơ sở dữ liệu đa ngôn ngữ


235

Tôi đang phát triển một phần mềm đa ngôn ngữ. Theo như mã ứng dụng, tính cục bộ không phải là vấn đề. Chúng tôi có thể sử dụng tài nguyên ngôn ngữ cụ thể và có tất cả các loại công cụ hoạt động tốt với chúng.

Nhưng cách tiếp cận tốt nhất trong việc xác định lược đồ cơ sở dữ liệu đa ngôn ngữ là gì? Giả sử chúng ta có rất nhiều bảng (100 hoặc nhiều hơn) và mỗi bảng có thể có nhiều cột có thể được bản địa hóa (hầu hết các cột nvarchar nên có thể bản địa hóa). Ví dụ, một trong các bảng có thể chứa thông tin sản phẩm:

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

Tôi có thể nghĩ ra ba cách tiếp cận để hỗ trợ văn bản đa ngôn ngữ trong các cột NAME và MÔ TẢ:

  1. Cột riêng cho từng ngôn ngữ

    Khi chúng tôi thêm một ngôn ngữ mới vào hệ thống, chúng tôi phải tạo các cột bổ sung để lưu trữ văn bản đã dịch, như thế này:

    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. Bảng dịch với các cột cho mỗi ngôn ngữ

    Thay vì lưu trữ văn bản dịch, chỉ có một khóa ngoại cho bảng dịch được lưu trữ. Bảng dịch chứa một cột cho mỗi ngôn ngữ.

    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. Bảng dịch với các hàng cho mỗi ngôn ngữ

    Thay vì lưu trữ văn bản dịch, chỉ có một khóa ngoại cho bảng dịch được lưu trữ. Bảng dịch chỉ chứa một khóa và một bảng riêng biệt chứa một hàng cho mỗi bản dịch sang ngôn ngữ.

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

Có những ưu và nhược điểm đối với từng giải pháp và tôi muốn biết kinh nghiệm của bạn với các phương pháp này là gì, bạn khuyên gì và làm thế nào để bạn thiết kế một lược đồ cơ sở dữ liệu đa ngôn ngữ.



3
Bạn có thể kiểm tra liên kết này: gsdesign.ro/blog/multil Language-database-design-approach mặc dù đọc các bình luận rất hữu ích
Fareed Alnamrouti

3
LANGUAGE_CODElà chìa khóa tự nhiên, tránh LANGUAGE_ID.
gavenkoa

1
Tôi đã thấy / sử dụng 2. và 3., tôi không khuyên họ, bạn dễ dàng kết thúc với các hàng mồ côi. Thiết kế @SunWiKung trông IMO tốt hơn.
Guillaume86

4
Tôi thích thiết kế của SunWuKungs, điều trùng hợp là những gì chúng tôi đã thực hiện. Tuy nhiên, bạn cần xem xét các hợp đồng. Trong Sql Server ít nhất, mỗi cột có một thuộc tính đối chiếu, xác định những thứ như độ nhạy trường hợp, tính tương đương (hoặc không) của các ký tự có dấu và các cân nhắc cụ thể về ngôn ngữ khác. Việc bạn có sử dụng các đối chiếu cụ thể theo ngôn ngữ hay không phụ thuộc vào thiết kế ứng dụng tổng thể của bạn, nhưng nếu bạn hiểu sai, sẽ khó thay đổi sau này. Nếu bạn cần các đối chiếu cụ thể theo ngôn ngữ, thì bạn sẽ cần một cột cho mỗi ngôn ngữ, không phải một hàng cho mỗi ngôn ngữ.
Elroy Flynn

Câu trả lời:


113

Bạn nghĩ gì về việc có một bảng dịch liên quan cho mỗi bảng có thể dịch được?

TẠO BẢNG T_PRODVEL (pr_id int, GIÁ SỐ (18, 2))

TẠO BẢNG T_PRODVEL_tr (pr_id INT FK, languagecode varchar, văn bản pr_name, văn bản pr_descr)

Theo cách này, nếu bạn có nhiều cột có thể dịch, nó sẽ chỉ yêu cầu một lần tham gia để có được nó + vì bạn không tự động tạo một bản dịch, có thể dễ dàng nhập các mục cùng với các bản dịch liên quan của chúng.

Mặt tiêu cực của điều này là nếu bạn có một cơ chế dự phòng ngôn ngữ phức tạp, bạn có thể cần phải thực hiện điều đó cho mỗi bảng dịch - nếu bạn đang dựa vào một số thủ tục được lưu trữ để làm điều đó. Nếu bạn làm điều đó từ ứng dụng thì điều này có thể sẽ không thành vấn đề.

Hãy cho tôi biết những gì bạn nghĩ - tôi cũng sắp đưa ra quyết định về điều này cho ứng dụng tiếp theo của chúng tôi. Cho đến nay chúng tôi đã sử dụng loại thứ 3 của bạn.


2
Tùy chọn này tương tự như tùy chọn của tôi nr 1 nhưng tốt hơn. Vẫn còn khó để duy trì và yêu cầu tạo các bảng mới cho các ngôn ngữ mới, vì vậy tôi không muốn thực hiện nó.
qbeuek

28
nó không yêu cầu bảng mới cho ngôn ngữ mới - bạn chỉ cần thêm một hàng mới vào bảng _tr phù hợp với ngôn ngữ mới của mình, bạn chỉ cần tạo bảng _tr mới nếu bạn tạo bảng có thể dịch mới

3
Tôi tin rằng đây là một phương pháp tốt. các phương thức khác yêu cầu hàng tấn liên kết trái và khi bạn tham gia nhiều bảng, mỗi bảng có bản dịch sâu 3 cấp và mỗi trường có 3 trường bạn cần 3 * 3 9 tham gia trái chỉ để dịch .. thông minh khác 3. Ngoài ra dễ dàng hơn để thêm các ràng buộc vv và tôi tin tưởng tìm kiếm là cộng hưởng hơn.
GorillaApe

1
Khi T_PRODUCTcó 1 triệu hàng, T_PRODUCT_trsẽ có 2 triệu. Làm thế nào để giảm hiệu quả sql nhiều?
Mithril

1
@Mithril Dù bằng cách nào bạn cũng có 2 triệu hàng. Ít nhất bạn không cần tham gia với phương pháp này.
David D

56

Đây là một vấn đề thú vị, vì vậy hãy xem xét.

Hãy bắt đầu bằng các vấn đề của phương pháp 1:
Vấn đề: Bạn đang không chuẩn hóa để tiết kiệm tốc độ.
Trong SQL (ngoại trừ PostGreSQL với hstore), bạn không thể truyền ngôn ngữ tham số và nói:

SELECT ['DESCRIPTION_' + @in_language]  FROM T_Products

Vì vậy, bạn phải làm điều này:

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 

Điều đó có nghĩa là bạn phải thay đổi TẤT CẢ các truy vấn của mình nếu bạn thêm một ngôn ngữ mới. Điều này tự nhiên dẫn đến việc sử dụng "SQL động", do đó bạn không phải thay đổi tất cả các truy vấn của mình.

Điều này thường dẫn đến một cái gì đó như thế này (và nó không thể được sử dụng trong các khung nhìn hoặc các hàm có giá trị bảng bằng cách này, đây thực sự là một vấn đề nếu bạn thực sự cần phải lọc ngày báo cáo)

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

Vấn đề với điều này là
a) Định dạng ngày rất cụ thể về ngôn ngữ, do đó bạn gặp vấn đề ở đó, nếu bạn không nhập ở định dạng ISO (điều mà lập trình viên làm vườn trung bình thường không làm được và trong trường hợp một báo cáo người dùng chắc chắn như địa ngục sẽ không làm cho bạn, ngay cả khi được chỉ dẫn rõ ràng để làm như vậy).

b) đáng kể nhất , bạn mất bất kỳ loại kiểm tra cú pháp . Nếu <insert name of your "favourite" person here>thay đổi lược đồ vì đột nhiên các yêu cầu thay đổi cánh và một bảng mới được tạo, bảng cũ còn lại nhưng trường tham chiếu được đổi tên, bạn không nhận được bất kỳ loại cảnh báo nào. Một báo cáo thậm chí hoạt động khi bạn chạy nó mà không chọn tham số cánh (==> guide.empty). Nhưng đột nhiên, khi một người dùng thực sự thực sự chọn một cánh ==>bùng nổ . Phương pháp này hoàn toàn phá vỡ bất kỳ loại thử nghiệm.


Phương pháp 2:
Tóm lại: Ý tưởng "Tuyệt vời" (cảnh báo - châm biếm), hãy kết hợp các nhược điểm của phương pháp 3 (tốc độ chậm khi có nhiều mục) với các nhược điểm khá kinh khủng của phương pháp 1.
Ưu điểm duy nhất của phương pháp này là bạn giữ được tất cả các bản dịch trong một bảng, và do đó làm cho bảo trì đơn giản. Tuy nhiên, điều tương tự có thể đạt được với phương thức 1 và quy trình lưu trữ SQL động và bảng (có thể là tạm thời) có chứa các bản dịch và tên của bảng mục tiêu (và khá đơn giản giả sử bạn đặt tên cho tất cả các trường văn bản của mình là tương tự).


Phương pháp 3:
Một bảng cho tất cả các bản dịch: Nhược điểm: Bạn phải lưu trữ n Khóa ngoài trong bảng sản phẩm cho n trường bạn muốn dịch. Do đó, bạn phải thực hiện n tham gia cho n trường. Khi bảng dịch là toàn cầu, nó có nhiều mục và tham gia trở nên chậm. Ngoài ra, bạn luôn phải tham gia bảng T_TRANSLATION n lần cho n trường. Đây là một chi phí khá cao. Bây giờ, bạn sẽ làm gì khi bạn phải điều chỉnh các bản dịch tùy chỉnh cho mỗi khách hàng? Bạn sẽ phải thêm 2x n tham gia vào một bảng bổ sung. Nếu bạn phải tham gia, hãy nói 10 bảng, với 2x2xn = 4n tham gia bổ sung, thật là một mớ hỗn độn! Ngoài ra, thiết kế này cho phép sử dụng cùng một bản dịch với 2 bảng. Nếu tôi thay đổi tên mục trong một bảng, tôi có thực sự muốn thay đổi mục trong bảng khác MERYI LẦN MỘT LẦN KHÔNG?

Ngoài ra, bạn không thể xóa và chèn lại bảng nữa, vì hiện tại đã có khóa ngoại trong BẢNG SẢN PHẨM ... tất nhiên bạn có thể bỏ qua cài đặt FK, sau đó <insert name of your "favourite" person here>có thể xóa bảng và chèn lại tất cả các mục có newid () [hoặc bằng cách chỉ định id trong phần chèn, nhưng việc chèn nhận dạng TẮT ], và điều đó sẽ (và sẽ) dẫn đến rác dữ liệu (và ngoại lệ tham chiếu null) thực sự sớm.


Phương pháp 4 (không được liệt kê): Lưu trữ tất cả các ngôn ngữ trong trường XML trong cơ sở dữ liệu. ví dụ

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

Sau đó, bạn có thể nhận giá trị bằng XPath-Query trong SQL, nơi bạn có thể đặt biến chuỗi trong

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

Và bạn có thể cập nhật giá trị như thế này:

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 

Nơi bạn có thể thay thế /lang/de/...bằng'.../' + @in_language + '/...'

Giống như hstore PostGre, ngoại trừ do chi phí phân tích cú pháp XML (thay vì đọc một mục từ một mảng kết hợp trong PG hstore), nó trở nên quá chậm cộng với mã hóa xml khiến nó quá đau để trở nên hữu ích.


Phương pháp 5 (theo khuyến nghị của SunWuKung, người bạn nên chọn): Một bảng dịch cho mỗi bảng "Sản phẩm". Điều đó có nghĩa là một hàng cho mỗi ngôn ngữ và một số trường "văn bản", do đó, nó chỉ yêu cầu MỘT (trái) tham gia trên N trường. Sau đó, bạn có thể dễ dàng thêm trường mặc định trong phần "Sản phẩm", bạn có thể dễ dàng xóa và chèn lại bảng dịch và bạn có thể tạo bảng thứ hai để dịch tùy chỉnh (theo yêu cầu), bạn cũng có thể xóa và chèn lại) và bạn vẫn có tất cả các khóa ngoại.

Hãy làm một ví dụ để xem CÔNG TRÌNH này:

Đầu tiên, tạo các bảng:

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

Sau đó điền dữ liệu

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

Và sau đó truy vấn dữ liệu:

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

Nếu bạn lười biếng, thì bạn cũng có thể sử dụng ISO-TwoLetterName ('DE', 'EN', v.v.) làm khóa chính của bảng ngôn ngữ, sau đó bạn không phải tra cứu id ngôn ngữ. Nhưng nếu bạn làm như vậy, bạn có thể muốn sử dụng thẻ ngôn ngữ IETF thay vào đó, điều này tốt hơn, bởi vì bạn nhận được de-CH và de-DE, đây thực sự không phải là thông minh ortography (gấp đôi thay vì ß ở mọi nơi) , mặc dù đó là ngôn ngữ cơ bản giống nhau. Đó chỉ là một chi tiết nhỏ có thể quan trọng đối với bạn, đặc biệt khi xem xét rằng en-US và en-GB / en-CA / en-AU hoặc fr-FR / fr-CA có vấn đề tương tự.
Trích dẫn: chúng tôi không cần nó, chúng tôi chỉ làm phần mềm bằng tiếng Anh.
Trả lời: Có - nhưng cái nào ??

Dù sao, nếu bạn sử dụng ID số nguyên, bạn linh hoạt và có thể thay đổi phương pháp của mình bất kỳ lúc nào.
Và bạn nên sử dụng số nguyên đó, bởi vì không có gì khó chịu, phá hoại và rắc rối hơn một thiết kế Db bị lỗi.

Xem thêm RFC 5646 , ISO 639-2 ,

Và, nếu bạn vẫn nói "chúng tôi" chỉ tạo ra ứng dụng của mình cho "chỉ một nền văn hóa" (như en-US thường) - do đó tôi không cần thêm số nguyên đó, đây sẽ là thời điểm tốt để đề cập đến Thẻ ngôn ngữ IANA , phải không?
Bởi vì họ đi như thế này:

de-DE-1901
de-DE-1996

de-CH-1901
de-CH-1996

(đã có một cải cách chính tả vào năm 1996 ...) Hãy thử tìm một từ trong từ điển nếu nó sai chính tả; điều này trở nên rất quan trọng trong các ứng dụng liên quan đến cổng dịch vụ công cộng và pháp lý.
Quan trọng hơn, có những khu vực đang thay đổi từ bảng chữ cái cyrillic sang bảng chữ cái Latinh, điều này có thể gây rắc rối hơn so với sự phiền toái bề ngoài của một số cải cách chỉnh hình tối nghĩa, đó là lý do tại sao điều này cũng có thể được xem xét quan trọng, tùy thuộc vào quốc gia bạn sống. Bằng cách này hay cách khác, tốt hơn là có số nguyên đó trong đó, chỉ trong trường hợp ...

Chỉnh sửa:
Và bằng cách thêm vào ON DELETE CASCADE sau

REFERENCES dbo.T_Products( PROD_Id )

bạn có thể chỉ cần nói: DELETE FROM T_Productsvà không vi phạm khóa ngoại.

Đối với đối chiếu, tôi sẽ làm như thế này:

A) Có DAL
B của riêng bạn ) Lưu tên đối chiếu mong muốn trong bảng ngôn ngữ

Bạn có thể muốn đặt các bộ sưu tập vào bảng riêng của họ, ví dụ:

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

C) Có tên đối chiếu có sẵn trong thông tin auth.user. Ngôn ngữ của bạn

D) Viết SQL của bạn như thế này:

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

ORDER BY GroupName COLLATE {#COLLATION}

E) Sau đó, bạn có thể làm điều này trong DAL của bạn:

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

Sau đó sẽ cung cấp cho bạn truy vấn SQL được soạn hoàn hảo này

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

ORDER BY GroupName COLLATE German_PhoneBook_CI_AI

Đáp ứng chi tiết tốt, cảm ơn nhiều. Nhưng bạn nghĩ gì về các vấn đề đối chiếu trong giải pháp Phương pháp 5. Có vẻ như đây không phải là cách tốt nhất khi bạn cần sắp xếp hoặc lọc văn bản dịch trong môi trường đa ngôn ngữ với các đối chiếu khác nhau. Và trong trường hợp như vậy, Phương pháp 2 (mà bạn "bị tẩy chay" rất nhanh :)) có thể là một lựa chọn tốt hơn với các sửa đổi nhỏ cho thấy đối chiếu mục tiêu cho mỗi cột được bản địa hóa.
Eugene Evdokimov

2
@Eugene Evdokimov: Có, nhưng "ĐẶT HÀNG B" NG "luôn luôn là một vấn đề, bởi vì bạn không thể chỉ định nó là một biến. Cách tiếp cận của tôi sẽ là lưu tên đối chiếu trong bảng ngôn ngữ và có tên này trong userinfo. Sau đó, trên mỗi Câu lệnh SQL, bạn có thể nói ORDER BY COLUMN_NAME {#collation}, và sau đó bạn có thể thay thế trong dal của mình (cmd.CommandText = cmd.CommandText.Replace ("{# COLLATION}", auth.user. Language.collation). Ngoài ra, bạn có thể sắp xếp mã ứng dụng của mình, ví dụ như sử dụng LINQ. Điều này cũng sẽ giúp xử lý tải cơ sở dữ liệu của bạn. Đối với các báo cáo, báo cáo sẽ được sắp xếp.
Stefan Steiger

oo Đây phải là câu trả lời SO dài nhất tôi từng thấy và tôi thấy mọi người thực hiện toàn bộ chương trình trong câu trả lời. Bạn tốt quá
Domino

Hoàn toàn có thể đồng ý giải pháp của SunWuKung là tốt nhất
Domi

48

Tùy chọn thứ ba là tốt nhất, vì một vài lý do:

  • Không yêu cầu thay đổi lược đồ cơ sở dữ liệu cho các ngôn ngữ mới (và do đó hạn chế thay đổi mã)
  • Không yêu cầu nhiều không gian cho các ngôn ngữ chưa được thực hiện hoặc bản dịch của một mục cụ thể
  • Cung cấp sự linh hoạt nhất
  • Bạn không kết thúc với các bảng thưa thớt
  • Bạn không phải lo lắng về các khóa null và kiểm tra xem bạn đang hiển thị bản dịch hiện có thay vì một số mục rỗng.
  • Nếu bạn thay đổi hoặc mở rộng cơ sở dữ liệu của mình để bao gồm các mục / thứ / có thể dịch khác, bạn có thể sử dụng cùng các bảng và hệ thống - điều này rất tách rời khỏi phần còn lại của dữ liệu.

-Adam


1
Tôi đồng ý, mặc dù cá nhân tôi có một bảng được bản địa hóa cho mỗi bảng chính, để cho phép các khóa ngoại được thực hiện.
Neil Barnwell

1
Mặc dù tùy chọn thứ ba là triển khai rõ ràng và hợp lý nhất cho vấn đề nhưng nó phức tạp hơn trước. Tôi nghĩ rằng việc hiển thị, chỉnh sửa, báo cáo phiên bản chung cần rất nhiều nỗ lực mà không phải lúc nào cũng chấp nhận được. Tôi đã thực hiện cả hai giải pháp, đơn giản hơn là đủ khi người dùng cần một bản dịch chỉ đọc (đôi khi thiếu) của ngôn ngữ ứng dụng "chính".
rics

12
Nếu bảng sản phẩm chứa một số trường dịch thì sao? Khi truy xuất sản phẩm, bạn sẽ phải thực hiện thêm một lần tham gia cho mỗi trường được dịch, điều này sẽ dẫn đến các vấn đề hiệu suất nghiêm trọng. Cũng có độ phức tạp bổ sung (IMO) để chèn / cập nhật / xóa. Ưu điểm duy nhất của việc này là số lượng bảng thấp hơn. Tôi sẽ sử dụng phương pháp do SunWuKung đề xuất: Tôi nghĩ rằng đó là sự cân bằng tốt giữa các vấn đề về hiệu suất, độ phức tạp và bảo trì.
Frosty Z

@ rics- Tôi đồng ý, bạn gợi ý gì cho ...?
hoại

@ Adam- Tôi bối rối, có lẽ tôi đã hiểu lầm. Bạn đề nghị cái thứ ba, phải không? Vui lòng giải thích chi tiết hơn về mối quan hệ giữa các bảng đó sẽ như thế nào? Ý bạn là chúng ta phải triển khai các bảng dịch và dịchEntry cho mỗi bảng trong DB?
hoại

9

Hãy xem ví dụ này:

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

Tôi nghĩ rằng không cần phải giải thích, cấu trúc mô tả chính nó.


điều này là tốt nhưng làm thế nào bạn sẽ tìm kiếm (ví dụ sản phẩm_name)?
Illuminati

Bạn đã có một ví dụ trực tiếp ở đâu đó trong mẫu của bạn? Bạn có nhận được bất kỳ vấn đề bằng cách sử dụng nó?
David Létourneau

Chắc chắn, tôi có dự án bất động sản đa ngôn ngữ, chúng tôi hỗ trợ 4 ngôn ngữ. Việc tìm kiếm hơi phức tạp, nhưng nó nhanh. Tất nhiên trong các dự án lớn, nó có thể chậm hơn mức cần thiết. Trong các dự án nhỏ hoặc vừa, ok.
bamburik

8

Tôi thường đi theo cách tiếp cận này (không phải sql thực tế), điều này tương ứng với tùy chọn cuối cùng của bạn.

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'

Bởi vì có tất cả các văn bản có thể dịch ở một nơi làm cho việc bảo trì dễ dàng hơn nhiều. Đôi khi các bản dịch được gia công cho văn phòng dịch thuật, bằng cách này bạn có thể gửi chúng chỉ bằng một tệp xuất lớn và nhập lại dễ dàng.


1
Mục đích nào của Translationbảng hoặc TranslationItem.translationitemidcột phục vụ?
Danman

4

Trước khi đi đến chi tiết kỹ thuật và giải pháp, bạn nên dừng lại một phút và hỏi một vài câu hỏi về các yêu cầu. Các câu trả lời có thể có một tác động rất lớn đến giải pháp kỹ thuật. Ví dụ về những câu hỏi như vậy sẽ là:
- Tất cả các ngôn ngữ sẽ được sử dụng mọi lúc?
- Ai và khi nào sẽ điền vào các cột với các phiên bản ngôn ngữ khác nhau?
- Điều gì xảy ra khi người dùng sẽ cần một ngôn ngữ nhất định của văn bản và không có ngôn ngữ nào trong hệ thống?
- Chỉ các văn bản được bản địa hóa hoặc cũng có các mục khác (ví dụ GIÁ có thể được lưu trữ bằng $ và € vì chúng có thể khác nhau)


Tôi biết rằng nội địa hóa là một chủ đề rộng lớn hơn nhiều và tôi nhận thức được các vấn đề mà bạn chú ý, nhưng hiện tại tôi đang tìm kiếm một câu trả lời cho một vấn đề rất cụ thể về thiết kế lược đồ. Tôi giả định rằng các ngôn ngữ mới sẽ được thêm dần dần và mỗi ngôn ngữ sẽ được dịch gần như hoàn toàn.
qbeuek

3

Tôi đã tìm kiếm một số lời khuyên cho nội địa hóa và tìm thấy chủ đề này. Tôi đã tự hỏi tại sao điều này được sử dụng:

CREATE TABLE T_TRANSLATION (
   TRANSLATION_ID
)

Vì vậy, bạn nhận được một cái gì đó như user39603 gợi ý:

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'

Bạn không thể rời khỏi bảng Dịch ra để bạn có được điều này:

    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'

1
Chắc chắn rồi. Tôi sẽ gọi cái ProductItembàn giống như ProductTextshoặc ProductL10nmặc dù. Có ý nghĩa hơn.
DanMan

1

Tôi đồng ý với ngẫu nhiên. Tôi không thấy lý do tại sao bạn cần một bảng "dịch".

Tôi nghĩ rằng, điều này là đủ:

TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName

1

Cách tiếp cận dưới đây sẽ khả thi? Giả sử bạn có các bảng có nhiều hơn 1 cột cần dịch. Vì vậy, đối với sản phẩm bạn có thể có cả tên sản phẩm và mô tả sản phẩm cần dịch. Bạn có thể làm như sau:

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

0

"Cái nào là tốt nhất" dựa trên tình hình dự án. Cái đầu tiên rất dễ chọn và duy trì, và hiệu suất cũng tốt nhất vì nó không cần nối các bảng khi chọn thực thể. Nếu bạn xác nhận rằng bài thơ của bạn chỉ hỗ trợ 2 hoặc 3 ngôn ngữ và nó sẽ không tăng, bạn có thể sử dụng nó.

Cái thứ hai là okey nhưng khó hiểu và duy trì. Và hiệu suất kém hơn lần đầu tiên.

Cái cuối cùng là tốt về khả năng mở rộng nhưng kém về hiệu suất. Bảng T_TRANSLATION_ENTRY sẽ ngày càng lớn hơn, thật tệ khi bạn muốn lấy danh sách các thực thể từ một số bảng.


0

Tài liệu này mô tả các giải pháp có thể và các ưu điểm và nhược điểm của từng phương pháp. Tôi thích "nội địa hóa hàng" vì bạn không phải sửa đổi lược đồ DB khi thêm ngôn ngữ mới.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.