Yii 1.1: I18n subdomains and url-rules

18 followers

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 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
...
 
'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 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 of your main domain (eg example.com) will probably suffer. Also you may run into a duplicate content 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 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
 
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
...
 
preload => array('log', 'i18n'),
 
...
?>

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

<?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
 
/* ../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 custom 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

Total 6 comments

#15831 report it
Norbert Sajtos at 2013/12/20 10:42am
createUrl

In this case only the rules are loaded to the current language.

How can you create a link to the other language?

ex: /kontakt/ -> /contact/

#3195 report it
coma at 2011/03/25 05:22am
URL rules replaced?

Does this actually replace the existing URL rules or just add additional from the language-rule files? Do I need to duplicate all of my rules there?

#3169 report it
marcius at 2011/03/23 01:03pm
Side effects

Using different subdomains means different cookies and problems with GA tracking of unique visitors. Wouldn't be better to use URLs like these:

http://www.example.com/en/contact
http://www.example.com/de/kontakt
#1069 report it
Y!! at 2009/12/19 05:53am
Short info

Since version 1.0.11 the Yii UrlManager finally supports subdomains and toplevel-domains. See http://www.yiiframework.com/doc/guide/topics.url under "Parameterizing Hostnames".

#1070 report it
Pascool at 2009/12/18 05:59pm
Y!!

Thanks! Works like a charm, now...

And yes i had to put dirname(dirname(FILE)) . '/i18n/urlRules' for urlRulesPath ;)

#1172 report it
schmunk at 2009/11/16 05:46pm
A bizrule for I18N
return array_key_exists(
 Yii::app()->language,
 Yii::app()->authManager->getRoles(Yii::app()->user->id)
);

You can attach this bizrule to any task or operation.

Ony if the user is in the same role as the current language, access is granted.

Note: Tested only with CDbAuthManager.

Leave a comment

Please to leave your comment.

Write new article
  • Written by: Y!!
  • Updated by: atrandafir
  • Category: Tutorials
  • Yii Version: 1.1
  • Votes: +7
  • Viewed: 22,801 times
  • Created on: Nov 7, 2009
  • Last updated: Apr 18, 2011
  • Tags: i18n, URL