Routes based on language

Recently I have encountered a problem to handle different application routes based on the current language.

These routes had to lead to the same controllers’ actions as with the default language.

You can find my solution below but the question is - is there a better way / what would you do to make it better?

The task:

  • Allow the translation of routes for different languages without duplicating actions.

Task example:

  • For default language (en-US) route is ‘site/contact’ - for language ‘xx’ it should be ‘rick/morty’ but still handled by actionContact() from SiteController.

Creating the urls:

I’ve extended UrlManager.




namespace common\components;


use Yii;

use yii\web\UrlManager as YiiUrlManager;


/**

 * UrlManager

 *

 */

class UrlManager extends YiiUrlManager

{

    

    public $enablePrettyUrl = true;

    public $showScriptName  = false;

    

    public $language;

    

    public $languageControllers = [

        'xx' => [

            'site' => 'rick'

        ],

    ];

    

    public $languageActions = [

        'xx' => [

            'contact' => 'morty',

        ],

    ];

    

    /**

     * Initializes UrlManager.

     */

    public function init()

    {

        parent::init();

        

        if (empty($this->language)) {

            $this->language = Yii::$app->language;

            // In case language is not set directly we take it from app config.

        }

    }

    

    public function createUrl($params)

    {

        if (!empty($params[0])) {

            $route = explode('/', $params[0]);


            // Translate controller's id

            if (isset($route[0]) && !empty($this->languageControllers[$this->language][$route[0]])) {

                $route[0] = $this->languageControllers[$this->language][$route[0]];

            }


            // Translate action's id

            if (isset($route[1]) && !empty($this->languageActions[$this->language][$route[1]])) {

                $route[1] = $this->languageActions[$this->language][$route[1]];

            }

            $params[0] = implode('/', $route);

        }

        

        return parent::createUrl($params);

    }

}




And used it as my urlManager component (component/config/main.php).




'components' => [

    'urlManager' => [

        'class' => 'common\components\UrlManager',

        'rules' => [

            '<controller:\w+>/<action:\w+>' => '<controller>/<action>',

        ],

    ],

],



So now if language is changed to ‘xx’ (i.e. like that http://yii2-cookbook.readthedocs.org/i18n-selecting-application-language/ ) I can write


Url::to(['site/contact'])

and created url is


/rick/morty

Now to deal with request. It is a bit more difficult here, I’m not sure if this is the best solution.

I’ve extended yii\web\Application.




namespace common\components;


/**

 * Application

 *

 * Translates route to existing controller and action.

 */

class Application extends \yii\web\Application

{

    

    public function runAction($route, $params = [])

    {

        $routeParts = explode('/', $route);

        $urlManager = $this->urlManager;


        // Translate controller's id back to the default language value

        if (isset($routeParts[0]) && !empty($urlManager->languageControllers[$urlManager->language])) {

            foreach ($urlManager->languageControllers[$urlManager->language] as $default => $localized) {

                if ($localized == $routeParts[0]) {

                    $routeParts[0] = $default;

                    break;

                }

            }

        }


        // Translate action's id back to the default language value

        if (isset($routeParts[1]) && !empty($urlManager->languageActions[$urlManager->language])) {

            foreach ($urlManager->languageActions[$urlManager->language] as $default => $localized) {

                if ($localized == $routeParts[1]) {

                    $routeParts[1] = $default;

                    break;

                }

            }

        }

        $route = implode('/', $routeParts);

        

        return parent::runAction($route, $params);

    }

}




To make it work entry script needs to be modified. I replaced




$application = new yii\web\Application($config);

$application->run();



With




$application = new common\components\Application($config);

$application->run();



So now with language ‘xx’ if request is ‘rick/morty’ application calls actionContact() from SiteController.

There is this obvious problem with url not being recognised if language has been changed but it can be fixed with few amendments and this code is created for application with static not dynamic language anyway.

This is it.

I’m looking forward to getting some tips from you guys.

You may want to look at this:

Selecting application language from Yii 2.0 Cookbook

In the article, a customization is done on UrlRule instead of UrlManager. It just tries to add a language specifier in the existing pattern of the rule, for example, it changes "site/contact" to "ja/site/contact".

So, I think it should be easy for you to change “site/contact” to “rick/morty” for a certain language using the same mechanism. It would be simpler and cleaner since you don’t have to care about url creation and url parsing with this approach.

[EDIT]

Sorry, I was wrong. Extending UrlRule won’t satisfy your needs, since you have to deal with the parameterised patterns like “<controller:\w+>/<action:\w+>”. You have to extend UrlManager.

But I think you should consider overriding "parseRequest()".

http://www.yiiframework.com/doc-2.0/yii-web-urlmanager.html#parseRequest()-detail

Thank you @softark

parseRequest is perfect - I don’t have to modify Application anymore.

I have moved the url translation mechanism to parseRequest() inside extended UrlManager.