Вопрос

Я разрабатываю многоязычное программное обеспечение.Что касается кода приложения, локализуемость не является проблемой.Мы можем использовать ресурсы, специфичные для конкретного языка, и располагать всевозможными инструментами, которые хорошо с ними работают.

Но каков наилучший подход к определению многоязыковой схемы базы данных?Допустим, у нас есть много таблиц (100 или более), и каждая таблица может содержать несколько столбцов, которые могут быть локализованы (большинство столбцов nvarchar должны быть локализуемыми).Например, в одной из таблиц может содержаться информация о продукте:

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

Я могу предложить три подхода к поддержке многоязычного текста в столбцах "НАЗВАНИЕ" и "ОПИСАНИЕ":

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

У каждого решения есть свои плюсы и минусы, и я хотел бы знать, каков ваш опыт работы с этими подходами, что вы рекомендуете и как бы вы приступили к разработке многоязыковой схемы базы данных.

Это было полезно?

Решение

Что вы думаете о наличии связанной таблицы перевода для каждой переводимой таблицы?

  

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)

Таким образом, если у вас есть несколько переводимых столбцов, потребуется только одно объединение, чтобы получить его +, поскольку вы не генерируете transidid, может быть проще импортировать элементы вместе с соответствующими переводами.

Отрицательной стороной этого является то, что если у вас есть сложный механизм восстановления языка, вам может потребоваться реализовать его для каждой таблицы перевода - если вы полагаетесь на какую-то хранимую процедуру для этого. Если вы сделаете это из приложения, это, вероятно, не будет проблемой.

Дайте мне знать, что вы думаете - я также собираюсь принять решение по этому вопросу для нашего следующего заявления. До сих пор мы использовали ваш третий тип.

Другие советы

Это интересный вопрос, так что давайте займемся некромантией.

Давайте начнем с проблем метода 1:
Проблема:Вы проводите денормализацию, чтобы сэкономить скорость.
В SQL (за исключением PostgreSQL с hstore) вы не можете передать язык параметров и сказать:

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 (чего обычный программист обычно не делает, а в случае отчета пользователь, черт возьми, точно не сделает за вас, даже если ему явно будет дано соответствующее указание).
и
б) наиболее существенно, ты отсутствие какой-либо проверки синтаксиса.Если <insert name of your "favourite" person here> изменяет схему, потому что внезапно требования к wing меняются, и создается новая таблица, старая оставлена, но поле ссылки переименовано, вы не получаете никаких предупреждений.Отчет даже работает когда вы запускаете его без выбора параметра wing (==> guid.пустой).Но внезапно, когда реальный пользователь действительно выбирает крыло ==> бум. Этот метод полностью исключает любые виды тестирования.


Способ 2:
В двух словах:"Отличная" идея (предупреждение - сарказм), давайте объединим недостатки метода 3 (низкая скорость при большом количестве записей) с довольно ужасными недостатками метода 1.
Единственное преимущество этого метода заключается в том, что вы сохраняете весь перевод в одной таблице и, следовательно, упрощаете обслуживание.Однако то же самое может быть достигнуто с помощью метода 1 и динамической хранимой процедуры SQL, а также (возможно, временной) таблицы, содержащей переводы, и имени целевой таблицы (и это довольно просто, если предположить, что вы назвали все свои текстовые поля одинаково).


Способ 3:
Одна таблица для всех переводов:Недостаток:Вы должны сохранить n внешних ключей в таблице products для n полей, которые вы хотите перевести.Следовательно, вам нужно выполнить n объединений для n полей.Когда таблица перевода глобальная, в ней много записей, и соединения становятся медленными.Кроме того, вам всегда приходится присоединяться к таблице T_TRANSLATION n раз для n полей.Это довольно накладные расходы.Итак, что вы делаете, когда вам необходимо выполнить индивидуальные переводы для каждого клиента?Вам нужно будет добавить еще 2x n соединений в дополнительную таблицу.Если вам нужно объединить, скажем, 10 таблиц, с дополнительными объединениями 2x2xn = 4n, какой беспорядок!Кроме того, такой дизайн позволяет использовать один и тот же перевод с двумя таблицами.Если я изменяю название элемента в одной таблице, действительно ли я хочу КАЖДЫЙ РАЗ изменять запись и в другой таблице?

Кроме того, вы больше не можете удалять и повторно вставлять таблицу, потому что теперь В ТАБЛИЦАХ ПРОДУКТОВ есть внешние ключи...вы, конечно, можете не устанавливать FKs, и тогда <insert name of your "favourite" person here> можно удалить таблицу и повторно вставить все записи с помощью newid() новый идентификатор() [или указав идентификатор во вставке, но имея идентификация-вставить ВЫКЛЮЧЕНО], и это очень скоро приведет (и будет) к мусору данных (и исключениям с нулевой ссылкой).


Способ 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 

Затем вы можете получить значение с помощью XPath-запроса в SQL, где вы можете поместить строковую переменную в

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 (в соответствии с рекомендациями SunWuKung, тот, который вы должны выбрать):Одна таблица перевода для каждой таблицы "Продукт".Это означает одну строку для каждого языка и несколько "текстовых" полей, поэтому требуется только ОДНО (левое) соединение для N полей.Затем вы можете легко добавить поле по умолчанию в таблицу "Продукт", вы можете легко удалить и повторно вставить таблицу переводов, и вы можете создать вторую таблицу для пользовательских переводов (по запросу), которую вы также можете удалить и повторно вставить), и у вас все еще есть все внешние ключи.

Давайте приведем пример, чтобы увидеть, как это РАБОТАЕТ:

Сначала создайте таблицы:

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' и т.д.) В качестве первичного ключа языковой таблицы, тогда вам не нужно искать идентификатор языка.Но если вы сделаете это, возможно, вы захотите использовать IETF-языковой тег вместо этого, что лучше, потому что вы получаете de-CH и de-DE, что на самом деле не совпадает с орфографией (везде двойное s вместо β), хотя это один и тот же базовый язык.Это всего лишь крошечная деталь, которая может быть важна для вас, особенно учитывая, что en-US и en-GB / en-CA / en-AU или fr-FR / fr-CA имеют схожие проблемы.
Цитата:нам это не нужно, мы делаем наше программное обеспечение только на английском языке.
Ответ:Да , но какой именно ??

В любом случае, если вы используете целочисленный идентификатор, вы проявляете гибкость и можете изменить свой метод в любое время позже.
И вы должны использовать это целое число, потому что нет ничего более раздражающего, разрушительного и хлопотного, чем неудачный дизайн базы данных.

Смотрите также RFC 5646, ISO 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, и не получите никакого нарушения внешнего ключа.

Что касается сортировки, я бы сделал это следующим образом:

А) Иметь свой собственный DAL
Б) Сохраните желаемое имя параметра сортировки в языковой таблице

Возможно, вы захотите поместить параметры сортировки в их собственную таблицу, например:

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

C) Имя параметра сортировки должно быть доступно в вашем auth.user.информация о языке

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

Третий вариант является лучшим по нескольким причинам:

  • Не требует изменения схемы базы данных для новых языков (и, следовательно, ограничения изменений кода)
  • Не требует много места для нереализованных языков или переводов определенного элемента
  • Обеспечивает максимальную гибкость
  • В итоге у вас не будет разреженных таблиц
  • Вам не нужно беспокоиться о нулевых ключах и проверке того, что вы отображаете существующий перевод вместо какой-либо нулевой записи.
  • Если вы измените или расширите свою базу данных, чтобы охватить другие переводимые элементы / things / etc, вы можете использовать те же таблицы и систему - это очень не связано с остальными данными.

-Адам

Посмотрите на этот пример:

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), это соответствует вашему последнему варианту.

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'

Потому что все переводимые тексты в одном месте значительно облегчают обслуживание. Иногда переводы передаются в бюро переводов, поэтому вы можете отправить им только один большой файл экспорта и импортировать его обратно так же легко.

Прежде чем перейти к техническим деталям и решениям, вам следует остановиться на минутку и задать несколько вопросов о требованиях. Ответы могут оказать огромное влияние на техническое решение. Примеры таких вопросов:
  - Будут ли все языки использоваться постоянно?
  - Кто и когда будет заполнять колонки версиями на разных языках?
  - Что происходит, когда пользователю понадобится определенный язык текста, а в системе его нет?
  - Только тексты должны быть локализованы или есть и другие элементы (например, PRICE может храниться в $ и & # 8364; потому что они могут отличаться)

Я искал несколько советов по локализации и нашел эту тему. Мне было интересно, почему это используется:

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'

Я согласен с рандомизатором. Я не понимаю, зачем вам таблица & Quot; перевод & Quot;.

Я думаю, этого достаточно:

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

Будет ли нижеследующий подход жизнеспособным? Скажем, у вас есть таблицы, в которых нужно перевести более 1 столбца. Таким образом, для продукта у вас может быть и название продукта & Amp; описание продукта, который нужно перевести. Не могли бы вы сделать следующее:

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

" Какой из них лучше " основано на ситуации проекта. Первый легко выбрать и поддерживать, а также производительность является наилучшей, поскольку при выборе объекта не требуется объединять таблицы. Если вы подтвердили, что ваш проект поддерживает только 2 или 3 языка, и он не увеличится, вы можете использовать его.

Второй вариант окей, но его трудно понять и поддерживать. И производительность хуже, чем у первого.

Последний хорош в масштабируемости, но плох в производительности. Таблица T_TRANSLATION_ENTRY будет становиться все больше и больше, это ужасно, когда вы хотите получить список сущностей из некоторых таблиц.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top