Как я делал переключатели языков в MODX

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

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

В настоящем программировании редко когда удается сделать что-то, что будет у всех на виду и этим можно будет хвалиться. Интерфейсы рисуют дизайнеры, тексты пишут авторы, а программисты добавляют всему этому капельку “магии” и заставляют всё вместе работать. Это тоже прекрасно и доставляет удовольствие, но иногда так не хватает возможности сказать: “Смотрите, а эту штуку на экране сделал я!” и показать результат работы своим друзьям. Примерно с такими мыслями я в свое время решил доделать то, что было начато в третьей версии MODX.

Ещё в самых начальных попытках делать MODX 3, Вася Наумкин удалил системную настройку manager_language. И не просто удалил, а сделал так, чтобы язык хранился в сессии и его можно было поменять в момент входа в систему. Стало намного удобнее того варианта, когда нужно было менять системную настройку. Нашлись и противники решения, так как пропала возможность заранее настроить доступ пользователям с нужным языком, что тоже довольно весомый аргумент. Куда важнее оказалось то, что пропала возможность вообще переключить язык без выхода из аккаунта. Но так как текущий язык хранится в сессии, ничего не мешает его в этой же сессии и поменять, нужно только дописать механизм такой смены.

Вторая проблема с языками появилась вместе с новым оформлением процесса установки. В старом виде выпадающий список языков выглядел в целом органично. В новом же, красивом, с шагами, старый неоформленный select выглядел совсем куце и скорее раздражал своим видом, нежели помогал. Решение нашлось не сразу, но было хорошо воспринято сообществом. Новый вид выбора языка обладает своими преимуществами и я далее в заметке о них расскажу, а теперь же вернемся к деталям реализации и посмотрим, как всё делалось поэтапно и какие задачи пришлось решать в процессе.

Переключатель языка

Старый список языков на странице входа был крайне избыточен, в нем было 186 языков, тогда как в самом MODX в виде переводов интерфейса доступно только 30, включая основной английский. Первым делом я удалил этот список языков и заменил его на актуальный, перенеся названия языков в константы класса SecurityLoginManagerController. Конечно же, следом пришлось переписать код для получения и вывода обновленного списка, так как раньше он формировался просто из файла словаря.

Держать строки, которые могут и должны подлежать переводу, жёстко записанными в коде, крайне плохая практика, поэтому я их перенес в файл словаря, но уже в новом формате. И следом же я добавил новый контроллер, задачей которого стало переключение языка в сессии. Код переключателя довольно примитивный, из параметров запроса он получает язык, на который нужно переключить интерфейс и меняет его в сессии, затем перезагружает страницу.

<?php

public function process(array $scriptProperties = [])
{
    $targetLanguage = $this->modx->getOption('switch', $scriptProperties, 'en');

    if (!in_array($targetLanguage, $this->modx->lexicon->getLanguageList())) {
        return;
    }

    $_SESSION['manager_language'] = $targetLanguage;

    $this->modx->sendRedirect(MODX_MANAGER_URL);
}

Выбор языка в интерфейсе

Для того, чтобы можно было выбрать язык для переключения в интерфейсе, я добавил специальный метод getLanguageMenu к классу modMenu, который сформировал список языков в главном меню (позже пришлось переместить пункт в меню профиля пользователя). После некоторой возни с оформлением этого списка, а он получился большой, я добавил разрешение, чтобы иметь возможность ограничить возможность менять язык. Ситуации бывают разные, к тому же MODX славится своей гибкостью, поэтому пришлось соответствовать общепринятым канонам.

Решение в целом было рабочее, но благодаря ценным советам от других разработчиков, получилось его ещё немного усовершенствовать. Проблема крылась в том, что всё ещё сохранялся жестко записанный список языков в классе modLexicon с оригинальными названиями языков, т.е. когда название языка записано на этом же языке, чтобы можно было легко отыскать знакомые слова. Более того, по задумке название языка так же нужно было переводить на другие языки, чтобы в интерфейсе на русском было написано по-русски “Английский язык”, для целостности восприятия.

Кеширование списка языков

Файлы словарей со списком языков были созданы, но чтобы сформировать список для пользователя, нужно было бы каждый раз при открытии страницы загружать и обрабатывать все 30 файлов, чтобы получить самоназвание языка. Следовательно, нужно было как-то эти списки кешировать. Сами словари и так уже кешируются в MODX, нужно было только добавить новый кэш и положить его в уже существующий каталог lexicon_topics. Код довольно простой, при запросе самоназвания языка вызывается метод getLanguageNativeName, который читает кэш и если значение в кэше есть, просто его возвращает, если же нет, загружает словарь для запрошенного языка, получает из него значение и сохраняет в кэш. Да, при первой загрузке списка он всё так же перебирает 30 файлов, но как только кэш сформирован, в этом больше нет необходимости.

<?php

public function getLanguageNativeName($language)
{
    $options = [xPDO::OPT_CACHE_KEY => self::CACHE_DIRECTORY];

    $names = $this->modx->cacheManager->get(self::NATIVE_CACHE, $options) ?: [];

    if (!array_key_exists($language, $names)) {
        $this->modx->lexicon->load("$language:core:languages");
        $names[$language] = $this->modx->lexicon('language_' . $language, [], $language);
        $this->modx->cacheManager->set(self::NATIVE_CACHE, $names, 0, $options);
    }

    return $names[$language];
}

В итоге получился в меру удобный способ переключить язык интерфейса прямо на лету, где основное название языка представлено в том виде, в котором оно знакомо носителям, но при этом всегда есть перевод на язык интерфейса в данный момент и так же есть код языка в международном формате, если первые два варианта всё ещё вызывают сомнения. Задача была не самой простой, но весьма интересной и надеюсь результат устроит будущих пользователей MODX 3.

Интерфейс переключения языка в MODX 3

Полный список изменений можно посмотреть на GitHub. Просто кликайте на нужный комит, чтобы посмотреть, какие изменения были им помечены.

Выбор языка при установке

Дизайн MODX 3, нарисованный в Sterc, достаточно бодро оформили в код и показали рабочий концепт, но вот с экранами установки получилась неувязка и установщик долгое время был старый, как в MODX Revolution. Через некоторое время Ваня Бочкарёв проделал огромную работу по переписыванию установщика в соответствии с макетами. Я подключился позже, после его просьбы помочь с некоторыми моментами по части логики работы установщика.

С логикой я разобрался, но как я писал в начале заметки, выпадающий список языков меня раздражал и хотелось его как-то исправить. Раздражал он тем, что занимал целый экран, будучи простым выпадающим списком. Переделывать порядок экранов или менять их количество очень не хотелось, код в недрах установщика откровенно говоря воняет.

Концепт без списка

Идея заменить список блоками с названиями языков пришла довольно быстро, после некоторого обсуждения вариантов она и осталась основной, но нужно было переделать сам механизм вывода языков на страницу, проведя значительный рефакторинг. К тому же хотелось унифицировать вид отображения доступных языков с таким же списком в панели управления, т.е. показывать самоназвание языка, его перевод на текущий выбранный и код на всякий случай. Чтобы этого добиться и оставить имеющуюся форму рабочей, select было решено заменить на radio, но замаскировать это средствами CSS.

Поиграв немного с размерами блоков, было решено оставить четыре в ряд. Делать размеры блока больше не было смысла, а меньше не позволяла длина строки с названием языка. Но возникла другая проблема, так как доступных языков 30 штук, при условии 4 в ряд, получалось 8 рядов, что крайне много и нужно было с этим что-то делать.

Самые популярные языки

Было понятно, что нужно как-то ограничить список языков и первая же идея была оставить только самые ходовые, а остальные спрятать и показывать при необходимости. Закономерно возник вопрос, а какие из них считать ходовыми или популярными? Я обратился за помощью к Марку Хамстре и он поделился статистикой своих сервисов (сервис мониторинга сайтов на MODX – SiteDash и магазин дополнений для MODX – modmore), которые собирают эти данные от подключенных сайтов. В русскоязычном сегменте самым популярным языком всегда будет русский, поэтому было важно узнать распределение “там”. Имея на руках список из семи самых популярных языков я начал писать логику их отображения.

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

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

<?php
$popular = ['en', 'de', 'nl', 'ru', 'fr', 'it', 'es'];
$exists = array_search($current, $popular, true);
array_unshift($popular, $exists ? $popular[$exists] : $current);
$popular = array_slice(array_unique($popular), 0, 7);

# -- some code here

$parser->set('popular', $popular);
$parser->set('others', array_diff_key($languages, $popular));

Для переключения между режимами “Только популярные” и “Все языки” был добавлен простой скрипт на чистом JavaScript, который просто показывает или прячет блоки с остальными языками в зависимости от состояния кнопки. Получилось симпатично на мой взгляд и в меру функционально. Язык по умолчанию выбирается автоматически, языки имеют и самоназвание и перевод, а большие панели-кнопки удобны для выбора на телефоне, если вдруг такая необходимость возникнет.

Выбор языка из популярных в установщике MODX 3

Список всех языков в установщике MODX 3

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

Постскриптум

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

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

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

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

На чытанне спатрэбілася 8 хвілін Нататка змяшчае 1636 слоў

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