
PHP a čeština IV. - vícejazyčné aplikace, 2. část
Hlavní výhodou databáze je, že přistupujeme pouze k datům, které potřebujeme.
Databázový stroj (server) může samozřejmě pracovat s daty různým způsobem,
avšak určitě efektivněji, než jak se s nimi pracuje přímo v PHP. Pro
demonstraci principů práce jsem zvolil databázový systém MySQL, který se
v kombinaci s PHP používá velmi často, je velmi výkonný a dobře dostupný
díky svým licenčním podmínkám. Rozdíly oproti jiným relačním databázím
(PostgreSQL, Firebird, Oracle atd.) jsou však minimální a tak lze program na
tyto systémy snadno upravit.
Vícejazyčné aplikace s použitím databáze
Prvním krokem bude opět návrh způsobu uložení dat, tedy datový model.
V tomto případě to bude velmi jednoduché, protože si vystačíme s jedinou
tabulkou. Klíčovým atributem bude onen identifikační klíč známý z předchozího
příkladu. Ostatní atributy budou jednotlivé jazykové mutace. Zatím jich je
pevný počet, ale je možné i přidávání dalších jazyků za provozu.
Předpokládejme, že délka klíče nepřekročí 50 znaků, proto použijeme datový
typ s touto délkou. Naopak délka ostatních textů může být libovolná -
ohraničíme ji hodnotou 65535 bajtů (používáme UTF-8, proto se znaků může
vejít méně!). Tabulku vytvoříme takto (vytvoří se také index nad klíčem):
CREATE TABLE Texty (klic VARCHAR(50) NOT NULL PRIMARY KEY, lang_cs TEXT,
lang_en TEXT, lang_de TEXT, UNIQUE INDEX ind (klic))
A můžeme tabulku naplnit daty. To se dá různě automatizovat, teď si ale
vystačíme s "ručním" naplněním. Data budou téměř identická jako u první metody.
Pozor na kódování, musí být UTF-8!
INSERT INTO Texty VALUES('nadpis','Testovací stránka','The testing page','Die Testseite');
INSERT INTO Texty VALUES('popis',
'Toto je vícejazyčná testovací stránka.',
'This is a multilingual testing page.',
'Das ist eine mehrsprachige Testseite.');
INSERT INTO Texty VALUES('demonstrace',
'Demonstruje mechanismus s použitím databáze.',
'Is demonstrates the mechanism with database.',
'Es demonstriert der Mechanismus mit Datenbasis.');
Databáze je tedy naplněna, můžeme se pustit do implementace PHP části.
Bude to opět velmi snadné. Přístup k databázi zahrneme do funkce:
<?
function text_by_lang($db,$klic,$lang) {
$sql = "SELECT $lang FROM Texty WHERE klic='$klic'";
$res = mysql_query($sql,$db);
$ta = mysql_fetch_row($res);
return $ta[0];
}
?>
Celé to pak bude vypadat velmi podobně jako v předchozím případě:
<?
require("funkce.php"); // Přidáme soubor s funkcí
header("Content-Type: text/html; charset=utf-8"); // Typ dat a znaková sada
$jazyk = $_GET["lang"]; // Nastavení jazyka podle požadavku
if ($jazyk == "") $jazyk = "cs"; // Není-li určen jazyk, použije se čeština
$con = mysql_connect("127.0.0.1","uzivatel","heslo"); // Připojení databáze
$jazyk = "lang_$jazyk"; // Úprava názvu jazyka
?>
<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<?
echo "<title>";
echo text_by_lang($con,"nadpis",$jazyk);
echo "</title>\n";
?>
</head>
<body>
<?
$str = text_by_lang($con,"nadpis",$jazyk);
echo "<h1>$str</h1>\n";
$str = text_by_lang($con,"popis",$jazyk);
echo "<p>$str</p>\n";
$str = text_by_lang($con,"demonstrace",$jazyk);
echo "<h1>$str</h1>\n";
</body>
</html>
V uvedeném příkladu nejsou ošetřeny chyby, které mohou nastat. Také uvedená
implementace připojení na databázi není úplně nejbezpečnější, ale v zájmu
jednoduchosti jsem tyto prvky vypustil. Pro velké množství projektů si
s podobně jednoduchou implementací vícejazyčné podpory vystačíme. Teprve u
těch opravdu velkých, a také u těch, kde nám opravdu záleží na stabilitě,
použijeme něco, co bylo za tímto účelem vytvořeno a co nabízí velké možnosti:
balík Translation z knihovny PEAR.
Využití balíku Translation
Než se budeme zabývat samotným balíkem Translation, bude dobré nastínit,
co je vlastně knihovna PEAR. PEAR je zkratka pro "PHP Extension and
Application Repository". Na začátku byla myšlenka, že by se mělo shromáždit
všechno, co bylo v PHP vytvořeno, co chce někdo nabídnout ostatním a co je
obecněji použitelné. A nejen shromáždit, ale také uspořádat, nastolit
určitou "štábní kulturu", vytvořit vhodný model pro údržbu kódu a jeho
distribuci a v neposlední řadě poskytnou pro toto všecho potřebnou podporu
(WWW stránky, zrcadla, konference atd.).
Kód v PEAR je uložen v balících, každý balík obsahuje kód k určitému účelu
(analogie s balíky u Javy). Balíky jsou uspořádány do stromové struktury
podle svého účelu. Na psaní kódu se vztahují pravidla nazývaná PCS (PEAR
Coding Standards).
Všechny balíky jsou uloženy v centrální databázi, ke které je přístup přes
HTML stránky a také prostřednictvím XML-RPC (pro automatizovaný přístup).
Balíky se jsou k dispozici pro stažení protokolem HTTP, jsou komprimovány
pomocí GZIP a obsahují popis v XML souboru. Pro údržbu verzí se používá
systém CVS.
Zvláštní částí PEAR jsou PHP Foundation Classes (PFC). Tato složka obsahuje
balíky zaměřené na kvalitu, kompatibilitu a stabilitu. Platí zde nejpřísnější
kritéria. Do základní instalace PHP jsou z celého PEAR přibírány komponenty
pouze právě z PFC. Bylo by dobré zmínit ještě ozančení PECL (PEAR Extension
Code Library). Jsou to rozšíření PHP; vztahují se na ně standardy PHP, ale
jsou šířeny jako balíky PEAR.
Jedním z balíků je také již zmíněný Translation. Vytvořili ho polští
programátoři s cílem získat snadno použitelný a robustní nástroj pro snadnou
tvorbu vícejazyčných PHP aplikací. Prostřednictvím PEAR ho dávají k dispozici
pro všeobecné použití.
Ke vlastnostem a schopnostem balíku Translation patří:
- dynamické přidávání a odebírání jazyků
- dynamické přidávání a odebírání textů pro jazyky
- parametrizovatelné dotazy (pro výběr jazykové mutace textu)
- získání META tagu do HTML hlavičky
- ošetření chyb
- efektivní práce s databází
Pro funkci balíku je samozřejmě nezbytná instalace základního balíku PEAR;
v posledních verzích PHP (konkrétně od 4.3.0) je již PEAR přímo součástí
distribučního balíku PHP.
Balík Translation obsahuje dva PHP soubory. V jednom z nich se nachází samotná
třída Translation, ve druhém jsou pomocné funkce určené pro manipulaci
s databází. Pro přístup k databázi se využívají prostředky poskytované PEAR.
Databáze vypadá tak, že v jedné tabulce obsahuje jednotlivé jazyky včetně
souvisejících dat a pak je pro každý jazyk zvláštní tabulka. Pro tři jazyky
(viz předchozí příklady) by databáze vypadala takto:
CREATE TABLE tr_langsavail (lang_id CHAR(2) NOT NULL default '' PRIMARY KEY,
name VARCHAR(128) DEFAULT NULL, metatags LONGTEXT, errortext LONGTEXT,);
CREATE TABLE tr_strings_cs (page_id VARCHAR(16) DEFAULT NULL,
string_id VARCHAR(32) NOT NULL DEFAULT '', string LONGTEXT,
UNIQUE INDEX page_id (page_id,string_id));
CREATE TABLE tr_strings_en (page_id VARCHAR(16) DEFAULT NULL,
string_id VARCHAR(32) NOT NULL DEFAULT '', string LONGTEXT,
UNIQUE INDEX page_id (page_id,string_id));
CREATE TABLE tr_strings_de (page_id VARCHAR(16) DEFAULT NULL,
string_id VARCHAR(32) NOT NULL DEFAULT '', string LONGTEXT,
UNIQUE INDEX page_id (page_id,string_id));
Již z popisu tabulek je vidět, že můžeme textové řetězce rozlišovat také
podle stránek, což se hodí tehdy, když potřebujeme rozlišovat řetězce
umístěné na různých stránkách.
Do vytvořené databáze vložíme data, zatím jen ta, která popisují jednotlivé
jazyky:
INSERT INTO tr_langsavail VALUES ('cs', 'Česky',
'<meta http-equiv="content-type" Content="text/html; charset=utf-8">',
'Česká verze nebyla nalezena.');
INSERT INTO tr_langsavail VALUES ('en', 'English',
'<meta http-equiv="content-type" Content="text/html; charset=utf-8">',
'English version not found.');
INSERT INTO tr_langsavail VALUES ('de', 'Deutsch',
'<meta http-equiv="content-type" Content="text/html; charset=utf-8">',
'Die deutsche Version nicht gefunden.');
Potom můžeme vložit i texty v jazykových mutacích. Rozlišení podle stránek
zde zůstane nevyužito:
INSERT INTO tr_strings_cs VALUES ('', 'nadpis', 'Testovací stránka');
INSERT INTO tr_strings_en VALUES ('', 'nadpis', 'The testing page');
INSERT INTO tr_strings_de VALUES ('', 'nadpis', 'Die Testseite');
INSERT INTO tr_strings_cs VALUES ('', 'popis',
'Toto je vícejazyčná testovací stránka.');
INSERT INTO tr_strings_en VALUES ('', 'popis',
'This is a multilingual testing page.');
INSERT INTO tr_strings_de VALUES ('', 'popis',
'Das ist eine mehrsprachige Testseite.');
INSERT INTO tr_strings_cs VALUES ('', 'demonstrace',
'Demonstruje mechanismus s použitím PEAR balíku Translation.');
INSERT INTO tr_strings_en VALUES ('', 'demonstrace',
'Is demonstrates the mechanism using PEAR Translation package.');
INSERT INTO tr_strings_de VALUES ('', 'demonstrace',
'Es demonstriert der Mechanismus mit PEAR Translation Paket.');
Protože práce s texty v různých jazycích je řešena v rámci balíku Translation,
můžeme přejít ihned k implementaci vlastní stránky generované pomocí PHP:
<?
require_once("translation/translation.class.php");
$tr = new Translation ("Test", $_GET['lang'], "mysql://uzivatel:heslo@127.0.0.1/translation");
?>
<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<?
echo "<title>"
$tr->gstr("nadpis")
"</title>";
echo $tr->getMetaTags();
?>
</head>
<body>
echo "<h1>"
$tr->gstr("nadpis")
"</h1>";
echo "<p>"
$tr->gstr("popis")
"</p>";
echo "<p>"
$tr->gstr("demonstrace")
"</p>";
</body>
</html>
Tento PHP kód je ještě jednodušší než ty předchozí. A to není zapotřebí ani
plnit ručně databázi, protože i toto dokáže zajistit balík Translation.
Je pouze nutné, aby databáze existovala. Jazyk se přidá voláním k tomu
určené funkce. Potom je samozřejmě ještě třeba přidat všechny texty v daném
jazyce. Přidání jazyka si můžeme ukázat např. na slovenštině (omlouvám se
za případné jazykové nedostatky):
<?
createNewLang("sk","Slovensky", "",
"<meta http-equiv="content-type" Content="text/html; charset=utf-8">",
"mysql://uzivatel:heslo@127.0.0.1/translation");
addTranslation("","nadpis","Testovacia stránka",
"mysql://uzivatel:heslo@127.0.0.1/translation");
addTranslation("","popis","Toto je viacjazyčná testovacia stránka",
"mysql://uzivatel:heslo@127.0.0.1/translation");
addTranslation("","demonstrace","Demonštruje mechanizmus za použitia PEAR balíka Translation",
"mysql://uzivatel:heslo@127.0.0.1/translation");
?>
Tento způsob přidávání jazyků má jednu nevýhodu. Neumožňuje definovat chybový
text pro případ, že daný text nebyl nalezen. Doufejme, že v novějších verzích
balíku bude již toto vyřešeno.
Jazyky je možné podobným způsobem také odebírat. K tomu slouží funkce
removeLang(). Odebrat lze i textové řetězce, a to vždy pro všechny jazyky
- pomocí funkce removeTranslation().
Ve třídě Translation jsou ještě dvě zajímavé metody, které stojí za to uvést.
První má název getLangName() a vrací název aktuálního jazyka. Metoda
getOtherLangs() je k ní komplementární, vrací pole obsahující všechny
ostatní jazyky a hodí se k přepínání mezi jednotlivými mutacemi.
Metoda gstr(), která realizuje načtení správného řetězce z databáze, má, jak
již bylo řečeno, možnost parametrizace. Běžně stačí pouze uvést klíč pro
výběr řetězce, ale možnosti jsou podstatně větší. Jednou z těchto možností
je explicitní specifikace stránky pro výběr textu. K tomu se používá tzv.
tečková notace, tedy tvar stránka.klíč (jinak se použije stránka určená
při vytváření instance třídy Translation).
Vlastní parametrizace se však provádí až druhým argumentem. Ten má podobu
asociativního pole, jehož prvky obsahují příslušné parametry. Tyto prvky
mohou být až tři:
-
action - výběr akce k provedení (viz hodnoty dále)
normal - implicitní hodnota; vybere se text podle aktuálního jazyka
translate - text se vybere podle jazyka specifikovaného v parametru
lang_id
-
lang_id - určuje explicitně jazyk pro výběr textu. V případě nastavení action
na translate je uvedení povinné. Naopak by se parametr neměl užívat v případě
action na hodnotě normal, protože se nevyužívá informací uložených v cache.
-
optimization - způsob optimalizace dotazů, rozdělení zátěže mezi PHP serverem
a databázovým serverem. Používá se pouze při action nastavené na translate.
Může nabývat těchto hodnot:
php - větší počet jednoduchých dotazů. Vhodné v případě, kdy PHP a databáze
běží na stejném stroji.
db - jediný složitější dotaz. Vhodné pro případy, kdy běží databáze na
jiném stroji než PHP. Nefunguje pro databázové systémy, které nepodporují
vnořené dotazy.
Závěr
Ačkoliv jsem se nezmínil o všech aspektech "soužití" češtiny a PHP, i tak je
zcela zřejmé, že se jedná o velmi komplexní problematiku. Všechny oblasti je
třeba řešit současně, čímž se samozřejmě situace dále komplikuje. Jak se
ale přitom ukazuje, nástrojů pro řešení těchto problémů je mnoho a máme
opravdu z čeho vybírat. Proto věřme, že se tyto nástroje budou využívat na
sto procent a my se budeme do budoucna setkávat pouze s takovými WWW stránkami,
které budou z tohoto hlediska zcela bez problémů.
Odkazy
Tento článek vyšel v prvním čísle časopisu PHP Magazín, který vydává společnost Software Media, s.r.o.
Další části seriálu:
|