Překlad statických textů

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

Podrobnější verze toho článku vyšla na serveru Interval v reakci na seriál o Gettextu.

Pokud vytváříme vícejazyčnou prezentaci, je nutné kromě textů vztahujících se k jednotlivým záznamům databáze přeložit také statické texty, které jsou součástí designu (navigace, popisky formulářových políček nebo tlačítek, alternativní popisky obrázků, …). Pro používání překladů je v PHP k dispozici standardní knihovna Gettext, která se používá i v jiných programovacích jazycích.

<?php
setlocale(LC_ALL, 'cs_CZ');
bindtextdomain("myapp", "./locale");
bind_textdomain_codeset("myapp", "utf-8");
textdomain("myapp"); // použije se soubor locale/cs_CZ/LC_MESSAGES/myapp.mo
echo ngettext("office", "offices", 5);
?>

Přestože mám rád řešení využívající zavedené existující produkty a Gettext je bezesporu kvalitní (např. má dobrou podporu pro množné číslo – angličtina má dva tvary, čeština u většiny slov tři, další jazyky mohou mít jiný počet tvarů), nemám tuto knihovnu v oblibě. Důvod je ten, že překlady se načítají z binárních souborů, které je nutné zkompilovat z podkladů, a tato kompilace není k dispozici v PHP. Při přidání jazykové hlášky se tedy neobejdeme bez externího nástroje, což je přinejmenším nepohodlné. Proto používám vlastní řešení – překlady jsou uložené v databázi a do textu je vypisuje funkce:

<?php
function lang($idf) {
    global $LANG; // nastaveno na základě části URL, session proměnné nebo cookie
    $result = mysql_query("SELECT preklad FROM preklady WHERE idf = '" . mysql_real_escape_string($idf) . "' AND jazyk = '$LANG'");
    return (mysql_num_rows($result) ? mysql_result($result, 0) : false);
}
?>

Pokud je na stránce překládaných textů hodně nebo pokud databáze běží na jiném serveru, takže každý dotaz něco stojí, je lepší všechny texty načíst najednou:

<?php
function lang_static($idf) {
    global $LANG;
    static $preklady;
    if (!isset($preklady)) {
        $preklady = array();
        $result = mysql_query("SELECT idf, preklad FROM preklady WHERE jazyk = '$LANG'");
        while ($row = mysql_fetch_assoc($result)) {
            $preklady[$row["idf"]] = $row["preklad"];
        }
        mysql_free_result($result);
    }
    return (isset($preklady[$idf]) ? $preklady[$idf] : $idf);
}
?>

Identifikátor může být číselný nebo textový, může to ale být také přímo jedna jazyková verze. Tabulku preklady lze naplnit např. tímto kódem:

<?php
foreach (glob("*.php") as $filename) {
    preg_match_all("~lang\\('(.*)'\\)~U", file_get_contents($filename), $matches);
    foreach ($matches[1] as $val) {
        mysql_query("INSERT INTO preklady (idf, jazyk, preklad) VALUES ('$val', '$LANG', '$val')");
    }
}
?>

Kód předpokládá, že nad sloupci idf, jazyk bude vytvořen unikátní klíč, což je ostatně vhodné tak jako tak.

Některé šablonovací systémy (např. HTMLTmpl) mají podporu pro překládané texty zabudovanou.

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

Jakub Vrána, Řešení problému, 15.7.2005, diskuse: 14 (nové: 0)

Diskuse

Arcao:

Mno my pouzivame castecne to druhe reseni. Texty se po nacteni z databaze ulozi do session a při každém dalším dotazu na stránku se vezmou texty ze session. Tím se ušetří dotaz do databáze.

ikona dgx:

jenže zbytečně se plní soubory sessions duplicitními překlady a dramaticky rostou nároky na kapacitu. To spíš ukládej do společného souboru.

tark:

Já používám v Pixine něco podobnýho - sloupce id,keyword,czech,english a pak si při vypisování stránky udělám sql select a udelam si "šablonovací" proměnné {trans-keyword}

Zdeněk Merta:

Jen nějak nechápu, proč by měla v případě použití Gettextu vadit nutnost použít externí nástroj pro kompilaci.

To budeme pomocí PHP kreslit grafiku, protože bychom jinak museli použít nějaký externí nástroj? :-D

ikona Jakub Vrána OpenID:

Vadí to třeba v případě, kdy chci klientovi dát možnost překládat si texty sám. Pokud se nástroj pro kompilaci nedá spustit přes web, tak je to problém.

Zdeněk Merta:

I tohle se dá řešit - stačí se domluvit s hostingem.

ikona spaze:

neni to uplne presne reakce na tebe, ale kdyz jsem driv zkousel gettext, tak mi nevyhledaval nektery retezce spravne (uz si presne nepamatuju, co to bylo, ale vim, ze mi obcas neco chybelo z .php). Je to ale par let zpet, takze situace je treba uz jina.

Dan:

Nošlo by to rozepsat ještě trošku podrobněji? Jde mi konkrétně o volání funkce ze stránky, kterou budu překládat?

ikona Jakub Vrána OpenID:

Třeba takhle: <h1><?php echo lang('Navigace'); ?></h1>

Michal Prynych:

Gettext má opravdu své mouchy a udělat v něm on-line překlad je téměř nadlidský úkol. Hlavně to není 100% spolehlivé řešení. Chci se tedy pustit do vlastního řešení, kvůli pohodlnosti je jasné, že to budu chtít ukládat do databáze. Otázkou potom tedy je, jak to z databáze dostat do aplikace, aby jí to zbytečně nezatěžovalo? Napadá mě soubor s PHP kódem, který by se includoval, ale to je zase čtení z disku. Přijde vám dobré řešení, uložit pole překladů do sdílené paměti? Aby se to nenačítalo pro každý request?

ikona Jakub Vrána OpenID:

Já nevidím moc velký problém v tom to při každém požadavku načítat vždy znovu z databáze (jedním dotazem, který získá všechny překlady najednou). Ona si to databáze stejně nakešuje. Pokud by profiler ukázal, že to aplikaci zdržuje, je možné použít vlastní keš (ať už sdílenou paměť nebo souborovou – tu si zase automaticky nakešuje např. eAccelerator).

Michal Prynych:

Jde mi o to, že těch překladů může být víc, již teď mám přes 300 KB textu pro každý jazyk, tedy přenos 300 KB sice z keše DB, ale pořád je tam ten přenos, navíc je to 300 KB paměti pro každý požadavek navíc mi přijde celkem dost. Na druhou stranu je asi pravda, že nejlepší bude to vyzkoušet a změřit.

Vojta:

Ahoj! Takhle nějak to řeším já, ale mám ještě nápad na drobné vylepšení. Ke každému textu můžeš přidat počítadlo, které se zvýší, jakmile je text načten z databáze do cache. Poté můžeš dopředu načíst např. prvních 50 řetězců s nejvyšší hodnotou počítadla. (Přesná hodnota závisí na počtu textů).

Miloš Brecher:

Vícejazyčnost webových aplikací je bohužel v budoucnu čím dál tím nezbytnější. Já bych to řešil vestavěným překladačem, který by do tabulky překládaných statických řetězců automaticky zapisoval ještě nepřeložené české řetězce, které by následně někdo ručně překládal a dopisoval. K českým řetězcům které by dohledal by vrátil přeložený ekvivalent, jinak by vrátil český řetězec. Jak ale zajistit výkonnost? V aplikaci obvykle dopředu nevíme jaké statické texty budeme potřebovat. Pokud nejsou texty rozsáhlé - číst celou tabulku překladů pro daný jazyk. Nebo odlišovat překlady pro samostatné moduly a číst všechny překlady pro daný modul.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 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.