Добавляем мультиязычность в 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.

 

Основные настройки WP Multilang

 

Вообще, здесь всё достаточно просто и тривиально. 

 

Язык сайта − соответственно основной язык, используемый сайтом.

 

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>&nbsp;
  <a href="http://SITE_URL/en/" class="b-language-selector-link " data-lang="en">LANG</a>&nbsp;
</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 .= '&nbsp;';
    }

    $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, означает что пост вообще не имеет никаких языковых настроек, поэтому такие записи отображаем в любом случае.