What's the cleanest way to add a prefix to every URL in CakePHP, like a language parameter?
http://example.com/en/controller/action
http://example.com/ru/admin/controller/action
It needs to work with "real" prefixes like admin
, and ideally the bare URL /controller/action
could be redirected to /DEFAULT-LANGUAGE/controller/action
.
It's working in a retro-fitted application for me now, but it was kind of a hack, and I need to include the language parameter by hand in most links, which is not good.
So the question is twofold:
Router::connect('/:controller/:action/*', ...)
should implicitly include the prefix.$this->params['lang']
or somewhere similar to be evaluated in AppController::beforeFilter()
.Router::url()
to automatically include the prefix in the URL, if not explicitly specified?
Router::url(array('controller' => 'foo', 'action' => 'bar'))
should return /en/foo/bar
Controller::redirect()
, Form::create()
or even Router::url()
directly need to have the same behavior, overriding every single function is not really an option. Html::image()
for instance should produce a prefix-less URL though.The following methods seem to call Router::url
.
Controller::redirect
Controller::flash
Dispatcher::__extractParams
via Object::requestAction
Helper::url
JsHelper::load_
JsHelper::redirect_
View::uuid
, but only for a hash generationOut of those it seems the Controller and Helper methods would need to be overridden, I could live without the JsHelper
. My idea would be to write a general function in AppController
or maybe just in bootstrap.php
to handle the parameter insertion. The overridden Controller and Helper methods would use this function, as would I if I wanted to manually call Router::url
. Would this be sufficient?
This is essentially all the code I implemented to solve this problem in the end (at least I think that's all ;-)):
/config/bootstrap.php
define('DEFAULT_LANGUAGE', 'jpn');
if (!function_exists('router_url_language')) {
function router_url_language($url) {
if ($lang = Configure::read('Config.language')) {
if (is_array($url)) {
if (!isset($url['language'])) {
$url['language'] = $lang;
}
if ($url['language'] == DEFAULT_LANGUAGE) {
unset($url['language']);
}
} else if ($url == '/' && $lang !== DEFAULT_LANGUAGE) {
$url.= $lang;
}
}
return $url;
}
}
/config/core.php
Configure::write('Config.language', 'jpn');
/app_helper.php
class AppHelper extends Helper {
public function url($url = null, $full = false) {
return parent::url(router_url_language($url), $full);
}
}
/app_controller.php
class AppController extends Controller {
public function beforeFilter() {
if (isset($this->params['language'])) {
Configure::write('Config.language', $this->params['language']);
}
}
public function redirect($url, $status = null, $exit = true) {
parent::redirect(router_url_language($url), $status, $exit);
}
public function flash($message, $url, $pause = 1) {
parent::flash($message, router_url_language($url), $pause);
}
}
/config/routes.php
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
Router::connect('/:language/', array('controller' => 'pages', 'action' => 'display', 'home'), array('language' => '[a-z]{3}'));
Router::connect('/:language/pages/*', array('controller' => 'pages', 'action' => 'display'), array('language' => '[a-z]{3}'));
Router::connect('/:language/:controller/:action/*', array(), array('language' => '[a-z]{3}'));
This allows default URLs like /controller/action
to use the default language (JPN in my case), and URLs like /eng/controller/action
to use an alternative language. This logic can be changed pretty easily in the router_url_language()
function.
For this to work I also need to define two routes for each route, one containing the /:language/
parameter and one without. At least I couldn't figure out how to do it another way.