
В основном показан пример использования yii2-translate-manager для перевода простых фраз и уведомлений. Реже можно встретить перевод статических страниц, еще реже перевод страниц хранящих контент в базе данных. И совсем редко вывод текущего языка в URL страницы. По последнему есть несколько готовых решений устанавливаемых с помощью Composer. Но опять же- URL будет содержать метку языка, а основную часть организации мультиязычности придется делать самому и не факт, что будет работать как надо и что-то не вылезет. Поэтому делаем все сами, это не так и сложно.
Перечислю, что мы будем иметь в итоге:
- кол-во языков не ограничено. Используемые языки задаются в конфигурационном файле main.php.
- указатель текущего языка (метка) указывается в URL страниц, что необходимо для SEO. Пример (русский использован в качестве основного языка и выбрана опция не выводить основной язык):
http://site.com
http://site.com/en
http://site.com/uk
http://site.com/contact
http://site.com/en/contact
http://site.com/uk/contact
- основной язык приложения (например русский) можно как отображать в URL так и отключить в настройках модуля;
- язык можно менять как выбрав соответствующую ссылку на странице так и прямо в адресной строке;
- правила маршрутизации компонента приложения UrlManager останутся на своих местах и не требуют изменения.
- сделаем перевод служебных и прочих одиночных сообщений (например меню, подвал сайта);
- сделаем перевод статических страниц (контакты, о нас…) которые будут храниться в виде соответствующих файлов представлений переведенных на нужные языки;
- сделаем перевод контента страниц (постов) из базы данных (кому это не надо – можно пропустить);
Широко охватим данную тему, достаточную для организации мультиязычности на большинстве сайтов и WEB-приложений Yii2.
В принципе, если не выводить указатель языка в URL страниц, то дело намного упрощается. Тогда можно сохранять выбранный пользователем язык в куку и потом, при каждой загрузке страницы, выводить контент на языке соответствующий сохраненному значению. Не нужно вставлять метку языка в ссылки и т.д. Но с точки зрения SEO оптимизации (индексирования сайта поисковыми системами) это плохо, т.к. страницы с разным содержимым (на разных языках) будут открываться по одному и тому же URL. Поэтому, приведу пример правильной и расширенной версии с учетом SEO.
Ниже представлен код файлов задействованных в создании мультиязычного приложения. Вы можете скачать архив с данными файлами по ссылке.
Файл конфигурации frontend\config\main.php
1. В начало массива возвращаемого директивой return пишем:
'sourceLanguage' => 'ru', // использован в качестве ключей переводовПараметр используется для yii2-translate-manager (подключается далее) при переводе отдельных фраз с помощью метода Yii::t(). Можно задать ключи общие для всех языков, по которым будут искаться слова, а можно как тут – указать язык по-умолчанию, который будет использован в качестве ключей и для него не нужно будет создавать отдельный файл с переводом.
2. Чуть ниже регистрируем модуль:
'modules' => [ 'languages' => [ 'class' => 'common\modules\languages\Module', //Языки используемые в приложении 'languages' => [ 'English' => 'en', 'Русский' => 'ru', 'Українська' => 'uk', ], 'default_language' => 'ru', //основной язык (по-умолчанию) 'show_default' => false, //true - показывать в URL основной язык, false - нет ], ],В параметре languages указываем, в виде ассоциативного массива, какие языки будут использованы в приложении. Ключи массивов далее можно использовать в качестве ссылок на смену текущего языка, а значения используются в виде языковых меток в URL. Так же значения используются при установки текущего языка приложения, поэтому нужно использовать стандартные обозначения.
В параметре default_language указываем какой язык устанавливать приложению по-умолчанию, например при вводе URL главной страницы.
В параметре show_default указываем нужно ли в адресной строке (в URL) отображать метку основного языка.
3. Еще ниже регистрируем класс предзагрузки:
'bootstrap' => [ 'log', 'languages' ],где указываем, что при выполнении первоначальной загрузки приложения (до обработки входящего запроса) нужно выполнить код из модуля 'languages', а именно его метод bootstrap(). Подробнее про предзагрузку читайте тут.
Это позволит установить язык приложения в зависимости от метки языка в URL.
4. В начало массива components – включаем перевод сообщений:
'i18n' => [ 'translations' => [ 'app' => [ 'class' => 'yii\i18n\PhpMessageSource', //'forceTranslation' => true, 'basePath' => '@common/messages', ], ], ],
- app – название категории к которой будут относиться переводы. Файл должен называться app.php и находиться по указанному пути. А текст для перевода указывается так: Yii::t('app', 'Блог')
- forceTranslation - указывается если в качестве ключей использовать фразы-константы которые так же нужно переводить. В нашем примере указан параметр 'sourceLanguage', поэтому не нужно задавать.
- в basePath передаем путь к папке с переводами. Т.к. русский указан в качестве ключей для перевода, нужно создать только языки на которые нужен перевод, например английский.
5. В массиве "components" есть вложенный массив "request", вставить в него строки:
'baseUrl' => '', // убрать frontend/web 'class' => 'common\components\Request'Первой строкой убираем из URL ненужные надписи с папкой входного скрипта.
Во второй строке указываем класс LangRequest переопределяющий класс фреймворка yii\web\Request, подробности далее.
6. В массив urlManager (являющийся элементом массива components) включаем ЧПУ, добавляем свой класс и нужные правила:
'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'class' => 'common\components\UrlManager', 'rules' => [ 'languages' => 'languages/default/index', //для модуля мультиязычности //далее создаем обычные правила '/' => 'site/index', '<action:(contact|login|logout|language|about|signup)>' => 'site/<action>', ], ],Указываем одно обязательное правило, нужно для работы модуля, а далее все как всегда.
Так же переопределяем стандартный класс UrlManager'. Размещаем его как и Request в common\components т.к. он может понадобиться не только для модуля мультиязычности.
Файл common\components\UrlManager.php
<?php /* * Добавляет указатель языка в ссылки */ namespace common\components; use Yii; class UrlManager extends \yii\web\UrlManager { public function createUrl($params) { //Получаем сформированную ссылку(без идентификатора языка) $url = parent::createUrl($params); if (empty($params['lang'])) { //текущий язык приложения $curentLang = Yii::$app->language; //Добавляем к URL префикс - буквенный идентификатор языка if ($url == '/') { return '/' . $curentLang; } else { return '/' . $curentLang . $url; } }; return $url; } }Переопределяем метод createUrl() класса UrlManager, чтобы он добавлял указатель языка во все внутренние ссылки сайта.
Файл common/components/Request.php
<?php namespace common\components; use Yii; class Request extends \yii\web\Request { private $_lang_url; public function getLangUrl() { $this->_lang_url = $this->getUrl(); //полный URL $url_list = explode('/', $this->getUrl()); $lang_url = isset($url_list[1]) ? $url_list[1] : null; //Удалить метку языка из URL if( $lang_url !== null && $lang_url === Yii::$app->language ) { $url = preg_replace("/^\/$lang_url/", '', $this->_lang_url); return $url; } return $this->_lang_url; } /* * Переопределяем метод для того, чтобы он использовал URL без метки языка. * Это позволит использовать обычные правила в UrlManager. */ protected function resolvePathInfo() { $pathInfo = $this->getLangUrl(); if (($pos = strpos($pathInfo, '?')) !== false) { $pathInfo = substr($pathInfo, 0, $pos); } $pathInfo = urldecode($pathInfo); // try to encode in UTF8 if not so // http://w3.org/International/questions/qa-forms-utf-8.html if (!preg_match('%^(?: [\x09\x0A\x0D\x20-\x7E] # ASCII | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )*$%xs', $pathInfo) ) { $pathInfo = utf8_encode($pathInfo); } $scriptUrl = $this->getScriptUrl(); $baseUrl = $this->getBaseUrl(); if (strpos($pathInfo, $scriptUrl) === 0) { $pathInfo = substr($pathInfo, strlen($scriptUrl)); } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) { $pathInfo = substr($pathInfo, strlen($baseUrl)); } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) { $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl)); } else { throw new InvalidConfigException('Unable to determine the path info of the current request.'); } if (isset($pathInfo[0]) && $pathInfo[0] === '/') { $pathInfo = substr($pathInfo, 1); } return (string) $pathInfo; } }
Тут мы переопределяем метод resolvePathInfo() класса yii\web\Request. Это делается для того, чтобы URL используемый в запросе и далее разбираемый на составные части не содержал метки языка, что позволит использовать стандартные правила для UrlManager.
Первой строкой данного метода
$pathInfo = $this->getLangUrl();
мы получаем URL без метки языка, который подготавливает метод getLangUrl() и больше ничего не меняем.
Теперь создаем файлы модуля.
Проще всего создать модуль используя генератор GII.

При создании файлов модуля в генераторе можно отключить строку создания файла представления:
…common\modules\languages\views\default\index.php
т.к. модуль будет использовать виджеты для вывода списка языков для выбора пользователем.
Итого, после использования генератора появится 2 файла – модуль и его контроллер, конечно можно создать их и вручную, как и остальные файлы модуля. Ниже привожу код всех файлов.
Файл common/modules/languages/Module.php
<?php namespace common\modules\languages; use common\modules\languages\models\LanguageKsl; use yii\base\BootstrapInterface; class Module extends \yii\base\Module implements BootstrapInterface { public $controllerNamespace = 'common\modules\languages\controllers'; public $languages; //Языки используемые в приложении public $default_language; //основной язык (по-умолчанию) public $show_default; //показывать в URL основной язык /* * Предзагрузка - выполнится до обработки входящего запроса. * Устанавливает язык приложения в зависимости от метки языка в URL, * а при ее отсутствии устанавливает в качестве метки текущий язык */ public function bootstrap($app) { if(YII_ENV == 'test') return; //для тестового приложения отключаем. $url = $app->request->url; //Получаем список языков в виде строки $list_languages = LanguageKsl::list_languages(); preg_match("#^/($list_languages)(.*)#", $url, $match_arr); //Если URL содержит указатель языка - сохраняем его в параметрах приложения и используем if (isset($match_arr[1]) && $match_arr[1] != '/' && $match_arr[1] != ''){ /* * Если в настройках выбрано не показывать язык используемый по-умолчанию * убираем метку текущего языка из URL и перенаправляем на ту же страницу */ if( !$this->show_default && $match_arr[1] == $this->default_language) { $url = $app->request->absoluteUrl; //Возвращает абсолютную ссылку $lang = $this->default_language; //язык используемый по-умолчанию $app->response->redirect(['languages/default/index', 'lang' => $lang, 'url' => $url]); } $app->language = $match_arr[1]; $app->formatter->locale = $match_arr[1]; $app->homeUrl = '/'.$match_arr[1]; /* * Если URL не содержит указатель языка и отключен показ основного языка в URL */ } elseif(!$this->show_default){ $lang = $this->default_language; //язык используемый по-умолчанию $app->language = $lang; $app->formatter->locale = $lang; /* * Если URL не содержит указатель языка, а в настройках включен показ основного языка */ } else { $url = $app->request->absoluteUrl; //Возвращает абсолютную ссылку $lang = $this->default_language; $app->response->redirect(['languages/default/index', 'lang' => $lang, 'url' => $url], 301); } } }
В основном файле модуля указываем пространство имен контроллеров модуля, а так же свойства которые определяются в конфигурации. Модуль реализует интерфейс BootstrapInterface для того, чтобы выполнить метод bootstrap($app) до обработки входящего запроса. Эту возможность я использовал для установки языка приложения исходя из языковой метки в URL, а в случае если такая метка не обнаружена, будет подставлена метка языка используемого по-умолчанию, в примере это русский язык.
Файл контроллера - common/modules/languages/controllers/DefaultController.php
<?php namespace common\modules\languages\controllers; use Yii; use yii\web\Controller; use common\modules\languages\models\LanguageKsl; class DefaultController extends Controller { /** * Обрабатывает переход по ссылкам для смены языка * Перенаправляет на ту же страницу с новым URL. */ public function actionIndex() { $language = Yii::$app->request->get('lang'); //язык на который будем менять /* * При перенаправлении сюда до разбора входящего запроса (метод run класса LanguageKsl) * передаем URL предыдущей страницы в get параметрах */ $url_referrer = Yii::$app->request->get('url'); /* * При перенаправлении сюда из виджета (нажатие по ссылке для смены языка) * получаем предыдущую страницу средствами Yii2 */ if(!$url_referrer) $url_referrer = Yii::$app->request->referrer; //предыдущая страница /* * Если все же предыдущая страница не получена - возвращаем на главную. */ if (!$url_referrer) $url_referrer = Yii::$app->request->hostInfo . '/'. $language; //устанавливает/меняет метку языка $url = LanguageKsl::parsingUrl($language, $url_referrer); // перенаправление Yii::$app->response->redirect($url); } }
Метод index() контроллера обрабатывает запрос с переданным ему GET параметром 'lang'. Данный параметр содержит метку языка на который пользователь желает переключиться. Далее в виджете мы сделаем соответствующие ссылки. Получив метку языка контроллер перенаправляет пользователя на ту же страницу на которой он нажал ссылку смены языка но уже с меткой выбранного языка.
Контроллер использует определенный функционал который вынесем в модель.
Файл common/modules/languages/models/LanguageKsl.php
<?php namespace common\modules\languages\models; use Yii; class LanguageKsl { static $list; //строка вида ru|uk|en| /* * Преобразование к строке вида ru|uk|en| * для использования в регулярных выражениях */ public static function list_languages(){ if(!self::$list){ $languages = Yii::$app->getModule('languages')->languages; $list = ''; array_walk($languages, function ($value) use (&$list){ $list .= $value . '|'; }); self::$list = $list; } return self::$list; } /** * Создает URL с меткой языка * Разбивает URL на подмассив $match_arr * 0. http://site.loc/ru/contact * 1. http://site.loc * 2. ru или uk или en * 3. остальная часть */ public static function parsingUrl($language, $url_referrer){ $list_languages = self::list_languages(); //список языков $host = Yii::$app->request->hostInfo; preg_match("#^($host)/($list_languages)(.*)#", $url_referrer, $match_arr); //добавляем разделитель if (isset($match_arr[3]) && !empty($match_arr[3]) && !preg_match('#^\/#', $match_arr[3])){ $separator = '/'; } else { $separator = ''; } $default_language = Yii::$app->getModule('languages')->default_language; $show_default = Yii::$app->getModule('languages')->show_default; //Удаляем основной язык из URL, если в настройках выбрано "не показывать" if($language == $default_language && !$show_default){ $match_arr[2] = null; } else { $match_arr[2] = '/'.$language.$separator; } // создание нового URL $url = $match_arr[1].$match_arr[2].$match_arr[3]; return $url; } }
Метод parsingUrl() разбирает текущий URL на части и далее заменяет или добавляет нужную метку языка формируя новый URL с новой меткой. Метод list_languages() преобразует языки указанные в конфигурации модуля к виду нужному для использования в регулярных выражениях метода parsingUrl().
Осталось сделать вывод ссылок для переключения языков на страницах сайта. Сначала я сделал это в виде отдельного файла-представления, позже решил сделать в виде виджета, что позволит легко создать необходимый вид, если, например, потребуется вывести данные ссылки в другом виде или же в нескольких местах страницы.
Основной файл виджета common/modules/languages/widgets/ListWidget.php
<?php namespace common\modules\languages\widgets; use Yii; use yii\base\Widget; use yii\helpers\Html; class ListWidget extends Widget{ public $array_languages; public function init() { $language = Yii::$app->language; //текущий язык //Создаем массив ссылок всех языков с соответствующими GET параметрами $array_lang = []; foreach (Yii::$app->getModule('languages')->languages as $key => $value){ $array_lang += [$value => Html::a($key, ['languages/default/index', 'lang' => $value])]; } //ссылку на текущий язык не выводим if(isset($array_lang[$language])) unset($array_lang[$language]); $this->array_languages = $array_lang; } public function run() { return $this->render('list',[ 'array_lang' => $this->array_languages ]); } }тут формируем массив готовых ссылок для вывода в представлении виджета, которые будут передавать GET параметр 'lang' с меткой нужного языка в контроллер модуля, где уже будет осуществляться смена языковой метки.
В common/modules/languages/widgets создаем папку views с файлами-представлений виджета. Создадим там один простой файл для примера.
Файл common/modules/languages/widgets/views/list.php
<div class="languages-klisl"> <?php foreach ($array_lang as $lang) { echo ' '.$lang.' '; } ?> </div>Для вывода виджета, в шаблоне или виде, например в блоке footer файла-шаблона frontend\views\layouts\main.php вставляем:
<?= common\modules\languages\widgets\ListWidget::widget() ?>
На данном этапе уже работает перевод фраз. Рассказываю как это делается.
Файлы с переводами должны называться как указано в конфигурации (файл main.php, массив компонента 'i18n'), в данном случае - app.php и размещаться (как указано там же) в common\messages\ и дальше папка с название языка (en, uk…).
Пример файла перевода common\messages\en\app.php:
<?php return [ 'Блог' => 'Blog', 'О нас' => 'About me', 'Контакты' => 'Contact', ];то есть в массив return нужно вписать все слова и фразы которые нужно переводить. В коде (обычно в шаблонах и файлах представлений), фразы которые требуют перевода заключать в вызов метода Yii::t().
Согласно нашей конфигурации так:
Yii::t('app', 'Блог')Русский у нас указан в качестве языка по-умолчанию, поэтому если текущий язык – русский, выведется слово «Блог», а если английский - 'Blog'.
Делаем перевод статичных страниц, то есть тех, которые хранят текст в коде, а не берут контент из БД. Целые страницы содержат слишком много текста, в связи с чем нецелесообразно использовать метод Yii::t().
В нужном контроллере создаем действие для каждой такой страницы:
public function actionStat() { $language = Yii::$app->language; //текущий язык //выводим вид соответствующий текущему языку return $this->render('statPages/stat-'.$language); }то есть вторая часть название файла вида берется из названия языка.
В данном случае в папке с видами создаем отдельную папку для статичных файлов statPages (это не обязательно), а в ней файлы с контентом соответствующего языка:
- stat-ru.php
- stat-uk.php
- stat-en.php
Мы сделали перевод фраз и статичных страниц, текущий язык уже отображается в адресной строке, пользователь может свободно переходить с одного на другой. Для многих сайтов (например сайт-визитка и прочих) этого уже вполне достаточно. Для полноты картины осталось рассмотреть вариант перевода когда контент хранится в базе данных. Это я вынес в отдельную статью.
Если вас интересует готовое решение - предлагаю скачать мое расширение устанавливаемое с помощью Composer. Оно реализует все описанное в данной статье. Страница расширения на GitHub тут. Если понравится - не забудь нажать звездочку :)
Men o'zbek yigitman. Shunga o'zbekcha gapiraman. Tushunmagan bo'lsangiz uzur.
ответ на комментарий Hasan Shodiev от 16.10.2017
Если у меня уже работает предзагрузка по похожей схеме. То есть, работает переключатель складов.
А теперь нужно сделать ещё языки. .
... en/sklad1/....
... en/sklad2/ ...
... ru/sklad1/ ...
... ru/sklad2/ ...
Может подскажите, как это можно реализовать? Или в каком направлении копать?
ответ на комментарий Natalja от 13.03.2018
НЕ подскажите новичку как убрать languages?lang=ua из формирования ссылки, а оставить site.com/ua/ и как при дефолтном языке ru убрать из формирования ссылок /ru/, чтобы вместо site.com/ru/url было site.com/url
ответ на комментарий Островщук от 01.06.2018
"... как при дефолтном языке ru убрать из формирования ссылок /ru/" - используйте мое расширение, ссылка в конце статьи. Там это уже реализовано. Или просто посмотрите как делается.
"...как убрать languages?lang=ua из формирования ссылки" - какой именно ссылки, ссылки переключения языка? Зачем?
Но вот на днях проект стал сильно "задумываться".... Нашел, что основанная часть раздумий попадает на Module.php ... в чем может быть проблема ?
ответ на комментарий Тарас от 18.06.2018
Остался один вопрос. Если можете, подскажите
Как выводить перевод заголовков в цикле.
К примеру, в базе есть заголовки, циклом вывожу на страницу.
<?php foreach ($post as $item): ?>
<?= $item->caption ?>
<?= $item->text ?>
<?php endforeach; ?>
Как подключить перевод к ним?
Спасибо
ответ на комментарий Вячеслав от 07.07.2018