Adding a prefix to every URL in CakePHP
Asked Answered
G

3

20

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:

  • What's the best way to structure Routes, so the language parameter is implicitly included by default without having to be specified for each newly defined Route?
    • Router::connect('/:controller/:action/*', ...) should implicitly include the prefix.
    • The parameter should be available in $this->params['lang'] or somewhere similar to be evaluated in AppController::beforeFilter().
  • How to get 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
    • Since 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 generation

Out 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?

Gwalior answered 25/11, 2009 at 3:17 Comment(0)
F
5

rchavik from IRC suggested this link: CakePHP URL based language switching for i18n and l10n internationalization and localization

In general, it seems that overriding Helper::url might be the solution.

Formulaic answered 25/11, 2009 at 4:34 Comment(1)
And according to the comments, overriding AppController::url would do it for controllers. So far so good, is there anything else? Do other parts call Router::url directly?Gwalior
G
14

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.

Gwalior answered 10/12, 2009 at 2:41 Comment(1)
@gacrux Note that this should be much less complex in Cake 1.3, which includes customizable prefixes. The above applies to Cake 1.2, which only knew one prefix. :)Gwalior
F
5

rchavik from IRC suggested this link: CakePHP URL based language switching for i18n and l10n internationalization and localization

In general, it seems that overriding Helper::url might be the solution.

Formulaic answered 25/11, 2009 at 4:34 Comment(1)
And according to the comments, overriding AppController::url would do it for controllers. So far so good, is there anything else? Do other parts call Router::url directly?Gwalior
S
0

An easier way might be to store the chosen language in a cookie and then not have to rewrite all the URLs. You could also potentially detect the user's browser language automatically.

However, search engines would be unlikely to pickup the various languages and you'd also lose the language if someone tried to share the link.

But love the full solution you posted, very comprehensive, thanks. :-)

Sergias answered 28/9, 2012 at 4:53 Comment(2)
The problem with "URL-less" language switches is search engines. They will only ever index a single language or will get their panties in a bunch if they see different content on the same URL. That only really works for private (login-only) areas of your site.Gwalior
Oh, yeah that's a good point. I'll update my answer to note that.Sergias

© 2022 - 2024 — McMap. All rights reserved.