Настройка мультиязычности в MODX Revo (Контексты + Babel)

Подключить мультиязычность на MODX Revolution не сложно, но нужен четкий план действий. Для лучшего понимания рассмотрим процедуру подключения на примере.

Допустим мы хотим подключить два дополнительных языка для сайте, то есть мы хотим получить следующий результат:

  • Русская версия сайта - http://site.ru/
  • Английская версия - http://site.ru/en/

Навигация по статье:


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

Для реализации данной задачи нам понадобится:

  • MODX Revolution (2.0 и старше), само собой
  • Доступ до .htaccess (или ht.access)
  • Плагин Babel (2.2 и старше)

Настройка .htaccess

Включите дружественные URL в "Системных настройках" MODX.

В корне сайта должен быть файл .htaccess (иногда файлы скрыты от просмотра, см. в настройках вашего FTP-клиента) или ht.access (его переименовываем в .htaccess).

В файле .htaccess проверяем правильность 'RewriteBase' и включен ли modRewrite ('RewriteEngine On').

ВАЖНО! .htaccess - файл дополнительной конфигурации веб-сервера Apache, если у вас другой сервер и без Apache конфигураций, то директивы в .htaccess у вас работать не будут. Но практически все современные сервера используют связку Apache + NGINX, т. е. работать .htaccess в скорее всего будет. Работает ли modRewrite можно проверить путем добавления обычных редиректов в .htaccess или путем настройки ЧПУ-ссылок (если они работают, то .htaccess активен).

Если сайт установлен в корневой каталог используемого домена, то в .htaccess пишем:

RewriteEngine On
RewriteBase /

Если сайт установлен не в корневой каталог, то пишем:

RewriteEngine On
RewriteBase /каталог/

Далее добавляем в наш .htaccess после наших редиректов, в самом конце, следующее (см. изображение ниже):

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(ru|en)/favicon.ico$ favicon.ico [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(ru|en)/assets(.*)$ assets$2 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(ru|en)?/?(.*)$ index.php?cultureKey=$1&q=$2 [L,QSA]

Где (ru|en) должны совпадать с: ru для корневой директории http://site.ru/ , а en - с http://site.ru/en/ (прописываются в настройках контекста ниже).

ВАЖНО! Директивы '# The Friendly URLs part' УДАЛЯЕМ или комментируем через # (по умолчанию включены), получится так (см. изображение ниже):

# The Friendly URLs part
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]

Настройка контекстов

В админке MODX нам нужно создать контекст для нового языка. Для этого перейдите по: "Система" -> "Контексты" и нажимаем "Создать новый".

Далее в качестве "Ключа контекста" вводим, например, для английского - "eng".

ВАЖНО! Эти ключи в дальнейшем будут использованные в плагине переключения контекстов.

Далее правый клик мыши по новому контексту и выбираем "Редактировать". Переходим по табу "Настройки контекста" и нажимаем "Создать новый".

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

Существующий контекст (ключ "web"): Новый контекст (ключ "eng"):

Base URL

  • Ключ: base_url (!именно base_url, MODX предлагает не тот ключ)
  • Имя: Base URL
  • Запись словаря для раздела: language
  • Значение: /

Base URL

  • Ключ: base_url
  • Имя: Base URL
  • Запись словаря для раздела: language
  • Значение: /en/

Culture key ([[++cultureKey]]).
Не путать с "Ключом контекста" ([[*context_key]])

  • Ключ: cultureKey
  • Имя: Culture key
  • Запись словаря для раздела: language
  • Значение: ru

Culture key

  • Ключ: cultureKey
  • Имя: Culture key
  • Запись словаря для раздела: language
  • Значение: en

Site start

  • Ключ: site_start
  • Имя: Site start
  • Запись словаря для раздела: language
  • Значение: 1

Site start

  • Ключ: site_start
  • Имя: Site start
  • Запись словаря для раздела: language
  • Значение: id страницы для Главной в английской версии.
    Все id создаем через Babel, об этом ниже.

Site name

  • Ключ: site_name
  • Имя: Site name
  • Запись словаря для раздела: language
  • Значение: Название сайта

Site name

  • Ключ: site_name
  • Имя: Site name
  • Запись словаря для раздела: language
  • Значение: Название сайта на английском языке

Site URL

  • Ключ: site_url
  • Имя: Site URL
  • Запись словаря для раздела: language
  • Значение: / (или http://site.ru/)

Site URL

  • Ключ: site_url
  • Имя: Site URL
  • Запись словаря для раздела: language
  • Значение: /en/ (или http://site.ru/en/)

Error page

  • Ключ: error_page
  • Имя: 404 page
  • Запись словаря для раздела: language
  • Значение: id страницы для 404 ошибки

Error page

  • Ключ: error_page
  • Имя: 404 page
  • Запись словаря для раздела: language
  • Значение: id страницы для 404 ошибки в английской версии.
    Все id создаем через Babel, об этом ниже.

Locale, используется для форматирования даты в текстовом виде

  • Ключ: locale
  • Имя: Setting locale
  • Запись словаря для раздела: language
  • Значение: ru_RU.UTF8

Locale

  • Ключ: locale
  • Имя: Setting locale
  • Запись словаря для раздела: language
  • Значение: en_US.UTF8

Для контекста "web" все настройки уже есть в "Системных настройках", но при этом для каждого контекста их можно переопределить.

Создание плагина "switchContext"

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

В панели управления переходим по табу "Элементы" и нажимаем на иконку "Новый плагин".

В поле имя пишем switchContext и помещаем следующий код:

<?php
/* Запускаем плагин только на фронтенде и с включенными sef-url */
if ($modx->context->key == 'mgr' || !$modx->getOption('friendly_urls') || $modx->event->name != 'OnHandleRequest') {
return;
}

/* Определяем текущий язык в cultureKey */
switch ($_REQUEST['cultureKey']) {
/* Переключаем контекст */
case 'en':
$modx->switchContext('eng');
break;

/* Добавляем дополнительные языки в плагин, если нужно
case 'de':
$modx->switchContext('dtsch');
break;
*/

/* Устанавливаем контекст по умолчанию */
default:
$modx->switchContext('web');
break;
}

/* Очищаем GET-параметр чтобы не допустить появлении ссылки вида cultureKey=xy при генерации URL других компонентов */
unset($_GET['cultureKey']);
?>

Далее переходим по вкладке "Системные события", включаем OnHandleRequest и сохраняем.

Установка Babel

Устанавливаем пакет Babel, в панели управления переходим по "Система" -> "Управление пакетами" и нажимаем "Загрузить дополнения" и находим там Babel, загружаем и устанавливаем пакет.

Проходим все необходимые условия установки пока не увидите "Настройки установки". Здесь уже должны быть контексты которые мы создали (по примеру это: web, eng; если поле пустое, то можно вписать ключи контекстов руками), другие настройки оставляем без изменений.

Создание мультиязычного контента

Как добавлять страницы сайта на других языках?

В панели управления сайта, во вкладке "Ресурсы", мы видим наши контексты (выводятся имена контекстов, которые вы заполнили при создании/редактировании контекста). Ресурсы каждого из контекстов отвечают за ресурсы языковых версий сайта.

Но как нам знать какой ресурс за какую страницу отвечает, т.е. как же связать страницы разных языков между собой?

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

Полученные id ресурсов и используем в настройках контекстов для страниц Главная и 404 (см. таблицу с настройками выше).

Переключатель языков на сайте.

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

<ul>
[[BabelLinks?
&showCurrent=`1`
]]
</ul>

Все параметры BabelLinks можно посмотреть тут - https://docs.modx.com/extras/revo/babel/babel.babellinks.

Очень редко бывает, что BabelLinks не выводит связанные страницы, хотя все верно настроено и опубликовано. Для этого проверьте создан ли TV связи ссылок - babelLanguageLinks, если TV нет, то создайте его с именем babelLanguageLinks (и названием Babel Translation Links) и укажите имя TV для Babel в "Системных настройках", а так же проверьте указаны ли ключи контекстов в тех же настройках. Или попробуйте переустановить Babel.

Вывод статического содержимого в зависимости от языка

Существует несколько методов вывода разного статического содержания (заголовки, адреса и копирайт в футере, кнопки и пр.) в зависимости от языка:

1. Для текстовых строк самый правильный вариант - это создавать переводы в лексиконах для каждого языка. И эти лексиконы вызывать в шаблоне. Переводы лексиконов и будут выводиться в зависимости от нужной языковой версии.

2. Для, к примеру, чанков можно создать для каждого копии с нужным языком, например, [[$footer_ru]], [[$footer_en]], а в шаблонах вызывать в виде [[$footer_[[++cultureKey]]]] (при генерации страницы MODX подставит нужное значение [[++cultureKey]] и выведет содержание чанка для активного языка).

3. Ну и можно проверять контекст и выводить нужное содержимое (как лексиконы, так и прочее) через фильтры ввода-вывода.

Условие вывода лучше прописывать именно через [[*context_key]], а, к примеру, не через [[++cultureKey]] т. к. Ключ контекста заполняется один раз и изменить его нельзя, а настройки же контекста можно менять. Например, условие будет таким:

[[*context_key:is=`web`:then=`г. Москва, ул. Льва Толстого, д. 1`:else=`Moscow, Leo Tolstoy st. 1`]]

С другой стороны, с использованием [[++cultureKey]], условие будет грамотнее читаться:

[[++cultureKey:is=`ru`:then=`г. Москва, ул. Льва Толстого, д. 1`:else=`Moscow, Leo Tolstoy st. 1`]]

В общем, выбор, как всегда, за вами :)

Автоматически определяем id переведенных страниц

Часто в статических элементах сайта прописаны ссылки на ресурсы обычной версии, и, соответственно, нужно прописать и id другой языковой версии сайта, но как их узнать, не выискивая перевода каждой страницы сайта (а если каталог огромный, то можно засеть на долгое время)?

В этом нам поможет сниппет BabelTranslation. Подробнее можно посмотреть тут - BabelTranslation. BabelTranslation выводит связанный id переведенной страницы нужного контекста:

[[BabelTranslation? &resourceId=`6` &contextKey=`eng`]]

Выведет в шаблоне связанный id в контексте eng для страницы с id=6 активного контекста (в нашем случае web).

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

[[*context_key:is=`web`:then=`6`:else=`[[BabelTranslation:default=`6`? &resourceId=`6` &contextKey=`eng`]]`]]

Тут мы выводим id=6, если контекст web и выводим связанный id для контекста eng через BabelTranslation, если же связанного id нет, то выведет id=6 (исходя из :default).

Конструкцию можно сделать еще проще, для этого всю конструкцию сохраняем в чанк [[$eng_id]] и задаем параметр [[+ru_id]] в чанке (чанк и параметр можно назвать как угодно). Код чанка:

[[*context_key:is=`web`:then=`[[+ru_id]]`:else=`[[BabelTranslation:default=`[[+ru_id]]`? &resourceId=`[[+ru_id]]` &contextKey=`eng`]]`]]

А так, например, выводим id для ссылки через наш чанк с параметром (если не найдет переведенную страницу, откроется исходная страница):

<a href="[[~[[$eng_id?ru_id=`6`]]]]">Ссылка на страницу</a>

Источники файлов в TV для нескольких контекстов

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

Символические ссылки для контекстов

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

По умолчанию между контекстами нельзя ставить символические ссылки, чтобы это включить - переходим в админке сайта "Системные настройки" выбираем в фильтре "Система и сервер" и ставим у "Разрешить перенаправление через контексты" - "Да"

Вот, собственно, и все, да здравствует мультиязычность в MODX Revolution :)




Комментарии к посту
  • Kalwin Kalwin 10 марта 2023, 13:32 #
    Сделал всё по вашему мануалу и столкнулся все равно со старой проблемой. Страница недоступна. Сайт выполнил переадресацию слишком много раз. Удалите файлы cookie. ERR_TOO_MANY_REDIRECTS
    • Руслан Алеев Руслан Алеев 28 апреля 2023, 15:36 #
      Видимо, что-то не то с .htaccess, перепроверьте.
    • Руслан Руслан 01 марта 2023, 10:40 #
      добрый день такой вопрос для вывода через getResources мы указываем id родителя но как быть в случае с мультиязычностью? указывать родителя в админке у каждого рессурса?
      • Руслан Алеев Руслан Алеев 28 апреля 2023, 16:24 (Комментарий был изменён) #
        Здравствуйте! Можно не указывать parents в getResources и тогда будет браться текущего.
        Если же в getResources нужно вызывать родителя не текущего ресурса, то можно или указать вручную, да, или чуть автоматизировать, см. блок cat-art.ru/blog/multiyazyichnost-modx-revo#block8
      • Андрей Андрей 15 июня 2022, 13:43 #
        Первый опыт работы с MODX. Сделал все по инструкции. После перевода, менял Псевдонимы у переведенных Ресурсов (для красоты Permalinks). На данный момент ни одна из переведенных страниц не открывается, везде выдает 404 Page not found. Друзья, пожалуйста отзовитесь и помогите. Проект лежит мертвым грузом, начальство просит срочно разобраться.
        • Евгений Евгений 20 августа 2021, 21:20 #
          спасибо за статью, в трех разных случаях меня выручала
          • Серый Серый 16 марта 2021, 17:10 #
            А как можно сделать так, что бы BabelTranslation передавал нам не один ID, а список? Например, мы в него список через запятую, она нам обратно тот же список через запятую, но ID переведенных страниц. Спасибо!
            • Максим Максим 26 февраля 2020, 18:45 #
              Физкультпривет! Пост отличный. Сделал пару мультиязычных сайтов точно следуя инструкциям. Все работает с двуязычными сайтами. Но вот делаю на три языка и уперся. У меня английский по умолчанию + немецкий и японский. Во-первых, страница index внутри каждого контента перебрасывает на главную (по умолчанию). То есть mysite.com/de/ перекидывает на mysite.com. Внутренние страницы переключаются между /de/bio.html, /jp/bio.html и просто bio.html — но контент показывается тот, что по умолчанию — то есть mysite.com/bio.html. И последнее — в переключалке языков не показывается слово для Японского языка «Japanese» (как в панели Manager, где мы делаем перевод страницы, так и в менюшке BabelLinks? &showCurrent=1). То есть в меню выводится English | German | 'пусто' (или в коде )… а откуда берутся названия языков? Спасибо заранее. Буду очень признателен за помощь. Мне еще делать похожее с китайской версией.
              • User User 23 декабря 2019, 14:59 #
                Добрый день. У меня проблема возникает с редиректами. После выполненных выше инструкций — привязки страницы к англоязычной версии и попытке перейти на нее — получаю ошибку ERR_TOO_MANY_REDIRECTS Что это может быть? htaccess чистый.
                • Nikolay Nikolay 12 декабря 2019, 05:28 #
                  Здравствуйте, спасибо за качественную статью, прошу помощи с «Автоматически определяем id переведенных страниц» Создаю чанк с именем eng_id и содержимым *context_key:is=web:then=+ru_id:else=BabelTranslation:default=+ru_id? &resourceId=+ru_id &contextKey=eng в этом же чанке захожу в параметры и создаю параметр ru_id пустой, или я что то недопонимаю? После прописываю ссылку Ссылка на страницу По логике исходя из того на какой языковой версии сайта я нахожусь он мне выдаст нужную страницу с переводом, но этого не происходит…
                  • Ирина Ирина 03 октября 2019, 13:39 #
                    Это просто нереально крутой текст! я смогла настроить мультиязычность! я мучалась несколько лет, бросала MODX на долго, и теперь с вашей помощью все вышло!
                    • Евгений Евгений 18 сентября 2019, 19:01 #
                      Все сдела лпо инструкции, выдает ошибку: ERR_TOO_MANY_REDIRECTS Циклическое перенаправление на странице Кэш браузера чистил, хтаксес вообще очистиль от всего лишнего на всякий случай: joxi.ru/12M3JyYslBW4nr
                      • Вячеслав Вячеслав 27 марта 2019, 08:52 #
                        Спасибо за хороший мануал! а вы ставили уже на MODX Revolution 2.7.0-pl advanced — babel? Core вынесен за пределы — публичной папки — папки manager и connectors переименованы ( ошибок при установки babel не было ) (PHP 7.2 Beget + https ) Делал все по этому мануалу — основной контекст норм работает — а вот дополнительный нет (при удаленных The Friendly URLs part)- 404 ошибку показывает на всех его стр — без шаблона — добавляю The Friendly URLs part — стр так же 404 но не без шаблона так же babel не выводит ­BabelLinks? &showCurrent=1 пустая ul в итоге Контексты — основной ключ web это англ версия сайта и доп это русский яз ключ ru .htaccess RewriteEngine On RewriteBase / # Prevent dot directories (hidden directories like .git) to be exposed to the public # Except for the .well-known directory used by LetsEncrypt a.o RewriteRule "/\.|^\.(?!well-known/)" — F # Rewrite www.domain.com -> domain.com — used with SEO Strict URLs plugin #Редирект С www на БЕЗ www RewriteCond %HTTP_HOST. RewriteCond %HTTP_HOST ^www.(.*)$ NC RewriteRule ^(.*)$ %1/$1 R=301,L # Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent # www.domain.com when your cert only allows secure.domain.com #RewriteCond %SERVER_PORT !^443 #RewriteRule (.*) example-domain-please-change.com/$1 R=301,L # The Friendly URLs part # RewriteCond %REQUEST_FILENAME !-f # RewriteCond %REQUEST_FILENAME !-d # RewriteRule ^(.*)$ index.php?q=$1 L,QSA #для языка RewriteCond %REQUEST_FILENAME !-d RewriteCond %REQUEST_FILENAME !-f RewriteRule ^(en|ru)/favicon.ico$ favicon.ico L,QSA RewriteCond %REQUEST_FILENAME !-d RewriteCond %REQUEST_FILENAME !-f RewriteRule ^(en|ru)/assets(.*)$ assets$2 L,QSA RewriteCond %REQUEST_FILENAME !-f RewriteCond %REQUEST_FILENAME !-d RewriteRule ^(ru|en)?/?(.*)$ index.php?cultureKey=$1&q=$2 L,QSA