Ukládání nepřeložených dat

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

Při ukládání jazykových verzí časem narazíme na problém, že texty v některých jazycích nebudou zatím k dispozici. Řešit se to dá několika možnými způsoby:

  1. Do sloupce s překladem uložíme text výchozí jazykové verze (např. anglické). Potom ale nepoznáme, které texty jsou zatím nepřeložené a které jsou jen shodou okolností stejné v obou jazykových verzích.
  2. Do nepřeloženého sloupce uložíme hodnotu NULL. Potom ale v tomto sloupci nebude možné hledat a bude problematičtější jeho zobrazování.
  3. Do nepřeloženého sloupce uložíme výchozí text prodloužený o nějaký speciální znak (např. `), který při výstupu ořízneme. Toto nečisté řešení je funkční až do chvíle, kdy chceme nad sloupcem použít unikátní klíč. Potom hodnoty 'abc`' a 'abc' budou z pohledu databáze různé, i když znamenají totéž. Problém s unikátními klíči se ostatně týká i předchozího případu.

Pokud se do nepřeloženého sloupce rozhodneme ukládat výchozí verzi, musíme počítat kromě větších diskových nároků také s tím, že sloupec musíme při změně výchozí jazykové verze změnit také. Pokud bychom se naopak rozhodli ukládat hodnotu NULL, musíme zapomenout na fulltextové vyhledávání nad více sloupci (MATCH(nadpis_cs, clanek_cs)), které je nerealizovatelné v případě, že jeden sloupec přeložený je a druhý ne.

Kam tedy uložit informaci o tom, jestli sloupec přeložený je, nebo není? Pokud je v každé tabulce s jazykovými verzemi jednosloupcový číselný primární klíč, můžeme ji uložit do tabulky neprelozeno(tabulka varchar(64), sloupec varchar(64), id int, neprelozeno set('cs', 'sk')). Pokud tabulky takto jednotnou strukturu nemají, bude lepší informaci uložit přímo vedle přeložených dat: vyrobky(id int, cena int, nazev_en varchar(50), nazev_cs varchar(50), nazev_sk varchar(50), nazev_neprelozeno set('cs', 'sk')). Aktualizace tabulky potom může vypadat takto:

<?php
$JAZYKY = array("en", "cs", "sk");

/** Naplnění jazykových verzí, prázdné hodnoty se považují za nepřeložené
* @param string název sloupce
* @return array pole obsahující hodnoty pro všechny jazykové varianty sloupce a informace o nepřeloženosti ve sloupci s koncovkou _neprelozeno
*/
function sloupec_jazyk($sloupec) {
    global $JAZYKY;
    $set = array();
    $neprelozeno = array();
    foreach ($JAZYKY as $jazyk) {
        if (!strlen($_POST[$sloupec . "_en"]) || strlen($_POST[$sloupec . "_$jazyk"])) {
            $set[$sloupec . "_$jazyk"] = "'" . mysql_real_escape_string($_POST[$sloupec . "_$jazyk"]) . "'";
        } else {
            $set[$sloupec . "_$jazyk"] = "'" . mysql_real_escape_string($_POST[$sloupec . "_en"]) . "'";
            $neprelozeno[] = $jazyk;
        }
    }
    $set[$sloupec . "_neprelozeno"] = "'" . implode(",", $neprelozeno) . "'";
    return $set;
}

$set = array();
$set["cena"] = intval($_POST["cena"]);
$set += sloupec_jazyk("nazev");
?>

Formulář pro zadání hodnot může vypadat takto:

<?php
foreach ($JAZYKY as $jazyk) {
    echo "$jazyk: <input name='nazev_$jazyk' value=\"" . (in_array($jazyk, explode(",", $row["nazev_neprelozeno"])) ? "" : htmlspecialchars($row["nazev_$jazyk"])) . "\" maxlength='50' /><br />\n";
}
?>

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, 8.2.2007, diskuse: 11 (nové: 0)

Diskuse

ikona MiSHAK:

No je to docela KISS :-).

dajaja:

Zajímavé, ale dalo by se spravit kompletní stažení webu? Zrovna css styly mě tolik nezajímají.

ikona Jakub Vrána OpenID:

Dotazu bohužel zcela nerozumím. Kompletní stažení webu mi bez problémů funguje a souvislost s CSS styly mi taky uniká.

Preklad:

Pokud mam preklad v databazi (a zrovna nepouzivam gettext), resim vse pres sloupec s jednickou nebo nulou. Jednicka prelozeno, nula neprelozeno. Kazdy prekladatel si potom (pres nejake webove rozhrani) muze nechavat rozpracovane casti, casti, kde si neni jist nebo kde se chce k necemu vracet atd. Kdyz ma prelozeno, potom jen zaskrtne "Prelozeno".

Potom mam jednoduchy skript, ktery vsechny preklady stahne, zpracuje a vytvori z nich soubory, ktere se pote includuji. Skript osetruje texty a vytvari PHP promenne, s kterymi pote pracuji. Kdyz neco neni prelozeno, nastavi se zakladni jazyk, ktery je vzdy prelozeny.

MySQL umi samozrejme cachovat data a u prekladu je to idealni. Preklad se meni jednou za cas, takze se dotaz provede jen jednou a pote uz taha z pameti. Volim ale radsi variantu se soubory, ktere lze take ulozit v pameti serveru, takze se ani ty nemusi pokazde nacitat.

ikona Jakub Vrána OpenID:

Ale tady nejde o překlad statických textů s pevným identifikátorem, ale o překlad sloupců závisejících na datech v tabulkách.

3rojka:

No myslím že už jsem viděl lepší lokalizaci. Lokalizované texty jsou uložené v samostatné tabulce, každá věta má klíč, kod jazyka a přeložený text pokud pro daný jazyk neexistuje záznam použije se defaultní jazyk. Připadne mi to jednoduché a taky rozšiřitelnější než vytvářet pro každý jazyk nový sloupec.

ikona Jakub Vrána OpenID:

Ano, tento přístup je popsán v článku odkazovaném na samém začátku textu. Je to samozřejmě snadněji rozšiřitelné, ale pracuje se s tím špatně. Zkuste si napsat dotazy řešící např. toto:

Výpis všech výrobků seřazených podle abecedy, u každého výrobku zobrazit jeho URL, název a popis.

Fulltextově vyhledat ve výrobcích podle názvu a popisu.

Na úrovni databáze zajistit unikátnost přeloženého sloupce (např. URL).

Ve zde popsané implementaci jsou všechny tyto dotazy triviální, v navrhované implementaci jen velice těžko uskutečnitelné.

finc:

Myslím, že toto vyřešit půjde. Problém je, že musím při každém dotazu zjišťovat, zda existuje nebo neexistuje preložená alternativa. Toto pomocí SQL vyřešit lze. Osobně bych použil, to co píše 3rojka a na join bych si vytvořil "view", který bych poté používal, čímž bych zamezil nebezpečí, že někde zapomenu použít kontrolu pro nepřeložený text.

ikona Jakub Vrána OpenID:

Pohledy samozřejmě vytvořit jde. Pominu-li, že budou poměrně ošklivé a pro jejich vytváření bude téměř nezbytné si udělat nějaký skript, tak se nesmí zapomenout na to, že při jakékoliv změně tabulky je nutné pohledy přegenerovat. To očekávanou jednodušší údržbu databáze značně nabourává.

Jak se zajistí efektivní řazení? Těžko - přidáním indexu (tabulka, sloupec, jazyk, preklad) se zbytečně budou indexovat i data, podle kterých se řadit nikdy nebude, navíc to nepokrývá řazení podle nepřeložených sloupců.

Fulltextové vyhledávání? Pominu-li to, že přijdeme o 50% hranici ignorovaných slov (což může být spíše výhoda), tak bude fulltextové vyhledávání v tabulce méně efektivní. Při indexu (nadpis, obsah) si totiž slovo obsažené v nadpisu i obsahu stačí do indexu uložit jednou, u rozděleného indexu tam musí být dvakrát.

Unikátní klíče? Leda přes poměrně komplikované (a opět raději generované) triggery.

I když má toto řešení samozřejmě i své výhody (jednoduché prohledání všech přeložených textů), tak je tak komplikované, že je jeho implementace a následná údržba spíše za trest.

ikona D1ce:

Pod tím se memůžu než podepsat. To co obhajuje 3rojka a finc byl první způsob, na který jsem narazil, a popravdě řečeno se mi vůbec nezamlouval, bez dobré znalosti MySQL bych ho snad ani správně nikdy nezprovoznil už díky tomu, co popisujete. Musím přiznat, že jsem o něco velmi vzdáleně podobného sám pokoušel, ale až inspirace těmito články mě posunula vpřed. Děkuji moc za ně a přeji mnoho dalších.

ikona 3rojka:

Ale jo asi máš pravdu, více meně s tebou souhlasím, ale zajímalo by mě jak bys řešil případ kdy každý jazyk překládá někdo jiný, resp někdo zadává defaultní a někdo jiný to překládá, pak totiž nastává problém že se změní defaultní verze, ale ten co to změní nemuže změnit nic jiného, takže to musí nechat na jindy. Asi by stačil slopeček nazev_updatovano.
Já to řeším ve taky sloupečkem. Moje řešení je někdy složitější, ale mě se prostě nelíbí míchání dat s lokalizací do jedné tabulky. Navíc se to mi více líbí mít celou lokalizaci všech tabulek pohromadě, rozlišování co k čemu patří se pak řeší bundlama atp. Možná by stálo za to si o tom pokecat více.

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.