SEO-conform Multilingual URLs + Language Selector Widget (i18n)

You have a multilingual application, and you want the URL of a page to be different for different languages, to account for SEO. The URL for the contact page for example should look like http://something.com/en/contact in english, and http://something.com/de/contact in german. This tutorial describes how to make it happen.
Note that currently selected language is always a part of the URL, and thus available to the application through $_GET. You don't have to use sessions variables to keep track of the language if you don't want to.

The code is mainly from this article in spanish, to which I added some tweaks (thanks to sebas for the link).
Tip: If you don't want/need the language to be part of the URL, take a look at this article. That method may be preferrable if SEO is not an issue for your application (e.g. backend-type applications for authorized users only), since it is simpler.

Now to the code...

1. Extend the CUrlManager

Create the file 'components/UrlManager.php' with the content:

<?php
class UrlManager extends CUrlManager
{
	public function createUrl($route,$params=array(),$ampersand='&')
	{
		if (!isset($params['language'])) {
			if (Yii::app()->user->hasState('language'))
				Yii::app()->language = Yii::app()->user->getState('language');
			else if(isset(Yii::app()->request->cookies['language']))
				Yii::app()->language = Yii::app()->request->cookies['language']->value;
			$params['language']=Yii::app()->language;
		}
		return parent::createUrl($route, $params, $ampersand);
	}
}
?>

For this approach to work, the language has to be a part of the URL. That is, $_GET['language'] has to be defined. To ensure this, we override the createUrl() function of the class. If the desired language is not present in the URL, we look if it is stored in a session variable, or in a cookie and set the application language accordingly. After that, we add the language to the parameters of the URL.
For the sake of completeness:The array $params contains the part of the URL that comes after the '?', that is the GET parameters, as key-value pairs. For http://something.com/controller/action?language=en&id=1, $params would be equal to array('language'=>'en', 'id'=>1).

2. Edit your Controller

Add the following code to 'components/Controller.php':

<?php
public function __construct($id,$module=null){
	parent::__construct($id,$module);
	// If there is a post-request, redirect the application to the provided url of the selected language 
	if(isset($_POST['language'])) {
		$lang = $_POST['language'];
		$MultilangReturnUrl = $_POST[$lang];
		$this->redirect($MultilangReturnUrl);
	}
	// Set the application language if provided by GET, session or cookie
	if(isset($_GET['language'])) {
		Yii::app()->language = $_GET['language'];
		Yii::app()->user->setState('language', $_GET['language']); 
		$cookie = new CHttpCookie('language', $_GET['language']);
		$cookie->expire = time() + (60*60*24*365); // (1 year)
		Yii::app()->request->cookies['language'] = $cookie; 
	}
	else if (Yii::app()->user->hasState('language'))
		Yii::app()->language = Yii::app()->user->getState('language');
	else if(isset(Yii::app()->request->cookies['language']))
		Yii::app()->language = Yii::app()->request->cookies['language']->value;
}
public function createMultilanguageReturnUrl($lang='en'){
	if (count($_GET)>0){
		$arr = $_GET;
		$arr['language']= $lang;
	}
	else 
		$arr = array('language'=>$lang);
	return $this->createUrl('', $arr);
}
?>

We extend the constructor method of our controller class, in which we set the application language. Since all our invidiual controller classes extend from this one, the application language will be set explicitly upon each request.
Note: If we don't set Yii::app()->language explicitly for each request, it will be equal to its default value set in the confg file. If it is not set in the config file, it will be equal to the value Yii::app()->sourceLanguage, which defaults to 'en_us'.
You can set default values for the language and sourceLanguage of your application in your config file with

'sourceLanguage'=>'en',
'language'=>'de',
3. Build a Language Selector Widget

Create the file 'components/widgets/LanguageSelector.php' with the content:

<?php
class LanguageSelector extends CWidget
{
    public function run()
    {
        $currentLang = Yii::app()->language;
		$languages = Yii::app()->params->languages;
        $this->render('languageSelector', array('currentLang' => $currentLang, 'languages'=>$languages));
    }
}
?>

Create the file 'components/widgets/views/languageSelector.php' with the content:

<div id="language-select">
<?php 
	if(sizeof($languages) < 4) {
		// Render options as links
		$lastElement = end($languages);
		foreach($languages as $key=>$lang) {
			if($key != $currentLang) {
				echo CHtml::link(
                     $lang, 
                     $this->getOwner()->createMultilanguageReturnUrl($key));
			} else echo '<b>'.$lang.'</b>';
			if($lang != $lastElement) echo ' | ';
		}
	}
	else {
		// Render options as dropDownList
		echo CHtml::form();
		foreach($languages as $key=>$lang) {
			echo CHtml::hiddenField(
                $key, 
                $this->getOwner()->createMultilanguageReturnUrl($key));
		}
		echo CHtml::dropDownList('language', $currentLang, $languages,
			array(
				'submit'=>'',
			)
		); 
		echo CHtml::endForm();
	}
?>
</div>

If the number of available languages is smaller than four, the languages are displayed as links, separated with a '|'. Otherwise a dropDownList is generated.
Note: When this post-request is processed in Controller.php (see above), the application will be redirected to one of the urls provided via hidden fields in this form, because at that point in time, the controller id will not exist and the call to $this->createUrl('') will throw an error.

4. Put the Widget on your Site

Add the following code into the header-div in 'views/layouts/main.php'

<div  id="language-selector" style="float:right; margin:5px;">
    <?php 
        $this->widget('application.components.widgets.LanguageSelector');
    ?>
</div>
5. Edit your Config File

Apply the following changes/additions to the file 'config/main.php':

<?php
'components'=>array(
	...
	'request'=>array(
		'enableCookieValidation'=>true,
		'enableCsrfValidation'=>true,
	),
	'urlManager'=>array(
		'class'=>'application.components.UrlManager',
		'urlFormat'=>'path',
		'showScriptName'=>false,
		'rules'=>array(
			'<language:(de|tr|en)>/' => 'site/index',
			'<language:(de|tr|en)>/<action:(contact|login|logout)>/*' => 'site/<action>',
			'<language:(de|tr|en)>/<controller:\w+>/<id:\d+>'=>'<controller>/view',
			'<language:(de|tr|en)>/<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
			'<language:(de|tr|en)>/<controller:\w+>/<action:\w+>/*'=>'<controller>/<action>',
		),
	),
),
'params'=>array(
	'languages'=>array('tr'=>'Türkçe', 'en'=>'English', 'de'=>'Deutsch'),
),
?>

We declare our new class 'UrlManager' as the class to be used by the urlManager component, and prefix <language:(de|tr|en)>/ to the keys of our rules array.

That's it. If something is unclear, wrong or incomplete, please let me know.

28 0
42 followers
Viewed: 75 701 times
Version: 1.1
Category: Tutorials
Written by: c@cba
Last updated by: c@cba
Created on: Dec 29, 2011
Last updated: 12 years ago
Update Article

Revisions

View all history

Related Articles