Nastavení jazyku v URL pluginem
25.2.2008 | | Zend Framework
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
4252x
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é.
Potreboval bych nasledujici url: www.nakejroot.cz/en/novinky
Controller se samozrejme muze menit, action je defaultne index. Jak mohu dosahnout pozadovaneho chovani?
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
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] - 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.
Vzdal sem se snu mit url lokalizovane, ale kazdopadne diky za odpoved.