Добавляем мультиязычность в Wordpress #3: WP Multilang
Смотрим отличную альтернативу WPGlobus.
В первой статье цикла я показывал и рассказывал про WPGlobus, а также показывал несколько хаков, с которыми он становится более юзабельным.
На момент написания той статьи, Valentyn Riaboshtan, судя по истории коммитов, уже несколько месяцев работал над своим замечательным плагином WP Multilang.
В данной статье, мы посмотрим, чем разработка Валентина получилась лучше, рекомендованного мной ранее, плагина WPGlobus, а также рассмотрим некоторые основы использования.
Традиционно для этого формата, в конце я привожу несколько приёмов и хаков для упрощения интеграции и добавления новых фишечек 😉
На заметку
Если у тебя уже сайт с WPGlobus и он полностью устраивает − нет никакого смысла переходить на WP Multilang!
На данный момент WPGlobus поддерживает работу с Wordpress 5, поэтому единственная киллер фича, ради которой есть смысл переходить − встроенная поддержка разделения отображения записей по языкам.
Особенности WP Multilang
На эту разработку я наткнулся, когда мы искали мультиязычный плагин, совместимый с Wordpress 5 и Gutenberg. На тот момент, WPGlobus не обладал достаточной поддержкой нового редактора, а проект изначально было задумано реализовывать на основе свежайшего релиза WP, чтобы у клиента не возникло в дальнейшем проблем и несовместимостей.
Примечателен этот плагин тем, что помимо интеграции с Gutenberg, он предоставляет встроенную возможность разделять статус отображения поста для разных языков, т.е. если для WPGlobus я городил кучу костылей для поддержки раздельных статусов, то здесь это уже встроенно.
Установка и настройка
Установка плагина − стандартная. Находим плагин WP Multilang в репозитории плагинов Wordpress, устанавливаем.
Обратите внимание!
Если на сайте используются нестандартные типы постов/таксономии, то не активируйте плагин сразу!
Перед активацией необходимо будет создать конфигурационный файл wpm-config.json.
Что желательно сделать перед активацией плагина?
Плагин использует специальный конфигурационный JSON файл, который должен находиться в папке с темой. Если плагин устанавливается в качестве замены WPGlobus и на сайте используются кастомные типы постов/таксономий, сперва создайте в корне темы файл с названием wpm-config.json, с подобным содержимым:
{
"post_types": {
"my_custom_post_type": {}
},
"taxonomies": {
"my_custom_taxonomy": {}
},
"options": {
"theme_mods_my_theme": {
"my_theme_custom_option": {}
}
}
}
В разделе post_types перечисляем названия наших кастомных типов постов, в разделе taxonomies − соответственно, список таксономий, в разделе options − опции темы.
По умолчанию, WP Multilang поддерживает перевод только встроенных типов post и page, и стандартные опции, такие как название сайта или описание.
ВНИМАНИЕ
Здесь и далее подразумевается что мы настраиваем тему с названием my_theme, а для кастомных типов префикс my_custom. Не забудьте заменить эти название на свои!
Внимание!
Обязательно проверьте правильность своего json файла! Если файл не является валидным JSON документом, или собран не в том же формате, как показано выше − у вас есть большой риск завалить Wordpress!
В случае, если вы всё таки уронили свой WP, деактивируйте плагин через FTP − например, переместив папку wp-content/plugins/WP Multilang в другое место.
Если всё сделано правильно, можно активировать плагин.
Базовая настройка
Плагин очень прост в настройке. По факту, там есть всего три набора настроек и утилит. При переходе к странице настроек, в первую очередь, открывается страница General.
Вообще, здесь всё достаточно просто и тривиально.
Язык сайта − соответственно основной язык, используемый сайтом.
Show untranslated − если опция включена, то в случае, если для текущего языка нет перевода строки (wordpress i18n), будет отображаться значение из шаблона переводов. Я эту опцию не рекомендую отключать, если вы как я предпочитаете использовать корректные фразы на английском в качестве шаблонных выражений в pot файлах и не дублируете их в po/mo.
Browser redirect − опция позволяет автоматически редиректить пользователя на нужный язык, исходя из настроек браузера.
Use prefix − использовать ли префикс для основного языка сайта. В этом случае, если у вас, например, основной язык русский, то все ссылки будут предваряться префиксом ru (ru/article/...).
Последняя опция Delete translations позволяет удалить все переводы при удалении плагина. Полезность данной опции для меня лично под вопросом − намного надёжнее будет написать собственный обработчик для выгрузки данных, если вы переходите на системы, где переводы хранятся в отдельных сущностях (я считаю что удаление данных само по себе не является хорошим тоном).
В разделе языки можно настроить каждый язык, в частности, можно указать локаль и код языка, формат даты/времени, а также выбрать флаг.
Название используется в качестве метки для вывода на сайте
Касательно последней вкладки Additional, я к сожалению особо рассказать ничего не могу.
В этом разделе можно удалить набор локализации для неиспользуемых языков. В данный момент в этом нет особого смысла, поскольку плагин поставляется только с русской и английской локализациями.
Вторая утилита позволяет импортировать данные qTranslate. В этом вопросе я не могу ничего подсказать, поскольку никогда не использовал этот плагин для перевода и не знаю как он работает.
Вывод переключателей языков
В WPGlobus нам приходилось пользоваться хаком для вывода языков в виде простого списка. В WPM с этим дела обстоят гораздо лучше.
Традиционно, можно добавить переключатель языков в меню. Для этого WP Multilang предоставляет кастомный пункт меню
Из коробки доступен вывод (Languages menu item type) в виде списка доступных языков − опция inline, вывод только одного языка − single, и вывод в виде выпадающего списка.
Настройка показать позволяет выбрать один из доступных режимов вывода − флаг, название или флаг и название сразу.
Кроме вывода через меню, доступен виджет Переключатель языка. Настройки виджета такие же:
Самой главной киллер-фичей для меня лично, по сравнению с WPGlobus, явилась поддержка разделения постов по языкам. Например, с этим плагином очень легко (*речь о встроенных типах постов, для кастомных типов придётся делать собственные обработки) создавать контент, доступный только для определённой аудитории:
Для меня этот функционал стал, по сути, единственной причиной, по которой я мигрировал с WPGlobus. Ранее я использовал некоторые свои наработки для реализации подобного в глобусе, но они, увы, несовместимы с современным Wordpress с интегрированным Gutenberg.
Хак №1: Собственная вёрстка для списка языков
Можно легко написать собственную функцию, которая позволит вывести список языков в любом виде и с любой вёрсткой. Например, я для проекта написал такой код:
<?php
/*
При вызове выводит список языков с такой вёрсткой
<div class="b-language-selector">
<a href="http://SITE_URL/" class="b-language-selector-link b-language-selector-link--active" data-lang="ru">LANG</a>
<a href="http://SITE_URL/en/" class="b-language-selector-link " data-lang="en">LANG</a>
</div>
*/
function my_site_custom_languages_selector_template () {
if (function_exists('wpm_get_languages')) {
$languages = wpm_get_languages();
$current = wpm_get_language();
$out = '<div class="b-language-selector">';
foreach ($languages as $code => $language) {
$toggle_url = esc_url(wpm_translate_current_url($code));
$css_classes = 'b-language-selector-link ';
if ($code === $current) {
$css_classes .= 'b-language-selector-link--active';
}
$out .= '<a href="' . $toggle_url . '" class="' . $css_classes . '" data-lang="' . esc_attr($code) . '">';
$out .= $language['name'];
$out .= '</a>';
$out .= ' ';
}
$out .= '</div>';
return $out;
}
}
Соответственно для программного вывода нужно использовать всего три функции:
- wpm_get_languages - возвращает массив всех доступных языков
- wpm_get_language - текущий язык сайта
- wpm_translate_current_url - возвращает ссылку, адаптированную под нужный язык
Хак №2: Миграция с WPGlobus
По идее, плагин может работать с синтаксисом, подобным оному в WPGlobus. Лично я решил переделать свою базу данных под нативный формат WPM.
Если кратко − WPGlobus использует формат {:ru}...{:}{:en}...{:}, т.е. символ {:} является разделителем переводов. В то же время, судя по исходникам, для WPM символ {:} является исключительно символом-терминатором (т.е. парсер рассчитывает, что после него ничего нет)
Судя по всему, формат WPGlobus, в принципе, работает, но я решил не рисковать (кто его знает, как плагин обработает обновление статьи с этими разделителями).
Хак №3: Интеграция с Pods
Не секрет, что Pods является лучшим плагином для создания кастомных типов постов/таксономий. Встроенный функционал позволяет нам делать многое прямо через админку, однако, не все встроенные функции Pods поддерживают работу с WPM.
В частности, если вы используете виджеты для вывода списка элементов Pods, то столкнётесь с такой проблемой, как отсутствие разграничения по языку.
Например, я создал pods тип для отзывов, и 6 отзывов на русском языке, выставив соответствующее ограничение для вывода:
Отзывы выводятся на странице таким шорткодом:
Кажется, всё окей, но есть одна проблема.
Если мы зайдём на страницу от лица англоговорящего пользователя, то мы всё равно увидим отзывы на русском языке, хоть ранее и ограничили их вывод в админке.
Чтобы научить Pods шорткоды дружить с WP Multilang, нужно определить фильтр, в котором мы проапгрейдим запрос к базе данных таким образом, чтобы учитывалась эта настройка.
<?php
/*
WPLang pods shortcode output integration
*/
function my_theme_pods_shortcode_findrecords_params ($params) {
$lang = get_query_var( 'lang' );
if ( ! $lang ) {
$lang = wpm_get_user_language();
}
if (isset($params['where'])) {
$params['where'] .= ' AND ';
}
else {
$params['where'] = '';
}
$params['where'] .= '(_languages.meta_value IS NULL OR _languages.meta_value LIKE "%' . esc_sql(serialize($lang)) . '%")';
return $params;
}
add_filter('pods_shortcode_findrecords_params', 'my_theme_pods_shortcode_findrecords_params');
Здесь мы получаем текущий язык пользователя с помощью функции get_query_var и модифицируем условия в SQL запросе.
Настройка, по которой определяется, для каких языков нужно выводить запись, находится в meta поле _languages, и содержит сериализованный массив выбранных языков. Мы фильтруем записи с помощью оператора LIKE и подстановки текущего языка в сериализованном виде.
Таким образом, Pods получает на входе корректно отфильтрованные данные.
Пограничный случай с NULL, означает что пост вообще не имеет никаких языковых настроек, поэтому такие записи отображаем в любом случае.