Статьи и новости

Контент на нескольких языках в Yii2. Словарь с переводом на русский и английский языки. Часть 1

Часть 1
Часть 2
Часть 3

Одним из наиболее удачных решений для создания мультиязычного контента в Yii2 для меня стало поведение yii2-multilingual-behavior от автора OmgDef. Предлагаю ознакомиться с примером реализации.

TODO: когда-то, возможно, тут появится кусок реализации переключателя языка. В сети есть.

Вводные данные

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

Прежде чем приступить к реализации, собственно, словаря, нам необходимо дать админу сайта возможность указывать для слова часть речи (существительное, прилагательное, глагол и т.д.). К слову, части речи также необходимо переводить на язык пользователя. Займемся этим.

Parts of speech

Приступая к реализации, установим расширение Yii2 multilingual behavior через Composer:

composer require --prefer-dist omgdef/yii2-multilingual-behavior

Миграции

Подготовим миграции. В основной таблице у нас будет всего два поля — id, на который в дальнейшем будут ссылаться слова из нашего словаря, и name — поле, по которому будет ориентироваться админ сайта.

public function safeUp()
    {
        /* Добавим этот кусок, чтобы точно быть увереными, что наша таблица будет нормально кушать юникод*/
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
        }
        
        /* Создание таблицы с нужными полями */
        $this->createTable('parts_of_speech', [
            'id' => $this->primaryKey(),
            'name' => $this->string(32),
        ], $tableOptions);
    }

Применим миграцию, а затем создадим еще одну — для транслитерации наших данных. Пример ниже — переработка кода из документации расширения.

public function safeUp()
    {
        $sql = "CREATE TABLE IF NOT EXISTS `parts_of_speech_translate` (
            `id` int(11) NOT NULL AUTO_INCREMENT,
            `parts_of_speech_id` int(11) NOT NULL, // ID части речи, на который будет ссылаться данная запись
            `language` varchar(6) NOT NULL, // Код языка
            `title` varchar(255) NOT NULL, // Первое поле, которое мы будем показывать пользователям как название части речи на родном для них языке
            `content` TEXT NOT NULL, // Возможно, в дальнейшем потребуется давать пользователям какую-то информацию о части речи. Попробуем это предусмотреть и создадим заготовку описания
            PRIMARY KEY (`id`),
            KEY `parts_of_speech_id` (`parts_of_speech_id`),
            KEY `language` (`language`)
        ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

        ALTER TABLE `parts_of_speech_translate` // Добавим внешний ключ для связности данных, укажем к какой таблице
            ADD CONSTRAINT `parts_of_speech_translate_ibfk_1` // какой ключ добавить
            FOREIGN KEY (`parts_of_speech_id`) // укажем имя поля в текущей таблице
            REFERENCES `parts_of_speech` (`id`) // с какой таблицей и каким полем связать
            ON DELETE CASCADE ON UPDATE CASCADE;"; // ну и как связывать

        $this->execute($sql); // затем выполним всю эту конструкцию, раз уж мы поленились перевести все это в ООП
    }

Модель

Создадим модель PartsOfSpeech при помощи GII. Поскольку она будет использоваться как в админке, так и на фронтэнде, положим ее в common/models.

Настроим модель согласно документации Yii2 multilingual behavior. Добавим в созданную модель следующий код:

#class PartsOfSpeech

public function behaviors()
{
    return [
        'ml' => [
            'class' => MultilingualBehavior::className(),
            'languages' => [
                'ru' => 'Russian', // По факту, сервис разрабатывается как раз для русскоязычной и англоязычной аудитории
                'en-US' => 'English',
            ],
            /* Закомментированные строки ниже можно удалить */

            //'languageField' => 'language', // Т.к. мы не меняли поле языка (не нужно в большинстве случаев), это значение не нужно переопределять
            //'localizedPrefix' => '', // Префикс не используем
            //'requireTranslations' => false', // Не будем делать перевод обязательным
            //'dynamicLangClass' => true', // Надо почитать, что это такое =)
            //'langClassName' => PostLang::className(), // Не переопределяем основной класс
            'defaultLanguage' => 'ru', // Укажем основной язык приложения
            'tableName' => "{{%parts_of_speech_translate}}", // В какой таблице лежат наши переводы
            'langForeignKey' => 'parts_of_speech_id', // Укажем, какое поле в таблице parts_of_speech_translate отвечает за связь с основной таблицей, в модели которой мы сейчас находимся
            'attributes' => [
                'title', 'content', // Передаем массив переводимых полей
            ]
        ],
    ];
}

Также переопределим метод поиска:

#class PartsOfSpeech
public static function find()
{
    return new MultilingualQuery(get_called_class());
}

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

#class PartsOfSpeech
use omgdef\multilingual\MultilingualTrait;
use omgdef\multilingual\MultilingualQuery;
use omgdef\multilingual\MultilingualBehavior;

Делаем CRUD

Вот за что я люблю Yii2, так это за его способность быстро написать за тебя сотню-другую строк кода. Сгенерим CRUD для нашей модельки.

И перейдем по ссылке http://admin.<domain>/parts-of-speech.

Сейчас, используя экшн create, мы видим только одно поле — Name, которое есть в нашей основной таблице (ID генератор фреймворка разумно скрыл). Чтобы добавлять также и переводы, отредактируем форму /backend/views/parts-of-speech/_form.php

Добавим поля для сохранения переводов в вид:

<div class="row">
    <div class="col-sm-6">
        <h3>Перевод на русский</h3>

        <?= $form->field($model, 'title')->textInput(['maxlength' => 255]) ?>

        <?= $form->field($model, 'content')->textarea() ?>

    </div>

    <div class="col-sm-6">
        <h3>English version</h3>

         <?= $form->field($model, 'title_en')->textInput(['maxlength' => 255]) ?>

         <?= $form->field($model, 'content_en')->textarea() ?>

    </div>
</div>

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

И она уже полноценно добавляет все нужные данные прямо в нашу базу. Почти.

Не забываем, что встроенный валидатор Yii2 не пропускает неизвестные данные в нашу модель, и — значит — в БД они не оказываются. Поэтому добавим валидатор safe (конечно, в продакшн так выпускать не стоит, но для примера — вполне подойдет, да и будет тема для еще одного поста).

public function rules()
{
    return [
       [['name'], 'string', 'max' => 32],
       [['title', 'title_en', 'content', 'content_en'], 'safe'],
    ];
}

А вот теперь все пишется в БД.

А вот выводится при редактировании — не все.

Чтобы корректно редактировать мультиязычные модели, в контроллере нужно изменить метод findModel($id).

protected function findModel($id)
{
    if (($model = PartsOfSpeech::find()->where(['id' => $id])->multilingual()->one()) !== null) {
        return $model;
    }

    throw new NotFoundHttpException('The requested page does not exist.');
}

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

<?php echo $model->title ?>

В данной переменной будет оказываться контент уже на языке пользователя. Но если вам по каким-то причинам надо вывести контент с конкретной локалью, можно воспользоваться нехитрым вызовом, который проигнорирует настройки языка пользователя:

<?php echo $model->title_en ?>

Разные мелочи

Описывать перевод CRUDа и т.д. тут я не вижу смысла — справитесь. А вот добавить на страницу просмотра записи недостающие поля — пожалуй, стоит. Впрочем, тут все еще проще:

<?= DetailView::widget([
    'model' => $model,
    'attributes' => [
        'id',
        'name',
        /* Добавим отсутствующие поля */
        'title',
        'content',
        'title_en',
        'content_en'
    ],
]) ?>

На этом первую часть реализации словаря считаю законченной.

Обсуждение: 2 комментария
  1. Юрий:

    А можно ссылку на готовый проект, пожалуйста?

    Ответить
    1. coderiter:

      Проект приватный, репозитория не будет.

      Ответить

Ваш комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *