There are several ways how to include language id in default route of Zend Framework. However, generally you end up with the solution not quite elegant and likely not totally trouble-free. I have seen people overwriting the default route by new one which mimics module route with additional language id. There is no need to throw the default module route away to do this. To get it right chain the plain language route with default route.
// application/Bootstrap.php
protected function _initRoutes() {
$locale = $this->getResource('locale');
// Create route with language id (lang)
$routeLang = new Zend_Controller_Router_Route(
':lang',
array(
'lang' => $locale->getLanguage()
),
array('lang' => '[a-z]{2}')
);
// Now get router from front controller
$front = $this->getResource('frontcontroller');
$router = $front->getRouter();
// Instantiate default module route
$routeDefault = new Zend_Controller_Router_Route_Module(
array(),
$front->getDispatcher(),
$front->getRequest()
);
// Chain it with language route
$routeLangDefault = $routeLang->chain($routeDefault);
// Add both language route chained with default route and
// plain language route
$router->addRoute('default', $routeLangDefault);
$router->addRoute('lang', $routeLang);
// Register plugin to handle language changes
$front->registerPlugin(new My_Controller_Plugin_Language());
}
I think that the code above has enough comments to clarify it, so lets forward to the controller plugin. If you are not familiar with chaining routes, refer to reference guide Zend_Controller_Router_Route_Chain.
In Language controller plugin we have to take care of actions, which have to be done if language has been changed. Setting the language id as a global router parameter is the most important. The another common task is to check, if there is available translation for the selected language.
// library/My/Controller/Plugin/Language.php
class My_Controller_Plugin_Language
extends Zend_Controller_Plugin_Abstract
{
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$lang = $request->getParam('lang', null);
$translate = Zend_Registry::get('Zend_Translate');
// Change language if available
if ($translate->isAvailable($lang)) {
$translate->setLocale($lang);
} else {
// Otherwise get default language
$locale = $translate->getLocale();
if ($locale instanceof Zend_Locale) {
$lang = $locale->getLanguage();
} else {
$lang = $locale;
}
}
// Set language to global param so that our language route can
// fetch it nicely.
$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter();
$router->setGlobalParam('lang', $lang);
}
}
In our Language plugin, we check if user selected language is available. In spite of its availability, we have valid language id in $lang, which is set as a global param. To achieve a better user experience you may want to store the user selected language in to session to be retrieved on later visits.
Update (2011-03-08): Updated the plugin to change the method “routeStartup” to “routeShutdown”, as the intention is to update the route with the default language when unmatched.




August 12, 2010 at 4:43 pm
A while ago, I wrote an article on this which takes a slightly different approach: http://joegornick.com/2009/12/02/zend-framework-best-practices-part-2-i18n/
Thoughts?
– Joe
August 13, 2010 at 9:13 am
I spend a lot of time to find a good solution. My solution was very similar to your’s but your’s looks very clean. Next project, I will test your solution!
August 16, 2010 at 8:15 am
So would this make the URL’s like
website.com/en/controller/action/param
or
website.com/controller/action/en
?
And would: website.com/controller/action still be valid?
And is this SEO friendly?
August 16, 2010 at 7:54 pm
Very nice and simple code !!
I just integrated it and found a minor bug (i think):
In the routeStartup method the lang param doesn’t exist yet. Changing the routeStartup method to routeShutdowm fixes this
Thx!
August 16, 2010 at 8:08 pm
@xerionth Nice spot! It should be routeShutdown and NOT routeStartup as mentioned in the post.
August 18, 2010 at 6:58 pm
This approach isn’t trouble free either. With this method, i am unable to simply add parameters (with a new route) from the application.ini file.
Also, when i want to add this route, i’d have to chain it to the lang route as well. And since I cannot do this in the application.ini, i would have to do this in the bootstrap. So i end up with a load of routes in the bootstrap.
So i wouldn’t call this method trouble free. Unless you provide some solutions for this? Because on IRC and the forum, nobody knows it.
August 18, 2010 at 7:28 pm
@alex505 – If you take a look at my link I posted above, my approach uses application.ini for routes.
kblunkka did point out some things I could improve with my approach, so I may update soon with those changes.
Hope this helps!
August 18, 2010 at 7:32 pm
Now when you asked I did some researching and found that somebody has already made this. The solution is quite similar what proposed here, but with ini configurations… http://www.m4d3l-network.com/developpement/php/zend-framework/add-language-route-to-your-zend-framework-project/
However there seems to be some kind of issue… http://framework.zend.com/issues/browse/ZF-8812
August 18, 2010 at 8:00 pm
@jgornick,
I had a look at your solution, but i’m kinda concerned about the fact that there is so much "rewriting". I have the feeling there might be a easier way to do this.
I have solved it almost using the method from this article, but now i have to chain the new route (the one with the parameter) to the lang route as well. But for now, i still cannot get the parameter.
Frustrating…
August 20, 2010 at 5:25 pm
This is my initial solution how to chain language route in application.ini
; Routes
resources.router.routes.module.type = Zend_Controller_Router_Route_Module
resources.router.routes.lang.type = Zend_Controller_Router_Route
resources.router.routes.lang.route = ":lang"
resources.router.routes.lang.reqs.lang = "[a-z]{2}"
resources.router.routes.lang.abstract = On
resources.router.routes.default.type = Zend_Controller_Router_Route_Chain
resources.router.routes.default.chain = "lang, module"
August 21, 2010 at 10:52 am
@alex505,
Here is my latest experiment. Just keep your routes in application.ini as is and add this routeStartup to auto chain every route with language.
// library/My/Controller/Plugin/Language.php
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$front = Zend_Controller_Front::getInstance();
$locale = Zend_Registry::get(‘Zend_Locale’);
$router = $front->getRouter();
$routeLang = new Zend_Controller_Router_Route(
‘:lang’,
array(
‘lang’ => $locale->getLanguage()
),
array(‘lang’ => ‘[a-z]{2}’)
);
$newDefaultRoutes = array();
$oldDefaultRoutes = array();
foreach ($router->getRoutes() as $name => $route) {
$newDefaultRoutes[$name] = $routeLang->chain($route);
$oldDefaultRoutes[$name.$name] = $route;
$router->removeRoute($name);
}
$router->addRoutes($oldDefaultRoutes + $newDefaultRoutes);
}
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$lang = $request->getParam(‘lang’);
$this->_setLanguage($lang);
}
protected function _setLanguage($lang)
{
// code
}
August 21, 2010 at 10:59 am
@kblunkka
Thanks for your reply! I currently have it working with all the routes in my bootstrap. I will defenitly try your method out. Thanks!
It isn’t really a big deal. But i think that having your routes in application.ini is more clean than having to add everything in your bootstrap.
Thanks!
August 25, 2010 at 9:19 am
@kblunkka
Really like your solution, I have used it in my current project. But I have a problem I cannot solve. I want to language route matches the url with no lang parameter. Using your routes config with one chain route and plain lang route.
The following works ok
/en/foo/bar – ‘en’ language, ‘foo’ controller, ‘bar’ action
/ru/foo/bar – ‘ru’ language, ‘foo’ controller, ‘bar’ action
But my site have a default ‘ru’ language and I do not want to pass lang information for russian urls. I want following:
/foo/bar – ‘ru’ language (by default), ‘foo’ controller, ‘bar’ action
But chain fails. I have tried to use RegExp route for language:
$routeLang = new Zend_Controller_Router_Route_Regex(
"(ru|en)?",
array(‘lang’ => ‘ru’),
array(1 => ‘lang’),
‘%s’
);
Lang part route matches, but next chained route fails. After debugging I have found that problem is inside Zend_Controller_Router_Route_Chain. See $separator setup and check in match() method.
August 25, 2010 at 3:16 pm
@alex505 – I don’t know what you mean by "rewriting", but all I’m doing is simply creating routes with and without the language specified, then adding them to the router. I’m simply manipulating strings here so it still should be pretty speedy. The only thing I worry about is adding many routes to the router and having the router parse them. But, as I understand it, the router will go through the list of routes and then stop processing as soon as it finds a match. This means that if you have LOTS of routes and it just so happens the one that matches is the last in the list, it might take a little bit longer to parse.
@satyrius – If you take a look at my solution, I support having a default language and create routes that use the default language when a language isn’t specified. A link to my blog post can be found here: http://joegornick.com/2009/12/02/zend-framework-best-practices-part-2-i18n/
I plan on doing some speeds tests soon to see how my approach performs to the approach described by @kblunkka. I also want to provide a pros/cons list for each solution and speed will be a big thing.
Good discussions here!
October 8, 2010 at 1:40 pm
Hi – I’ve followed the example in the tutorial but need some help with getting the view url helper to automatically add the lang parameter to whatever href it generates.
I can see the ‘default’ route contains chained :lang but it’s not being output in my href.
E.g. if i navigate to page http://localhost/en/news/list and my list.phtml view contains this:
<? echo $this->url(array(‘controller’ => ‘news’, ‘action’ => ‘item’, ‘id’ => 999)) ?>More</a>
I would expect to see output /en/news/item/999 – however, i don’t get the /en/ part output – am i missing something?
Thanks!
April 16, 2011 at 2:38 pm
I’m quite a beginner with Zend Framework but i can’t get your code to work.
I’ve created the _initRoutes() in my Bootstrap and created the plugin, but "$locale = $this->getResource(‘locale’);" returns NULL to me, so $locale->getLanguage() ends with a fatal error.
Same with "$front = $this->getResource(‘frontcontroller’);"
Thanks for your article.
April 16, 2011 at 3:41 pm
@zonta
You have to bootstrap your locale resource. Try to add this into your application.ini …
resources.locale.default = "en_US"
In addition, you may have to add …
$this->bootstrap(‘locale’);
before the line …
$locale = $this->getResource(‘locale’);
April 16, 2011 at 4:00 pm
i’ve added
resources.locale.default = "en_US"
and
$this->bootstrap(‘locale’);
$this->bootstrap(‘FrontController’);
because of the same problem.
Now the error is
Message: No entry is registered for key ‘Zend_Translate’
which refers to
$translate = Zend_Registry::get(‘Zend_Translate’);
inside plugin file.