Переключение контекстов мультиязычного сайта

Написал 2 инструкции для простого переключения контекстов в классической ситуации нескольких языковых версий.

Директории

Плагин для ситуации, когда домен один, а языковые версии отличаются префиксом в адресе: mysite.com/ru/page.html и mysite.com/en/page.html.
То есть, условия такие: - Все контексты на одном домене, и у них отличается base_url
  • Используются friendly urls
  • base_url может не совпадать с именем контекста
Идея заключается в выборке всех контекстов и сравнения их базового адреса с запросом. Создавать директории и копировать index.php не нужно.
Итак, у нас два контекста: web и en c такими настройками:
Создаём новый плагин, добавляем для него событие OnHandleRequest и копируем этот код:
<?php
// Работаем только на фронтенде и только с friendly urls
if ($modx->event->name != 'OnHandleRequest' || $modx->context->key == 'mgr' || !$modx->getOption('friendly_urls')) {return;}

// Получаем запрашиваемый url
$alias = $modx->getOption('request_param_alias', null, 'alias', true);
$request = &$_REQUEST[$alias];

// Выбираем контексты с настройкой base_url
$q = $modx->newQuery('modContextSetting', array('key' => 'base_url', 'value:!=' => ''));
$q->select('context_key,value');

$contexts = array();
$tstart = microtime(true);
if ($q->prepare() && $q->stmt->execute()) {
    // Учитываем наш запрос в БД
    $modx->queryTime += microtime(true) - $tstart;
    $modx->executedQueries++;
    // Разбираем результаты
    while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
        $base_url = trim($row['value'], '/');
        $context = $row['context_key'];
        // Если запрос начинается с base_url какого-то контекста
        if (preg_match('/^('.$base_url.')\//i', $request)) {
            // То переключаемся на этот контекст
            // Web инициализируется в index.php - на него переключаться не нужно
            if ($context != 'web') {
                $modx->switchContext($context);
            }
            // Вырезаем base_url из запроса, чтобы MODX нашел ресурс по uri
            $request = preg_replace('/^'.$base_url.'\//', '', $request);
            // Дело сделано - выходим из цикла
            break;
        }
    }
}
Кстати говоря, еще можно у основного контекста web убрать base_url, чтобы он открывался без /ru/, а второй контекст был по-прежнему с /en/. Например, вот сайт документации - русская версия и английская.

Поддомены, или разные домены

При конфигурации поддоменами адреса у нас будут такие: ru.mysite.com/page.html и en.mysite.com/page.html.
Этот случай проще, так как нужно только сравнить запрашиваемый хост со значениями в БД и получить соответствующий контекст. Здесь не нужно менять запрос и результат выборки может быть только один.
Настройки контекстов такие:
Точно также создаём плагин, и отмечаем событие OnHandleRequest.
<?php
// Работаем только на фронтенде
if ($modx->event->name != 'OnHandleRequest' || $modx->context->key == 'mgr') {return;}

// Определяем запрашиваемый хост
$host = $_SERVER['HTTP_HOST'];

// Выбираем контекст с настройкой base_url
$q = $modx->newQuery('modContextSetting', array('key' => 'http_host', 'value' => $host));
$q->select('context_key');

$tstart = microtime(true);
if ($q->prepare() && $q->stmt->execute()) {
    // Учитываем наш запрос в БД
    $modx->queryTime += microtime(true) - $tstart;
    $modx->executedQueries++;
    // Получаем ключ контекста
    if ($context = $q->stmt->fetch(PDO::FETCH_COLUMN)) {
        // Web инициализируется в index.php - на него переключаться не нужно
        if ($context != 'web') {
            $modx->switchContext($context);
        }
    }
}
Как вы понимаете, в настройках контекстов должны быть уникальные http_host.

Заключение

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

Обновлено 03.11.2014г.

Пример сниппета для вывода ссылок на документ в другом контексте для первого случая - директорий.
<?php
$tplRu = '<a href="[[+link]]">ru</a>';
$tplEn = '<a href="[[+link]]">en</a>';
$tplRuActive = '<span>ru</span>';
$tplEnActive = '<span>en</span>';

$output = '';
if ($modx->context->key == 'web') {
    $output .= $tplRuActive . ' | ' . $tplEn;
    $link = 'en/';
    if ($modx->getOption('site_start') != $modx->resource->id && $modx->getCount('modResource', array('uri' => $modx->resource->uri, 'context_key' => 'en'))) {
        $link .= $modx->resource->uri;
    }
}
else {
    $output .= $tplRu . ' | ' . $tplEnActive;
    $link = '/';
    if ($modx->getOption('site_start') != $modx->resource->id && $modx->getCount('modResource', array('uri' => $modx->resource->uri, 'context_key' => 'web'))) {
        $link .= $modx->resource->uri;
    }
}

return str_replace('[[+link]]', $link, $output);
Здесь web - русский контекст, а en - английский. Обязательное условие - одинаковые uri ресурсов в разных контекстах. Если нет соответствия, то ссылка будет в корень контекста.

77 комментариев

Виталий Валерьевич
Полезно, благодарствую!
Сергей Шлоков
У меня контексты через субдомены настроены. Нет такого же красивого плагина?
Василий Наумкин
С поддоменами всё проще, дописал.
Сергей Шлоков
Спасибо. Поменял свой шедевр на твоё творение :)
Николай
Еще не опробовал, но уже крайне благодарен за труды. Несколько месяцев назад я пытался победить мультиязычность.. Это боль. Я так и не смог сделать нормальным переключением несколько языков. Не в какую не хотел работать тот простенький плагин..
Ни в какую не переключает на русский язык
Настройки контекстов: base_url: / * base_url: /ru/ cultureKey: en * cultureKey: ru site_start: 1 * site_start: 4 site_url: http://mysite.com/ * site_url: http://mysite.com/ru/
В логе ошибка: [2014-01-11 13:19:01] (ERROR @ /index.php) Could not load context: ru
Чикин Артур
У тебя нету контекста ru.
Есть два контекста - английский по дефолту web и русский ru. Ресурсы есть в обоих контекстах. Плагин использую babel
p.s. влияют ли как-то на это настройки кэширования?
Чикин Артур
У тебя контекст отвечающий за русский язык как называется?
RUSSIAN, ключ ru
Поправил .htaccess, язык меняется Но ошибка в логе осталась
Также в основной версии (web - английский) сниппет babel постоянно слетает и не показывает русский язык Чанк [[%babel.language_[[+cultureKey]]? &topic=default &namespace=babel]]
Т.е. получается, что контекст ru каким-то образом не распознается
Чикин Артур
Смотри суда:

// Если запрос начинается с base_url какого-то контекста
        if (preg_match('/^('.$base_url.')\//i', $request)) {
            // То переключаемся на этот контекст
preg_match режет и сравнивает полученный запрос с именем контекста. Получается что контекст должен называться ru для нормальной работы а не RUSSIAN меняй preg_match под свои нужды.
Василий Наумкин
Ты зачем человека путаешь? Сравнивается base_url, а не key контекста. base_url может не совпадать с именем контекста - в заметке так и написано.
Там скорее всего что-то в .htaccess - у меня его нет.
Чикин Артур
Ой точно немного не так понял. Удали тогда ветку с комментарием.
Вячеслав Серков
У меня подобная ошибка. Контексты: Web - ru, en. Я удалил base_url у контекста web
.htaccess
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(ru|en)/assets(.*)$ assets$2 [L,QSA]
Как решить ситуацию с Babel ?
Василий Наумкин
Пришли доступы на bezumkin@ya.ru - погляжу
Проблема решилась чисткой кэша на сайте в и браузере и перелогиванием из админки
Также bezumkin подсказал, что Babel плохо дружит с AjaxManager. Пришлось выключить второй, в угоду первому)
Проблема с PayPal оплатой. В английской дефолтной версии все норм, в русской при cancel order переходит на http://website.com/assets/components/minishop2/payment/paypal.php?action=cancel&token=ТОКЕН также и при удачной покупке. Все из-за getOption ('site_url') как я понимаю, т.к. в русской версии это http://website.com/ru/
В файле paypal.php следущий код: $success = $cancel = $modx->getOption('site_url'); if ($id = $modx->getOption('ms2_payment_paypal_success_id', null, 0)) { $success = $modx->makeUrl($id, $context, $params, 'full'); } if ($id = $modx->getOption('ms2_payment_paypal_cancel_id', null, 0)) { $cancel = $modx->makeUrl($id, $context, $params, 'full'); }
$redirect = !empty($_GET['action']) && $_GET['action'] == 'success' ? $success : $cancel; $modx->sendRedirect($redirect);
что тут нужно поменять?
Илья Никитин
Всё работает как часы, спасибо! А вопрос скорее по модыксу и бабелю. Решил, как обычно, идти по пути наименьшего сопротивления и заюзать BabelTranslation для составления ссылок на соответствующие иноязычные страницы дабы не быть привязанным к одинаковым урлам. До этого урлы составлялись с помощью выражения типа
[[++site_url]]en/[[~[[*id]]]]`]]
и оное стабильно выдавало то, что нужно. Однако заменив
[[~[[*id]]]]`]]
на
[[~[[BabelTranslation? &contextKey=`en`]]]]
я получил вместо относительного адреса полный, что разумеется не входило в мои планы, ибо оный идёт без виртуальной подпапки en/, зато с http и прочими причиндалами. Отчего так проиходит и чего с этим можно поделать? Ежели оставить без
[[~]]
то оба выводять нужные номера страниц. Почему же линк от текущего айди получается относительный, а от бабелевского полный?.
Илья Никитин
И даже принудительная установка типа вывода линка в виде параметра
&scheme=`-1`
или abs не производят на BabelTranslation никакого эффекта. Чудеса...
Илья Никитин
Ещё одна засада - если не устанавливать site_url для контекстов, то из админки через кнопку "просмотр" попадаешь совсем не туда, куда хотелось бы, а именно на адрес без языковой поддиректории. Если же установить site_url равным, допустим, http://www.mysite.ru/en/ то все пути к ресурсам в assets, относительные и прописанные через ++site_url, соответственyо сбиваются. Это я всё к тому, что стоит дописать для не слишком продвинутых граждан вроде меня, что для нормальной работы всё-таки желательно site_url прописать, да добавить в .htaccess нечто типа:
# redirect all requests to /de/assets* and /en/assets* to /assets*
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(en|de)/assets(.*)$ assets$2 [L,QSA]
Илья Никитин
Сорри, про site_url на скрине не сразу углядел, однако про .htaccess упомянуть стоит.
Василий Наумкин
Ну вот у меня, например, нет ни Apache2, ни .htaccess.
И всё работает.
Илья Никитин
Ну не всем же так везёт! )
Василий Наумкин
Кто мешает? Бери да делай.
Илья Никитин
Не, я только за! Но когда у клиента уже готовый сайт, на оплаченом за год хостинге, а моя задача только омультиязычить его, то всё же проще дописать три строчки в .htaccess, чем убедить заказчика переносить сайт, а потом его ещё и переносить. )) И думаю мой случай не такой уж уникальный...
Василий Наумкин
Давненько я не видел хостингов без nginx, но это уже лирика.
Если по делу, то я прописываю site_url, а все скрипты и стили - через base_url. И тогда нет никаких проблем на любых серверах.
Перенаправлять через .htaccess, когда можно сразу правильно прописать ссылку - не стоит.
Илья Никитин
Однако, на хостинг меня вообще не пустили, в чём я заказчика и не виню. Для данной работы это совершенно необязательно.
Спасибо за науку "по делу".
Добрый день, не совсем понимаю этот способ, он идет с баблом? или без него, просто у меня и бабл не работает, выводит языки а переключения нету... не могу понять в чем дело, надо настроить сайт на 3 языка
Inga Urbanovskaya
Nет события OnHandleRequest. MODx Evo 1 0 12
Василий Наумкин
Я тебе больше скажу - в Evolution нет даже контекстов, чтобы их переключать!
Inga Urbanovskaya
И вариантов нет?
Василий Наумкин
Не знаю. Про Evolution нужно спрашивать на http://modx.im
А для мобильной версии это можно как-то применить? Ресурсы-то одни и те же получаются, нужно другие шаблоны подключать.
Василий Наумкин
Это про переключение контекстов, а не шаблонов - совсем разные вещи.
Шаблон документа можено менять при загрузке страницы, плагином на события OnLoadWebPageCache и OnLoadWebDocument.
Сергей Владимирович
Здравствуйте! Не подскажете как заставить работать Gallery c контекстом en. Дело в том, что при генерации изображений Gallery добавляет к ссылки изображения значение en, и из-за этого изображения не показываются. Скорее всего даже дело не в Gallery а в phpthumb. Может есть какие нибудь настройки в контексте en, чтоб ссылки на изображения генерировались одинаковые как для контекста web, так и для контекста en? Настройки делал как в вашей статье.
Василий Наумкин
Скорее всего нужно указать базовый мета-тег в head шаблона:
<base href="[[++base_url]]" />
Ну и проследить, чтобы галерея выдавала относительные ссылки - тогда всё должно работать.
Честно говоря, я не очень помню, как работает Gallery, потому что пользуюсь ms2Gallery.
Какой код надо ставит на главной для селектора языков
Николай Филимонов
Отличный плагин, спасибо. Правда есть одна небольшая проблема. Может я конечно, что-то не так настроил, но почему то на контексте web, modx правильно генерирует url, если задать его в виде site.com/?id=2, а в другом контексте (en) modx ссылку не генерирует из site.com/en/?id=2, а оставляет её как есть, соответственно перехода не происходит. ЧЯДНТ? .htaccess стандартный.
UPD: get параметр page работает на обоих контекстах правильно, а вот с id беда(
Извините пожалуйста за вопрос. Он был уже в комментах. Это нужно Babel ставить еще?
Василий Наумкин
Ну если в статье нет ни единого упоминания Babel, наверное он нафиг не нужен для переключения контекстов?
-)) спасибо за ответ!
Могу ли я с Вами связаться по почте? Готов пожертвовать рубями -) muzapapa@gmail.com
Василий Наумкин
Нет, я занят.
Владимир Камуз
А как корректней зделать переключатель языков. У меня получилось так:

<span class="pad_lang">Язык</span>
[[If?
   &subject=`[[*id]]`
   &operator=`inarray`
   &operand=`1,26`
   &then=`<span><a href="/ru/" class="ru"></a></span><span><a href="/en/" class="en"></a></span>`
   &else=`<span><a href="/ru/[[~[[*id]]&scheme=`-1`]]" class="ru"></a></span><span><a href="/en/[[~[[*id]]&scheme=`-1`]]" class="en"></a></span>`
]]
Вариант рабочий, но больше нравится реализация как на http://docs.modx.pro/
Николай
У меня так сниппет
<?php
switch ($modx->context->key) {
    case 'web' : $tpl = '<span id="languages"><span>EN</span> | <a href="/ru/">RU</a></span>'; break;
    case 'ru' : $tpl = '<span id="languages"><a href="/">EN</a> | <span>RU</span></span>'; break;
    default: $tpl = '<span id="languages"><span>EN</span> | <a href="/ru/">RU</a></span>'; break;
}
return $tpl;
но на истину не претендую
Владимир Камуз
Спасибо
Владимир Камуз
Но хотелось бы ещё чтобы Василий ответил
Василий Наумкин
У меня вот такой сниппет:
<?php
$tplRu = '<a href="[[+link]]">ru</a>';
$tplEn = '<a href="[[+link]]">en</a>';
$tplRuActive = '<span>ru</span>';
$tplEnActive = '<span>en</span>';

$output = '';
if ($modx->context->key == 'web') {
    $output .= $tplRuActive . ' | ' . $tplEn;
    $link = 'en/';
    if ($modx->getOption('site_start') != $modx->resource->id && $modx->getCount('modResource', array('uri' => $modx->resource->uri, 'context_key' => 'en'))) {
        $link .= $modx->resource->uri;
    }
}
else {
    $output .= $tplRu . ' | ' . $tplEnActive;
    $link = '/';
    if ($modx->getOption('site_start') != $modx->resource->id && $modx->getCount('modResource', array('uri' => $modx->resource->uri, 'context_key' => 'web'))) {
        $link .= $modx->resource->uri;
    }
}

return str_replace('[[+link]]', $link, $output);
Прям скопипастил как есть с http://docs.modx.pro. Если у документов в разных контекстах одинаковый uri, то будет очень хорошо переключаться.
Владимир Камуз
Большое спасибо
Николай
Я наверное начинаю сходить с ума. Но у меня не хотят работать сниппеты на побочном контексте. http://shop.plugingrid.com/ - Это основной контекст web. Тут все работает и работало как часики. http://plugingrid.com/ а вот это уже контекст plugingrid.com, на главной странице есть сниппет слайдера, он работает. И это единственный сниппет, что работает на этом контексте. http://plugingrid.com/blog/ Тут у меня вызов pdoPage который выводит записи в блоге, но ничего не выводит. Да более того, на любой информационной страничке есть:
<div class="b-content">
            <div class="b-page b-page_white">
                <div class="b-headingFirst" style = "text-transform: uppercase;[[*parent:is=`4`:then=`font-size: 20px;`]]">[[*longtitle]]</div>
                <div class = "mycontent">[[*content]]</div>
                <div class = "mycontent">[[*html]]</div>
            </div>
Ниже вызов сниппета Тест, который содержит в себе echo 'WORK!!'; die;
[[!test]]
        </div>
http://plugingrid.com/blog/ Но по факту это самое echo 'WORK!!'; не работает.. И die; тоже.
Василий Наумкин
Видимо, что-то не так настроил.
Николай
Весь этот невероятный несколько дневный марафон бреда был из за невозможности очистки кэша. Тфу тфу тфу вроде бы все работает как нужно.
Василий Наумкин
из за невозможности очистки кэша
Как так? Что-то с правами на файлы?
Василий Митонов
Есть два контекста, web и nsk У меня для nsk страницы должны быть индентичны для web Есть страница http://site.ru/index.php?id=1 она должна так же открываться на поддомене http://nsk.site.ru/index.php?id=1 Сейчас ошибка: 503 Error Page not found
Василий Наумкин
С какой радости у тебя будет одна и та же страница с id = 1 в двух разных контекстах?
У каждой страницы свой контекст. Если нужно открывать одну страницу по разным адресам - то это решается настройкой веб-сервера, а не контекстами.
Николаевич
Огромное спасибо за плагин, но столкнулся с проблемой. На сайте два контекста web и deb и два домена соответственно web.by и dev.by
Проблема с ссылками вида [[~1]]В контексте web выводится как web.by/1 а в контексте dev как dev.by/1 Как заставить ссылку выводить правильный адрес? Благодарю
MODx 2.3.1, есть 3 контекста (web, ru, en), основной сайт и поддомены. Переключаются как надо, но cultureKey для 2 и 3 почему-то игнорируется и используется системный.
Есть идеи?
Василий Наумкин
Кэш? Очепятка?
Больше идей нет.
На опечатки проверял и кеш очищал из админки. Банально удалил директорию кеша)
Спасибо.
Юрий Фомин
Доброго времени суток! При попытке организовать переключение контекстов по варианту с разными доменами, получаю страничку с: "Welcome to nginx! If you see this page, the nginx web server is successfully installed and working. Further configuration is required. For online documentation and support please refer to nginx.org. Commercial support is available at nginx.com. Thank you for using nginx.". Проверено на двух сайтах, оба находятся на simpledream. Что не так?
Василий Наумкин
Скорее всего, не добавил свои домены сайту в панели управления и nginx не знает, какой сайт показывать по запрошенному имени.
Юрий Фомин
не сработало... :( , буду копать дальше, спасибо за комент.
Василий Наумкин
Что значит, "не сработало"?
У нас там, вообще-то, техподдержка есть. Пиши, разберемся.
Тимофей
Василий, спасибо за ваш метод, который работает в отличие от методик с помощью Бабеля от "умельцев", которые они копипастят друг у друга с одинаковыми ошибками. Вопрос: Можно ли сделать так, чтобы при переключении версии открывалась страница, соответствующая той, на которой находился пользователь? Например, он был на странице контактов русской версии (www.mysite.ru/contacts), а при переключении языка его перебрасывает снова на главную но только уже английской версии (www.mysite.ru/en/), хотя должно на www.mysite.ru/en/contacts.
Василий Наумкин
Можно, дописал пример в заметку.
Тимофей
Спасибо за оперативный ответ. А где на сайте можно почитать про то, как настроить одинаковые uri ресурсов в разных контекстах? Возможно, вопрос глупый, но я новичок в PHP, поэтому прошу сильно ногами не пинать.
Василий Наумкин
Просто должна быть одинаковая структура документов в обоих контекстах и одинаковые алиасы у них - это всё делается в админке при создании ресурсов.
Походи по docs.modx.pro, посмотри на адреса. Например http://docs.modx.pro/components/pdotools/ и http://docs.modx.pro/en/components/pdotools/
Тимофей
Благодарю за подсказку. Разобрался. Все работает отлично. Если у кого-то возникнут проблемы, как у меня, советую обратить внимание - в настройках системы (Дружественные URL) параметр: Проверять на дублирование URI во всех контекстах (global_duplicate_uri_check) - ставим "нет". В этом случае проверка уникальности касается только текущего контекста.
UPD: Единственный момент - если пощелкать туда-сюда, в итоге все равно перекидывает на главную. Но это уже мелочи.
Дмитрий
Здравствуйте, сделал все как написано в случае с поддоменами при входе на поддомен пишет следующее:
Fatal error: Call to a member function checkPolicy() on a non-object in /var/www/p93747/data/www/***.com/core/model/modx/modx.class.php on line 1876
Насколько понимаю проблема с настройками политик доступа, никак не могу разобраться
Саша Друмс
Может кто подскажет, как во втором контексте (en) сделать страницу ошибки 404? В системных настройках указал страницу из контекста web. В контексте en выскакивает: 503 Error Page not found The page you requested was not found.
Василий Наумкин
То есть, ты считаешь это нормальным - указывать 404 страницу не из того контекста?
Саша Друмс
UPD: проблема решена. забыл, что можно указывать параметры (error_page) в настройках контекста.
Василий Наумкин
Именно.
Я настроил все классно работает, потом подумал добавил babel чисто для быстрого переключения +создания страниц в разных контекстах. Вышло довольно удобно. То есть сам функционал последующий для вывода и прочее от babel не использую. Спасибо !!!
Василий Наумкин
На здоровье!
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
03.12.2024, 13:13:34
Генерация - это создание статичный файлов, для их работы потом pm2 не нужен, только правильная настр...
Василий Наумкин
22.11.2024, 03:33:54
Спасибо!
inna
06.11.2024, 15:47:13
Да. Все работает. Спасибо.
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. ...
Василий Наумкин
20.03.2024, 21:21:52
Volledig!
Андрей
14.03.2024, 13:47:10
Василий! Как всегда очень круто! Моё почтение!
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо! Извини, тупанул.