Pregunta

Estoy desarrollando un software multilingüe.Tan lejos como el código de la aplicación que se va, la posibilidad de localización no es un problema.Podemos utilizar el lenguaje específico de los recursos y tener todo tipo de herramientas que funcionan bien con ellos.

Pero ¿cuál es el mejor enfoque en la definición de un multilenguaje esquema de base de datos?Digamos que tenemos un montón de tablas (100 o más), y cada tabla puede tener varias columnas que puede ser localizada (la mayoría de las columnas de tipo nvarchar debe ser localizable).Por ejemplo, una de las tablas puede contener información de los productos:

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

No puedo pensar en tres enfoques para apoyar texto en varios idiomas en NOMBRE y DESCRIPCIÓN de las columnas:

  1. Columna separada para cada idioma

    Cuando se añade un nuevo lenguaje para el sistema, debemos crear columnas adicionales para almacenar el texto traducido, como este:

    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. Traducción de tabla con columnas para cada idioma

    En lugar de almacenar el texto traducido, sólo una clave externa para las traducciones tabla se almacena.Las traducciones de la tabla contiene una columna para cada idioma.

    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. La traducción de las tablas con filas para cada idioma

    En lugar de almacenar el texto traducido, sólo una clave externa para las traducciones tabla se almacena.Las traducciones de la tabla contiene sólo una clave, y una tabla contiene una fila para cada traducción a un idioma.

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

Hay pros y los contras de cada solución, y me gustaría saber ¿cuáles son sus experiencias con estos planteamientos, ¿qué recomienda usted y cómo usted va sobre el diseño de un multilenguaje esquema de base de datos.

¿Fue útil?

Solución

¿Qué piensa acerca de tener una tabla de traducción relacionada para cada tabla traducible?

  

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)

De esta manera, si tiene varias columnas traducibles, solo necesitaría una sola combinación para obtenerlo + ya que no está generando automáticamente un translationid, puede ser más fácil importar elementos junto con sus traducciones relacionadas.

El lado negativo de esto es que si tiene un mecanismo complejo de retroceso del idioma, es posible que necesite implementarlo para cada tabla de traducción, si confía en algún procedimiento almacenado para hacerlo. Si haces eso desde la aplicación, probablemente esto no sea un problema.

Déjame saber lo que piensas: también estoy a punto de tomar una decisión sobre esto para nuestra próxima aplicación. Hasta ahora hemos usado su 3er tipo.

Otros consejos

Este es un tema interesante, así que vamos a necromance.

Vamos a empezar por los problemas de método 1:
Problema:Estás eliminar la normalización para guardar la velocidad.
En SQL (excepto PostGreSQL con hstore), no se puede pasar un parámetro de idioma, y decir:

SELECT ['DESCRIPTION_' + @in_language]  FROM T_Products

Así que usted tiene que hacer esto:

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 

Lo que significa que usted tiene que cambiar TODAS sus consultas si se agrega un nuevo idioma.Esto, naturalmente, lleva a la utilización de "SQL dinámico", por lo que usted no tiene que alterar todas sus consultas.

Esto usualmente resulta en algo como esto (y no puede ser utilizado en las vistas o funciones con valores de tabla por el camino, que realmente es un problema si usted realmente necesita para filtrar la fecha de presentación de reporte)

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

El problema con esto es
a) Fecha de formato es muy específico del lenguaje, así que tienes un problema, si no de entrada en formato ISO (que el promedio de jardín-variedad programador no suele hacer, y en el caso de un reporte de un usuario seguro como el infierno no voy a hacer para usted, incluso si expresamente lo autoricen).
y
b) más significativamente, usted suelta cualquier tipo de comprobación de sintaxis.Si <insert name of your "favourite" person here> modifica el esquema, porque de repente los requisitos para el ala de cambio, y se crea una tabla nueva, la vieja izquierda, pero el campo de la referencia cambia el nombre, no recibe ningún tipo de advertencia.Un informe aún funciona cuando se ejecuta sin seleccionar el ala parámetro (==> guid.vacío).Pero de repente, cuando un usuario selecciona, de hecho, un ala ==> boom. Este método completamente rompe cualquier tipo de pruebas.


Método 2:
En pocas palabras:"Genial" idea (advertencia - el sarcasmo), vamos a combinar las desventajas del método 3 (velocidad lenta cuando muchas entradas) con la más horrible de las desventajas del método 1.
La única ventaja de este método es que se mantenga todas las traducciones en una tabla, y por lo tanto hacen que el mantenimiento sea sencillo.Sin embargo, la misma cosa puede ser logrado con el método 1 y una dinámica de procedimiento almacenado de SQL, y (posiblemente temporal) de la tabla que contiene las traducciones, y el nombre de la tabla de destino (y es bastante simple suponiendo que usted nombró a todos su campos de texto de la misma).


Método 3:
Una tabla para todas las traducciones:Desventaja:Usted tiene que almacenar n de Claves foráneas en la tabla de productos para n campos que desea traducir.Por lo tanto, usted tiene que hacer n combinaciones de n campos.Cuando la tabla de traducción es global, tiene muchas entradas, y se une a volverse lento.También, usted siempre tiene que unirse a la T_TRANSLATION tabla n veces n campos.Esto es bastante elevada.Ahora, ¿qué hacer cuando se debe acomodar personalizado traducciones al cliente ?Usted tendrá que agregar otro 2x n une en una tabla adicional.Si usted tiene que unirse , digamos 10 mesas, con 2x2xn = 4n adicional se une, qué lío !Además, este diseño hace posible el uso de la misma traducción con 2 tablas.Si puedo cambiar el nombre del elemento en una tabla, ¿realmente quiero cambiar una entrada en otra mesa así CADA VEZ ?

Además de que no se puede borrar y volver a insertar la tabla de más, porque ahora hay claves foráneas EN LA TABLA de PRODUCTO(s)...por supuesto, usted puede omitir la configuración de la FKs y, a continuación, <insert name of your "favourite" person here> puede eliminar la tabla, y vuelva a insertar todas las entradas con newid() [o especificando el id en el insert, pero tener identidad-insertar OFF], y que sería (y) los datos-basura (y nula referencia excepciones) muy pronto.


Método 4 (no incluidos):Almacenamiento de todos los idiomas en un campo XML en la base de datos.e.g

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

Entonces usted puede obtener el valor de XPath-Consulta en SQL, donde se puede poner la cadena de la variable en

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

Y usted puede actualizar el valor como este:

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 

Dónde puede reemplazar /lang/de/... con '.../' + @in_language + '/...'

Como el PostGre hstore, salvo que, debido a la sobrecarga del análisis de XML (en lugar de leer una entrada de un array asociativo en PG hstore) se vuelve demasiado lento además de la codificación xml hace demasiado dolorosas como para ser útil.


Método 5 (según lo recomendado por SunWuKung, la que debe elegir):Una tabla de traducción para cada "Producto" de la tabla.Eso significa que una fila por cada idioma, y varios "texto" campos, por lo que sólo se requiere de UN (a la izquierda) se unen en N campos.A continuación, puede agregar fácilmente un defecto de campo en el "Producto"de la tabla, usted puede fácilmente eliminar y volver a insertar la tabla de traducción, y puede crear una segunda tabla personalizada-traducciones (en la demanda), que también se puede eliminar y volver a insertar), y usted todavía tiene todas las claves externas.

Vamos a hacer un ejemplo para ver que esto FUNCIONA:

En primer lugar, crear las tablas:

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

A continuación, completar los datos

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

Y, a continuación, la consulta de datos:

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

Si usted es perezoso, entonces usted también puede usar la ISO-TwoLetterName ('DE', 'EN', etc.) como clave principal de la tabla de idiomas, entonces usted no tiene que buscar el identificador de idioma.Pero si usted lo hace, usted tal vez desee utilizar la IETF-etiqueta de idioma en su lugar, lo que es mejor, debido a que usted recibe de-CH y de-de, que no es realmente el mismo ortography-sabio (doble s en lugar de ß en todas partes), aunque es la misma base de la lengua.Que sólo como un pequeño detalle que puede ser importante, especialmente teniendo en cuenta que es-es y es-es/es-CA/en-u AU fr-fr/fr-CA tiene problemas similares.
Cita:nosotros no lo necesitamos, solo hacemos nuestro software en inglés.
Respuesta:Sí - pero cual ??

De todos modos, si utiliza un IDENTIFICADOR entero, es flexible, y puede cambiar su método en cualquier momento posterior.
Y usted debe utilizar esta entero, porque no hay nada más molesto, más destructiva y más problemático que un fallido Db de diseño.

Ver también RFC 5646, ISO 639-2,

Y, si aún estás diciendo "nosotros" sólo hacer que nuestra aplicación para "solo uno la cultura" (como en-US generalmente), por lo que no necesito ese extra entero, este sería un buen momento y lugar para mencionar la IANA etiquetas de idioma, ¿no ?
Porque como esto:

de-DE-1901
de-DE-1996

y

de-CH-1901
de-CH-1996

(hubo una ortografía de la reforma en 1996...) Trate de encontrar una palabra en un diccionario, si está mal escrito;esto se vuelve muy importante en las aplicaciones de tratar con las disposiciones legales y de servicio público de los portales.
Lo que es más importante, hay regiones que están cambiando de cirílico para alfabetos latinos, que sólo se puede ser más problemático que el superficial molestia de algún oscuro de la ortografía de la reforma, que es la razón por la que esto podría ser una consideración importante también, dependiendo de en qué país vive.De una forma u otra, es mejor tener que entero allí, sólo en caso de...

Editar:
Y por la adición de ON DELETE CASCADE después de

REFERENCES dbo.T_Products( PROD_Id )

usted puede simplemente decir: DELETE FROM T_Products, y no recibe una infracción de clave externa.

Como para la intercalación, me gustaría hacer algo como esto:

A) Tener su propia DAL
B) Guardar la deseada nombre de intercalación en la tabla de idiomas

Usted podría poner la intercalaciones en su propia tabla, por ejemplo:

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

C) Tener el nombre de intercalación disponibles en su auth.usuario.la información de idioma

D) Escribir SQL como este:

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

ORDER BY GroupName COLLATE {#COLLATION}

E) a Continuación, usted puede hacer esto en su DAL:

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

Que se encargará de dar esta perfectamente integrado Consulta 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

La tercera opción es la mejor, por algunas razones:

  • No requiere alterar el esquema de la base de datos para nuevos idiomas (y, por lo tanto, limitar los cambios de código)
  • No requiere mucho espacio para idiomas o traducciones no implementadas de un elemento en particular
  • Proporciona la mayor flexibilidad
  • No terminas con tablas dispersas
  • No tiene que preocuparse por las claves nulas y comprobar que está mostrando una traducción existente en lugar de alguna entrada nula.
  • Si cambia o expande su base de datos para abarcar otros elementos / cosas traducibles / etc., puede usar las mismas tablas y sistema, esto está muy desacoplado del resto de los datos.

-Adam

Eche un vistazo a este ejemplo:

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

Creo que no hay necesidad de explicar, la estructura se describe a sí misma.

Por lo general, optaría por este enfoque (no sql real), esto corresponde con su última opció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'

Porque tener todos los textos traducibles en un solo lugar hace que el mantenimiento sea mucho más fácil. A veces, las traducciones se subcontratan a oficinas de traducción, de esta manera puede enviarles un solo gran archivo de exportación e importarlo de nuevo con la misma facilidad.

Antes de pasar a detalles técnicos y soluciones, debe detenerse por un minuto y hacer algunas preguntas sobre los requisitos. Las respuestas pueden tener un gran impacto en la solución técnica. Ejemplos de tales preguntas serían:
  - ¿Se usarán todos los idiomas todo el tiempo?
  - ¿Quién y cuándo llenará las columnas con las diferentes versiones de idioma?
  - ¿Qué sucede cuando un usuario necesitará cierto idioma de un texto y no hay ninguno en el sistema?
  - Solo se deben localizar los textos o también hay otros elementos (por ejemplo, PRECIO se puede almacenar en $ y & # 8364; porque pueden ser diferentes)

Estaba buscando algunos consejos para la localización y encontré este tema. Me preguntaba por qué se usa esto:

CREATE TABLE T_TRANSLATION (
   TRANSLATION_ID
)

Entonces obtienes algo como user39603 sugiere:

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'

¿No puedes dejar la traducción de la tabla para que obtengas esto?

    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'

Estoy de acuerdo con el aleatorizador. No veo por qué necesitas una tabla & Quot; translation & Quot ;.

Creo que esto es suficiente:

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

¿Sería viable el siguiente enfoque? Supongamos que tiene tablas en las que se necesita traducir más de 1 columna. Entonces, para el producto, podría tener tanto el nombre del producto & Amp; Descripción del producto que necesita traducción. ¿Podría hacer lo siguiente:

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

" ¿Cuál es mejor " se basa en la situación del proyecto. El primero es fácil de seleccionar y mantener, y también el rendimiento es mejor, ya que no es necesario unir las tablas cuando se selecciona la entidad. Si confirmó que su proyecto solo admite 2 o 3 idiomas y no aumentará, puede usarlo.

El segundo es okey pero es difícil de entender y mantener. Y el rendimiento es peor que el primero.

El último es bueno en escalabilidad pero malo en rendimiento. La tabla T_TRANSLATION_ENTRY se hará cada vez más grande, es terrible cuando quieres recuperar una lista de entidades de algunas tablas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top