Uložení překladů do samostatné tabulky

Školení, která pořádám

Pro ukládání jazykových verzí doporučuji strukturu, kde se pro každý jazyk vytvoří sloupce přímo v tabulce, tedy např. vyrobky(id, nazev_cs, nazev_en, popis_cs, popis_text, cena). S touto strukturou se pracuje velice pohodlně, většina operací je přímočarých, jediný oříšek představuje přidání jazyka, pro které je lepší si napsat skript, a získání přehledu o nepřeložených textech. Další operace, která není úplně triviální, je prohledání všech textů ve všech tabulkách – musíme postupně prohledat všechny tabulky a všechny jejich sloupce – to je však stejně pracné i u jednojazyčné verze.

Pokud bychom všechny texty ze všech tabulek umístili do zvláštní tabulky texty_cs(tabulka varchar(64), sloupec varchar(64), radek int, preklad text) (zvláštní tabulka pro každý jazyk je z důvodu nastavení vah fulltextového vyhledávání, nepřeložené texty neukládáme), stalo by se prohledávání všech textů hračkou, navíc by se možná zjednodušilo i přidání jazyka. Jak bychom vyřešili ostatní operace?

Získání dat

Pro získání dat bude nejlepší si vytvořit pohled:

CREATE VIEW vyrobky_cs AS
SELECT vyrobky.id, IFNULL(nazev_cs.preklad, nazev_en.preklad) AS nazev, IFNULL(popis_cs.preklad, popis_en.preklad) AS popis, vyrobky.cena
FROM vyrobky
LEFT JOIN texty_en nazev_en ON nazev_en.tabulka = 'vyrobky' AND nazev_en.sloupec = 'nazev' AND nazev_en.radek = vyrobky.id
LEFT JOIN texty_cs nazev_cs ON nazev_cs.tabulka = 'vyrobky' AND nazev_cs.sloupec = 'nazev' AND nazev_cs.radek = vyrobky.id
LEFT JOIN texty_en popis_en ON popis_en.tabulka = 'vyrobky' AND popis_en.sloupec = 'popis' AND popis_en.radek = vyrobky.id
LEFT JOIN texty_cs popis_cs ON popis_cs.tabulka = 'vyrobky' AND popis_cs.sloupec = 'popis' AND popis_cs.radek = vyrobky.id

Nesmíme zapomenout na to, že tyto pohledy je nutné při každé změně struktury tabulek vytvořit znovu. Očekávané jednodušší přidání jazyka v tomto řešení se tedy nekoná, protože je pro něj potřeba vytvořit všechny pohledy.

Třídění

Pro efektivní třídění je vhodné si vytvořit index. Abychom to mohli udělat u tohoto řešení, museli bychom se rozloučit s konceptem, že nepřeložené texty nebudeme vůbec ukládat. Potom bychom museli index vytvořit nad sloupci (tabulka, sloupce, preklad), čímž bychom index zbytečně udržovali i pro sloupce, podle kterých nikdy řadit chtít nebudeme.

Unikátnost záznamů

Unikátnost záznamů nemůžeme zajistit jednoduchým unikátním klíčem. Pokud chceme zajistit konzistenci dat za všech okolností, nedá se použít ani řešení na aplikační úrovni, takže zbývá vytvoření triggeru:

CREATE TRIGGER texty_cs_bi BEFORE INSERT ON texty_cs FOR EACH ROW
SET NEW.preklad = IF(NEW.tabulka = 'vyrobky' AND NEW.sloupec = 'url' AND (
	SELECT COUNT(*) FROM texty_en LEFT JOIN texty_cs USING(tabulka, sloupec, radek)
	WHERE texty_en.tabulka = NEW.tabulka AND texty_en.sloupec = NEW.sloupec
	AND texty_en.radek != NEW.radek AND IFNULL(texty_cs.preklad, texty_en.preklad) = NEW.preklad
) > 0, NULL, NEW.preklad)

Využívá se toho, že sloupec preklad nemůže mít hodnotu NULL, takže v případě konfliktu unikátnosti příkaz skončí chybou. Podobné triggery bychom museli vytvořit i pro aktualizaci a smazání dat (protože tím se pro daný záznam použije výchozí jazyková verze) a samozřejmě i pro výchozí jazykovou verzi, kde by se musely kontrolovat kolize nepřeložených textů se všemi jazyky.

Fulltextové prohledávání

Pro vyhledání ve všech textech lze použít následující dotaz:

SELECT texty_en.tabulka, texty_en.radek
FROM texty_en
LEFT JOIN texty_cs USING (tabulka, sloupec, radek)
WHERE IF(texty_cs.preklad IS NULL, MATCH(texty_en.preklad) AGAINST ('hledat'), MATCH(texty_cs.preklad) AGAINST ('hledat'))
GROUP BY texty_en.tabulka, texty_en.radek

Dotaz neřeší, jakým způsobem nalezená data zobrazit. Spíš než do dotazu připojovat další tabulky sloužící pro získání nadpisů v jednotlivých tabulkách, bych doporučoval si tyto nadpisy načíst odděleným dotazem.

Pokud budeme prohledávat jen jednu tabulku ve více sloupcích, nebude vyhledávání tak efektivní, jako kdybychom ji dělali najednou přes všechny sloupce. Při dotazu přes jednotlivé sloupce se totiž záznam pro každé slovo ve více sloupcích musí do indexu uložit vícekrát, při hledání přes složený index tam stačí jen jednou.

Nepřeložené hodnoty

Nalezení nepřeložených hodnot je velice jednoduchá operaci, stačí vzít všechny záznamy, které jsou v tabulce s výchozím jazykem a nejsou v tabulce s překladem.

Závěr

Přestože uvedené řešení vypadá na první pohled jako čistší, pro jeho extrémní komplikovanost bych ho k reálnému nasazení nedoporučoval.

Přijďte si o tomto tématu popovídat na školení Návrh a používání MySQL databáze.

Jakub Vrána, Dobře míněné rady, 2.3.2007, diskuse: 35 (nové: 0)

Diskuse

Matej:

"Pro ukládání jazykových verzí doporučuji strukturu, kde se pro každý jazyk vytvoří sloupce přímo v tabulce, tedy např. vyrobky(id, nazev_cs, nazev_en, popis_cs, popis_text, cena)"

Doufam, ze toto byl ftip na uvod, hodi se maximalne pro 3 tabulkouvou aplikaci pro kamarada.

Vsechny texty do zvlastni tabulky? Dal uz radsi ani nectu...

mark:

mohl byste prosím věcně rozebrat v čem vidíte klíčové nedostatky toho řešení ?

ikona Jakub Vrána OpenID:

Naopak, řešení s uložením textů přímo do tabulky se báječně hodí třeba i pro 150tabulkovou aplikaci. Stačí si napsat jednoduchý skriptík pro přidání jazyka a pro práci se všemi jazykovými sloupci najednou.

Ukládání všech textů do zvláštní tabulky tento článek kritizuje, mohl sis přečíst alespoň závěr :-).

Ivan:

Pridavam sa k Matejovi (sk_name, sk_nice_name, en_name, en_nice_name). S tym rozdielom, ze jazykovu verziu je po dokonceni vhodne exportovat do array(), kazda podstranka ma svoje pole. Teda napr. v subore lang_sk.php mam polia ako msg_index, msg_login, msg_register atd ... takyto pristup je ovela rychlejsi.

ikona Jakub Vrána OpenID:

A s čím přesně souhlasíš? Bavíme se pořád o překladech s návazností na data v tabulce (např. na výrobky)? O jakých polích pro podstránky to potom mluvíš?

Jarda:

Já již delší dobu zpracovávám jazykové mutace formou oddělené tabulky pro překlady. Kde funkční komplet tvoří:

1) tabulka s výčtem jazyků (cs, en, de, sk)
2) tabulka s daty, které není nutné překládat
3) tabulka s překlady

Pokud je aplikace dobře napsána, pro přidání nového jazyka stačí jen do tabulky s výčtem jazyků přidat nový řádek.

ikona Jakub Vrána OpenID:

Tedy způsob, který je zkritizován v tomto článku. Můžeš se s námi podělit o to, jakým způsobem řešíš úlohy zde naznačené (především získání dat, třídění a unikátnost záznamů)?

Jarda:

Víceméně jsem dříve vycházel z tohoto článku:

http://interval.cz/clanky/vicejazycne-webove-…-relacni-databaze/

který však určitě budeš znát, soudě podle příspěvku v diskuzi.

Matej:

Ja pouzivam stejny zpusob jako Jarda.

Precetl sem si zaver a musim konstatovat ze reseni v clanku ma k cistote velmi, ale velmi daleko, jak s _cz/_en primo v 1 tabulce, tak reseni s 1 tabulkou pro preklad.

CREATE VIEW vyrobky_cs AS
SELECT vyrobky.id, IFNULL(nazev_cs.preklad, nazev_en.preklad) AS nazev, IFNULL(popis_cs.preklad, popis_en.preklad) AS popis, vyrobky.cena
FROM vyrobky
LEFT JOIN texty_en nazev_en ON nazev_en.tabulka = 'vyrobky' AND nazev_en.sloupec = 'nazev' AND nazev_en.radek = vyrobky.id
LEFT JOIN texty_cs nazev_cs ON nazev_cs.tabulka = 'vyrobky' AND nazev_cs.sloupec = 'nazev' AND nazev_cs.radek = vyrobky.id
LEFT JOIN texty_en popis_en ON popis_en.tabulka = 'vyrobky' AND popis_en.sloupec = 'popis' AND popis_en.radek = vyrobky.id
LEFT JOIN texty_cs popis_cs ON popis_cs.tabulka = 'vyrobky' AND popis_cs.sloupec = 'popis' AND popis_cs.radek = vyrobky.id

vs

SELECT
vp.nazev,
vp.popis
FROM vyrobky as v
inner join tblvyrobkyPreklad as vp on (vp.id_vyrobek = v.uid and vp.id_jazyk = @jazykID)

Nemluve o tom kdyz je tam tech veci potreba prelozit vice.
Etc, damo mluvit, praxe nauci :o).

ikona Jakub Vrána OpenID:

Abych to vyjasnil: Ukládat všechny texty do jedné tabulky pokládám za špatné řešení - z článku to je myslím zřejmé. Článek tady není jako návod k řešení tohoto problému, ale naopak jako rozbor všech problémů, které jsou s tímto řešením spojené. Napsal jsem ho proto, že si programátoři často myslí, že právě tohle bude to správné řešení.

Ukládat texty do stejné tabulky jako data považuji za správné řešení.

Kompromisní řešení (ke každé tabulce ještě jedna tabulka s překlady) nepovažuji za příliš dobré a to z těchto důvodů:

1. Pokud chci přidat sloupec s překladem, musím nejprve zjistit, jestli už tabulka s překlady existuje nebo ne a podle toho udělat buď ALTER TABLE nebo CREATE TABLE.

2. V každém dotazu je zbytečně navíc právě tolik joinů, z kolika tabulek chci získat překlady.

3. Pokud chci nad překládaným sloupcem vytvořit unikátní klíč, musím zapomenout na to, že přidání jazyka provedu pouhým přidáním řádku do tabulky s jazyky – do všech tabulek je potřeba přidat unikátní klíč pro nový jazyk. Naznačený dotaz také nepočítá s tím, že by řádek v tabulce s překlady chyběl – při přidání jazyka musím data nakopírovat.

4. Pokud nad překlady použiji fulltextové vyhledávání, smíchají se slova jednotlivých jazyků mezi sebou, takže např. nebude fungovat 50% hranice pro ignorovaná slova. Kromě toho bude vyhledávání pomalé, protože nejde vytvořit složený normální a fulltextový index. V dotazu WHERE jazyk = @jazykID AND MATCH (...) se použije buď normální index nebo fulltextový a všechny nalezené řádky se budou muset projít bez indexu.

Matej:

1. nechapu, co ma spolecnyho create/alter table s strukturou dat.

2. ano, ale noa?

3. nevidim duvod proc nad prekladem tvorit unikatni klice, nevim k cemu by me byl unikatni klic nad prekladem clanku, pro unikatni klic mam uid.
Kdyz zadavame objekt do db, tak ho zadavame VZDY v nejakem jazyku, tzn ano vytvori se objekt + jeho preklad. Pri pridani jazyka se nedeje nic, muze tam byk klidne outer join nebo se objekt nezobrazi, zalezi na logice ne na db modelu.

4. zalezi na databazi, nevim jak je to v mysql, nedelam male projekty. Ale plati, ze je to tak pomale, jak to programuje spatny programator.

Asi ti vzdy stacilo _cz/_en. Nedovedu si vubec predstavit tvuj model v bezne i mensi aplikaci, pri pridani jazyka upravit vsechny dotazy, ruzny ciselniky atd atd.

ikona Jakub Vrána OpenID:

1. Když chci přidat sloupec, nemůžu to udělat bez znalosti toho, která tabulka už přeložené sloupce má a která ne. Jde o pohodlí při provádění změn ve struktuře databáze.

2. Nepovažuješ za nevýhodu, že jsou dotazy dvakrát komplikovanější? Já ano.

3. Je URL přeložitelná informace? Je. Potřebuji nad ním unikátní klíč? Potřebuji.
Předpokládajme, že pokud překlad neexistuje, chci místo něj vidět anglickou verzi. Napiš sem prosím dotaz, který zobrazí výrobky s názvem a popisem v češtině a pokud česká verze neexistuje, tak v angličtině. Poněkud se to komplikuje, že? Navíc tento model vůbec nepřipouští situaci, že název přeložený bude a popis ne (v praxi poměrně běžné).

4. Co je to prosímtě za argument? S tímto datovým modelem to bude pomalé, i kdyby se programátor mohl přetrhnout.

Nikoliv. Mnou popisovaný model je prověřen 150tabulkovou databází s momentálně 16 jazyky s poměrně častými a různorodými změnami (a kromě toho řadou dalších menších projektů). Žádné dotazy ani číselníky se opravovat nemusí. Dotazy se píšou ve tvaru "SELECT nazev_$LANG".

Matej:

1. aha
2. vetsinou neexistuje idealni reseni, vzdy se musi nekde ustoupit, ja si tam tu radku napisu, jednu jedinou a uz nikdy vice.
3. aha, co napriklad informace, kdy vzniknul preklad, kdo ho vytvoril, kdo ho schvalil? a ejhle mame tu 3x 16 sloupecku. tvy reseni ti zavira dvere rozsirovani funkcnosti. Btw tos 16x po sobe rozsiroval sechny tabulky o sloupecky? :o)
4. pomalejsi ano, ale je to standart, dokonce sem si precetl clanek na intervalu a nekdo v komentarich psal, ze autor objevuje kolo, koukam, ze ne vsichni ho znaji :o)

Finc a jeho posledni veta to vystihuji hezky.

Nicmene pouzival sem a stale pokud me situace donuti neco delat v php pouzivam tvuj web, kde najdu spousta uzitecnych veci, ale navrh databaze bych si od tebe urcite nechat nedelal :o)

ikona Jakub Vrána OpenID:

2. Kam se napíše řádek jenom jednou? Ke každé tabulce s jazykovými daty v celé aplikaci. Takže ne jednou, ale třeba tisíckrát.

3. Informace o tom, kdy vzniknul překlad, kdo ho vytvořil a kdo schválil se obvykle nevypisují, proto je samozřejmě nesmysl je ukládat k datům o výrobku. Naopak se s nimi obvykle pracuje pohromadě, proto nepovažuji za ideální ani jejich ukládání v překladových tabulkách. Pro umístění těchto dat se mi jeví jako ideální jedna společná tabulka.

4. Je mi celkem jedno, jestli to někdo považuje za standard. Já jsem tato tři řešení zanalyzoval, vyjmenoval jejich výhody a nevýhody, nastínil řešení možných problémů a vlastnosti jednoho z nich jsou jasně lepší než dvou ostatních.

K výkonovým problémům přidám ještě nemožnost efektivního řazení - např. čeština se v MySQL řadí podle collate utf8_czech_ci, další jazyky mají vlastní collate, některé společné utf8_general_ci. Pokud jsou jednotlivé jazykové verze smíchané v jednom sloupci, mohu si vytvořit index pouze pro jedno collate - jazyky používající jiné budou buď seřazené špatně, nebo je musím řadit bez indexu.

Budu rád, když si ode mě návrh databáze nenecháš udělat, protože aplikace napsaná podle mého návrhu bude A. rychleji napsaná, B. snadněji spravovatelná, C. podstatně efektivnější, takže bude mít značnou konkurenční výhodu :-).

ikona Jakub Vrána OpenID:

3. Vytvoření databáze spočívá v naplnění tabulky jazyky (id, kodovani) a následném spuštění CREATE TABLE vyrobky (id int, cena decimal(9,2), nazev_$LANG varchar(50)) přes jednoduchý skript, který nahradí $LANG za to, co je v tabulce jazyky (včetně použitého způsobu řazení) a pošle to do databáze.

Přidání sloupce znamená spuštění ALTER TABLE vyrobky ADD popis_$LANG text přes tentýž skript.

Přidání jazyka znamená přidání řádku do tabulky jazyky a spuštění jednoduchého skriptu, který projde všechny sloupce všech tabulek, doplní sloupečky pro nový jazyk obsahující hodnoty z výchozí jazykové verze a přidá indexy.

Matej:

Jezis, takova prasarna.
Kazdopadne bych tento flame uzavrel, pokracovat asi nema smysl.

ikona Jakub Vrána OpenID:

Jak je to u tebou preferovaného řešení?

Přidání sloupce do tabulky: pokud si nechci pamatovat nebo vždy zjišťovat, které tabulky už jazykové sloupce mají a které ne - musím to rovněž udělat přes skript (už jenom proto, že založení nové tabulky s překlady není zcela triviální).

Přidání jazyka: pokud chci mít místo nepřeložených textů uloženou výchozí jazykovou verzi (abych do všech dotazů nemusel připojovat další tabulku sloužící pro načtení výchozí jazykové verze, aby se daly využívat unikátní indexy a aby fungovalo fulltextové vyhledávání), musím to opět udělat skriptem.

Když k tomu přidám např. i ten nejjednodušší dotaz - jednoduchý výpis číselníku:

-- moje řešení
SELECT id, nazev_$LANG FROM ciselnik ORDER BY poradi;

-- tvoje řešení v nejjednodušší variantě, kde jsou zkopírované i nepřeložené texty
SELECT ciselnik.id, ciselnik_lang.nazev
FROM ciselnik
INNER JOIN ciselnik_lang ON ciselnik.id = ciselnik_lang.id AND ciselnik_lang.jazyk = '$LANG'
ORDER BY ciselnik.poradi;

Potom si kladu otázku, čemu vlastně říkáš prasárna.

Já jsem prostě jen řešení tohoto problému domyslel do větších důsledků a ověřil ho praxí.

Matej:

Pamatovat? Rikam, ze se pohybujem kazdy uplne jinde.
Takze:
ke kazdemu projektu je analyza, soucasti analyzy je i datova struktura a metodika (mozna tu sou pro tebe nejaky novy pojmy).
Pokud chci pridat pole ktere se bude prekladat, kouknu do analyzy, vyhledam tabulku. V metodice se pise, ze ke kazde tabulce objekt je jeji preklad v podobe objektMutace.
Pokud tam neni zalozim a upravim proceduru, pokud tam je pridam.
Nevim jak komplikovane je zalozeni nove tabulky, mozna pokud to v mysql delas pres cmd tak je.

Dotazy mas spravne, muj je o 1 radek vetsi.

Pridani jazyka: 1 radek v ciselniku jazyku.

Jak tu psal Finc, navrh datove struktury vychazi z pozadavku na system, ty je mas jiny nez je mam ja. Pro muj system je tve reseni naprosto nevyhovujici, stejne jako je me pro tvuj, ja to beru.

ikona Jakub Vrána OpenID:

Vždyť jsem napsal "pamatovat nebo vždy zjišťovat" = "Pokud chci pridat pole ktere se bude prekladat, kouknu do analyzy, vyhledam tabulku." A ty píšeš, že se každý pohybujeme úplně jinde...

V čem v tvém schématu spočívá založení tabulky s překlady? Tabulka musí obsahovat sloupce primárního klíče tabulky s daty + cizí klíč k nim, sloupec pro uložení ID jazyka správného typu + cizí klíč k němu, primární klíč nad těmito dvěma nebo více sloupci a potom teprve sloupce s překladem. Děláš to pokaždé ručně? Nebo na to máš skript nebo uloženou proceduru? Pokud je obecná, tak jistě není dvakrát jednoduchá. Výsledek je ten, že dělat tuto operaci ručně může vést k chybám a pokud na to potřebuješ skript, tak nemáš co vyčítat mému řešení.

"Pridani jazyka: 1 radek v ciselniku jazyku."
Tak si to rozmysli - používáš relativně jednoduchý dotaz uvedený na začátku tohoto vlákna nebo přidání jazyka znamená přidání jednoho řádku do tabulky s jazyky? Protože pokud chci místo nepřeložených dat zobrazovat výchozí jazykovou verzi (což je zcela samozřejmý požadavek), tak oboje najednou nejde. Pokud přidání jazyka zjednodušíš na vložení jednoho řádku do tabulky s jazyky, musíš všechny dotazy zesložitit tak, aby v případě neexistence překladu použily výchozí jazykovou verzi (tedy přidat do dotazu další tabulku stejného jména).

Oceňuji, že mi s Fincem nabízíte kompromisní uzavření této diskuse, bohužel to ale nemohu přijmout. Váš návrh některé věci vůbec neumožňuje (ve variantě bez kopírování nepřeložených dat využití unikátních indexů), jiné neumožňuje dělat efektivně (správné třídění, vyhledávání) a další kazí (fulltextové vyhledávání). Jako „protihodnotu“ za tyto ústrky získám o řád složitější dotazy pro získání dat. A proč to vše? Kvůli falešnému pocitu čistoty návrhu - „falešnému“ píšu proto, že elementární změny ve struktuře dat stejně není možné dělat jednoduchým způsobem.

ikona Jakub Vrána OpenID:

Pro srovnání ještě ukážu, jak by vypadal jednoduchý dvojtabulkový dotaz, kdybychom nekopírovali nepřeložené hodnoty (kvůli tomu, aby šlo přidání jazyka udělat prostým přidáním jednoho řádku do tabulky s jazyky):

-- moje řešení
SELECT IFNULL(vyrobky.nazev_$LANG, vyrobky.nazev_en), IFNULL(skupiny.skupina_$LANG, skupina_en)
FROM vyrobky
INNER JOIN skupiny ON vyrobky.skupina = skupiny.id
ORDER BY skupiny.poradi, 1;

-- tvoje řešení
SELECT IFNULL(vyrobky_en.nazev, vyrobky_lang.nazev), IFNULL(skupiny_en.skupina, skupiny_lang.skupina)
FROM vyrobky
INNER JOIN vyrobky_lang AS vyrobky_en ON vyrobky.id = vyrobky_en.id AND vyrobky_en.jazyk = 'en'
LEFT JOIN vyrobky_lang ON vyrobky.id = vyrobky_lang.id AND vyrobky_lang.jazyk = '$LANG'
INNER JOIN skupiny ON vyrobky.skupina = skupiny.id
INNER JOIN skupiny_lang AS skupiny_en ON skupiny.id = skupiny_en.id AND skupiny_en.jazyk = 'en'
LEFT JOIN skupiny_lang ON skupiny.id = skupiny_lang.id AND skupiny_lang.jazyk = '$LANG'
ORDER BY skupiny.poradi, 1;

Ejhle, dotaz je zase přibližně třikrát delší a složitější.

Štěpán Svoboda:

SELECT IFNULL(skupiny.skupina_$LANG, skupina_en) ...

Narazil jsi také na problém s mysql chybou
illegal mix of collations, kterou vyhodí mysql za předpokladu že na českém sloupci je collation=utf8_czech_ci a na anglickém utf8_general_ci

Hledal jsem o tom něco na fóru mysql ale na nic rozumného jsem nenarazil.

ikona Jakub Vrána OpenID:

Řeším to přetypováním na BINARY.

Radek:

Uvedes konkretni priklad pretypovani?Diky.

marbert:

Napadl me koncept, kdy by se jazykove verze ukladaly do jednoho sloupce v xml a vytahovaly (v myslq 5.1) pres ExtractValue(). Updatovaly pres UpdateXML(). Podle takto vytazene hodnoty by slo relativne snadno i radit.

Prijde mi, ze toto reseni kombinuje vyhody vsech navrzenych (bez naroku na spravu, ukladaji se jen prelozene retezce,...), jen nevim jak by na tom bylo s rychlosti.

Co si o tom myslite?

ikona Jakub Vrána OpenID:

V tomto řešení nespatřuji žádné výhody.

ikona finc:

No tak to je mazec, koukám, že i pěkný flame.
Co se týče jazykové verze webu, je třeba rozdělit dvě věci.
1. hodnoty statické, které jsou spojeny přímo s webem
2. hodnoty dynamické, tzn. popis produktu, atd.

K oboum z nich bych jistě přistupoval odlišným způsobem. Ke statickým tak, že by byly označeny nějakou logikou (vlastními tagy či funkcí). Klíčem by byl nějaký vlastní identifikátor a jazyková verze. Pak by jistě neměl být problém takové texty vypsat.

Co se týče hodnot dynamických, tam bych postupoval trochu odlišněji. Měl vlastní tabulku, kde by klíčem bylo id z tabulky, které se hodnota týka + tabulka které se to týká + sloupec, kterého se týká. Nevýhodou je pozdější práce při změně názvu sloupce či tabulky.

Nebo bych si pro každou tabulky vytvořil vlastní s prefixem, že se jedná o jazykovou verzi (př. produkty, produkty_lang). V produkty_lang by klíčem byla id záznamu z produkty + jazyková verze a sloupce typu varchar (neboli sloupce, které  mají být přeloženy) byly převedené z tabulky produkty, kde by neexistovali. K tomu bych samozřejmě napsal dynamické query, které by automaticky joinovalo k hlavní tabulce i její lang verzi a tím získal všechny požadované hodnoty. Jinak joiny by byly nejspíše dva, to v případě, že by pro vybranou jazykovou verzi neexistovala alternativa. Defaultní jaz.verze by musela existovat vždy, což se dá zajistit aplikací při tvorbě textů.
Nevýhodou tohoto řešení je změna výchozí jazykové verze, ale pokud by byl dobře napsán dynamický query, tak by to snad nebyl až takový problém.

Vytvářet sloupce pro každou jazykovou verzi? Sakra, to už je apríl?

ikona D1ce:

1) Souhlas
2) Připadá mi to ještě krkolomnější.

IMHO flame vzniká proto, že pan Vrána poukazuje na nedostaky obecně uznávané pravdy. Zkuste po webu hledat lokalizace webových aplikací.

IMHO dle mě je opmíjená verze, že různé jazykové verze jsou spolu minimálně svázány a vyvíjejí se nezávisle na sobě. Tím myslím, že například na cs.example.com a en.example.com jsou 2 podobné instalace aplikace a synchronizuje se jen to nejnutnější.

ikona finc:

Souhlasím, že to není zrovna jednoduché řešení, zejména při psaní query bych musel dávat pozor. Na to ale stále existuje možnost vytvoření pohledu + kontrola referencí. Otázkou zůstává, jakou web aplikaci tvořím. Pokud se jedná o malý eshop nebo cms aplikaci, tak bych k nim jistě přistupoval odlišnými způsoby, než k obsáhlé intranet aplikaci.
Jistě bych neporušoval obecně známé pravidlo, že uživatelským vstupem neměním strukturu databáze. Pokud tak činím, není to doporučené řešení, s čímž souhlasím.
Otázkou také je, co všechno chci nebo nechci překládat, jestli budu potřebovat nějaké super-hyper vyhledávání, nebo zda budu koukat na jednoduchost a snadnou rozšířitelnost. Prostě těch aspektů je tolik, že se obávám, že neexistuje všeobecný "návod", jak toto vyřešit.
Je to něco jako pojednání o tom, zda použít nějaký super-hyper MVC framework, nebo použít mixovanou app. logiku s prezentační, protože tvořím malinky webík pro kamaráda.

ikona Jakub Vrána OpenID:

Já samozřejmě také dodržuji pravidlo, že uživatelský vstup by neměl měnit strukturu databáze. Jde o to stanovit, co je ještě uživatelský vstup a co už změna struktury. Dám jiný příklad:

Uživatel se rozhodne, že nechce u čísel evidovat dvě desetinná místa, ale tři. Co udělám? Změním DECIMAL(9, 2) na DECIMAL(10, 3) a hotovo. A co pokud si to chce nastavovat sám? Možnost číslo jedna - opět změním DECIMAL(9, 2) na DECIMAL(10, 3) a hotovo. Možnost číslo dva - do tabulky s nastavením přidám sloupec určující, kolik desetinných míst se má brát v potaz a v každém dotazu se na hodnotu tohoto sloupce podívám. Pokud na to zapomenu, dojde k chybě. O tom, že se všechny dotazy zkomplikují o získání této hodnoty nemluvě.

A tak i když je druhé řešení lepší z pohledu teorie návrhu databázových aplikací, je v praxi horší, protože je náročnější na používání, více vede k chybám a je pomalejší.

ikona finc:

No nevím. Záleží kde si určíš pravidla, co je a co není uživatelský vstup. Jinak řečeno, jistě nebudu uživateli poskytovat možnost měnit velikost nebo typ sloupce v DB. Důvod je jasný zejména těm, kteří mají zkušenosti s implementací nějakého projektu lidem se základními PC znalostmi ;)
Mohli bychom se tady dohadovat do nekonečna, ale na samotném návrhu a vývoji je hezké to, že existuje několik cest z nichž správné mohou být klidně věechny, které mě napadnou :)
Překlady jsou tak moc specifickou vlastností každého projektu, že, podle mého, vážně neexistuje "všeobecný" způsob.

ikona Jakub Vrána OpenID:

Řešení, které popisuješ, obhajuje Matej. Jeho nevýhody už jsem popsal: http://php.vrana.cz/ulozeni-prekladu-do-samostat….php#d-4234

Vytváření sloupců pro každou jazykovou verzi není apríl, ale naopak v důsledku to nejlepší řešení tohoto problému.

ikona Dominik Franek:

V současné době hledám z různých zdrojů, jak ideálně vytvořit vícejazyčný web. Můj původní návrh počítal právě s několika tabulkami:

languages - seznam dostupných jazyků webu
tree - prostě nějaká stromová struktura
texts - a přiřazené texty k jednotlivým položkám těch tree.

Ovšem po přečtení této diskuze a argumentech p.Vrány se mi začíná líbit jeho řešení. To co upřednostňuji je totiž rychlost na straně běžného uživatele.
To, že přidání jazyka bude komplikované (navíc jak často se bude provádět???? max. v řádu desítek) pro administrátora, či že by bylo více kódu je pořád na nižší úrovni, než když se budou správně řadit položky dle jazyků, fulltextové vyhledávání bude fungovat. Jak jsem se dočetl, má to i další výhody, bohužel jsem je již tak přímo nepochopil, proto bych klidně uvítal i další vysvětlení :)

Oponenti p.Vrány by ovšem také mohli uvést výhody jejich řešení, já se v současné době totiž stále rozhoduji, které řešení zvolit. To od pana Vrány se mi zdá na straně uživatele výhodnější, ikdyž analytická část projektu jde vniveč. Argument typu "složité přidání jazyka" je úplně zcestný, neboť administrátor bude jednak přidání provádět jednou za čas a druhak k tomu má více času (třeba celou noc? :) )

Dík moc a těším se na reakce a další nápady případně další odkazy.

Dominik Franěk

brano:

Este jedno riesenie by bolo -
produkt(id,spolocne_vlastnosti)
produkt_text_en(id, text)
produkt_text_de(id, text)
...

1. Funguje triedenie a fulltext
2. Dotazy jednoduche (SELECT * FROM produkt JOIN produkt_text_$lang ... )
3. Pridanie jazyka pomerne jednoduche

ikona Jakub Vrána OpenID:

Ještě prosím uveď, jak se pracuje s nepřeloženými hodnotami (po přidání nového jazyka nebo po přidání nového produktu bez překladu).

A pak taky zda bude nějak ošetřena situace, že všechny překladové tabulky budou prázdné.

A dále situaci, kdy chceme hledat podle společné vlastnosti a třídit podle textu (složený index přes více tabulek totiž vytvořit nejde).

singha:

Pokud přidávání nových sloupců s novým jazykem do databáze dělá vhodně navržený skript a koncový uživatel dostává rychlé a přehledné informace v administračním rozhraní/shopu (tj. vůbec netuší, jaká je struktura databáze, ale je šťastný, že všechno perfektně a rychle funguje), tak Jakubovo řešení je lepší než jiná "standardní" řešení programátorů, kteří jsou "někde jinde".

Přiznám se však, že geometrická řada nárůstu počtu sloupců (X údajů v Y jazycích = X*Y sloupců) mě coby programátora nenechává úplně klidným.

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.