Несколько мультиязычных сайтов в одной админке

Вот как реализовать переключение контекстов на нескольких доменах, но когда языки находятся в папках.
Вот пример:
  • site1.ru/ru
  • site1.ru/en
  • site2.ru/ru
  • site2.ru/en
По ссылке bezumkin.ru/sections/tips_and_tricks/2439/ Василий даёт 2 плагина, один только для вложенных в папки языков, другой только для поддоменов. Как можно совместить эти 2 плагина, чтобы все сайты на одной админке переключались правильно?

Я поднял этот вопрос, в результате которого мы нашли решение. Думаю это решение пригодится многим.

Вместе с Сергеем (спасибо ему большое за идеи) у нас получилось найти работающее решение (возможно есть вариант и изящнее).

Плагин

Плагин на OnHandleRequest:
<?php
// Работаем только на фронтенде
if ($modx->event->name != 'OnHandleRequest' || $modx->context->key == 'mgr') {return;}

$alias = $modx->getOption('request_param_alias', null, 'alias', true);
$request = &$_REQUEST[$alias];

// Определяем язык папки из URL
preg_match('|/(.*)/|', $_SERVER['REQUEST_URI'], $m);
$lang = trim($m[1]);

// Выбираем все существующие на сайте языки
$q = $modx->newQuery('modContextSetting');
$q->select('value');
$q->where(array('key' => 'cultureKey'));
$q->prepare();
$q->stmt->execute();
$langs = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
$lang_arr = array();
foreach ($langs as $l) {
    array_push($lang_arr, $l['value']);
}

// Если папка пуста, указываем русский язык
if (!in_array($lang,$lang_arr)) $lang = 'ru';

// Формируем нужный host_language
$host_language = $_SERVER['HTTP_HOST'].'_'.$lang;   

// Выбираем контекст с настройкой host_language
$q = $modx->newQuery('modContextSetting', array('key' => 'host_language', 'value' => $host_language));
$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);
			$request = preg_replace('/^'.$lang.'\//', '', $request);
		}
	}
}

Контексты

В настройки контекстов добавляем ключ host_language со значением site.ru_en, где «en» = cultureKey.
Остальные настройки можно взять из статьи Василия bezumkin.ru/sections/tips_and_tricks/2439/

Проблема

1. Плагин не работает почему-то с ресурсами-контейнерами, поэтому пришлось обойти костылём и убрать«Суффикс контейнера» в настройках системы [container_suffix].

2. Проверки на несуществующие языки типа /de/ решаются просто — достаточно во всех контекстах сайта прописать настройку страницы 404 [error_page] для каждого контекста отдельно, даже для web. Тогда если человек пройдёт по несущесвующей ссылке site2.ru/de/, то он попадёт на 404-ю страницу, которая указана сайту site2.ru

Возможные варианты применения решения:

1. Экономия места на хостинге, не нужно переплачивать за лишний хостинг.
2. Удобство управления, особенно когда есть 5 связанных между собой сайтов с 3-мя языками каждый. Очень скоро именно на таком проекте я испытаю этот плагин!
3. Если грамотно настроить права, то разные группы пользователей (менеджеров) смогут заходить в одну и ту же админку не подозревая, что на ней крутятся ещё много других сайтов. К тому же заходят они в админку на своём домене, например site1.ru/manager и site2.ru/manager

P.S.
Если вы знаете как решить эту задачу красивее — можете предложить в комментариях!
Василий Столейков
19 мая 2015, 11:38
modx.pro
12
5 960
+3

Комментарии: 38

Dmitry Rodionov
19 мая 2015, 14:46
+1
1)Выполнять не в цикле:
$lang_arr = array_unique($lang_arr);
2)не обязательно проверяя «in_array» уникализировать значения
3)нет проверки на язык отсутствующий у домена
например есть набор контекстов:
test1.com_ru
test1.com_en
test1.com_de
test2.com_ru
test2.com_en
если мы запросим для test2.com/de что получится?

P.S. я бы предложил выбрать сначала все контексты с текущим доменом, а потом уже среди них выбрать тот что с языком
    Василий Столейков
    19 мая 2015, 15:06
    0
    1) Точно, не заметил. Исправил.
    2) Убрал. Логично.
    3) Добавил проверку с редиректом на 404-ю страницу текущего контекста

    P.S. Я пробовал так, но что-то у меня не завелось…
      Василий Столейков
      19 мая 2015, 15:12
      0
      Только что-то сомневаюсь, что моя проверка и редирект правильно работают…
      Буду пробовать.
        Dmitry Rodionov
        19 мая 2015, 15:20
        0
        текущий контекст это какой? разумнее сделать именно выборку домена а потом уже дефолтного контекста в рамках домена, это при условии что отсечение по домену идет по DNS, т.е. невозможно запросить домен которого нет, хотя на этот случай тоже можно сделать проверку и выдавать домен заглушку или домен по-умолчанию
          Василий Столейков
          19 мая 2015, 15:22
          0
          Ок, давай попробуем и ваш вариант (у меня он с первого раза не завёлся)…
            Dmitry Rodionov
            19 мая 2015, 15:28
            0
            Хочешь чтобы я написал решение или сам?
            План такой:
            1)берем контексты по домену, если их нет показываем что-то по умолчанию(web например)
            2)если контексты нашли проверяем язык внутри них, если наш язык не найден — берем первый контекст или контекст по-умолчанию и выбираем его
            3)если найден контекст с нужным языком — берем его

              Василий Столейков
              19 мая 2015, 15:39
              0
              Доработаю вначале проверку для первого плагина, потом возьмусь за твой план.
              Если хочешь, можешь попробовать написать!!!
        Alex P
        06 ноября 2015, 00:14
        0
        А не подскажете как выводить в wayfinder меню?

        Что бы в версии en выводился чанк $enmenu а в русскоязычной $menu

        Слышал, что можно методом else, them а как не знаю…

        конструкции вида:
        &parents=`[[*context_key:is=`web`:then=`1`:else=`[[*context_key:is=`de`:then=`5`:else=`4`]]`]]`
        Спасибо
          Павел Гвоздь
          06 ноября 2015, 06:12
          +1
          Изучай Феном, чтобы сайт из-за подобных конструкций не превратился в «неповоротливую корову».
            Alex P
            06 ноября 2015, 09:51
            0
            Спасибо! а родными методами как организовать..?

            Есть же стандартный метод вывода?

            Как модиксу проверить если контекст en, то выводить чанк в wayfinder [[$enmenu]]
            Если контекст web то выводить [[$menu]]

            Спасибо
              Павел Гвоздь
              06 ноября 2015, 10:01
              2
              0
              Извини, конечно, но меня давно не возбуждают эти стандартные методы и вдаваться в подробности этих ужасов снова нет никакого желания.

              {if $_modx->config.cultureKey == 'ru'}
                  культурКей Ru
              {elseif $_modx->config.cultureKey == 'en'}
                  культурКей En
              {/if}

              Либо проверить контексты:
              {if $_modx->context.key == 'web'}
                  контекст web
              {elseif $_modx->context.key == 'en'}
                  контекст en
              {/if}

              Либо твой пример:
              {if $_modx->context.key == 'web'}
                  {include 'menu'}
              {elseif $_modx->context.key == 'en'}
                  {include 'enmenu'}
              {/if}

              Присмотрись в сторону Fenom, уверяю тебя, ни грамма не пожалеешь об этом.
                Alex P
                06 ноября 2015, 10:08
                0
                Спасибо, Вы даже написали мою конструкцию, вроде выглядет красиво код.
                {if $_modx->context.key == 'web'}
                    {include 'menu'}
                {elseif $_modx->context.key == 'en'}
                    {include 'enmenu'}
                {/if}
                prntscr.com/8zlelh

                Но не отображается, потому что пакет не поставлен..? fenom

                Я просто почему стандартными средствами., потому что сам не люблю добавления ставить. кажется, что каждое дополнение будет тормозить работу скриптов в целом, ошибался.
                  Павел Гвоздь
                  06 ноября 2015, 10:22
                  0
                  pdoTools надо ставить для работы с Феномом.
                  В работе с Феном много тонкостей. Например, скорей всего на твоём сайте, если ты поставишь pdoTools с включённым Феном, у тебя повываливаются ошибки и страница не отобразится, как надо. Это связано с тем, что Феном любой код (даже JS, даже JSON в параметрах сниппетов стандартным выводом) с фигурной скобой {, если после неё идёт какой-либо текст, а не пробел, таб или перенос строки, будет считать «за своего» и пытаться обработать. Естественно синтаксис его не устроит и он вывалит ошибку. Приведу пример:

                  Мы на странице вызываем код fancybox вот так:
                  $("a[rel='ajaxModal']").fancybox({'type':'iframe', 'arrows':false});
                  Феном видит это безобразие и в логе ошибок в бекенде сообщает нам, что он не понимает синтаксис, который мы указали для него на странице. Что нам надо сделать, чтобы удовлетворить потребность Фенома в корректном синтаксисе? Да просто после фигурной скобы поставить пробел, вот так:
                  $("a[rel='ajaxModal']").fancybox({ 'type':'iframe', 'arrows':false});

                  Или есть у нас сниппет вызываемый стандартными средствами MODX с параметром, в котором указана JSON строка вот так:
                  [[!pdoResources? &sortby=`{"id":"DESC"}`]]
                  Феном не устроит подобное отношение и он, как и в прошлом примере скажет нам, что синтаксис не уместен. Что мы делаем? Правильно! Просто ставим пробел после скобы:
                  [[!pdoResources? &sortby=`{ "id":"DESC"}`]]

                  И так со всем кодом на странице. Когда все подобные моменты на странице будут исправлены — Феном будет радовать нас своими возможностями и скоростью работы.
                    Alex P
                    06 ноября 2015, 10:27
                    0
                    Меня это не пугает, буду изучать fenom) pdotools поставил, но всеравно конструкция в шаблоне не отображает меню.

                    а сейчас решил наплодить темплейты — в Главной [[$menu]] в копия главной [[$enmenu]] ппц как топор))
                      Alex P
                      06 ноября 2015, 10:29
                      0
                      Думаю есть какой то код типа ignore, что бы fenom не обрабатывал не корректный для него синтаксис
                Василий Столейков
                06 ноября 2015, 18:46
                0
                А что мешает переименовать чанки в соответствии с языком и его подставлять?
                Например чанки [[$enmenu]] и [[$rumenu]], а потом просто подставлять системный параметр:
                [[$[[++cultureKey]]menu]]
                Можно и по другим параметрам, но принцип ясен.
                  Alex P
                  08 ноября 2015, 00:16
                  0
                  Да, спасибо! я так сделал:

                  [[$menu_[[++cultureKey]]]]


                  а чанки menu_ru и menu_en
        Dmitry Rodionov
        19 мая 2015, 15:23
        0
        дополнительная проверка по контексту интересна, но наверное надо сделать что-то одно чтобы управлять было удобнее и прозрачнее
        $context=$host.'-'.$lang;
        
        if ($modx->getCount('modContext', $context)) {
            $modx->switchContext($context);
            $request = preg_replace('/^'.$lang.'\//', '', $request);
        }
          Василий Столейков
          19 мая 2015, 15:37
          0
          Выкинул лишние проверки, спасибо! Теперь код стал чище и прозрачнее.
          Но проверку для несуществующего языка (/de/) ещё не дописал…
          Сергей Шлоков
          19 мая 2015, 16:28
          0
          Ну, справедливости ради, хотел бы заметить, что я предлагал другое решение, попроще и с минимальным количеством запросов к БД.
          Создаем контексты для все языков с ключами host-language — site1-ru (для site1.ru), site1-en (для site1.ru/en/), site2-ru (для site2.ru), site2-en (для site2.ru/en/). Контекст web не рабочий. В нем одна страница 404.
          Вот плагин.
          <?php
          // Работаем только на фронтенде
          if ($modx->event->name != 'OnHandleRequest' || $modx->context->key == 'mgr') {return;}
          
          $alias = $modx->getOption('request_param_alias', null, 'alias', true);
          $request = &$_REQUEST[$alias];
          
          // Определяем язык папки из URL
          preg_match('|/(.*)/|', $_SERVER['REQUEST_URI'], $m);
          $lang = trim($m[1]);
          //Сравниваем с языками из массива. Языки указывает админз, он знает какие, незачем делать запрос к БД
          if (! in_array($lang,array('en','de','sp'))) $lang = 'ru';
          
          // Определяем домен
          preg_match('/(.+)(\.com|ru)/', $_SERVER['HTTP_HOST'], $m);
          $host = trim($m[1]);
          //Формируем ключ контекста
          $context = $host.'-'.$lang;
          //Проверяем наличие контекста, если есть, переключаем
          if ($modx->getCount('modContext', $context)) {
              $modx->switchContext($context);
              $request = preg_replace('/^'.$lang.'\//', '', $request);
          }
          //Если контекст не найден, то остаемся в контексте web, у которого страница 404 (заглушка).
            Dmitry Rodionov
            19 мая 2015, 16:36
            0
            если будет 2 домена site1.ru и site1.eu к примеру, надо будет немного подправить вариант ;)
              Сергей Шлоков
              19 мая 2015, 16:45
              0
              Ну не пользователи же добавляют домены и языки. Это работа админа :)
              Василий Столейков
              19 мая 2015, 16:37
              0
              //Прописываем языки в массиве. Админ знает какие, незачем делать запрос к БД
              if (! in_array($lang,array('en','de','sp'))) $lang = 'ru';
              Да, я решил добавить запрос в БД, чтобы решение было универсальнее.
              // Определяем домен
              preg_match('/(.+)(\.com|ru)/', $_SERVER['HTTP_HOST'], $m);
              А если все домены в разных доменных зонах? И новые домены будут добавляться непонятно каких зон? Как автоматизировать этот момент?
              //Если контекст не найден, то остаемся в контексте web, у которого страница 404 (заглушка).
              Не стал развивать этот момент, т.к. тогда пришлось бы переносить весь контекст web в другой контекст, а потом настраивать всё это. К тому же синхронизация ресурсов в Babel-е может слететь. Особенно неудобно это будет, если в контексте web настроенный рабочий магазин с тысячами товаров.
                Сергей Шлоков
                19 мая 2015, 16:48
                0
                А если все домены в разных доменных зонах? И новые домены будут добавляться непонятно каких зон? Как автоматизировать этот момент?
                Админ для начала должен будет создать контекст со всеми настройками и это никак не автоматизируешь. А уж добавить за 2 секунды еще одну зону я думаю он будет в состоянии.
                В конце концов можно регулярку сделать более универсальной — убрать зоны.
                  Василий Столейков
                  19 мая 2015, 16:59
                  0
                  Как-то Василий вспомнил одну интересную поговорку «лучше день потерять потом за пять минут долететь». Я тоже уже кучу времени потерял с этим решением в ущерб работе с мыслю о инвестиции в будущую экономию времени на настройку — гораздо проще раз поставить плагин и забыть о нём.

                  В моём варианте я тоже вначале срезал у регулярки зоны, правда не знаю как она себя поведёт в ситуации с поддоменами типа sub.site.ru — я не силён в регулярках. Поэтому я не использую её для определения хоста, а запрашиваю контекст из БД через $host_language, который у меня указан вместе с доменной зоной (site.ru_en)
                    Сергей Шлоков
                    19 мая 2015, 18:21
                    0
                    Да я без претензий, поэтому и написал «справедливости ради».
                Василий Столейков
                19 мая 2015, 17:06
                0
                Я тебя упомянул в статье, потому что без твоих идей я бы не справился, спасибо тебе!
                Василий Столейков
                19 мая 2015, 17:01
                0
                Проверки на несуществующие языки типа /de/ оказывается и не нужно было писать в плагине. Достаточно было во всех контекстах сайта прописать настройку страницы 404 [error_page] для каждого контекста отдельно, даже для web.
                Все мои задачи уже решены!
                  Dmitry Rodionov
                  19 мая 2015, 17:48
                  0
                  ну если по одному домену выдается информация с другого — странно это выглядит как минимум

                  2. Проверки на несуществующие языки типа /de/ решаются просто — достаточно во всех контекстах сайта прописать настройку страницы 404 [error_page] для каждого контекста отдельно, даже для web. Тогда если человек пройдёт по несущесвующей ссылке site2.ru/de/, то он попадёт на 404-ю страницу, которая указана сайту site2.ru
                  судя по твоему коду, будет не найден ключ вида host_language и по умолчанию показан контекст web
                    Василий Столейков
                    19 мая 2015, 17:54
                    0
                    Не понял что ты имеешь ввиду? У меня задача была чтобы языки были не на отдельных доменах, а в подпапках определённого домена. И если указывается несуществующая подпапка языка, то соответственно это несуществующая ошибка главного языка, и это нормально.
                      Dmitry Rodionov
                      19 мая 2015, 17:58
                      0
                      переключение контекста в какой момент идет? и по каким критериям? и по каким критериям он может не переключится, а лучше конечно взять да протестировать на домене который не в контексте web
                      Если считаешь что правильно все, то ладно)
                        Василий Столейков
                        19 мая 2015, 18:01
                        0
                        Уже тестировал на сайтах:
                        Оба этих сайта в одной админке. У первого языки ru (web) и en, а у второго ru, en, ro. Можешь погонять, пока они ещё в тестовом режиме.
                          Dmitry Rodionov
                          19 мая 2015, 18:33
                          0
                          домен на контексте web какой?
                            Василий Столейков
                            19 мая 2015, 18:36
                            0
                            bazstudio.com/ — web
                            bazstudio.com/en/ — en
                            macinatorul.com/ — macinatorul-ru
                            macinatorul.com/en/ — macinatorul-en
                            macinatorul.com/ro/ — macinatorul-ro
                              Dmitry Rodionov
                              19 мая 2015, 19:06
                              0
                              надо чтобы на первом домене где web было больше языков чем на втором, пример

                              bazstudio.com/ — web
                              bazstudio.com/en/ — en
                              bazstudio.com/ro/ — ro
                              macinatorul.com/ — macinatorul-ru
                              macinatorul.com/en/ — macinatorul-en

                              при обращении к macinatorul.com/ro/
                              будет показываться 404 с bazstudio.com, а не с macinatorul.com
                                Василий Столейков
                                19 мая 2015, 19:42
                                0
                                Добавил ещё 2 языка, полёт нормальный.
                                bazstudio.com/bg/
                                bazstudio.com/ro/
                                  Dmitry Rodionov
                                  19 мая 2015, 19:48
                                  0
                                  По ссылке:
                                  macinatorul.com/bg/
                                  Выдает:
                                  host_language: bazstudio.com_ru
                                  а должен macinatorul-ru
                                  особенно если разные дизайны будут и наполнение это сильно будет влиять

                                  хотя чего я доказываю, если устраивает как работает, пожалуйста ;)
                  Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                  38