Recent comments

In i18n routes with CakePHP 1.3
pierre_martin wrote: Hi everyone and thank you for the feedbacks! Since this article was published, we improved...
In i18n routes with CakePHP 1.3
Mateusz Mucha wrote: Thank you!
In i18n routes with CakePHP 1.3
Piotr wrote: Wow! That's great and usefull. Ways easier than with old methods. I added some code to update...
In i18n routes with CakePHP 1.3
Lee wrote: Once you set $url['lang'] when empty is doesn't update $this-params with the default...
In CakePHP Migrations plugin: easily version and deploy whole applications
Remigijus wrote: When I am trying to run 'migration generate' for first time, my migration file 'up' has value...

Categories

There are no categories to display

RSS Feed for comments

i18n routes with CakePHP 1.3

Written on Thu, Aug 5th 2010, 10:00 in

Internationalizing a CakePHP application can be tricky when it comes to deal with i18n urls. We will see in this article how the Custom route classes introduced by CakePHP 1.3 could be used to add the current language to your urls in a few lines of code.

EDIT: This proof of concept has now been improved and a better version of the code below can be found in CakeDC's I18n plugin on Github

Requirements

This article will not go too deep in internationalizing an application as many resources already exist about it. We suppose the following:

  • Your application defines the current language on given the language code passed in the url
  • The available languages are configured via Configure::write('Config.languages', array('eng', 'fre', 'deu'));
  • You use the CakePHP array syntax for defining urls:
    • $this->Html->link('link', array('controller' => 'posts', 'action' => 'view', $post['Post']['id']));
    • $this->redirect(array('controller' => 'posts', 'action' => 'index'));
    • Router::url(array('controller' => 'posts', 'action' => 'index'), true);

Custom routes were already introduced by Mark Story on his blog, so we will not do it again here... before continuing be sure you have read "Using custom Route classes in CakePHP"

Show me some code!

I18nRoute

As I said (or not), routes are probably the best place for customizing your urls and add information in them... much more better at least than overriding the Helper::url() method in an AppHelper class!

Custom routes introduced a way to customize how routes are processed in a very easy and powerful way (i.e ~20 lines of code). It is a bit like wrapping the Router class in CakePHP 1.2, a good example of this was the CroogoRouter.

First, we are going to create an I18nRoute class extending CakeRoute in the "/libs/routes/i18n_route.php" file. Here is its code:

<?php
class I18nRoute extends CakeRoute {
/**
 * Constructor for a Route
 * Add a regex condition on the lang param to be sure it matches the available langs
 *
 * @param string $template Template string with parameter placeholders
 * @param array $defaults Array of defaults for the route.
 * @param string $params Array of parameters and additional options for the Route
 * @return void
 * @access public
 */
	public function __construct($template, $defaults = array(), $options = array()) {
		$options = array_merge((array)$options, array(
			'lang' => join('|', Configure::read('Config.languages'))
		));
		parent::__construct($template, $defaults, $options);
	}

/**
 * Attempt to match a url array.  If the url matches the route parameters + settings, then
 * return a generated string url.  If the url doesn't match the route parameters false will be returned.
 * This method handles the reverse routing or conversion of url arrays into string urls.
 *
 * @param array $url An array of parameters to check matching with.
 * @return mixed Either a string url for the parameters if they match or false.
 * @access public
 */
	public function match($url) {
		if (empty($url['lang'])) {
			$url['lang'] = Configure::read('Config.language');
		}
		return parent::match($url);
	}

}

The most important part of the code is in the "match()" method. We just add the current language to the url "lang" named param if it was not set. The constructor was also overriden to add a regex pattern for the "lang" param. Thus, only lang prefixes defined in your list of available languages will be parsed by the route.

Define your routes

It is now time to use this custom route in your application. Here is how the default route for pages could be defined in "/config/routes.php":

App::import('Lib', 'routes/I18nRoute');
Router::connect('/:lang/pages/*', array('controller' => 'pages', 'action' => 'display'), array('routeClass' => 'I18nRoute'));
  1. import the library file containing the custom route
  2. add a ":lang" param in where you want the language code appear in the url
  3. tell the Router you want to use this custom class (third param)

Link from everywhere!

Now you won't have to worry about the language code transmitted in your urls... every generated link will contain the current language code. If you want to switch the language (for instance switching to the French version of your application), you will just have to add the "lang" param to the url array.

Here are some examples of urls which would be generated on the "/eng/posts/index" page:

$this->Html->link(__('French', true), array_merge($this->passedArgs, array('lang' => 'fre'))); // /fre/posts/index
$this->Html->link('link', array('controller' => 'posts', 'action' => 'view', $post['Post']['id'])); // /eng/posts/view/2

Disclaimer

This code is experimental and the article shows you how to use CustomRoutes to implement this basic feature. Many improvements could be added to fit your needs (no language code for the default application lang, short languages code...)

Even if the tests we made were successful, we have not used this code in production yet so there may be "real word" use cases that are not handled correctly with this solution... if you find one, please tell us in the comments!

Back to Pierre's articles

Comments:

Add comment
  • Once you set $url['lang'] when empty is doesn't update $this->params with the default language. Would this possible?? Tanks, Lee

    Reply | Lee | posted on 13/8/10

  • Wow! That's great and usefull. Ways easier than with old methods. I added some code to update the Config.language value in a parse() method :

    Reply | Piotr | posted on 1/9/10

  • Thank you!

    Reply | Mateusz Mucha | posted on 9/9/10

  • Hi everyone and thank you for the feedbacks! Since this article was published, we improved this proof of concept and created a better version of this i18nRoute. This code is open source, and is part of CakeDC's I18n plugin available on github: http://github.com/CakeDC/i18n Check it out!

    Reply | pierre_martin | posted on 17/9/10

openID

What is OpenID?

OpenID is a new open standard that lets you sign in to web sites with a single URL that you own. This URL can be your homepage or blog, or it can be provided to you by a web site you use. In either case, you only have to sign in once to your OpenID provider and so you only need to maintain a single password.Learn more.

How is CakeDC using OpenID?

You can use your OpenID identity when posting comments on the site. When you see a form field with OpenID logo entering your OpenID identity is sufficient to allow your post. We also accept Google or Yahoo! identities. Simply use either "google.com" or "yahoo.com" and our OpenID library will locate your information from the appropriate source.