Простая мультиязычность для простого сайта

Ниже рассмотрено 2 варианта реализации сайта на 2-х языках:
1. Без лексиконов, с дублированием шаблонов.
2. С лексиконами, без дублирования шаблонов:
а) без дополнительной таблицы, для лексиконов используется существующая таблица system_settings;
б) с дополнительной таблицей для лексиконов.

1. Вступление

Под простым сайтом понимается сайт с небольшим количеством шаблонов и дополнительных языковых версий.

Сначала рассмотрим, что собой вообще представляет мультиязычный сайт с технической точки зрения, на примере для 2-х языков — русского (основной язык) и английского. Это:
— комплект страниц на русском языке
— комплект страниц на английском языке
— переключатель языка.

Подробнее по переключателю языка.
К примеру, есть страница на русском языке в категории Новости

Статья в блог тестовая один (10)

и есть такая же страница на английском языке в аналогичной категории

Blog article test one (16)

В скобках указан их id.

Так вот, когда мы находимся на указанной русскоязычной странице, то ее адрес (ссылка) будет, к примеру
site.ru/news/news-1
а под кнопкой переключения будет не что иное, как ссылка на такую же страницу на английском языке, грубо говоря (упрощенно), ссылка выглядит примерно так
<a href="[~16~]"><img src="assets/templates/images/flag_en.png" alt="">EN</a>

И кликая по этой кнопке — ссылке мы физически не язык переключаем, а просто по ссылке переходим на альтернативную страницу на другом языке. И получим страницу по такому адресу
site.ru/en/news/news-1

Не правда ли, как все просто?

Вот из этого и будем исходить.
Далее я покажу, как сделать мультиязычный сайт на 2-х языках с набором инструментов «из коробки», без применения специальных компонентов для мультиязычных сайтов.

2. Сначала делаем сайт на одном на одном языке

Или, к примеру, такой сайт на одном языке уже есть, но вас попросили добавить английскую (или какую другую) языковую версию.
Здесь особо писать нечего, сайт как сайт.
У меня это выглядело так



3. Добавляем второй язык

Разрешаем повторяющиеся псевдонимы (в конфигурации сайта, вкладка Дружественные URL).
Делаем папку en с таким же псевдонимом, шаблон — blank (вообще-то без разницы) и ставим на странице редирект на первую внутреннюю страницу, я это сделал сниппетом FirstChildRedirect.



Внутрь этой папки копируем все имеемые русскоязычные страницы в той же структуре. Для русскоязычных страниц можно делать папку ru, можно не делать, кому как нравится и насколько большой сайт. Я не делал и дальше в примере будет без нее.
На главной странице англоязычной версии меняем псевдоним на main.

Аналогично все шаблоны тоже копируем для англоязычной версии. Да, в этом варианте мультиязычности используется 2 комплекта шаблонов. И это минус. Зато все остальное — плюсы, и для небольшого сайта их больше.
В итоге должно получиться примерно так



Обратите внимание, что шаблоны сгруппированы по отдельным категориям. Я просто взял те что есть в evo, в дальнейшем id категорий нам пригодятся. А можно сделать и свои категории, например, ru и en. Это будет важно в дальнейшем, когда будем автоматизировать процесс создания альтернативных (т.е. на другом языке) страниц.

Аналогично делаем копии и чанков к шаблонам.



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

4. Создаем два TV параметра

Для двух языков хватило бы и одного, но лучше изначально сделать два на случай, если впоследствии придется языковые версии добавлять. Тип ввода Text.
ТВ параметр с названием altEn подключаем ко всем шаблонам русскоязычных страниц.
ТВ параметр с названием altRu подключаем ко всем шаблонам англоязычных страниц.

5. Сопрягаем страницы

Во все TV параметры вписываем id альтернативной страница (страницы на другом языке).
Пример для ранее приведенных страниц
Статья в блог тестовая один (10)
Blog article test one (16)
Здесь в ТВ altEn на странице с id=10 впишем 16, а в ТВ altRu на странице с id=16 впишем 10.
И так по всем ранее созданным страницам — и русскоязычным, и англоязычным.

6. Делаем кнопку переключения языка

В шапке сайта шаблона для русского языка прописываем кнопку, теперь уже реально
<a href="[[if? &is=`[*altEn*]:empty` &then=`[~12~]` &else=`[~[*altEn*]~]`]]"><img src="assets/templates/images/flag_en.png" alt="">EN</a><

где 12 — это id главной страницы англоязычной версии.
if нужен для проверки на пустоту ТВ параметра, и если он не заполнен, чтобы перекидывало на главную, а не на 404.

В шапке сайта шаблона для английского языка прописываем кнопку так
<a href="[[if? &is=`[*altRu*]:empty` &then=`/` &else=`[~[*altRu*]~]`]]"><img src="assets/templates/images/flag_ru.png" alt="">RU</a>


7. Редактирование шаблонов и чанков для англоязычной версии

7.1. Изменение сниппетов для англоязычной версии, например, DLMenu для меню, вывода анонсов с помощью DocLister, multiTV и аналогичных сводится к изменению параметра, который определяет источник, т.е. parents, docid и аналогичные, например, меню для русскоязычной версии
[[DLMenu?
&parents=`0`
&maxDepth=`2`
&outerTpl=`@CODE:<ul id="main-menu">[+wrap+]</ul>`
&rowTpl=`@CODE:<li><a href="[+url+]" title="[+title+]">[+title+]</a></li>`
...
]]

и оно же для англоязычной
[[DLMenu?
&parents=`11`
&maxDepth=`2`
&outerTpl=`@CODE:<ul id="main-menu">[+wrap+]</ul>`
&rowTpl=`@CODE:<li><a href="[+url+]" title="[+title+]">[+title+]</a></li>`
...
]]

Нормально переделываются (адаптируются) даже TagSaver и evoSearch.

7.2. Отдельно остановлюсь на хлебных крошках DLcrumbs и приведу пример полностью

В русскоязычной версии у меня было так
[[DLcrumbs? &showCurrent=`1`
&ownerTPL=`@CODE:<ul class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">[+crumbs.wrap+]</ul>`]]

В англоязычной версии теперь будет так
[[DLcrumbs? &showCurrent=`1`
&ownerTPL=`@CODE:<ul class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">[+crumbs.wrap+]</ul>`
&tplFirst=`@CODE:<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">
    <meta itemprop="position" content="[+iteration+]" />
    <a href="[~12~]" title="Main page" itemprop="item">Main page</a>
</li>`
&addWhereList=`c.id NOT IN (11)`
&hideMain=`0`]]


7.3. Меняются все русскоязычные подзаголовки и тексты в шаблонах, боковой колонке, футере и где есть еще.

8. Вишенка для SEO

В чанк head русскоязычной версии добавляем
<link rel="alternate" hreflang="en" href="[(site_url)][[if? &is=`[*altEn*]:empty` &then=`en` &else=`[~[*altEn*]~]`]]">

В чанк headEn англоязычной версии добавляем
<link rel="alternate" hreflang="ru" href="[(site_url)][[if? &is=`[*altRu*]:empty` &then=`` &else=`[~[*altRu*]~]`]]">


9. Заходим на сайт и проверяем

Хотя можно было посмотреть уже после п.6.

10. Редиректы

Если псевдоним main нужно скрыть из URL, чтобы главная страница англоязычной версии выводилась не так
site.ru/en/main
а так
site.ru/en

это можно это сделать в .htaccess, например
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^en/main$ http://%{HTTP_HOST}/en/ [R=301,L]
RewriteRule ^en/$ /en/main [L]


Вот и весь мультиязычный сайт.

Дальше я еще покажу как можно автоматизировать сопряжение, чтобы вручную не создавать альтернативные страницы и не проставлять id страниц в подключенные к шаблонам TV параметры altEn и altRu.

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

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

За старания, конечно, плюс.
avatar
Про дублирование я сразу написал, что это минус. В дальнейшем можно будет подумать на этот счет.
Зато эта штука точно запустится сразу и будет работать без всяких-яких, и не поломается ни при каких обстоятельствах — там ломаться нечему.
И сделать можно за один вечер. У меня на писанину больше ушло ((
Про лучше и изящнее — просто не успел, ниже выложил, там тоже найдется что покритиковать, но все же))
avatar
Эту задачу было бы интереснее решить в 3.x, ну или через EvoTwig (заодно можно было бы написать расширение для твига для лексиконов).
avatar
11. Плагин miniLang

Сразу код
/**
 * miniLang
 * 
 * Мультиязычность - создание альтернативных страниц
 * 
 * @category    plugin
 * @version     1.0.0
 * @internal    @events OnDocFormSave
**/

switch ($modx->Event->name) {
    case 'OnDocFormSave': {
    	if ($mode == "new") {
        	include_once(MODX_BASE_PATH.'assets/lib/MODxAPI/modResource.php');
        	$doc = new modResource($modx);
        	$doc->edit($id);
        	$template = $doc->get('template');
			$pagetitle = $doc->get('pagetitle');
			$image = $doc->get('image');
			
			// Определение родителя альтернативной версии
			$parent = $doc->get('parent');
			if ($parent != 0) {
				$value = $doc->get('parent');
				$tmplvarid = '9';
				$out = $modx->db->getValue('Select contentid from '.$modx->getFullTableName('site_tmplvar_contentvalues').' where value="'.$value.'" and tmplvarid="'.$tmplvarid.'"');
			}else{ 
				$out = '11';
			}
			$newparent = $out;
			
			// Определение шаблона альтернативной версии
			$table1 = $modx->getFullTableName( 'site_templates' );
			$res = $modx->db->select("description", $table1, "id='".$template."'");
			while ($row=$modx->db->getRow($res)) {
				$description = $row['description'];
			}
			$category = '2';
			$newtemplate = $modx->db->getValue('Select id from '.$table1.' where description="'.$description.'" and category="'.$category.'"');
			
			// Создание страницы альтернативной версии
        	        if ($template != $newtemplate) {
				$doc->create(array(
    				'pagetitle' => $pagetitle,
    				'template' => $newtemplate,
    				'parent' => $newparent,
					'published' => 0,
					'altRu' => $id,
					'image' => $image
				));
				
		// Запись id созданной альтернативной страницы в ТВ основной страницы
		    $newid = $doc->save(true, false);
		    $table = $modx->getFullTableName('site_tmplvar_contentvalues');
		    $fields = array(
                    	'value' => $newid,  
                    	'contentid'  => $id,  
                    	'tmplvarid' => 10
                    );   
    		    $modx->db->insert( $fields, $table);  
		}
			
    	    }
	}
}

Плагин составлялся исходя из того, что основной язык сайта русский, и добавления страниц на сайт так же будет сначала на русском языке, а потом на альтернативном (английском).
Что делает плагин:
1. При создании страницы на русском языке в любой категории или в корне сайта, при ее сохранении дополнительно создается страница в англоязычной версии в такой же категории или тоже в ее корне.
2. Альтернативная страница создается не опубликованной (в плагине можно изменить).
3. На альтернативную страницу передается заголовок (для последующей идентификации) и изображение (чтобы меньше работы было, но можно и поменять).
4. Выполняется двухстороннее сопряжения — в оба ТВ altEn и altRu вписываются нужные id страниц, т.е. вручную их больше прописывать не надо.

Основная страница



Альтернативная страница



Таким образом после создания русскоязычной версии остается только перейти на англоязычную, поменять заголовок и текст, опубликовать.

Комментарии по коду прописаны, что еще осталось пояснить:
tmplvarid = 9 — это altRu
tmplvarid = 10 — это altEn
category = 2 — категория, в которой находятся шаблоны англоязычной версии
description — это описание шаблона, в данном случае оно пошло «в дело», с помощью него сделано сопряжение шаблонов, чтобы шаблон создаваемой альтернативной страницы использовал аналогичный шаблон, какой выбран у основной страницы.
Поэтому, если посмотрите скриншот, он одинаков для каждой пары шаблонов, например у шаблонов и Блог, и Блог En — blog.



12. TV параметры altEn и altRu

Они теперь вроде как и не у дел, можно даже скрыть, чтобы «не путались под ногами».
А можно и оставить на случай форс-мажора.

Я оставил, но «приукрасил». В качестве украшательства рассматривались несколько вариантов, в итоге остановился на ddTree (DropDown Tree) (да, он еще живой, на v 7.3 полет нормальный).
Выглядит теперь так



Это все, что я хотел написать в рамках этого топика. Дополнения и критика, как всегда приветствуется.
Еще раз хочу подчеркнуть, что это для простых сайтов, когда проще и быстрее продублировать несколько шаблонов, чем ставить и настраивать какой-то сложный и объемный компонент.
  • paic
  • 0
avatar
Дублировать шаблоны только ради того, чтобы вписать в них другие идентификаторы ресурсов для вызовов DlMenu, DlCrumbs и прочих сниппетов, и другие названия чанков — это очень избыточно.

Тут как минимум следующие решения напрашиваются:

1 — Аналогично тому, как это сделано в evoBabel, можно задавать свои плейсхолдеры вида [+menu_parent_id+]. Заполнить которые можно хоть в сниппете, хоть в плагине.
И вызывать уже

[[DLMenu?
&parents=`[+menu_parent_id+]`
&outerTpl=`[+menu_outer_tpl+]`
&maxDepth=`2`

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

2. Можно в prepare-сниппетах в зависимости от языковой версии подменять tpl и ownerTPL на соответствующий чанк.
Но это уже какие-то мега-костели.

Мне доводилось забирать сайты на modx, которые были вроде и маленькими, но там было наделано по 40-50 шаблонов на все случаи жизни. После «прореживания» и удаления дублирующего кода удавалось уменьшить их на 5-10 штук ;)

И еще, любой маленький сайт из 10-20 страниц может неожиданно разрастись страниц до 1000, если заказчик проявит неожиданно энтузиазм. И если там будет куча одинаковых по сути шаблонов, то проще будет свихнуться, чем поддерживать это.

В целом, установка и настройка evoBabel даже на последних версиях Evo и PHP (у меня — работает) не выглядит такой уж сложной по сравнению с дублированием шаблонов, чанков и всего подобного.
Комментарий отредактирован 2021-04-14 17:26:22 пользователем Dreamer
avatar
Спасибо за отзыв.
Я в курсе, как работает evoBabel и другие, начиная с e-KAO и YAMS ))
Но задача не стояла сделать еще один evoBabel.

Задача стояла показать как максимально просто сделать мультиязычный сайт, не прибегая к специальным компонентам, с кучей плагинов, сниппетов, дополнительными таблицами в базе. Это один момент.

А второй заключается в том, что в таком варианте ничем не скрыта физика работы подобного сайта — имеется ввиду, что если под кнопкой переключателя языка ссылка, то ссылка и пишется, а не плейсхолдер или еще что-то.
Тому кто делает мультиязычный сайт впервые это в любом случае полезно знать, вне зависимости на чем он будет делать.

Касательно разрастания сайта — для этого и сделал плагин miniLang — клепайте на здоровье хоть 1000, хоть сколько захотите страниц и категорий, все сопряжения устанавливаются автоматически:
— сопряжение страниц
— сопряжение родителей
— сопряжение шаблонов

А со временем может и добавлю лексиконы и уберу дублирующие шаблоны, подумаю. Но добавлять дополнительные таблицы в базу пока не хочется, пока смотрю в сторону ClientSettings.
avatar
А что будет, если нужно добавить третий язык, допустим, французский — третий комплект шаблонов делать?
И в каждом соответственно уже будет по две ссылки на другие языки, то есть их в сумме шесть штук потребуется разметить: ru-> en, ru->fr, fr->en, fr->ru, en->ru, en->fr.

А потом мы друг решим довнести какую-то функциональность в сайт — и нужно будет править уже не два шаблона, а три.

ИМХО, избыточное дублирование кода проблем больше породит, чем кажущаяся сложность работы спец. компонентов. Опять-таки, у вас тоже без плагина не обошлось, и это тоже некий элемент «скрытности физики работы» вносит.

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

Еще важный момент — тот же evoBabel можно доавтоматизировать до такого состояния, что администратор сайта сможет добавить сам дополнительный язык, создав ресурс с соответствующим шаблоном и дозаполнив модуль evobabellexicon соответствующими значениями. Конечно, соображаловка некоторая понадобится, чтобы заполнить поля, в которых внесены идентификаторы всяких родительских/служебныхх ресурсов, но не придется лезть в шаблоны и чанки и что-то там править. В вашем варианте пока без правки шаблонов и чанков добавление дополнительного языка не обойдется, что существенно усложняет задачу «легкого» применения.

Обдумайте :)
avatar
Ну что ж вы меня все пытаетесь убедить в том, с чем я и так согласен))

Еще раз — обратите внимание на Название темы:
Простая мультиязычность для простого сайта.

Я же никого не заставляю это повторять, если сайт сложный, со многими неизвестными в ТЗ со всякими если и возможно по расширяемости и прочему, если много языков, или если просто не нравится.

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

Да и на практике огромное количество сайтов работают как их сделали на 2-х языках, так и работает годами.

А по объему работы сделать копии шаблонов и отредактировать их никак не больше, чем с применением специальных компонентов. Опять же — если сайт простой.

С применением специального компонента вы все лексиконы сначала прописываете в модуле, а потом идете в шаблоны и заменяете там все на плейсхолдеры. Т.е. одну и ту же работу делаете дважды, вне зависимости от сложности сайта.

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

И еще же вам надо поставить, запустить и настроить сам компонент с модулем-плагинами-сниппетами-таблицами.

По моему плагину.
Без плагина сайт тоже будет работать.
Потому что этот плагин — это в дополнение, а не вместо. Сделан на случай какого-либо статейника, блога и подобного активного сайта. Но можно, конечно, и сразу ставить и пользоваться.
avatar
Ну если тема именно в том, чтобы не использовать evoBabel, то для связи было бы проще и логичнее использовать Selector.
Там тебе и дерево нужное, и выпадающий список удобный.
  • 1px
  • 0
avatar
Для связи используется id альтернативных страниц в TV altEn и altRu, а вот как их проставлять, это уже кому как нравится, можно
1. Вручную
2. Автоматически с помощью плагина miniLang п.11
3. Можно и Selector'ом, как у меня в п.12 показан ddTree — я остановился на нем чисто из-за визуализации дерева (где находится), но им можно и подключать, и переподключать, если такое потребуется. И Selector, и ddTree, и другие подобные — физически они в ТВ проставляют id, а все остальное — это внешний вид и удобства.
avatar
Я не критикую, я предлагаю варианты, раз уж собираем тут примеры решения «а можно и вот так вот».
avatar
Так я и не против, просто дополнительно пояснил. Еще могу добавить, что нужно обращать внимание, чтобы админ с пом. подобных дополнений не смог поставил больше одного id.
avatar
13. Лексиконы.

Решил все-таки сделать вариант с лексиконами и соответственно, без дублей шаблонов.

В качестве хранилища для лексиконов предлагается два варианта:

13.1. Без использования отдельной таблицы в базе данных.

Лексиконы хранятся в multiTV, а multiTV — модуле ClientSettings. При этом используется уже существующая таблица system_settings

13.2. С использованием отдельной дополнительной таблицы в базе данных.

Для вывода и редактирования так же используется ClientSettings с multiTV в режиме dbtable.

Скриншот, как выглядит и один и второй вариант, здесь они сразу оба, но использовать разумеется надо какой-то один, кому какой больше нравится


Как на мой взгляд — вариант 1 удобнее и для добавления новых строк в лексикон, и для редактирования существующих.

13.3. Использование.

Для вывода лексиконов используются плейсхолдеры вида
[+lang.blog+]

а в шаблонах и чанках теперь будет так
[!DLMenu? 
&parents=`[+lang.menu+]`
&maxDepth=`2`
...
!]

<h3>[+lang.text+]</h3>

и т.д.
  • paic
  • 0
avatar
14. Вариант 1, без использования отдельной дополнительной таблицы в базе данных.

14.1. Устанавливаем, если на сайте еще нет, ClientSettings и multiTV.

14.2. Создаем конфигурацию multiTV, у меня это файл lexicon.config.inc.php с конфигурацией

<?php
$settings['display'] = 'horizontal';
$settings['fields'] = array(
    'title' => array(
        'caption' => 'Назначение',
        'type' => 'text',
        'width' => '200'
    ),
    'plh' => array(
        'caption' => 'Плейсхолдер',
        'type' => 'text',
        'width' => '200'
    ),
    'ru' => array(
        'caption' => 'Значение RU',
        'type' => 'text',
        'width' => '200'
    ),
    'en' => array(
        'caption' => 'Значение EN',
        'type' => 'text',
        'width' => '200'
    )
);


14.3. Создаем конфигурацию для ClientSettings, у меня она такая

<?php
return [
	'caption' => 'Лексикон',
	'introtext' => 'Настройка языковых версий',
	
    'settings' => [
	    'lexicon' => [
            'caption' => 'Замены',
            'type' => 'custom_tv:multitv',
        ]
    ],
];


14.4. Заполняем лексиконы.

14.5. Создаем плагин miniLang с таким кодом
/**
 * miniLang
 * 
 * Мультиязычность - создание альтернативных страниц и подключение лексиконов
 * 
 * @category    plugin
 * @version     1.0.1
 * @internal    @events OnDocFormSave,OnParseDocument
**/

switch ($modx->Event->name) {
    case 'OnDocFormSave': {
    	if ($mode == "new") {
        	include_once(MODX_BASE_PATH.'assets/lib/MODxAPI/modResource.php');
        	$doc = new modResource($modx);
        	$doc->edit($id);
        	$template = $doc->get('template');
			$published = $doc->get('published'); // добавлено
			$pagetitle = $doc->get('pagetitle');
			$image = $doc->get('image');
			
			// Определение родителя альтернативной версии
			$parent = $doc->get('parent');
			if ($parent != 0) {
				$value = $doc->get('parent');
				$tmplvarid = '9';
				$out = $modx->db->getValue('Select contentid from '.$modx->getFullTableName('site_tmplvar_contentvalues').' where value="'.$value.'" and tmplvarid="'.$tmplvarid.'"');
			}else{ 
				$out = '11';
			}
			$newparent = $out;
			
			// Определение шаблона альтернативной версии исключено
			
			// Создание страницы альтернативной версии
        	if ($published != 0) {  // Проверка через $published вместо $template
			$doc->create(array(
    			'pagetitle' => $pagetitle,
    			'template' => $template, // $template вместо $newtemplate
    			'parent' => $newparent,
			'published' => 0,
			'altRu' => $id,
			'image' => $image
			));
				
			// Запись id созданной альтернативной страницы в ТВ основной страницы
			$newid = $doc->save(true, false);
			$table = $modx->getFullTableName('site_tmplvar_contentvalues');
		    $fields = array(
                    	'value' => $newid,  
                    	'contentid'  => $id,  
                    	'tmplvarid' => 10
                    );   
    			$modx->db->insert( $fields, $table);  
			}
    	        }
	}
	case 'OnParseDocument': {
		// Определение принадлежности страницы корневой папке языка
		if ($modx->documentObject['parent']=='0') {
  			$lang=0;
		} else {
  			$lang=$modx->documentIdentifier;
  			do {
    			foreach ($modx->documentMap as $mapEntry) {
      				$parentId=array_search($lang, $mapEntry);
      				if ($parentId) break;
    			}
    		if ($parentId) $lang=$parentId;
  			} while ($parentId);
		}
		
		// Псевдоним корневой папки языка
		if ($lang == 11) {$alias = 'en';}else {$alias = 'ru';}
		
		// Запись лексиконов в сессию
		$glossary = array();
                $lang_lexicon = $alias;
		$sql = $modx->getConfig('company_lexicon');
		$items = json_decode($sql, true);
		foreach ($items as $item) {
          	$glossary[$item['plh']] = $item[$lang_lexicon]; 
        }
		$_SESSION['miniLang'] = $lang_lexicon;
                $_SESSION['glossary'] = $glossary;
		
		// Считывание языковых плейсхолдеров с префиксом lang и подстановка в них значений соответствующих языку страницы
		if ( preg_match_all("~\[\+lang\.(.*)\+\]~U",
    		$modx->documentOutput, $matches)) {
			foreach ($matches[1] as $placeholder) {
    			$modx->setPlaceholder('lang.'.$placeholder,$_SESSION['glossary'][$placeholder]);
  			}
		}

	}
}


Пояснения.

1. Предыдущая версия на событие OnDocFormSave упростилась и немного изменилась, ввиду того что теперь для разных языковых версий используется один шаблон, и нет необходимости выполнять сопряжение шаблонов как в случае использования дублирующих шаблонов.

2. Добавился код на событие OnParseDocument, собственно, это и есть управление лексиконами.
Здесь:
0 и 11 — это корневые папки русскоязычных страниц и англоязычных соответственно.
company_lexicon — имя параметра в таблице системных настроек, состоящий из префикса модуля ClientSettings и имени multiTV. Если вы префикс по умолчанию client_ не меняли, то у вас будет client_lexicon.

3. Учитывая, что на 2 языка теперь один комплект шаблонов и чанков, потребуется к каждому шаблону подключать оба ТВ altEn и altRu (или использовать один), так же потребуется пересмотреть организацию кнопок переключения языков, хлебных крошек и других элементов сайта в сторону их унификации (то что раньше легко решалось применением дублирующего шаблона). Здесь трудно дать один какой то рецепт, т.к. все сайты разные.

P.S. Использовались идеи e-kao и evoBabel, спасибо авторам указанных дополнений.
  • paic
  • 0
avatar
15. Вариант 2, с использованием отдельной дополнительной таблицы в базе данных.

15.1. Устанавливаем, если на сайте еще нет, ClientSettings и multiTV.

15.2. Создаем конфигурацию multiTV, у меня это файл lex.config.json с конфигурацией

{
    "display": "dbtable",
    "table": "lexicon",
    "caption": "Таблица языков",
    "fields": {
        "id": {
            "caption": "id"
        },
        "plh": {
            "caption": "Плейсхолдер"
        },
        "ru": {
            "caption": "Значение ru"
        },
        "en": {
            "caption": "Значение en"
        },
        "name": {
            "caption": "Назначение"
        }
    },
    "columns": [
        {
            "fieldname": "id",
            "width": "10"
        },
        {
            "fieldname": "plh",
            "width": "50"
        },
        {
            "fieldname": "ru",
            "width": "100"
        },
        {
            "fieldname": "en",
            "width": "100"
        },
        {
            "fieldname": "name",
            "width": "100"
        }
    ],
    "form": [
        {
            "caption": "Строка",
            "content": {
                "id": {},
                "plh": {},
                "ru": {},
                "en": {},
                "name": {}
            }
        }
    ],
    "configuration": {
        "radioTabs": 0,
        "sorting": 1
    }
}


15.3. Создаем конфигурацию для ClientSettings, у меня она такая

<?php
return [
	'caption' => 'Лексикон',
	'introtext' => 'Настройка языковых версий',
	
    'settings' => [
	    'lex' => [
            'caption' => 'Замены',
            'type' => 'custom_tv:multitv',
        ]
    ],
];


15.4. Создаем таблицу в базе данных, префикс таблицы надо указать свой, если он отличается от evo_

DROP TABLE IF EXISTS `evo_lexicon`;
CREATE TABLE `evo_lexicon` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `plh` varchar(50) NOT NULL DEFAULT '',
  `ru` varchar(255) NOT NULL DEFAULT '',
  `en` varchar(255) NOT NULL DEFAULT '',
  `name` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `plh` (`plh`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


15.5. Заполняем лексиконы.

15.6. Создаем плагин miniLang с таким кодом
/**
 * miniLang
 * 
 * Мультиязычность - создание альтернативных страниц и подключение лексиконов
 * 
 * @category    plugin
 * @version     1.0.2
 * @internal    @events OnDocFormSave,OnParseDocument
**/

switch ($modx->Event->name) {
    case 'OnDocFormSave': {
    	if ($mode == "new") {
               include_once(MODX_BASE_PATH.'assets/lib/MODxAPI/modResource.php');
        	$doc = new modResource($modx);
        	$doc->edit($id);
        	$template = $doc->get('template');
			$published = $doc->get('published'); // добавлено
			$pagetitle = $doc->get('pagetitle');
			$image = $doc->get('image');
			
			// Определение родителя альтернативной версии
			$parent = $doc->get('parent');
			if ($parent != 0) {
				$value = $doc->get('parent');
				$tmplvarid = '9';
				$out = $modx->db->getValue('Select contentid from '.$modx->getFullTableName('site_tmplvar_contentvalues').' where value="'.$value.'" and tmplvarid="'.$tmplvarid.'"');
			}else{ 
				$out = '11';
			}
			$newparent = $out;
			
			// Определение шаблона альтернативной версии исключено
			
			// Создание страницы альтернативной версии
        	        if ($published != 0) {  // Проверка через $published вместо $template
				$doc->create(array(
    				'pagetitle' => $pagetitle,
    				'template' => $template, // $template вместо $newtemplate
    				'parent' => $newparent,
				'published' => 0,
				'altRu' => $id,
				'image' => $image
			    ));
				
			// Запись id созданной альтернативной страницы в ТВ основной страницы
			$newid = $doc->save(true, false);
			$table = $modx->getFullTableName('site_tmplvar_contentvalues');
			$fields = array(
                    	'value' => $newid,  
                    	'contentid'  => $id,  
                    	'tmplvarid' => 10
                    );   
    			$modx->db->insert( $fields, $table);  
			}
    	        }
	}
	case 'OnParseDocument': {
		// Определение принадлежности страницы корневой папке языка
		if ($modx->documentObject['parent']=='0') {
  			$lang=0;
		} else {
  			$lang=$modx->documentIdentifier;
  			do {
    			foreach ($modx->documentMap as $mapEntry) {
      				$parentId=array_search($lang, $mapEntry);
      				if ($parentId) break;
    			}
    		if ($parentId) $lang=$parentId;
  			} while ($parentId);
		}
		
		// Псевдоним корневой папки языка
		if ($lang == 11) {$alias = 'en';}else {$alias = 'ru';}
		
		// Запись лексиконов в сессию
		$glossary = array();
                $lang_lexicon = $alias;
		$sql = $modx->db->query("SELECT * FROM " . $modx->getFullTableName('lexicon'));
               while ($row = $modx->db->getRow($sql)) {
          	    $glossary[$row['plh']] = $row[$lang_lexicon]; 
                }
		$_SESSION['miniLang'] = $lang_lexicon;
                $_SESSION['glossary'] = $glossary;
		
		// Считывание языковых плейсхолдеров с префиксом lang и подстановка в них значений соответствующих языку страницы
		if ( preg_match_all("~\[\+lang\.(.*)\+\]~U",
    		    $modx->documentOutput, $matches)) {
		    foreach ($matches[1] as $placeholder) {
    		    $modx->setPlaceholder('lang.'.$placeholder,$_SESSION['glossary'][$placeholder]);
  			}
		}
	}
}


Плагин аналогичный, как в п.14, отличается источником получения данных лексиконов и их обработкой.
Комментарий отредактирован 2021-04-19 19:45:25 пользователем paic
  • paic
  • 0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.