Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

25. 2. 2008 - Komentáře (9) Zend Framework

Nastavení jazyku v URL pluginem

Konečně jsem předělal přepínaní jazyků na webu tak, aby každá jazyková verze měla jedinečná URL. Tím pádem odpadlo i používání cookies/sessions. Mořil jsem se s tím poměrně dlouho, ale nakonec jsem to zlomil. Protože mám celý web postaven na Zend Frameworku, hledal jsem řešení, jak to udělat co nejvíc tak, aby to zapadlo do konceptu Zend Frameworku, nakonec jsem ale musel použít trochu hack. K napsání článku mě přivedla i diskuze na fóru.

Možností, jak udělat přepínání jazyků je několik. Já jsem původně používal možnost s přepínáním jazyků prostřednictvím parametru v URL (entry?lang=en) a ukládání do cookies. Problém byl, že obě jazykové verze mají stejné URL. Parametr slouží jen k přepnutí. To je dostatečné pro intranety, ale na veřejném internetu se to moc nehodí, protože vyhledávače pak indexují jen jednu variantu.

Při použití Zend Frameworku máme zhruba tyto možnosti:

  • použít klasické řešení routy s definicí jazyka v URL při registraci
  • napsat si vlastní router
  • ve front controller pluginu si pohrát s URL
  • to samé jako předešlý bod, ale udělat to v bootstrapu

Klasický způsob

Jako nejčistější se jeví první řešení. Přesně zapadá do filozofie frameworku a přes URL view helper lze snadno vytvářet url pro použití ve view. Stačí při definici rout v bootstrapu definovat alternativní cestu pro jazykovou verzi

 //default module project controller route
 $this->_router->addRoute('projectsRoute',
     new Zend_Controller_Router_Route(':lang/projects/:action/:name',
     array('controller'=>'project','action'=>'index','name'=>null))
 );

Toto bude fungovat pokud bude označení jazyka v URL vždy přítomné. Pokud nebude přítomné, tak to nezafunguje ani pokud je v poli s parametry přednastaven lang parametr. Pokud tedy chceme používat implicitní jazyk v url, tak je potřeba takovou routu explicitně :-) definovat.

 //default module project controller route - with the language switcher
 $this->_router->addRoute('projectsRoute',
     new Zend_Controller_Router_Route(':lang/projects/:action/:name',
     array('controller'=>'project','action'=>'index','name'=>null))
 );
 
 //default module project controller route - default language is cs
 $this->_router->addRoute('projectsRouteDefLang',
     new Zend_Controller_Router_Route('projects/:action/:name',
     array('lang'=>'cs','controller'=>'project','action'=>'index','name'=>null))
 );

Problém je ten, že to musíte udělat pro každou Vámi definovanou routu. Pokud máte rout více (já jich mám okolo deseti), tak se Vám jejich počet zdvojnásobí, tím se pak i proces hledání správné cesty zhruba dvakrát zpomalí. Proto jsem toto řešení nepoužil.

Hrátky s URL

Ostatní řešení mají jedno společné, je potřebné z URL vypreparovat jazyk a takto upravené url pak vrátit dál do procesu jeho zpracování v routeru. Proto se dají tato řešení označit za tak trochu hack. Řešení se liší jen v tom, kde se uvedená akce provádí. Já jsem se rozhodl pro front controller plugin, protože ho už stejně používám. Přetížíme tedy metodu routeStartup().

class MainAppPlugin extends Zend_Controller_Plugin_Abstract
{
//==============================================================================
/**
 * Plugin hook before front controller begins evaluating the request.
 *
 * Sets language and locale if necessary parameters are presented in the url.
 * This is a hack, because directly modifies the $request->pathInfo, which
 * is the used for matching routes.
 */
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
 //set host part for the base application URL
 AppBaseUrl::setHost($request->getServer("SERVER_NAME"));
 
 $app=HomeWebApp::getInstance();
 
 //pathinfo = requestUri - baseUrl
 //e.g.:
 //whole URL = http://server_name/homepage/blog/myitem
 //requestUri = /homepage/blog/myitem, baseUrl=/homepage, pathInfo=/blog/myitem
 $pathInfo=$request->getPathInfo();
 
 //check if lang identfier is in the pathinfo
 if (preg_match('#^/([a-z]{2}/|[a-z]{2}$)#',$pathInfo,$matches)) {
  if (HomeWebApp::DEBUG) $app->getLog()->info("lang matched in pathInfo");
  $urlLang=rtrim($matches[1],'/');
  //remove lang identifiers from the pathInfo and sets as new pathinfo
  $pathInfo = strlen($pathInfo)==3 ? "/" : substr($pathInfo,3);
  $request->setPathInfo($pathInfo);
 } else {
  //language isn't in the url, default lang "without url specification" is cs
  $urlLang="cs";
 }
 
 //set application language
 $app->setLang($urlLang);
 //set language in all urls
 if ($urlLang!="cs") AppUrlEnum::getInstance()->setLang($urlLang);
 
 //set language in viewrenderer views
 $viewRenderer=Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
 //set language
 $viewRenderer->view->currentLang=$app->getLocale()->getLanguage();
}

Je nutné si uvědomit, že Zend Framework používá pro vyhodnocování URL pathInfo proměnnou v request objektu. Takže ta nejdůležitější část za preg_match se pokusí najít v pathInfo označení jazyka, pokud tam je, tak ho z pathInfo odstraní a takto osekané pathInfo pak vrátí zpět do requestu, kde na něm proběhne standardní vyhodnocení routování a následný dispatch. Pokud v pathInfo nastavení jazyka není, nic se nedělá, jen se nastaví implicitní jazyk.

Metoda dále volá další metody pro nastavení locale a překladů a nastavuje jazyk pro view ve viewRendereru.

Protože na práci s URL používám speciální objekt, není těžké do něj přidat podporu pro jazyk. To má zásadní výhodu, není třeba na nic ostatního v aplikaci sahat a ke změně URL v celé aplikaci dojde automaticky.

Vše si můžete prohlédnout v aktuálním subversion


Komentáře (9)

  1. spaze - 25. 2. 2008 17:54

    Pěkný začátek, ptal jsem se na v podstatě tu samou věc na Frameworks I , ale odpověď "nende" se mi nelíbila :) Nicméně weby na ZF nestavím (zatím) a vlastně weby vůbec nestavím :P a kdyžtak mám vlastní controller/dispatcher, který toto, resp. to, co přijde poskytuje, tak jsem po tom dál nepátral.

    Co by se mi moc líbilo, kdyby česká verze byla na
    http://mirin.cz/blog/cs/nastaveni-jazyku-v-url-pluginem
    anglická na
    http://mirin.cz/en/blog/url-language-selection-using-plugin
    a pak ruzné cviky jako třeba
    http://mirin.cz/cs/blog/url-language-selection-using-plugin
    přesměruje na
    http://mirin.cz/en/blog/url-language-selection-using-plugin
    a obráceně, bude fungovat lang-nego a přesměrování, pokud jazyk nebude v URL uveden apod. Toto v ZF by bylo naprosto dokonalé.

  2. koubel - 26. 2. 2008 15:02

    Ano, taky jsem nad tím kdysi uvažoval, asi by to i nad ZF také šlo udělat, ale na můj vkus je to taková ne moc důležitá věc s velkým množstvím práce. Každopádně ZF na takové věci nějakým přednastaveným chováním nemyslí.

  3. tomasr - 21. 3. 2008 10:21

    Zaujal me 1. zpusob pouziti, skusil sem to tedy zabudovat do bootstrapu, ale nejak to furt nefunguje, vlastne to nic nedela.
    Potreboval bych nasledujici url: www.nakejroot.cz/en/novinky

    Controller se samozrejme muze menit, action je defaultne index. Jak mohu dosahnout pozadovaneho chovani?

  4. koubel - 23. 3. 2008 13:34

    já bych to tipnul na tyhle dvě routy

    addRoute('newsRoute',
         new Zend_Controller_Router_Route(':lang/novinky',array('controller'='index','action'='index'))
    );
     
    addRoute('newsRouteDefault',
         new Zend_Controller_Router_Route('novinky',array('controller'='index','action'='index','lang'='cs'))
    );
    

    ta první by měla zareagovat na /en/novinky, /cs/novinky atd a ta druha na /novinky

  5. Koubas - 3. 8. 2008 12:39

    Já jsem se nakonec v jednom projektu, kde naštěstí není SEO na prvním místě, rozhodl použít variantu s cookies + prvotní nastavení jazyka, který je nastaven v prohlížeči a zasílá se v každém http requestu (čili standardní autodetekce v Zend_Translate). Tzn. když člověk příjde na web poprvé, nastaví se mu jazyk automaticky (jako třeba na Googlu) a cookie, která má přednost, se vytvoří až po tom, co jazyk explicitně změnil. Minimálně o Googlu jsem se dozvěděl, že prochází stránky tak, že v hlavičce requestu zkouší různá nastavení jazyka a pokud zjístí, že se přitom určitá stránka změnila, tak web indexuje pro různé jazyky zvlášť. Jak jsou na tom jiné world wide vyhledávače nevím, co se týče těch lokálních, tak tam bude předpokládám samozřejmostí, že v hlavičce posílají ten správný preferovaný jazyk. Každopádně jsem zvědavý, co to udělá :)

  6. zf user - 4. 10. 2008 15:20

    Ahoj, mozna to moc dobre nechapu, ale vpripade, ze mi nevadi stal pritomnost jazyka (tedy i defaultniho) v url, stejne budu potrebovat pro kazdou stranku zvlastni routu?

    Neco jako

    $this->_router->addRoute('projectsRoute',
         new Zend_Controller_Router_Route(':lang'
     );
    
    nejde udelat? Jde mi oto udelat obecnou routu, psat tam konkretni controller a action mi prijde dost nesikovne.

  7. tomasr - 24. 3. 2009 05:42

    Narazil jsem na problem, ktery byl zmineny vyse. A to totiz na preklad i samotnyho url do zvoleneho jazyka. V bootstrapu mam routu, ktera mi zajistuje routovani podle jazyka a lokalizace je plne funkcni. Jakym zpusobem by se dalo jednoduse resit to aby v ceske verzi bylo url v cestine a v anglicke anglicky? Dalo by se to resit jen v samotnem bootstrapu?

  8. koubel - 25. 3. 2009 14:05

    [6] - podle mě ne, jen se do rout přidá ten parametr lang
    [7] - co si tak pamatuju, tak se to musí dělat přes dvě routy, jedna pro
    /projekty a druhá pro /projects, obě budou ukazovat na jeden controller a view a bude se lišit jen lang. Někde jsem zahlédl, že se snad v ZF připravuje řešení, které by tyhle scénáře podporovalo.

    Už je to dost dlouho, co jsem se tím vším zabýval, tak snad to ještě platí, nějaké podrobné rady už ze mě asi nevypadnou.

  9. tomasr - 17. 4. 2009 06:31

    No ale to je dost tezko realizovatelny. To by musela bejt routa na kazdou url pro kazdej jazyk. Takze treba 100 a vic.
    Vzdal sem se snu mit url lokalizovane, ale kazdopadne diky za odpoved.

Komentáře jsou uzavřeny.