Yii и мультиязычный сайт. Правильные URL и гибкость в работе

в 11:48, , рубрики: multilanguage, php, yii, yii framework, метки: , , ,

Yii и мультиязычный сайт. Правильные URL и гибкость в работе
При написании одного проекта, возникла необходимость в организации мультиязычности на сайте. Причем количество языков не должно ограничиваться двумя и URL должны быть человеко-понятные и SEO оптимизированные. Тоесть ссылки на сайте должны быть вида:
mysupersite.ru/ru/contacts для русского языка
mysupersite.ru/en/contacts для английского языка
Так как опыт у меня не очень большой, я начал вопрошать гугл. Вариантов, как оказалось, достаточно много, однако из всех мне приглянулся один вариант, который я использовал и слегка модифицировал.

1. Расширяем CUrlManager.

Создаем файл 'components/UrlManager.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);
    }
}
?>

Согласно нашему условию, выбранный язык должен быть частью URL. Это значит, что $_GET['language'] должен быть определен. Для реализации этого мы переопределяем функцию createUrl() класса CUrlManager. Если язык в строке не указан, тогда мы его ищем в переменной сессии, затем в кукисах, и если до этого пользователь не менял язык то устанавливаем язык приложения по умолчанию. И затем формируем правильную строку URL уже с языком как параметр.

2. Редактируем наш Controller

Добавляем следующий код в '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);
}
?>

Мы расширяем конструктор класса и добавляем язык для приложения. Так как все контроллеры будут наследоваться с этого контроллера, язык приложения будет установлен явно на каждый запрос.
Если не установленYii::app()->language явно для каждого запроса в URL, он будет браться из конфигурационного файла приложения. Если же он не указан в конфигурационном фале, он будет идентичен Yii::app()->sourceLanguage, который по умолчанию 'en_us'.
Все эти параметры можно изменить в конфигурационном файле protectedconfigmain.php

'sourceLanguage'=>'en',
'language'=>'ru',

3. Создаем Language Selector Widget

Создаем файл в 'components/widgets/LanguageSelector.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));
    }
}
?>

И вьюху для нашего виджета 'components/widgets/views/languageSelector.php':

<div id="language-select">
<?php
    if(sizeof($languages) < 4) { // если языков меньше четырех - отображаем в строчку
        // Если хотим видить в виде флагов то используем этот код
        foreach($languages as $key=>$lang) {
            if($key != $currentLang) {
                echo CHtml::link(
                     '<img src="/images/'.$key.'.gif" title="'.$lang.'" style="padding: 1px;" width=16 height=11>', 
                     $this->getOwner()->createMultilanguageReturnUrl($key));                };
        }
        // Если хотим в виде текста то этот код
        /*
        $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>

Для отображения флагов, необходимо разместить в папке /images/ указатели языков с именами типа en.gif, ru.gif, ua.gif, md.gif.

4. Размещаем Widget на сайте

Добавлем следующий код внутри header-div в 'views/layouts/main.php'

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

5. Редактируем Конфигационный файл приложения

<?php
'components'=>array(
    ...
    'request'=>array(
        'enableCookieValidation'=>true,
        'enableCsrfValidation'=>true,
    ),
    'urlManager'=>array(
        'class'=>'application.components.UrlManager',
        'urlFormat'=>'path',
        'showScriptName'=>false,
        'rules'=>array(
            '<language:(ru|ua|en)>/' => 'site/index',
            '<language:(ru|ua|en)>/<action:(contact|login|logout)>/*' => 'site/<action>',
            '<language:(ru|ua|en)>/<controller:w+>/<id:d+>'=>'<controller>/view',
            '<language:(ru|ua|en)>/<controller:w+>/<action:w+>/<id:d+>'=>'<controller>/<action>',
            '<language:(ru|ua|en)>/<controller:w+>/<action:w+>/*'=>'<controller>/<action>',
        ),
    ),
),
'params'=>array(
    'languages'=>array('ru'=>'Русский', 'ua'=>'Український', 'en'=>'English'),
),
?>

6. Добавляем .htaccess

RewriteEngine on

# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# otherwise forward it to index.php
RewriteRule . index.php

Вот вроде бы и все. У меня заработало и глюков пока не замечено. На вопросы отвечу.
Информацию почти всю взял отсюда: SEO-conform Multilingual URLs + Language Selector Widget (i18n)

Автор: Bamburillo


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js