Schemat wielojęzycznej bazy danych
Pracuję nad oprogramowaniem wielojęzycznym. Jeśli chodzi o kod aplikacji, Lokalizacja nie stanowi problemu. Możemy korzystać z zasobów specyficznych dla języka i mieć wszelkiego rodzaju narzędzia, które dobrze z nimi współpracują.
Ale jakie jest najlepsze podejście do definiowania schematu wielojęzycznej bazy danych? Załóżmy, że mamy wiele tabel (100 lub więcej), a każda tabela może mieć wiele kolumn, które mogą być zlokalizowane (większość kolumn nvarchar powinna być możliwa do zlokalizowania). Na przykład jedna z tabel może przechowuj informacje o produkcie:
CREATE TABLE T_PRODUCT (
NAME NVARCHAR(50),
DESCRIPTION NTEXT,
PRICE NUMBER(18, 2)
)
Mogę wymyślić trzy podejścia do obsługi wielojęzycznego tekstu w kolumnach z nazwą i opisem:
-
Oddzielna kolumna dla każdego języka
Kiedy dodajemy nowy język do systemu, musimy utworzyć dodatkowe kolumny do przechowywania przetłumaczonego tekstu, jak to:
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) )
-
Tabela tłumaczeń z kolumnami dla każdego języka
Zamiast przechowywać przetłumaczony tekst, tylko klucz obcy do tabeli tłumaczeń jest przechowywany. Tabela tłumaczeń zawiera kolumnę dla każdego języka.
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 )
-
Tabele tłumaczeń z wierszami dla każdego języka
Zamiast przechowywać przetłumaczony tekst, przechowywany jest tylko klucz obcy do tabeli tłumaczeń. Tabela tłumaczenia zawiera tylko klucz, a oddzielna tabela zawiera wiersz dla każdego tłumaczenia na język.
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) )
Są plusy i minusy każdego rozwiązania, i chciałbym wiedzieć, jakie są Twoje doświadczenia z te podejścia, co polecacie i jak można przejść o projektowaniu wielojęzycznego schematu bazy danych.
10 answers
Co sądzisz o powiązanej tabeli tłumaczeniowej dla każdej tabeli tłumaczeniowej?
Utwórz tabelę 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)
W ten sposób, jeśli masz wiele translatowalnych kolumn, wymagałoby to tylko jednego join, aby go uzyskać + ponieważ nie automatyzujesz translationid, może być łatwiej zaimportować elementy razem z ich powiązane tłumaczenia.
Negatywną stroną tego jest to, że jeśli masz złożony mechanizm rezerwowy języka, może być konieczne zaimplementowanie go dla każdej tabeli tłumaczeń - jeśli polegasz na jakiejś procedurze składowanej, aby to zrobić. Jeśli zrobisz to z aplikacji, prawdopodobnie nie będzie to problemem.
Daj mi znać, co myślisz - ja również zamierzam podjąć decyzję w tej sprawie do naszego następnego wniosku. Do tej pory użyliśmy twojego trzeciego typu.
Trzecia opcja jest najlepsza, z kilku powodów:
- nie wymaga zmiany schematu bazy danych dla nowych języków (a tym samym ograniczenia zmian w kodzie)
- nie wymaga dużo miejsca na nie zaimplementowane języki lub tłumaczenia konkretnego elementu
- zapewnia największą elastyczność
- nie kończysz z rzadkimi tabelami
- nie musisz się martwić o klucze null i sprawdzanie, czy wyświetlasz istniejące tłumaczenie zamiast jakiegoś null wejście.
- Jeśli zmienisz lub rozszerzysz swoją bazę danych o inne możliwe do przetłumaczenia elementy/rzeczy / etc, możesz użyć tych samych tabel i systemu - jest to bardzo niezwiązane z resztą danych.
- Adam
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 14:36:16
To interesująca sprawa, więc zróbmy nekromancję.
Zacznijmy od problemów metody 1:
Problem: denormalizujesz, aby zaoszczędzić prędkość.
W SQL (z wyjątkiem PostGreSQL z hstore) nie można przekazać języka parametru i powiedzieć:
SELECT ['DESCRIPTION_' + @in_language] FROM T_Products
Więc musisz to zrobić:
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
Co oznacza, że musisz zmienić wszystkie zapytania, jeśli dodasz nowy język. Prowadzi to naturalnie do używania "dynamicznego SQL", więc nie musisz zmieniać wszystkich swoich zapytania.
Zazwyczaj skutkuje to czymś takim (i nie może być używane w widokach lub funkcjach o wartości tabeli, co naprawdę jest problemem, jeśli faktycznie musisz filtrować datę raportowania)
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
Problem z tym jest
a) formatowanie daty jest bardzo specyficzne dla języka, więc masz problem, jeśli nie wprowadzasz formatu ISO (czego Przeciętny programista garden-variety zwykle nie robi, a w przypadku raportu użytkownik na pewno nie zrobi za ty, nawet jeśli wyraźnie polecił to zrobić).
oraz
b) najbardziej istotne , ty luźne wszelkiego rodzaju sprawdzanie składni . Jeśli <insert name of your "favourite" person here>
zmieni schemat, ponieważ nagle zmieniają się wymagania dotyczące skrzydła i powstaje nowa tabela, stara pozostała, ale zmieniono nazwę pola odniesienia, nie otrzymasz żadnego ostrzeżenia. Raport działa nawet , gdy go uruchamiasz bez wybierania parametru wing (==>guid.pusta). Ale nagle, gdy rzeczywisty użytkownik faktycznie wybiera skrzydło = = >boom . ta metoda całkowicie przerywa wszelkiego rodzaju testy.
Metoda 2:
W skrócie: "świetny" pomysł (Ostrzeżenie - sarkazm), połączmy wady metody 3 (powolna prędkość, gdy wiele wpisów) z raczej okropnymi wadami metody 1.
Jedyną zaletą tej metody jest to, że zachowujesz wszystkie tłumaczenia w jednej tabeli, a tym samym ułatwiasz konserwację. Jednak to samo można osiągnąć za pomocą metody 1 i a dynamiczna procedura składowana SQL oraz (być może tymczasowa) tabela zawierająca tłumaczenia i nazwę tabeli docelowej (i jest dość prosta, zakładając, że wszystkie pola tekstowe zostały nazwane tak samo).
Metoda 3:
Jedna tabela dla wszystkich tłumaczeń:
Wada:
W tabeli produktów należy zapisać n kluczy obcych dla n pól, które chcesz przetłumaczyć.
W związku z tym, musisz zrobić N łączy dla n pól.
Gdy tabela tłumaczeń jest globalna, ma wiele wpisów, a dołącza się powoli.
Ponadto, zawsze musisz dołączyć do tabeli T_TRANSLATION N razy dla n pól.
To spory wydatek.
Teraz, co zrobić, gdy trzeba dostosować niestandardowe tłumaczenia na klienta ?
Będziesz musiał dodać kolejne 2x N joins na dodatkowej tabeli.
Jeśli musisz dołączyć, powiedzmy 10 tabel, z 2x2xn = 4N dodatkowe połączenia , co za bałagan !
Ponadto, ta konstrukcja umożliwia użycie tego samego tłumaczenia z 2 tabelami.
Jeśli zmienię nazwę elementu w jednej tabeli, czy naprawdę chcę zmienić wpis do innej tabeli za każdym razem ?
Poza tym nie można już usuwać i wstawiać tabeli ponownie, ponieważ w tabeli produktów są teraz klucze obce... można oczywiście pominąć ustawianie FKs, a następnie {[16] } można usunąć tabelę i ponownie wstawić wszystkie wpisy za pomocą nevid () [lub przez podanie id w wstawce, ale mając identity-insert OFF ], co doprowadziłoby (i doprowadziłoby) do śmieci danych (i wyjątków od null) naprawdę szybko.
Metoda 4 (niewymieniona w czołówce)): Przechowywanie wszystkich języków w polu XML w bazie danych. np.
-- 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
Następnie możesz uzyskać wartość za pomocą XPath-Query w SQL, gdzie możesz umieścić zmienną string w
filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla
I możesz zaktualizować wartość w następujący sposób:
UPDATE YOUR_TABLE
SET YOUR_XML_FIELD_NAME.modify('replace value of (/lang/de/text())[1] with ""I am a ''value ""')
WHERE id = 1
Gdzie można zastąpić /lang/de/...
'.../' + @in_language + '/...'
Trochę jak PostGre hstore, tyle że ze względu na narzut parsowania XML (zamiast odczytu wpisu z tablicy asocjacyjnej w PG hstore) staje się zbyt wolny, a kodowanie xml sprawia, że jest zbyt bolesne, aby być użytecznym.
Metoda 5 (zgodnie z zaleceniami SunWuKung, którą powinieneś wybrać): Jedna tabela tłumaczeń dla każdej tabeli "produktu". Oznacza to jeden wiersz na język i kilka pól "tekstowych", więc wymaga tylko jednego (lewego) złączenia na N polach. Następnie możesz łatwo dodać domyślne pole w tabeli "produkt", możesz łatwo usunąć i ponownie wstawić tabelę tłumaczeń i możesz utworzyć drugi tabela dla niestandardowych tłumaczeń (na żądanie), które można również usunąć i wstawić ponownie), a nadal masz wszystkie klucze obce.
Zróbmy przykład, aby zobaczyć to działa:
Najpierw Utwórz tabele:
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
Następnie wypełnij dane
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
A następnie odpytywać dane:
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
Jeśli jesteś leniwy, możesz również użyć ISO-TwoLetterName ('DE',' EN', itp.) jako klucz podstawowy tabeli języków, nie musisz szukać identyfikatora języka. Ale jeśli tak więc może chcesz użyć IETF-language tag , co jest lepsze, ponieważ dostajesz de-CH i de-DE, co naprawdę nie jest tą samą ortografią (podwójne s zamiast ß wszędzie), chociaż jest to ten sam język bazowy. To tylko mały szczegół, który może być dla ciebie ważny, zwłaszcza biorąc pod uwagę, że en-US i en-GB/en-CA/en-AU lub fr-FR/fr-CA ma podobne problemy.
cytat: nie potrzebujemy go, robimy tylko nasze oprogramowanie w języku angielskim.
odpowiedź: Tak - ale który ??
W każdym razie, jeśli używasz integer ID, jesteś elastyczny i możesz zmienić metodę w dowolnym momencie.
I powinieneś użyć tej liczby całkowitej, ponieważ nie ma nic bardziej irytującego, destrukcyjnego i kłopotliwego niż spartaczona konstrukcja Db.
Zobacz też RFC 5646, ISO 639-2,
I, jeśli nadal mówisz "my" tylko składamy wniosek o "tylko jedną kulturę" (jak zwykle en-US) - dlatego nie potrzebuję tego dodatkowego integer, to byłby dobry czas i miejsce, aby wspomnieć IANA language tags , czyż nie ?
Bo idą tak:
de-DE-1901
de-DE-1996
I
de-CH-1901
de-CH-1996
W 1996 roku przeprowadzono reformę ortografii...)
Spróbuj znaleźć słowo w słowniku, jeśli jest źle napisane; staje się to bardzo ważne w aplikacjach zajmujących się portalami prawnymi i publicznymi.Co ważniejsze, są regiony, które zmieniają się z cyrylicy na alfabet łaciński, który może być po prostu bardziej kłopotliwa niż powierzchowna uciążliwość jakiejś niejasnej reformy ortografii, dlatego też może to być ważna kwestia, w zależności od kraju, w którym mieszkasz. Tak czy inaczej, lepiej mieć tę liczbę całkowitą, na wszelki wypadek...
Edit:
I dodając ON DELETE CASCADE
po
REFERENCES [dbo].[T_Products] ([PROD_Id])
Możesz po prostu powiedzieć: DELETE FROM T_Products
i nie uzyskać naruszenia klucza obcego.
Co do zestawiania, to zrobiłbym to tak:
A) mieć swój własny DAL
B) Zapisz żądaną nazwę kolacji w tabeli języka
Możesz umieścić zestawienia we własnej tabeli, np.:
SELECT * FROM sys.fn_helpcollations()
WHERE description LIKE '%insensitive%'
AND name LIKE '%german%'
C) mieć nazwę sortowania dostępną w auth.użytkownik.informacje o języku
D) napisz swój SQL tak:
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE {#COLLATION}
E) wtedy możesz to zrobić w swoim DAL:
cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)
Który następnie da ci to doskonale skomponowane zapytanie 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-04 10:30:11
Spójrz na ten przykład:
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)
)
Myślę, że nie ma potrzeby wyjaśniać, struktura sama się opisuje.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-12 07:22:08
Zwykle wybrałbym takie podejście (nie rzeczywiste sql), odpowiada to ostatniej opcji.
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'
Ponieważ posiadanie wszystkich możliwych do przetłumaczenia tekstów w jednym miejscu sprawia, że konserwacja jest o wiele łatwiejsza. Czasami tłumaczenia są zlecane biurom tłumaczeń, w ten sposób możesz wysłać im tylko jeden duży plik eksportowy i równie łatwo zaimportować go z powrotem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 09:37:47
Przed przejściem do szczegółów technicznych i rozwiązań, należy zatrzymać się na chwilę i zadać kilka pytań na temat wymagań. Odpowiedzi mogą mieć ogromny wpływ na rozwiązanie techniczne. Przykłady takich pytań to:
- Czy wszystkie języki będą używane przez cały czas?
- Kto i kiedy wypełni kolumny różnymi wersjami językowymi?
- Co się dzieje, gdy użytkownik będzie potrzebował określonego języka tekstu i nie ma go w systemie?
- Tylko teksty są być zlokalizowane lub istnieją również inne przedmioty (na przykład cena może być przechowywana w $ I€, ponieważ mogą być różne)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 09:59:32
Szukałem wskazówek dotyczących lokalizacji i znalazłem ten temat. Zastanawiałem się dlaczego to jest używane:
CREATE TABLE T_TRANSLATION (
TRANSLATION_ID
)
Więc masz coś takiego jak user39603 sugeruje:
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'
Nie możesz po prostu zostawić tłumaczenia tabeli, więc dostaniesz to:
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'
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-06 18:00:39
Zgadzam się z randomizerem. Nie rozumiem, dlaczego potrzebujesz tabeli "tłumaczenie".
Myślę, że to wystarczy:
TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-04 09:31:22
Czy poniższe podejście byłoby realne? Powiedzmy, że masz tabele, w których więcej niż 1 kolumna wymaga tłumaczenia. Tak więc w przypadku produktu możesz mieć zarówno nazwę produktu , jak i opis produktu, które wymagają tłumaczenia. Czy można wykonać następujące czynności:
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)
)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-13 16:03:31
"który z nich jest najlepszy" opiera się na sytuacji projektu. Pierwszy z nich jest łatwy do wyboru i utrzymania, a także wydajność jest najlepsza, ponieważ nie trzeba łączyć tabel podczas wybierania jednostki. Jeśli potwierdziłeś, że Twój obiekt obsługuje tylko 2 lub 3 Języki i nie zwiększy się, możesz go użyć.
Drugi jest okey, ale jest trudny do zrozumienia i utrzymania. A wydajność jest gorsza niż pierwsza.
Ostatni jest dobry w skalowalności, ale zły w wydajność. Tabela t_translation_entry staje się coraz większa, to straszne, gdy chcesz pobrać listę encji z niektórych tabel.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-03 05:34:02