Difference between #5 and #4 of SEO-conform Multilingual URLs + Language Selector Widget (i18n)

unchanged
Title
SEO-conform Multilingual URLs + Language Selector Widget (i18n)
unchanged
Category
Tutorials
unchanged
Tags
multilanguage, language, seo, i18n, widget
changed
Content
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. 
<br>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](http://yiiframework.com.ar/2010/04/como-realizar-un-sitio-multilenguaje-con-yii-mediante-get/
"") in spanish, to which I added some tweaks (thanks to [sebas
](http://www.yiiframework.com/user/54/ "sebas")for the link).
<br>**Tip:** If you don't want/need the language to be part of the URL,
take a look at [this
article](http://www.yiiframework.com/wiki/293/manage-target-language-in-multilingual-applications-a-language-selector-widget-i18n/
""). 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]
<?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.
<br>**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]
<?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.
<br>**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'. 
<br>You can set default values for the language and sourceLanguage of your
application in your config file with
~~~
[php]
'sourceLanguage'=>'en',
'language'=>'de',
~~~

### 3. Build a Language Selector Widget 
Create the file 'components/widgets/LanguageSelector.php' with the content:
~~~
[php]
<?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:
~~~
[php]
<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));CHtml::link(
                     $lang, 
                    
$this->getOwner()->createMultilanguageReturnUrl($key));
			} else echo '<b>'.$lang.'</b>';
			if($lang != $lastElement) echo ' | ';
		}
	}
	else {
		// Render options as dropDownList
		// Use: CHtml::form($action='', $method='post',
$htmlOption=array())
		echo CHtml::form();
		// for each language, add a hidden field with the MultilanguageReturnUrl
for that language. 	
		foreach($languages as $key=>$lang) {
			echo CHtml::hiddenField($key,
$this->getOwner()->createMultilanguageReturnUrl($key));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.

<br>**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'
~~~
[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]
<?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.