Difference between #3 and #2 of I18n subdomains and url-rules

unchanged
Title
I18n subdomains and url-rules
unchanged
Category
Tutorials
unchanged
Tags
changed
Content
This guide is for more or less advanced users. If you just started with Yii or
didn't worked with any web-framework before, you may should come back later :-)

### Some thoughts

In Yii, we normally define (static) url rules in the main config file. Currently
there's no core support for subdomains or dynamic url rules based on
informations like cookies.

When you develop a multilanguage website, you will probably define the url rules
in english language, regardless of the client language. That works well, but for
professional portal style sites, you may want to use another approach regarding
better user experience and
[SEO](http://en.wikipedia.org/wiki/Search_engine_optimization) reasons.

Let's think of a website in english and german language. The rule for the
contact formular would probably look something like this:

~~~
[php]
<?php
...

'components' => array(
  'urlManager' => array(
    'rules' => array(
      'contact' => 'site/contact',
    ),
  ),
),

...
?>
~~~

That means we could access the contact formular via:

~~~
http://www.example.com/contact
~~~

Now think about this:

~~~
http://en.example.com/contact
http://de.example.com/kontakt
~~~

As you can see, we use the proper [language
code](http://www.anglistikguide.de/info/tools/languagecode.html) as subdomain
(en for english, de for german) and the word "contact" as url rule.
Like already noted, this approach is unfortunately not supported by the Yii
core.

Before I'll show you a solution that will solve this scenario, please keep the
following disadvantages and benefits in mind. Evaluate conscientious for
yourself if it's woth for your project.

### Disadvantages

* If you use different subdomains for one site, the
[pagerank](http://en.wikipedia.org/wiki/Pagerank) of your main domain (eg
example.com) will probably suffer. Also you may run into a [duplicate
content](http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=66359)
problem when for example the page titles are always the same regardless of the
active language subdomain.

* Google itself states that it's better to use top-level domains instead of
subdomains. So if you indeed own "example.com" and
"example.de", you shouldn't use the following solution. But you're of
course free to modify the solution for the use of top-level domains instead of
subdomains.

* For small multilanguage sites, I would suggest to just use english url rules
with one main domain. The real benefits probably won't show up for a website
with less than 10.000 unqiue visitors a day.

### Benefits

* You can split your website into a real multilanguage portal like for example
YouTube and measure the performance with one Google Analytics account per
subdomain.

* Google will display
[sitelinks](http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=47334)
for each language instead of probably mixed up stuff. In general, the whole SEO
possibilities will be better.

* The user experience will be better.

### The Solution

First, we create a new application component that will include all the special
properties and functions we need.

~~~
[php]

<?php

class I18n extends CApplicationComponent
{

    public $supportedLanguages;
    public $urlRulesPath;
    public $activeLanguage;

    public function init()
    {

		if (true ===
(bool)preg_match("/^(?<protocol>(http|https):\/\/)(((?<languageCode>[a-z]{2})\.)*)((.*\.)*(?<domain>.+\.[a-z]+))$/",
Yii::app()->request->hostInfo, $matches))
		{
			
			if (2 !== strlen($matches['languageCode']) && false !== ($language =
$this->isSupportedLanguage($matches['languageCode'])))
			{
				$this->activeLanguage = $language;	
			}
			else
			{
				
				$this->activeLanguage = $this->guessClientLanguage();
				
				$redirectUrl =
"{$matches['protocol']}{$this->activeLanguage['code']}.{$matches['domain']}";
				
			}
			
		}
		else
		{
			throw new CException("Unable to parse host info - please request this
page with a domain (eg http://example.com/) instead of an ip-address!");
		}
			
		Yii::app()->setLanguage($this->activeLanguage['code']);
		
		Yii::app()->urlManager->rules =
include("{$this->urlRulesPath}/{$this->activeLanguage['code']}.php");
						
		if (false !== Yii::app()->urlManager->cacheID && null !==
($cache = Yii::app()->getComponent(Yii::app()->urlManager->cacheID)))
		{
			$keyPrefix = $cache->keyPrefix;
			$cache->keyPrefix =
"{$keyPrefix}.{$this->activeLanguage['code']}";
			Yii::app()->urlManager->init();
			$cache->keyPrefix = $keyPrefix;					
		}
		else
		{
			Yii::app()->urlManager->init();
		}
		
		if (true === isset($redirectUrl))
		{
			Yii::app()->request->redirect($redirectUrl);
		}

	}
	
	public function guessClientLanguage()
	{

		if (false === isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) || false ===
(bool)preg_match("/([a-z]{2}+)[-_][A-z]{2}+/",
$_SERVER['HTTP_ACCEPT_LANGUAGE'], $match) || false === ($language =
$this->isSupportedLanguage($match[1])))
		{
			if (false === ($language = $this->getFallbackLanguage()))
			{
				throw new CException("Unable to return the fallback language - please
set it in config!");
			}
		}
		
		return $language;
		
	}
	
	public function getFallbackLanguage()
	{

		foreach ($this->supportedLanguages as $supportedLanguage)
		{
			if (true === isset($supportedLanguage['fallback']) && true ===
$supportedLanguage['fallback'])
			{
				return $supportedLanguage;
			}
		}

		return false;

	}

	public function isSupportedLanguage($languageCode)
	{

		foreach ($this->supportedLanguages as $supportedLanguage)
		{
			if ($supportedLanguage['code'] === $languageCode)
			{
				return $supportedLanguage;
			}
		}

		return false;

	}

}

?>

~~~

In the main config, we will add this custom application component to the preload
array.

~~~
[php]
<?php
...

preload => array('log', 'i18n'),

...
?>
~~~

Also we add it to the components array with some example properties.

~~~
[php]
<?php
...

'components' => array(
  'i18n' => array(
    'class' => 'I18n',
    'urlRulesPath' => dirname(__FILE__) . '/i18n/urlRules',
    'supportedLanguages' => array(
      array('code' => 'en', 'name' => 'english', 'fallback' => true),
      array('code' => 'de', 'name' => 'german'),
    ),
  ),
),

...
?>
~~~

The url rule files in "protected/i18n/urlRules" have to be like this:

~~~
[php]
<?php

/* ../protected/i18n/urlRules/de.php */

return array(
  'kontakt' => 'site/contact',
),

?>
~~~

On a request, this chain will apply:

* The web application will preload and init() our I18n component.

* Now our I18n component comes in play and parses the host info.

* If we're not already on a valid subdomain, the component tries to guess the
client language and checks if that language is supported. If that's not the
case, the fallback language is used as active language. After that a
redirect-url gets generated.

* The application language will be set to the just figured out active language.

* The custom url rules will be included and passed to the urlManager component.
The urlManager will be re-initialized to apply the cutom rules.

* If a redirect-url was previously set, a redirect to that url will be executed.

### Summary

This is a simple solution. For example I use a more advanced I18n component that
can do translations, guesses the client language by country-code and so on. Also
I can set different work-modes (top-level domain, subdomain, path).

It's in the end up to you what you need, but I hope this example solution could
give you a clue of how this can be done.

Feel free to share your thoughts and remarks - thank you.

### Links

Russian version -
[link](http://dbhelp.ru/i18n-subdomains-and-url-rules/page/)