Сервис на Yii2: Доработка шаблона приложения и i18n

В прошлый раз мы к нашему приложению на basic-шаблоне добавили функционал хранения пользователей в базе данных и добавили формы подтверждения электронного адреса и восстановления пароля. Сегодня мы сделаем последние подготовительные штрихи: доработаем интерфейс и переведём всё с английского языка.

Репозиторий проекта на GitHub

Часть 1: Установка и настройка приложения
Часть 2: Настройка IDE и модульная структура
Часть 3: Перенос пользователей в БД

На данный момент мы получили приложение стандартного вида:

Сейчас у нас имя приложения (и имя компании) вписано в шаблоны вручную. Заголовок окна главной страницы формируется в файле представления index.php модуля main:

<?php
/* @var $this yii\web\View */
$this->title = 'My Yii Application';
?>

А имя компании вписано в главный шаблон views/layouts/main.php:

NavBar::begin([
    'brandLabel' => 'My Company',
    ...
]);
...
<footer class="footer">
    <div class="container">
        <p class="pull-left">&copy; My Company <?= date('Y') ?></p>
        <p class="pull-right"><?= Yii::powered() ?></p>
    </div>
</footer>

Для удобства подставим вместо этих имён значение Yii::$app->name:

<?php
/* @var $this yii\web\View */
$this->title = Yii::$app->name;
?>

И в views/layouts/main.php:

NavBar::begin([
    'brandLabel' => Yii::$app->name,
    ...
]);
...
<footer class="footer">
    <div class="container">
        <p class="pull-left">&copy; <?= Yii::$app->name ?></p>
        <p class="pull-right"><?= date('Y') ?></p>
    </div>
</footer>

Также это значение уже используется нами как имя отправителя при отправке писем в модели ContactForm и других:

Yii::$app->mailer->compose()
    ->setTo($email)
    ->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name])
    ...

Теперь по умолчанию нам будет выводиться значение My Application из соответствующего поля объекта нашего приложения. Действительно, в базовом классе приложения фреймворка имеется поле name с именем приложения по умолчанию:

namespace yii\base;
 
abstract class Application extends Module
{
    ...
    public $name = 'My Application';
    ...
}

Теперь мы спокойно можем изменить это имя в конфигурационном файле config/common.php:

return [
    'name' => 'SeoKeys',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    ...
];

Теперь имя приложения в логотипе, подвале и заголовке окна главной страницы будет одинаковым:

Но никто не мешает нам в качестве логотипа использовать изображение. Мы это делать не будем. Перейдём к руссификации интерфейса.

Язык интерфейса приложения

Если сейчас перейти на любую внутреннюю страницу, то можно заметить, что хлебные крошки и остальные элементы интерфейса выполнены на английском языке. Если попробовать неправильно заполнить любую форму, то сообщения об ошибках тоже выводятся на английском:

Для исправления ситуации можно сразу указать язык приложения 'languageвconfig/web.php`:

$config = [
    'id' => 'app',
    'language'=>'ru-RU',
    ...
];

Теперь все системные сообщения фреймворка и ссылка на главную страницу в хлебных крошках будут выводиться на русском:

Остались непереведёнными пункты меню, имена полей форм и фрагменты хлебных крошек. Это всё вписано в представления вручную, поэтому мы могли бы заменить их сами:

echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'items' => array_filter([
        ['label' => 'Главная', 'url' => ['/main/default/index']],
        ['label' => 'Связь', 'url' => ['/main/contact/index']],
        Yii::$app->user->isGuest ?
            ['label' => 'Регистрация', 'url' => ['/user/default/signup']] :
            false,
        Yii::$app->user->isGuest ?
            ['label' => 'Вход', 'url' => ['/user/default/login']] :
            ['label' => 'Выход',
                'url' => ['/user/default/logout'],
                'linkOptions' => ['data-method' => 'post']],
    ]),
]);

Но тогда всё станет на другом языке, но будет так же жёстко вписано в код. Если же мы собираемся обеспечить свободную поддержку нескольких языков, то можно воспользоваться имеющемся во фреймворке решением по интернационализации.

Первым делом добавим папки messages/en и messages/ru в корень приложения и создадим в них по файлу app.php. После этого в конфигурационном файле config/common.php укажем компоненту Yii::$app->i18n эти файлы переводов:

return [
    ...
    'language'=>'ru',
    ...
    'components' => [
        ...
        'i18n' => [
            'translations' => [
                'app' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'forceTranslation' => true,
                ],
            ],
        ],
    ],
];

Теперь заменим надписи пунктов меню. А именно, все тексты заменим на конструкцию Yii::t('app', *), где в качестве ключей подставим некие идентификаторы:

echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'items' => array_filter([
        ['label' => Yii::t('app', 'NAV_HOME'), 'url' => ['/main/default/index']],
        ['label' => Yii::t('app', 'NAV_CONTACT'), 'url' => ['/main/contact/index']],
        Yii::$app->user->isGuest ?
            ['label' => Yii::t('app', 'NAV_SIGNUP'), 'url' => ['/user/default/signup']] :
            false,
        Yii::$app->user->isGuest ?
            ['label' => Yii::t('app', 'NAV_LOGIN'), 'url' => ['/user/default/login']] :
            ['label' => Yii::t('app', 'NAV_LOGOUT'),
                'url' => ['/user/default/logout'],
                'linkOptions' => ['data-method' => 'post']],
    ]),
]);

И пройдёмся по всем имеющимся у нас представлениям и поменяем заголовки, тексты, надписи и кнопки:

$this->title = Yii::t('app', 'TITLE_RESET_PASSWORD');
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="user-default-password-reset-request">
    <h1><?= Html::encode($this->title) ?></h1>
 
    <p><?= Yii::t('app', 'PLEASE_FILL_FOR_RESET_REQUEST') ?></p>
 
    <div class="row">
        <div class="col-lg-5">
            <?php $form = ActiveForm::begin(['id' => 'password-reset-request-form']); ?>
            <?= $form->field($model, 'email') ?>
            <div class="form-group">
                <?= Html::submitButton(Yii::t('app', 'BUTTON_SEND'), ['class' => 'btn btn-primary']) ?>
            </div>
            <?php ActiveForm::end(); ?>
        </div>
    </div>
</div>

Обратите внимание, что по умолчанию если исходный язык sourceLanguage совпадает с текущим targetLanguage, то перевод не производится и строка возвращается как есть. Но мы вместо строк используем их идентификаторы, и нам нужно принудительно «переводить» их даже с английского на английский. Именно для принудительной обработки переводов в конфигурации нашего источника у транслятора мы установили параметр forceTranslation в true.

Или на странице обратной связи:

$this->title = Yii::t('app', 'TITLE_CONTACT');
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="main-contact-index">
    <h1><?= Html::encode($this->title) ?></h1>
 
    <?php if (Yii::$app->session->hasFlash('contactFormSubmitted')): ?>
 
        <div class="alert alert-success">
            <?= Yii::t('app', 'CONTACT_THANKS'); ?>
        </div>
 
    <?php else: ?>
 
        <div class="row">
            <div class="col-lg-5">
                <?php $form = ActiveForm::begin(['id' => 'contact-form']); ?>
                <?= $form->field($model, 'name') ?>
                <?= $form->field($model, 'email') ?>
                <?= $form->field($model, 'subject') ?>
                <?= $form->field($model, 'body')->textArea(['rows' => 6]) ?>
                <?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
                    'captchaAction' => '/main/contact/captcha',
                    'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>',
                ]) ?>
                <div class="form-group">
                    <?= Html::submitButton(Yii::t('app', 'BUTTON_SEND'), ['class' => 'btn btn-primary', 'name' => 'contact-button']) ?>
                </div>
                <?php ActiveForm::end(); ?>
            </div>
        </div>
 
    <?php endif; ?>
</div>

Аналогично доработаем все остальные представления и шаблоны писем в папке mail:

<?php
 
/* @var $this yii\web\View */
/* @var $user app\modules\models\User */
 
$confirmLink = Yii::$app->urlManager->createAbsoluteUrl(['user/default/email-confirm', 'token' => $user->email_confirm_token]);
?>
 
<?= Yii::t('app', 'HELLO {username}', ['username' => $user->username]); ?>
 
<?= Yii::t('app', 'FOLLOW_TO_CONFIRM_EMAIL') ?>
 
<?= $confirmLink ?>
 
<?= Yii::t('app', 'IGNORE_IF_DO_NOT_REGISTER') ?>

Все нуждающиеся в переводе тексты мы будем выводить не напрямую, а через метод Yii::t(), принимающий категорию и идентификатор сообщения для перевода. Этот метод попробует найти отображение этой строки на нужном нам языке.

Этот компонент не настолько умный, чтобы самостоятельно переводить тексты. Никакой магии здесь нет. Для его работы нам нужно самим подготовить все переводы и поместить в наши файлы.

В messages/en/app.php переместим из наших приедставлений и моделей все исходные надписи на английском языке:

return [
    'NAV_HOME' => 'Home',
    'NAV_CONTACT' => 'Contact',
    'NAV_SIGNUP' => 'Signup',
    'NAV_LOGIN' => 'Login',
    'NAV_LOGOUT' => 'Logout',
 
    'BUTTON_SEND' => 'Send',
    'BUTTON_SAVE' => 'Save',
    'BUTTON_UPDATE' => 'Update',
    'BUTTON_DELETE' => 'Delete',
 
    ...
 
    'HELLO {username}' => 'Hello, {username}!',
    'FOLLOW_TO_RESET_PASSWORD' => 'Follow the link below to reset your password:',
    'FOLLOW_TO_CONFIRM_EMAIL' => 'Follow the link below to confirm your email:',
    'IGNORE_IF_DO_NOT_REGISTER' => 'If you do not register on our site just remove this mail.'
];

Скопируем всё это в файл messages/ru/app.php и заменим сообщения на нужные для языка ru:

return [
    'NAV_HOME' => 'Главная',
    'NAV_CONTACT' => 'Связь',
    'NAV_SIGNUP' => 'Регистрация',
    'NAV_LOGIN' => 'Вход',
    'NAV_PROFILE' => 'Профиль',
    'NAV_LOGOUT' => 'Выход',
 
    'BUTTON_SEND' => 'Отправить',
    'BUTTON_SAVE' => 'Сохранить',
    'BUTTON_UPDATE' => 'Редактировать',
    'BUTTON_DELETE' => 'Удалить',
 
    ...
 
    'HELLO {username}' => 'Здравствуйте, {username}!',
    'FOLLOW_TO_RESET_PASSWORD' => 'Для смены пароля пройдите по ссылке:',
    'FOLLOW_TO_CONFIRM_EMAIL' => 'Для подтверждения адреса пройдите по ссылке:',
    'IGNORE_IF_DO_NOT_REGISTER' => 'Если Вы не регистрировались на нашем сайте, то просто удалите это письмо.'
];

В итоге изменились тексты, элементы хлебных крошек и кнопки:

Осталось только изменить названия полей форм. На странице входа они до сих пор остались подписанными как Username, Password, Remember Me.

Имена полей любой модели (и ActiveRecord в частности) формируются методом yii\base\Model::getAttributeLabel:

class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable
{
    ...
 
    public function getAttributeLabel($attribute)
    {
        $labels = $this->attributeLabels();
        return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
    }
 
    public function generateAttributeLabel($name)
    {
        return Inflector::camel2words($name, true);
    }
}

Он пытается найти имя в методе attributeLabels текущей модели, а если не находит, то генерирует надпись автоматически. В нашем классе LoginForm, например, этого метода нет (нами он непереопределён), поэтому к нашим полям:

class LoginForm extends Model
{
    public $username;
    public $password;
    public $rememberMe = true;
 
    ...
}

с помощью Inflector::camel2words автоматически генерируются имена Username, Password и Remember Me. Чтобы заменить имена полей на свои нужно добавить к моделям метод attributeLabels по аналогии с тем, который мы генерировали в модели User:

class LoginForm extends Model
{
    public $username;
    public $password;
    public $rememberMe = true;
 
    ...
 
    public function attributeLabels()
    {
        return [
            'username' => Yii::t('app', 'USER_USERNAME'),
            'password' => Yii::t('app', 'USER_PASSWORD'),
            'rememberMe' => Yii::t('app', 'USER_REMEMBER_ME'),
        ];
    }
}

Аналогично обходим остальные модели, добавляем имена полей и дописываем их переводы в файлы messages/en/app.php и messages/ru/app.php.

Теперь все надписи в формах генерируются верно:

Помимо этого, у нас есть вбитые вручную сообщения для ошибок валидации. Переведём и эти сообщения в моделях User, SignupForm`:

public function rules()
{
    return [
        ...
        ['username', 'unique', 'targetClass' => User::className(), 'message' => Yii::t('app', 'ERROR_USERNAME_EXISTS')],
        ...
        ['email', 'unique', 'targetClass' => User::className(), 'message' => Yii::t('app', 'ERROR_EMAIL_EXISTS')],
        ...
    ];
}

и в прочих моделях:

class LoginForm extends Model
{
    ...
 
    public function validatePassword()
    {
        if (!$this->hasErrors()) {
            $user = $this->getUser();
 
            if (!$user || !$user->validatePassword($this->password)) {
                $this->addError('password', Yii::t('app', 'ERROR_WRONG_USERNAME_OR_PASSWORD'));
            } elseif ($user && $user->status == User::STATUS_BLOCKED) {
                $this->addError('username', Yii::t('app', 'ERROR_PROFILE_BLOCKED'));
            } elseif ($user && $user->status == User::STATUS_WAIT) {
                $this->addError('username', Yii::t('app', 'ERROR_PROFILE_NOT_CONFIRMED'));
            }
        }
    }
 
    ...
}

Теперь если все переводы вбиты верно, то интерфейс полностью стал отображаться на другом языке.

Удаление лишнего

При разработке интерфейсов важно сделать так, чтобы ничего не мешало. Окинув взглядом приведённые выше скриншоты попробуем оценить, мешает ли нам что-то из элементов, занимает ли много лишнего места?

Да, есть такой большой элемент. Это заголовок <h1></h1>:

$this->title = Yii::t('app', 'TITLE_CONTACT');
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="main-contact-index">
    <h1><?= Html::encode($this->title) ?></h1>
    ...
</div>

Во-первых, он выводится крупным шрифтом, а во-вторых, он повторяет «хвост» хлебных крошек, то есть никакой дополнительной информации не несёт. Значит его можно спокойно удалить. Но мы вместо удаления из всех представлений можем его просто спрятать. Для этого в файл стилей web/css/site.css добавим пару правил:

h1 {
    display: none;
}
 
.jumbotron h1 {
    display: block;
}

Теперь без лишнего заголовка интерфейс стал проще и контент поднялся выше:

И пользоваться сервисом на маленьких мониторах стало удобнее.

Поддержка IE8

Браузер Internet Explorer поддерживает HTML5-теги и медиа-запросы в CSS для адаптивной вёрстки только со своей девятой версии. Но уже давно известен вариант эмуляции этого в старых версиях с помощью сторонних скриптов html5shiv и respond. Обычно разработчики это делают подключением этих скриптов в секцию <head></head> страницы:

<!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->

Мы можем это сделать также, вписав в шаблон этот код. Но если мы хотим использовать возможности Yii2, для этого можно создать отдельный бандл с указанием ему условия [if lt IE 9]:

namespace app\assets;
 
use yii\web\AssetBundle;
use yii\web\View;
 
class IESupportAsset extends AssetBundle
{
    public $js = [
        'https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js',
        'https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js'
    ];
    public $jsOptions = [
        'condition'=>'lt IE 9',
        'position' => View::POS_HEAD,
    ];
}

И вписать его в список зависимостей главного бандла приложения AppAsset:

namespace app\assets;
 
use yii\web\AssetBundle;
 
class AppAsset extends AssetBundle
{
    public $basePath = '@webroot';
    public $baseUrl = '@web';
    public $css = [
        'css/site.css',
    ];
    public $js = [
    ];
    public $depends = [
        'app\assets\IESupportAsset',
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
    ];
}

Но в последнее время с ростом скорости интернета наметилась тенденция отхода от загрузки ресурсов со сторонних хранилищ (CDN), чтобы не тратить время на обращение к DNS-серверам для каждого домена. Да и использование скриптов из своих папок позволяет нормально работать с локальной копией приложения на компьютере без доступа к сети.

Плюс к тому, почти все популярные ресурсы (вроде шрифтов, стилей и JS-скриптов) давно доступны в пакетном менеджере Bower, поэтому мы можем спокойно загружать их и обновлять через Composer с плагином для работы с Bower (это тот самый загадочный fxp/composer-asset-plugin, который нужно подключать глобально при установке Yii2-app-*).

Так что установим эти скрипты html5shiv и respond к себе:

composer require bower-asset/html5shiv:* bower-asset/respond:*

и создадим для них бандлы для подключения к шаблону Yii2. Так как скрипты установились в две разные папки внутрь vendor/bower, то нам нужно будет создать отдельный бандл для каждого скрипта.

Первый:

namespace app\assets;
 
use yii\web\AssetBundle;
use yii\web\View;
 
class Html5ShivAsset extends AssetBundle
{
    public $sourcePath = '@bower/html5shiv/dist';
    public $js = [
        'html5shiv.min.js',
    ];
    public $jsOptions = [
        'condition'=>'lt IE 9',
        'position' => View::POS_HEAD,
    ];
}

Второй:

namespace app\assets;
 
use yii\web\AssetBundle;
use yii\web\View;
 
class RespondAsset extends AssetBundle
{
    public $sourcePath = '@bower/respond/dest';
    public $js = [
        'respond.min.js',
    ];
    public $jsOptions = [
        'condition'=>'lt IE 9',
        'position' => View::POS_HEAD,
    ];
}

Не знаю, почему у пакета Respond поддиректория называется dest вместо dist, но напишем как есть.

Теперь, чтобы подключить их глобально для всех страниц шаблона, в AppAsset объявим от них зависимости:

namespace app\assets;
 
use yii\web\AssetBundle;
 
class AppAsset extends AssetBundle
{
    public $basePath = '@webroot';
    public $baseUrl = '@web';
    public $css = [
        'css/site.css',
    ];
    public $js = [
    ];
    public $depends = [
        'app\assets\Html5ShivAsset',
        'app\assets\RespondAsset',
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
    ];
}

И, обновив вкладку в браузере, в исходном коде страницы увидим автоматически подключенные скрипты:

<!--[if lt IE 9]>
<script src="/assets/db84ef52/html5shiv.min.js"></script>
<![endif]-->
<!--[if lt IE 9]>
<script src="/assets/ff150184/respond.min.js"></script>
<![endif]-->

Теперь эти скрипты будут у нас всегда и мы сможем их автоматически обновлять через Composer.

Таким же образом можно подключать любые свои или сторонние JS и CSS пакеты. Так что при необходимости прикрутить любой набор шрифтов или jQuery-плагин сначала поищите его здесь и, если найдёте, установите командой composer require bower-asset/<имя>, потом создайте для него бандл и подключайте по требованию в нужном месте или глобально.

Чтобы папки в web/assets автоматически обновлялись (чтобы не вычищать их вручную после каждого обновления) можно в config/web-local.php включить использование символических ссылок:

'components' => [
    ...
    'assetManager' => [
        'linkAssets' => true,
    ],
    ...
]

Замена JQuery с 2.1 на 1.11

По умолчанию Yii2 работает с JQuery 2.*. Вторая ветка отличается от первой только отсутствием поддержки старых браузеров. Соответственно, при необходимости мы можем заменить скрипт второй версии на соответствующий скрипт первой.

В Yii1 для этого пришлось бы скачивать свой скрипт и подменять путь в файле конфигурации приложения. В Yii2 дело с этим обстоит проще. Фреймворк не содержит JQuery внутри себя, а использует стронний компонент. Действительно, если внутри самого фреймворка открыть файл composer.json, то там мы увидим зависимость:

"require": {
    ...
    "bower-asset/jquery": "2.1.*@stable | 1.11.*@stable",
    ...
}

и Composer загружает максимальную подходящую версию.

Одним из ключевых моментов работы с Composer явлается то, что он получает список зависимостей из файлов composer.json каждого компонента, из чего потом комбинирует результирующий список пакетов и загружает нужные версии.

Следовательно, если несколько пакетов зависят от одного и того же компонента, то Composer рассчитает подходящую для всех версию пакета и загрузит ее. Поэтому мы в своём приложении можем объявить такую же зависимость от bower-asset/jquery, указав более конкретную версию:

composer require bower-asset/jquery:1.*

и Composer благополучно загрузит первую ветку вместо второй.

На данный момент основа полностью готова. Интерфейс успешно доработан, переведён и добавлена дополнительная кроссбраузерность.

Теперь начнём делать функционал. В следующей части сделаем страницу профиля пользователя:

Просмотр и редактирование профиля

Подписывайтесь на рассылку (форма в сайдбаре), чтобы не пропустить продолжение и получать полезные фишки, и оставляйте свои предложения или замечания в комментариях. Спасибо!

Другие статьи

В прошлой части мы исправляли и переводили интерфейс нашего приложения. Сегодня мы, как анонсировалось ранее, добавим страницу просмотра профиля пользователя и выведем формы для редактирования информации о пользователе и для смены пароля.

Ура! Вы меня уговорили. На улице потеплело и я теперь добрый. Приглашаю всех желающих на бесплатные вебинары по веб-программированию в общем и по разработке на Yii2 Framework в частности. Конечно не такие полезные, как на картинке, но тоже сойдёт :)

Возникла идея с разрешения собеседника записать и выложить небольшую спонтанную консультацию. Первый блин записи со скайпа вышел комом: голоса друг на друга накладываются и по громкости разные. Кто осилит досмотреть до конца, тот герой :)

Ну вот опять! Сел сочинить небольшой комментарий к посту на форуме и разошёлся на целую статью. В общем, публикую здесь. Кстати, заходите на форум (ссылки ниже в статье). Там весело. Пока немного отдохнул после практикума. Оказалось, что двухмесячные курсы сильно выматывают расходами энергии на подготовку и проведение. Пришлось на два месяца забросить другие дела. Теперь пора с новыми силами возвращаться в блог.

Комментарии

 

Жека Ватрушков

Дмитрий, спасибо за качественные интересные уроки. Не думали делать практикум по Yii2 ?

Ответить

 

Дмитрий Елисеев

Может быть. Если надумаю, то сообщу. Подпишитесь на обновления, если ещё не подписаны. Спасибо! :)

Ответить

 

Александр

Спасибо за статью, у вас очень хорошо получается писать. В yii1 был простой и доступный для всех механизм минимификации js и сss, а также объединения этого всего в один файл для заливки на сервер, но во 2-й версии его к сожалению нет. Не могли бы вы посвятить одну из статей именно этому вопросу или хотя бы дать ссылку на адекватные материалы по данной проблеме.

Ответить

 

Евгений

Дмитрий, благадарствую за проделанную работу))

Следующий урок будет по созданию профиля пользователя и привязке к юзеру?

Ответить

 

SSDD

Спасибо, ждем rbac !!!

Ответить

 

Anton

Так про RBAC отлично расписано в официальном HOW-TO, надо только взять и попробовать :)

Ответить

 

SSDD

Ну да скорей всего нужно попробовать, но все равно ждем rbac )

Ответить

 

Дмитрий Елисеев

Добавил пункт про замену версии JQuery.

Ответить

 

SSDD

Дмитрий создайте репозиторий этого проекта в гитхабе

Ответить

 

Дмитрий Елисеев
Ответить

 

Дмитрий Елисеев

UPD: Убрал жёстко вписанные переводы и добавил мультиязычность с Yii::t().

Ответить

 

Etki

This username exists / This username is present in our database / Dear sir, the username you kindly entered appears to be already present in our records

Ответить

 

Etki

А вообще по хорошему нужно не сами имена по-английски писать, а т.н. токены (button.save - хорошо, forms.registration.button.save - еще лучше, forms.registration.submitButton - практически идеально). Любой Home, Add может оказаться используемым в двух разных местах даже в пределах одной секции, а при замене слова придется менять его не только в оригинальном месте, но и во всех переводах (чего не сделаешь через ctrl-r, потому что можно задеть соседние переводы).

Ответить

 

Дмитрий Елисеев

Спасибо! Поменял на идентификаторы.

Ответить

 

KiTE

По поводу i18n стоит добавить что справочники с переводами можно создавать автоматически. Для этого используется консольная команда:

php yii message @app/config/message.php

Она парсит исходный код проекта, и извлекает найденные переводы в справочники. Если появились новые - добавляет, старые и неиспользуемые в коде - помечает. Переводы разносятся в файлы по категориям.

@app/config/message.php - настройки парсера. Может выглядеть следующим образом:

<?php
return [
    'sourcePath' => dirname(__DIR__),
    'languages' => ['ru'],
    'translator' => 'Yii::t',
    'sort' => true,
    'removeUnused' => false,
    'only' => ['*.php'],
    'except' => [
        '.git',
        '.gitignore',
        '.gitkeep',
        '/messages',
        '/vendor',
    ],
    'format' => 'php',
    'messagePath' => dirname(__DIR__) . '/messages',
    'overwrite' => true,
];
Ответить

 

Сергей Рыжков

Здравствуйте, спасибо за ваш труд. А подскажите, как все это дело запилить в модальных окнах?

Ответить

 

des

Вот те на. Уii 2 не рботает в ие ниже 9. пичаль, пичаль. Сильно функционал от даунгрейда страдает? И еще: добавьте как можно поправить шаблон чтобы при создании нового круда текст был русским.

Ответить

 

Игорь

Дмитрий, спасибо за интересную статью!
На сайте фреймворка в документации по Yii2 в разделе Internationalization говорится - "For consistency, all locale IDs used in Yii applications should be canonicalized to the format of ll-CC ..." и в примерах для указания русского языка в качестве "target language" используется соответственно "ru-RU". В статье вы используете краткий формат 'ru' ('language'=>'ru'). Поясните - почему был выбран такой формат и в чем его преимущества.

Ответить

 

Дмитрий Елисеев

Для ru помимо ru-RU имеются и сублокали ru-BY, ru-KG, ru-KZ, ru-MD, ru-UA. Интернационализация в Yii2 методе PhpMessageSource::loadMessages устроена так, что фреймворк ищет переводы и для конкретной локали вроде ru-UA, и для общей ru и склеивает их вместе. Так что вместо глобального ru можете указать конкретный язык ru-RU. При этом переводы из папки ru продолжат работать.

Ответить

 

Дмитрий Елисеев

Добавил примечание про forceTranslation.

Ответить

 

Сергей

Здравствуйте.

Скажите, а есть где-нибудь что-нибудь попроще? Т.е. написано у вас хорошо, но много сленга типа "бандлы". Понятно, что это все через некоторое время любой будет знать, но пока читать трудновато.
В принципе, я бы подрядился перевести бы это на литературный русский, если на то будет интерес. Плюс чуток опечаток у вас - если поправлю, без обид?

Еще два момента:
1. Сюда бы очень вписалось бы еще добавление логотипа.
2. Если уж есть переключение на русский - то можно было бы написать, как подобные переключения делать налету. Что-то типа: "вот иконка флага английского, вот русского - и вот интерфейсы переключаются сразу".

Ответить

 

Дмитрий Елисеев

Попроще не искал, но есть ещё и интерактивное руководство.

В Yii2 ресурсы оформляются через AssetBundle, вот и называю их пока бандлами. Если переведёте, то будет хорошо.

По моментам:

1. Просто надпись меняем на логотип:

NavBar::begin([
    'brandLabel' => Html::img(...),
    ...
]);

2. Можно сделать хранение текущего языка в сессии и переключать по GET-параметру lang. А ссылки переключения выводить в цикле с адресами Url::current(['lang' => $lang]).

Ответить

 

Andrewkha

При работе с композером появляются следующие ошибки:

- большое количество Deprecation Notice: Composer\Package\Version\Versionparser::parseLinks is deprecated. Use....
- дальше идет стадия обновления зависимостей, но все завершается следующей ошибкой

[UnexpectedValueException]
Could not parse version constraint <=2.*:Invalid version string "2.*"

composer self-update делал, не помогает

Ответить

 

Дмитрий Елисеев

Проблему починили. Обновите плагин:

composer global require "fxp/composer-asset-plugin:~1.0.0"
Ответить

 

Александр Захаров

modules/user/models/PasswordResetForm.php на GitHub

public function __construct($token, $config = [])
    {
        if (empty($token) || !is_string($token)) {
            throw new InvalidParamException('Password reset token cannot be blank.');
        }
        $this->_user = User::findByPasswordResetToken($token);
        if (!$this->_user) {
            throw new InvalidParamException('Wrong password reset token.');
        }
        parent::__construct($config);
    }


остался без перевода

Ответить

 

Дима

Спасибо огромное за статью. Очень доходчиво объяснил, как работать с мультиязычностью. Облазил кучу сайтов, но так и ничего не понял. А у тебя всё доходчиво описано.

Ответить

 

Богдан

а как сделать возможность юзеру переключать язык?

Ответить

 

Nikolay Votintsev

Дмитрий, хотелось бы услышать ваше мнение по двум вопросам:

1. Какие вы видите преимущества у хранения сообщений в PHP-файлах против использования Gettext?

2. Не считаете ли вы, что использование идентификаторов вместо строк немного накладно для разработчика (в разрезе трудозатрат)? Да и то, что весь перевод хранится в одном файле не делает проект суше (особенно, при его росте). Так понимаю, идентификаторы используются для того, чтобы избежать перекрывания одинаковых английских слов при различном переводе. Может быть целесообразнее использовать для каждой предметной области свой файл, а в качестве идентификаторов использовать сообщения?

Ответить

 

Дмитрий Елисеев

1. В Yii2 нет нативной работы с Gettext, так что пока никакой разницы.

2. Да, про перекрытие верно. Например, "Update" - это и текст кнопки, и заголовок окна и ещё много чего. Много путаницы. А для роста можно не только по нескольким файлам, но и переводы разных модулей по папкам самих модулей разносить.

Ответить

 

Denis Vodonaev

Дмитрий, подскажите, а как сделать чтобы язык переключался в зависимости от ЧПУ?
Ну например:
site.ru/ru/index - Сайт на русском
site.ru/en/index - Сайт на английском
Заранее спасибо!

Ответить

 

Александр

Добрый день, Дмитрий!
В моем composer.json в секции require я не нашел:

"bower-asset/jquery": "2.1.*@stable | 1.11.*@stable",


И, кстати, в вашем гитхаб-проекте данная строчка добавляется только на коммите b424414.
При этом у меня в директории ./vendor/bower/ есть пакет jquery и на страницах в секции head jquery.js скрипт тоже загружается (в футере). В документации я не нашел описания момента загрузки jquery по-умолчанию (возможно, плохо искал), но я полагаю он загружается как зависимость какого другого скрипта (например, bootstrap js). Могли бы вы чуть прояснить данный момент?

Ответить

 

Дмитрий Елисеев

Этой строкой он вписан в composer.json самого фреймворка. В моём коммите я именно форсирую подключение версии 1.* для поддержки IE8.

Ответить

 

lost

>>"... Но тогда всё станет на другом языке, но будет так же жёстко вписано в код. ..."
Возможно, первое "но" там случайно?

И спасибо за статьи, очень хороший блог, подписался.

Ответить

 

Сергей Беловенцев

Дмитрий вопрос с сообщением на advanced где должна быть папка messages. Своя во frontend своя в backend?

Ответить

 

Дмитрий Елисеев

Если что-то общее, то можно в common, если разное, то отдельно. Как Вам удобнее.

Ответить

 

Сергей

Добрый день, Дмитрий!

Возник такой вопрос, по поводу ресурсов. Может у вас будет такая тема, про использование ресурсов и их подключение!

А вопрос такой, посоветуйте как разделять ресурсы backend и frontend, как их подключать и где лучше хранить?

Т.к. нигде не могу найти "cookbook", могу только лишь предположить, на примере Symfony, хранить ресурсы в модулях в папке Resources либо же Assets, а подключать если что-то глобальное в layout, а локальное во вьюхах. Т.к. не могу понять, как разделять ресурсы с помощью AppAsset.

Ответить

 

Дмитрий Елисеев

Для виджетов в модуле делаете папку assets и рядом делаете MyWidgetAsset вроде как здесь. А для глобальных layouts делаем два AppAsset для frontend и backend.

Ответить

 

Анатолий

Спасибо! Радует ваша уверенность, это помогает. Легко пишете, легко читается.
Не могу понять, есть ли необходимость в поддержке старых браузеров?
С одной стороны очевидно, что затраты ресурсов на поддержку минимальны, с другой стороны ощущается некий балласт прошлого ))

Ответить

 

Иван Зайцев

Здравствуйте, Дмитрий!
Проблема с кодировкой файлов. У всех файлов фреймворка кодировка windows-1251.
и PhpStorm создает новые файлы в этой кодировке. Проблема с отображением кириллических символов. Проблема известная, и есть способы решения, в которых предполагается перенастройка на utf-8. Однако как бы я не перенастраивал кодировку, кириллические символы не отображаются, если сам файл остается в 1251.
Скажите, как Вы решаете эту проблему?
Заранее спасибо!

Ответить

 

Дмитрий Елисеев

> У всех файлов фреймворка кодировка windows-1251.

У меня абсолютно всё в utf-8. И файлы, и PhpStorm. Проблемы нет.

Ответить

 

Иван Зайцев

Вопрос по поводу файлов messages/en, messages/ru: они хранятся в корне проекта, и в них прописаны данные для всех модулей. А можно ли сделать так, чтобы переводы, относящиеся к конкретному модулю хранились в самом модуле. И для каждого модуля иметь свои файлы переводов.
Я подумал что такое решение было бы лучше для обеспечения модульности. Или я ошибаюсь, и все переводы лучше хранить в одном файле независимо от модулей?

Ответить

 

Дмитрий Елисеев

В одной из сделующих частей это есть.

Ответить

 

Игорь

Я так и не разобрался, как менять язык "на лету". То есть как сделать чтобы на основной странице (или в профиле пользователя - не суть важно) было меню с выбором языка и можно было быстро поменять язык во всем приложении?

Ответить

 

Дмитрий Елисеев

Просто присвоить где-нибудь:

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

А так хоть в профиле пользователя Yii::$app->identity->language храните, хоть в cookies.

Ответить

Оставить комментарий

Войти | Завести аккаунт


(никто не увидит)



Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>