Multiple languages in a CakePHP 2.* application in 5 steps

Written on 1 August 2012, 10:21pm

Tagged with: , ,

Below is a solution that I implemented in a CakePHP project needing internationalization (i18n).
It involves showing the language in the URL (so the URLs will look like app.com/:lang/:controller/:action/...) and storing it in the Session + Cookies.
It is basically the implementation described on http://nuts-and-bolts-of-cakephp.com. I only added some minor changes to make it work under CakePHP 2.* (the solution in the link above was implemented in November 2008, for CakePHP 1.3).
The implementation below was tested on CakePHP 2.2 (1 August 2012).
Feel free to add your comments/questions in the form below.

Comments inline:


// Step 1: app/Config/routes.php
Router::connect('/:language/:controller/:action/*',
                       array(),
                       array('language' => '[a-z]{3}'));

//Step 2: app/Config/core.php
Configure::write('Config.language', 'eng');

//Step 3: create app/View/Helper/MyHtmlHelper.php
App::uses('HtmlHelper', 'View/Helper');
class MyHtmlHelper extends HtmlHelper {
	public function url($url = null, $full = false) {
        if(!isset($url['language']) && isset($this->params['language'])) {
          $url['language'] = $this->params['language'];
        }
        return parent::url($url, $full);
   }
}

//Step 4: app/Controller/AppController.php
class AppController extends Controller {
	public $components = array('Cookie','Session');
	//set an alias for the newly created helper: Html<->MyHtml
	public $helpers = array('Html' => array('className' => 'MyHtml'));

	public function beforeFilter() {
          $this->_setLanguage();
        }

	private function _setLanguage() {
	//if the cookie was previously set, and Config.language has not been set
	//write the Config.language with the value from the Cookie
	    if ($this->Cookie->read('lang') && !$this->Session->check('Config.language')) {
	        $this->Session->write('Config.language', $this->Cookie->read('lang'));
	    } 
	    //if the user clicked the language URL 
	    else if ( 	isset($this->params['language']) && 
		($this->params['language'] !=  $this->Session->read('Config.language'))
	    		) {
	    	//then update the value in Session and the one in Cookie
	        $this->Session->write('Config.language', $this->params['language']);
	        $this->Cookie->write('lang', $this->params['language'], false, '20 days');
	    }
	}

	//override redirect
	public function redirect( $url, $status = NULL, $exit = true ) {
		if (!isset($url['language']) && $this->Session->check('Config.language')) {
			$url['language'] = $this->Session->read('Config.language');
		}
		parent::redirect($url,$status,$exit);
	}
}

//add the links to the languages:
//Step 5: app/View/...
echo $this->Html->link('English', array('language'=>'eng')); 
echo $this->Html->link('Fran├žais', array('language'=>'fre')); 

Update August 3rd 2012: Added ‘private’ visibility for the _setLanguage function
Update September 28th 2012: Modified the first step (app/Config/routes.php), adding more restrictive rules for the languages. Before, if the user somehow loaded an URL like /css/foo/bar – then ‘css’ was incorrectly considered a language.


	Router::connect('/:language/:controller/:action/*',
                       array(),
                       array('language' => 'eng|fre'));

	Router::connect('/:language/:controller',
                       array('action' => 'index'),
                       array('language' => 'eng|fre'));	

	Router::connect('/:language',
                       array('controller' => 'welcome', 'action' => 'index'),
                       array('language' => 'eng|fre'));