Язык интерфейса пользователя и плагин i18n modified в Cotonti: полное руководство

Как работает $usr['lang'] в Cotonti и плагин i18n modified: локали, перевод страниц и категорий, extrafields, hreflang, мультиязычные URL и устранение неполадок.

0 Опубликована Опубликовано в: Cotonti Siena CMF > Cotonti - чтиво. Документация.

Переменная $usr['lang'] в Cotonti и плагин i18n modified: полное справочное руководство

Оглавление

  1. Введение
  2. Назначение переменной $usr['lang']
  3. Источники значения и приоритет
  4. Связанные переменные и константы
  5. Использование в PHP-расширениях
  6. Использование в шаблонах XTemplate
  7. Архитектура плагина i18n modified
  8. Ключевые сущности плагина
  9. Настройки плагина i18n modified
  10. Работа с локалями
  11. Перевод структуры (категорий)
  12. Перевод страниц
  13. Интеграция с дополнительными полями
  14. Переключатель языков в шапке сайта
  15. Теги страниц и переопределение заголовков
  16. Интеграция с тегами и корзиной
  17. Права доступа и безопасность
  18. Мультиязычные URL
  19. SEO-возможности
  20. Логирование и сообщения
  21. Практические сценарии
  22. Шаблоны и теги плагина
  23. Связанные функции ядра
  24. Типичные паттерны
  25. Устранение неполадок
  26. Заключение
  27. Полезные ссылки

1. Введение

Переменная $usr['lang'] — ключевой элемент многоязычной архитектуры Cotonti CMF. Она хранит код языка, применяемый к текущему посетителю сайта, и определяет, какие языковые пакеты будут загружены движком при обработке запроса. Понимание принципов работы этой переменной необходимо при разработке и локализации тем, модулей и плагинов.

Однако в современном Cotonti одной переменной $usr['lang'] уже недостаточно для организации полноценного мультиязычного сайта. Язык интерфейса — это лишь часть задачи. Вторая, не менее важная часть — это язык контента: заголовки статей, описания категорий, тексты страниц и дополнительные поля. Для решения этой задачи служит плагин i18n modified — модифицированная версия штатного плагина мультиязычности Cotonti.

Данное руководство объединяет два уровня знаний:

  • уровень ядра — переменная $usr['lang'], её источники, приоритет и применение в PHP и шаблонах;
  • уровень расширения — архитектура, настройки и возможности плагина i18n modified.

Материал построен на основе анализа исходного кода Cotonti V.1, файла system/common.php, файла system/functions.php, а также файлов плагина i18n modified: файла локализации, файла функций, обработчиков страниц, интеграции с дополнительными полями, интеграции с тегами, шаблонных тегов и конфигурации.


2. Назначение переменной $usr['lang']

$usr['lang'] — часть глобального массива $usr, который создаётся в system/common.php при инициализации пользователя. Переменная отвечает за идентификацию языка интерфейса для текущего запроса.

Значение $usr['lang'] используется движком в нескольких местах:

  • при загрузке языковых файлов ядра (main, users, admin, message);
  • при загрузке языкового файла темы ({theme}.{lang}.lang.php);
  • при загрузке языковых файлов расширений (модули и плагины);
  • при генерации пользовательских тегов в шаблонах;
  • при локализации дат, склонений и других форматных данных.

От значения $usr['lang'] зависит, какие строки интерфейса увидит пользователь: подписи кнопок, названия пунктов меню, системные сообщения, названия месяцев и дней недели, формы склонения числительных.

Помимо этого, $usr['lang'] используется в механизме выбора языка браузера через функцию cot_lang_determine(), которая анализирует заголовок HTTP_ACCEPT_LANGUAGE и сопоставляет его с доступными языковыми пакетами.


3. Источники значения и приоритет

Значение $usr['lang'] вычисляется в system/common.php в несколько этапов. Каждый этап имеет свой приоритет и условия применения.

3.1. Значение по умолчанию

При инициализации массива $usr устанавливается значение по умолчанию:

$usr = [
    'id' => 0,
    'name' => '',
    'ip' => cot_getCurrentUserIp(),
    'level' => 0,
    'sessionid' => '',
    'lastvisit' => 30000000000,
    'lastlog' => 0,
    'timezone' => cot_timezone_offset($cfg['defaulttimezone'], true),
    'timezonename' => $cfg['defaulttimezone'],
    'newpm' => 0,
    'messages' => 0,
    'theme' => $cfg['defaulttheme'],
    'scheme' => $cfg['defaultscheme'],
    'lang' => $cfg['defaultlang'],
    'maingrp' => COT_GROUP_GUESTS,
    'groups' => [COT_GROUP_GUESTS]
];

$cfg['defaultlang'] берётся из datas/config.php и представляет собой код языка сайта по умолчанию. Для гостей и неавторизованных посетителей это значение остаётся неизменным на протяжении всего запроса.

3.2. Значение для авторизованного пользователя

При успешной авторизации значение $usr['lang'] переопределяется:

$usr['lang'] = $cfg['forcedefaultlang'] ? $cfg['defaultlang'] : $row['user_lang'];

Логика выбора следующая:

  • если в конфигурации включена принудительная установка языка (forcedefaultlang = true), применяется $cfg['defaultlang'] для всех пользователей независимо от их личных предпочтений;
  • если принудительная установка выключена, используется личный язык пользователя, сохранённый в поле user_lang таблицы cot_users.

3.3. Приоритет источников

ИсточникУсловие примененияЗначение
$row['user_lang']Пользователь авторизован, forcedefaultlang = falseЛичный выбор языка
$cfg['defaultlang']forcedefaultlang = true или гостьЯзык сайта по умолчанию

3.4. Принудительная установка языка

Параметр forcedefaultlang управляется из административной панели: Панель администрирования → Конфигурация → Локализация.

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

3.5. Сохранение личного языка

При обновлении профиля пользователя значение сохраняется в таблице cot_users:

$db->update($db_users, ['user_lang' => $newLang], "user_id = {$usr['id']}");

После сохранения новое значение вступает в силу со следующего запроса, так как $usr['lang'] вычисляется заново при каждой инициализации.


4. Связанные переменные и константы

4.1. Переменная $lang

Глобальная переменная $lang — сокращённый синоним для $usr['lang']. Устанавливается в common.php после определения $usr:

$lang = $usr['lang'];

Используется в функциях ядра и расширениях, где нет прямого доступа к массиву $usr. Например, в функции cot_langfile():

function cot_langfile($name, $type = ExtensionsDictionary::TYPE_PLUGIN, $default = 'en', $lang = null): ?string
{
    global $cfg;
    if (!is_string($lang)) {
        global $lang;
    }
    // ...
}

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

4.2. Теги в шаблонах XTemplate

ТегНазначение
{PHP.usr.lang}Код языка из массива $usr
{PHP.lang}Синоним предыдущего
{HTML_LANG}Пользовательский тег, задаваемый плагином i18n modified
{PHP.i18n_locale}Код активной локали контента (плагин i18n modified)

4.3. Переменные плагина i18n modified

Плагин i18n modified вводит ряд собственных переменных, доступных в PHP-обработчиках и шаблонах:

ПеременнаяНазначение
$i18n_localeАктивная локаль контента
$i18n_localesМассив доступных локалей
$i18n_fallbackРезервная локаль (обычно язык по умолчанию)
$i18n_adminФлаг: является ли пользователь администратором i18n
$i18n_writeФлаг: имеет ли пользователь право на запись переводов
$i18n_readФлаг: имеет ли пользователь право на чтение переводов
$i18n_editФлаг: имеет ли пользователь право на редактирование переводов
$i18n_notmainФлаг: активная локаль не является языком по умолчанию

4.4. Константы ядра Cotonti

Константы групп (COT_GROUP_GUESTS, COT_GROUP_MEMBERS, COT_GROUP_ADMINS и другие) определяют права доступа, которые тесно связаны с правами i18n. Пользователь с правами администратора i18n может переводить структуру и управлять любыми переводами.


5. Использование в PHP-расширениях

5.1. Загрузка языкового файла расширения

Стандартный способ загрузки локализации модуля или плагина — функция cot_langfile():

require_once cot_langfile('myplugin', 'plug');
require_once cot_langfile('mymodule', 'module');

Функция автоматически подставляет текущий язык ($lang, то есть $usr['lang']) и подбирает соответствующий файл:

  • plugins/myplugin/lang/myplugin.{lang}.lang.php;
  • modules/mymodule/lang/mymodule.{lang}.lang.php.

5.2. Условные конструкции по языку

Проверка текущего языка используется для вывода разного контента разным аудиториям:

if ($usr['lang'] == 'ru') {
    $t->assign('WELCOME_MESSAGE', 'Добро пожаловать');
} elseif ($usr['lang'] == 'en') {
    $t->assign('WELCOME_MESSAGE', 'Welcome');
} else {
    $t->assign('WELCOME_MESSAGE', 'Hello');
}

5.3. Динамическое формирование тегов

Префикс языка может использоваться в имени тегов шаблона:

$tagName = 'INDEX_NEWS_' . strtoupper($usr['lang']);
$t->assign($tagName, $newsContent);

5.4. Генерация URL с языковым префиксом

При работе с плагином i18n modified URL формируется с параметром l, содержащим код языка:

$url = cot_url('page', ['c' => 'news', 'l' => $usr['lang']]);

Если язык совпадает с языком по умолчанию и включена опция omitmain, параметр l не добавляется.

5.5. Работа с функцией cot_lang_determine()

Функция cot_lang_determine() анализирует заголовок HTTP_ACCEPT_LANGUAGE и возвращает код языка, наиболее подходящий пользователю. Она используется при первичной установке Cotonti и в установщике:

$lang = cot_import('lang', 'P', 'ALP');
if (empty($lang)) {
    $lang = cot_lang_determine();
}

Функция возвращает код языка, только если для него существует файл main.{lang}.lang.php в каталоге /lang/ или /system/lang/.


6. Использование в шаблонах XTemplate

6.1. Прямой вывод кода языка

<html lang="{PHP.usr.lang}">

Однако для атрибута lang в HTML рекомендуется использовать полные IETF-теги (uk-UA, en-US), поэтому стандартный вывод кода часто недостаточен.

6.2. Условное отображение блоков

<!-- IF {PHP.usr.lang} == 'ru' -->
    <div class="block-ru">
        <p>Этот блок виден только русскоязычным пользователям.</p>
    </div>
<!-- ENDIF -->

<!-- IF {PHP.usr.lang} != 'en' -->
    <div class="block-non-en">
        <p>Этот блок скрыт от англоязычных пользователей.</p>
    </div>
<!-- ENDIF -->

6.3. Переключение языка

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

<!-- BEGIN: I18N_LANG -->
<div class="dropdown">
    <a class="btn-icon dropdown-toggle" data-bs-toggle="dropdown" title="{PHP.i18n_locale}">
        <i class="fa-solid fa-language me-2"></i>
        <small>
            <!-- IF {PHP.i18n_locale} == 'ru' -->RU<!-- ENDIF -->
            <!-- IF {PHP.i18n_locale} == 'en' -->EN<!-- ENDIF -->
            <!-- IF {PHP.i18n_locale} == 'ua' -->UA<!-- ENDIF -->
        </small>
    </a>
    <ul class="dropdown-menu dropdown-menu-end">
        <!-- BEGIN: I18N_LANG_ROW -->
        <li>
            <a class="dropdown-item" href="{I18N_LANG_ROW_URL}" title="{I18N_LANG_ROW_TITLE}">
                {I18N_LANG_ROW_TITLE}
            </a>
        </li>
        <!-- END: I18N_LANG_ROW -->
    </ul>
</div>
<!-- END: I18N_LANG -->

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

Плагин i18n modified генерирует полный IETF-тег для атрибута lang в теге <html>. Это позволяет использовать:

<html lang="{HTML_LANG}">

Значение HTML_LANG формируется на основе карты соответствий коротких кодов и полных IETF-тегов. Например, ua преобразуется в uk-UA.


7. Архитектура плагина i18n modified

Плагин i18n modified имеет модульную структуру. В нём можно выделить несколько слоёв.

7.1. Языковой файл

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

7.2. Функции API

В файле функций определены основные операции:

  • загрузка локалей;
  • загрузка переводов структуры;
  • получение перевода категории;
  • получение перевода страницы;
  • список локалей для категории и страницы;
  • проверка, включена ли интернационализация для категории;
  • построение пути категории с учётом перевода;
  • сохранение перевода;
  • интеграция с тегами.

Эти функции формируют ядро плагина и используются другими частями.

7.3. Обработчики страниц

Отдельный файл для перевода страниц обрабатывает добавление, редактирование и удаление переводов. Есть файл для перевода структуры, работающий в административной части. Есть файлы, подключаемые к хукам и добавляющие теги в шаблоны: header.tpl, page.tpl, а также переопределяющие теги страниц в функции генерации.

7.4. Конфигурация

В установочном файле перечислены параметры: cats, locales, omitmain, rewrite, cookie. Эти параметры определяют, какие категории участвуют в мультиязычности, какие локали доступны, как строится URL, и нужно ли запоминать язык в cookie.

7.5. Интеграция с дополнительными полями

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

7.6. Интеграция с тегами

Если установлен плагин tags, i18n modified добавляет колонку локали в таблицу связей тегов и изменяет первичный ключ. Это позволяет хранить теги отдельно для каждого языка.

7.7. Интеграция с корзиной

Если активен плагин trashcan и он настроен для корзины страниц, то при удалении перевода он сначала помещается в корзину.

Такая архитектура делает плагин довольно гибким. Он не монолитен: каждая часть отвечает за свою задачу. При этом все части связаны через общие функции и таблицы.


8. Ключевые сущности плагина

Чтобы понять, как работает плагин, нужно разобраться в нескольких ключевых сущностях.

  • Локаль — это код языка, например, ru, en, ua, pl. В настройках плагина локали указываются списком, каждая строка которого имеет формат code|Name. Например, en|English. Имя используется для отображения в переключателе языков. Код используется в URL и в базе данных.
  • Язык по умолчанию — это основной язык сайта. Он берётся из общей конфигурации Cotonti. Плагин автоматически добавляет его в список локалей, если его там нет. К языку по умолчанию могут применяться особые правила: например, параметр языка может быть опущен в URL, если соответствующая настройка включена.
  • Резервный язык (fallback) — это язык, используемый, если для текущей локали нет перевода. В файлах он упоминается как i18n_fallback. Судя по логике, если перевода нет, отображается оригинал на языке по умолчанию. Это стандартное поведение для мультиязычных систем.
  • Активная локаль — это язык, выбранный пользователем в данный момент. Он передаётся через параметр l в URL и может сохраняться в cookie, если включена соответствующая настройка.
  • Перевод страницы — это запись в отдельной таблице, содержащая заголовок, описание, текст и дополнительные поля для конкретной страницы и конкретной локали. Переводы страниц можно добавлять, редактировать и удалять. Каждый перевод имеет автора, дату и локаль.
  • Перевод структуры — это запись в другой таблице, содержащая заголовок и описание категории для конкретной локали. Переводами структуры управляет только администратор.
  • Дополнительные поля перевода — это поля, создаваемые через систему extrafields для таблицы переводов страниц. Они позволяют хранить произвольные данные на разных языках.
  • Категории, участвующие в i18n — это корневые категории, перечисленные в настройке cats. Если категория не входит в этот список, то механизмы мультиязычности могут к ней не применяться. Проверка выполняется путём определения родителей категории.
  • Оригинал — это исходная страница или категория на языке по умолчанию. Оригинал не перезаписывается переводом. Перевод существует параллельно.
  • Локализованное значение — это перевод страницы или категории. В интерфейсе перевода показываются оба варианта: оригинал и локализованное значение.

9. Настройки плагина i18n modified

В установочном файле перечислены пять основных настроек. Рассмотрим их подробно.

9.1. cats

«Коды категорий». Это список кодов категорий, для которых включена интернационализация. В русском языковом файле подсказка уточняет: «Коды категорий, разделённые запятыми». То есть администратор указывает, какие категории должны поддерживать переводы. Если категория не указана, она, вероятно, не будет участвовать в мультиязычности. Это позволяет не перегружать переводом те разделы, где он не нужен.

9.2. locales

«Локали сайта». Это список локалей сайта. В русском файле подсказка такая: «Каждая локаль на новой строке, формат: locale_code|Locale title». То есть каждая строка содержит код и отображаемое имя, разделённые вертикальной чертой. Например, en|English. Этот список используется для построения переключателя языков, для выбора локали при переводе и для проверки валидности локали.

9.3. omitmain

«Опускать языковой параметр в URL, если указан основной язык». Это переключатель. Если он включён, то для основного языка языковой параметр может не добавляться в URL. Это делает ссылки на основном языке чище. Если он выключен, языковой параметр добавляется всегда. В русском файле настройка описана как «Опустить языковой параметр в URL, если он указывает на основной язык».

9.4. rewrite

«Включить перезапись URL для языкового параметра». Включает SEO-дружественные URL для языкового параметра. В русском файле подсказка: «Требует ручного обновления .htaccess». То есть, если администратор хочет, чтобы язык в URL выглядел как часть пути, а не как параметр, ему нужно включить эту настройку и вручную обновить правила перенаправления на сервере. Это важный момент: плагин не делает этого автоматически.

«Запоминать выбор языка в cookie». Если включено, выбранный язык запоминается в cookie. Тогда при следующем визите пользователь автоматически увидит сайт на выбранном языке. В русском файле: «Запоминать выбранный язык в cookie».

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


10. Работа с локалями

Локали загружаются специальной функцией. Она разбивает строку по строкам, разбивает каждую строку по вертикальной черте, обрезает пробелы, проверяет, что код и имя не пустые, и добавляет локаль в массив. Если языка по умолчанию нет в списке, он добавляется автоматически. При этом имя берётся из общего списка языков Cotonti, если оно там есть; иначе используется сам код.

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

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

В переключателе языков для каждой локали формируется ссылка с сохранением текущих GET-параметров. Это важно: если пользователь находится на странице с фильтрами или параметрами, они не теряются при смене языка. Если включена настройка omitmain и текущая локаль совпадает с резервным языком, параметр l удаляется из URL. Если включена cookie и язык сохранён в cookie, логика omitmain может не применяться. Это сделано для избежания дублирования URL и конфликтов.

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


11. Перевод структуры (категорий)

Перевод структуры доступен только администратору. Это следует из проверки прав: перед выполнением сценария срабатывает блок, если у пользователя нет прав администратора i18n.

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

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

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

Таблица переводов структуры имеет пагинацию. Количество элементов на странице берётся из общей настройки maxrowsperpage, если она задана и положительна; иначе используется значение по умолчанию 15. Это позволяет комфортно работать с большим количеством категорий.

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

Если в настройке cats не указано ни одной категории, администратору показывается предупреждение со ссылкой на страницу настроек плагина. Это помогает быстро исправить конфигурацию.

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


12. Перевод страниц

Перевод страниц — это более сложный процесс, потому что он доступен не только администратору, но и переводчикам и авторам. В файле обработчика страниц есть три основные ветви: добавление, редактирование и удаление.

12.1. Добавление перевода

Сначала проверяется, что страница существует и идентификатор корректный. Если страницы нет, выдаётся 404. Затем загружаются данные страницы. Если действие — добавление, перевод не загружается; создаётся пустой массив. Если действие — редактирование, загружается существующий перевод для указанной локали.

При добавлении перевода сначала проверяется метод запроса. Если это POST, импортируется выбранная локаль. Проверяется, что локаль входит в список доступных. Если нет — ошибка «Недопустимая локаль». Затем проверяется, существует ли уже перевод для этой страницы и этой локали. Если да — ошибка «Перевод уже существует». Затем формируется массив данных перевода: идентификатор страницы, локаль, идентификатор переводчика, имя переводчика, дата, заголовок, описание, текст. Также импортируются дополнительные поля, если они есть. Проверяется длина заголовка: если она меньше двух символов — ошибка «Заголовок слишком короткий». Если ошибок нет, запись вставляется в базу данных. Затем выполняются хуки, выводится сообщение «Добавлено», ведётся логирование, формируется URL страницы с учётом локали и выполняется перенаправление.

Если запрос не POST или есть ошибки, отображается форма добавления перевода. В форме есть селектор локали, который исключает язык по умолчанию и уже существующие переводы. Показываются оригинальный заголовок, описание и текст страницы, а также поля для ввода перевода. Для текста используется визуальный редактор. Дополнительные поля выводятся в шаблон отдельными блоками.

12.2. Редактирование перевода

Редактирование доступно, если перевод существует и пользователь является администратором i18n, или имеет права на редактирование, или является автором перевода. При POST импортируется новая локаль. Проверяется её валидность. Если локаль изменилась, проверяется, существует ли уже перевод для новой локали. Если да — ошибка. Затем обновляются дата, заголовок, описание и текст. Если локаль изменилась, обновляется и она. Дополнительные поля импортируются с передачей старых значений. Если есть ошибки, форма отображается заново с заполненными полями. Если ошибок нет, запись в базе данных обновляется. Затем хуки, сообщение «Обновлено», логирование, формирование URL и перенаправление.

Форма редактирования также имеет селектор локали. Он исключает язык по умолчанию и занятые локали, кроме текущей. Значение селектора берётся из POST при ошибке или из текущей локали. Поля формы заполняются введёнными данными при ошибке или данными текущего перевода. Дополнительные поля выводятся аналогично добавлению.

12.3. Удаление перевода

Удаление доступно администратору или автору перевода. Если активен плагин trashcan и он настроен для корзины страниц, перевод сначала помещается в корзину. Затем запись удаляется из базы данных. Выполняются хуки, выводится сообщение «Удалено», ведётся логирование, формируется URL страницы и выполняется перенаправление.

Если действие не распознано или прав недостаточно, выдаётся сообщение об ошибке.

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


13. Интеграция с дополнительными полями

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

В форме перевода для каждого дополнительного поля генерируется элемент ввода и заголовок. Эти данные передаются в шаблон. Для каждого поля формируются теги с именем поля в верхнем регистре, а также общие теги EXTRAFLD. Это позволяет выводить дополнительные поля в шаблоне как по отдельности, так и в цикле.

В файле, который добавляет таблицу переводов в белый список дополнительных полей, сказано, что дополнительные поля можно создавать для таблицы i18n_pages. В описании указано, что шаблонные теги — I18N_PAGE_FORM_XXXXX и I18N_PAGE_FORM_XXXXX_TITLE. Это означает, что администратор может создать дополнительное поле, например, «material», и в шаблоне перевода использовать тег I18N_PAGE_FORM_MATERIAL.

Кроме того, дополнительные поля перевода выводятся в шапке и в page.tags. В шапке формируются теги I18N_HEADER_XXXXX, I18N_HEADER_XXXXX_TITLE, I18N_HEADER_XXXXX_VALUE. В page.tags формируются теги I18N_XXXXX_TITLE, I18N_XXXXX, I18N_XXXXX_VALUE, а также динамический блок EXTRAFLD.

В pagetags.main дополнительные поля перевода добавляются в массив тегов страницы с префиксом I18N_PAGE_. Если перевода нет, теги сбрасываются в пустые значения.

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


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

В файле header.tags.php реализован переключатель языков и генерация SEO-тегов. Рассмотрим его возможности.

Сначала строится выпадающий список языков. Для каждой локали определяется класс selected, если она совпадает с текущей. Сохраняются все GET-параметры. Если включена настройка omitmain и локаль совпадает с резервным языком, параметр l удаляется. Если включена cookie и язык сохранён в cookie, условие omitmain может не применяться. Определяется, находимся ли мы внутри плагина, и формируется URL. Если мы в админ-панели, URL строится с префиксом admin. Этот фикс важен, потому что без него переключение языка в админ-панели могло бы приводить к некорректному URL.

Затем в шаблон передаются теги: URL, код локали, флаг (для английского используется флаг Великобритании), заголовок, класс, selected. Эти теги можно использовать в header.tpl для отрисовки переключателя.

Далее устанавливается полный IETF-тег для атрибута lang. Для этого используется карта соответствия коротких кодов и полных тегов. Например, ua преобразуется в uk-UA. Если соответствия нет, используется короткий код. Полученное значение передаётся в шаблон как HTML_LANG. Это позволяет использовать <html lang="{HTML_LANG}"> в header.tpl.

Затем генерируются теги alternate hreflang для переведённых страниц. Если мы на странице модуля page и есть идентификатор, получается список локалей, на которые переведена страница. Для каждой локали, кроме языка по умолчанию, генерируется URL с параметром l и тег link с атрибутом hreflang. Также генерируется тег x-default. Если omitmain включён, x-default указывает на URL без языкового префикса. Если выключен, указывает на URL с префиксом языка по умолчанию. Все теги передаются в шаблон как ALTERNATE_TAGS.

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

Таким образом, шапка получает всё необходимое: переключатель, корректный lang, hreflang, x-default и дополнительные поля.


15. Теги страниц и переопределение заголовков

В файле page.tags.php назначаются теги для страницы. Если интернационализация включена, формируется список локалей для страницы. Если есть переводы, строится переключатель языка для страницы. Для каждой локали формируется URL с учётом алиаса или идентификатора, добавляется параметр l, если это необходимо. Передаются теги URL, код, заголовок, класс, selected.

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

Если пользователь является администратором, добавляется кнопка удаления перевода с подтверждением.

Также выводятся дополнительные поля перевода: для каждого поля формируются теги I18N_XXXXX_TITLE, I18N_XXXXX, I18N_XXXXX_VALUE, а также динамический блок EXTRAFLD.

Это позволяет page.tpl выводить переключатель языка, кнопки управления переводом и дополнительные поля.

В файле pagetags.main.php теги страниц переопределяются в функции генерации. Если интернационализация включена и текущий язык не основной, загружается перевод категории. Если есть перевод категории, формируются URL категории, URL валидации, URL редактирования, путь категории, хлебные крошки, заголовок категории, описание категории. Также добавляются ссылки администратора: редактирование, одобрение, отправка на одобрение. Если перевода категории нет, используется оригинал.

Если есть перевод страницы, формируются URL страницы, заголовок, хлебные крошки, описание, текст, обрезанный текст, флаг обрезки, ссылка «Читать далее», дата обновления. Также добавляются дополнительные поля перевода.

Если перевода нет, теги дополнительных полей сбрасываются.

Если у пользователя есть права на запись и перевод существует, добавляется ссылка на редактирование перевода.

Все эти теги сливаются с основными тегами страницы. Это позволяет шаблонам страниц использовать переведённые значения автоматически.

В файле page.main.php показано, как переопределяются заголовок страницы, подзаголовок и описание. Если интернационализация включена и текущий язык не основной, загружаются перевод страницы и перевод категории. Если есть перевод страницы, формируются параметры заголовка с учётом переведённого заголовка и переведённой категории. Устанавливается подзаголовок и описание. Затем данные перевода сливаются с данными страницы. Это означает, что все последующие обработчики видят уже переведённые значения.

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


16. Интеграция с тегами и корзиной

16.1. Интеграция с тегами

Если установлен плагин tags, i18n modified добавляет поддержку локали в теги. В функции установки интеграции проверяется, установлен ли tags. Если да, подключается API тегов. Затем проверяется, существует ли колонка tag_locale в таблице связей тегов. Если нет, она добавляется. После этого удаляется первичный ключ и создаётся новый, включающий tag_locale. Это позволяет хранить теги отдельно для каждого языка.

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

16.2. Интеграция с корзиной

Если активен плагин trashcan и он настроен для корзины страниц, при удалении перевода страницы он сначала помещается в корзину. Для этого загружается запись перевода, формируется описание и вызывается сервис корзины. Только после этого запись удаляется из таблицы переводов. Это позволяет восстановить удалённый перевод, если он был удалён по ошибке.


17. Права доступа и безопасность

Плагин использует несколько уровней прав. Переменные i18n_admin, i18n_write, i18n_read, i18n_edit, i18n_notmain, i18n_locale, i18n_fallback определяют, что пользователь может делать.

Администратор i18n может переводить структуру, редактировать и удалять любые переводы. Пользователь с правом на запись может добавлять переводы и редактировать свои переводы. Автор перевода может редактировать и удалять свой перевод. Гость может только читать.

Проверки прав выполняются перед выполнением действий. Если прав недостаточно, выдаётся сообщение об ошибке или выполняется перенаправление.

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


18. Мультиязычные URL

Плагин поддерживает несколько режимов URL. Языковой параметр может передаваться как l в строке запроса. Если включена настройка rewrite, язык может быть частью SEO-дружественного URL. Если включена настройка omitmain, параметр может быть опущен для основного языка. Если включена cookie, выбранный язык запоминается.

Это даёт гибкость: можно делать простые URL для основного языка и языковые префиксы для остальных. Можно использовать SEO-дружественные URL, но для этого нужно вручную обновить .htaccess.

Пример URL с параметром:

https://example.com/page.php?c=news&l=en

Пример URL с SEO-префиксом (при включённом rewrite):

https://example.com/en/news

Пример URL с omitmain для основного языка:

https://example.com/news

19. SEO-возможности

Плагин генерирует полный IETF-тег для атрибута lang, alternate hreflang теги для переведённых страниц и тег x-default. Это улучшает индексацию многоязычных страниц поисковыми системами. Также переводятся заголовки, описания и мета-теги, что положительно сказывается на SEO.

Генерация hreflang выполняется на основе списка локалей, на которые переведена текущая страница. Для каждой локали формируется URL с параметром l и тег <link rel="alternate" hreflang="...">. Тег x-default указывает на версию, которая будет показана пользователям, чьи языковые предпочтения не совпадают ни с одной из указанных локалей.


20. Логирование и сообщения

Плагин логирует действия: добавление, редактирование и удаление переводов страниц и категорий. Сообщения выводятся пользователю: «Добавлено», «Обновлено», «Удалено», «Недопустимая локаль», «Перевод уже существует», «Заголовок слишком короткий», «Нет товаров» и другие. Количество добавленных, обновлённых и удалённых элементов структуры выводится с подстановкой чисел.

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


21. Практические сценарии

  • Добавление нового языка. Администратор добавляет локаль в настройках, указывает код и имя. Язык появляется в переключателе. Затем переводит структуру и страницы.
  • Перевод статьи. Автор открывает страницу, нажимает «Перевести», выбирает локаль, заполняет заголовок, описание, текст и дополнительные поля. Сохраняет. Перевод появляется на сайте.
  • Перевод категории. Администратор заходит в перевод структуры, выбирает локаль, заполняет заголовки и описания категорий. Сохраняет.
  • Настройка SEO-дружественных URL. Администратор включает rewrite и обновляет .htaccess. Язык становится частью URL.
  • Запоминание языка. Администратор включает cookie. Пользователь выбирает язык, и он сохраняется.
  • Удаление перевода. Администратор или автор удаляет перевод. Если активен trashcan, перевод отправляется в корзину.

22. Шаблоны и теги плагина

Плагин использует несколько шаблонов: i18n.page.tpl для формы перевода страницы, i18n.structure.tpl для таблицы перевода структуры, i18n.locales.tpl для выбора локали. Также добавляются теги в header.tpl и page.tpl.

22.1. Теги в header.tpl

  • I18N_LANG_ROW_URL
  • I18N_LANG_ROW_CODE
  • I18N_LANG_ROW_TITLE
  • I18N_LANG_ROW_CLASS
  • I18N_LANG_ROW_SELECTED
  • HTML_LANG
  • ALTERNATE_TAGS
  • I18N_HEADER_XXXXX

22.2. Теги в page.tpl

  • I18N_LANG_ROW_*
  • PAGE_I18N_TRANSLATE
  • PAGE_I18N_DELETE
  • I18N_XXXXX
  • I18N_EXTRAFIELD_TITLE
  • I18N_EXTRAFIELD_VALUE

22.3. Теги в шаблоне перевода страницы

  • I18N_ACTION
  • I18N_TITLE
  • I18N_ORIGINAL_LANG
  • I18N_LOCALIZED_LANG
  • I18N_PAGE_TITLE
  • I18N_PAGE_DESC
  • I18N_PAGE_TEXT
  • I18N_IPAGE_TITLE
  • I18N_IPAGE_DESC
  • I18N_IPAGE_TEXT
  • I18N_PAGE_FORM_XXXXX
  • I18N_PAGE_FORM_XXXXX_TITLE
  • I18N_PAGE_FORM_EXTRAFLD
  • I18N_PAGE_FORM_EXTRAFLD_TITLE

22.4. Теги в шаблоне перевода структуры

  • I18N_ACTION
  • I18N_ORIGINAL_LANG
  • I18N_TARGET_LANG
  • I18N_CATEGORY_ROW_TITLE
  • I18N_CATEGORY_ROW_DESC
  • I18N_CATEGORY_ROW_CODE_NAME
  • I18N_CATEGORY_ROW_CODE_VALUE
  • I18N_CATEGORY_ROW_ITITLE_NAME
  • I18N_CATEGORY_ROW_ITITLE_VALUE
  • I18N_CATEGORY_ROW_IDESC_NAME
  • I18N_CATEGORY_ROW_IDESC_VALUE
  • I18N_CATEGORY_ROW_ODDEVEN
  • I18N_PAGINATION_PREV
  • I18N_PAGNAV
  • I18N_PAGINATION_NEXT

Это позволяет полностью настроить внешний вид под конкретный шаблон.


23. Связанные функции ядра

ФункцияНазначениеОтношение к языку
cot_langfile()Возвращает путь к языковому файлуИспользует $lang
cot_lang_determine()Определяет язык браузера по HTTP_ACCEPT_LANGUAGEВозвращает код языка
cot_declension()Склонение слов по числамУчитывает $lang
cot_get_plural()Определяет форму множественного числаУчитывает $lang
cot_date()Форматирование датыЛокализует названия месяцев и дней
cot_translit_encode()Транслитерация строкИспользует языковые таблицы
cot_translit_decode()Обратная транслитерацияИспользует языковые таблицы

23.1. Функция cot_langfile()

function cot_langfile($name, $type = 'plug', $default = 'en', $lang = null): ?string

Порядок поиска:

  1. lang/{$lang}/modules/{$name}.{$lang}.lang.php (для модуля);
  2. modules/{$name}/lang/{$name}.{$lang}.lang.php;
  3. modules/{$name}/lang/{$name}.{$default}.lang.php.

Если $lang не передан явно, берётся глобальная переменная $lang (синоним $usr['lang']).

23.2. Функция cot_lang_determine()

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


24. Типичные паттерны

24.1. Мультиязычные теги

Создание динамических тегов в PHP и вывод в шаблоне:

PHP:

$tagBase = 'WELCOME_' . strtoupper($usr['lang']);
$t->assign($tagBase, $welcomeText);

TPL:

<!-- IF {PHP.usr.lang} == 'ru' -->
    {WELCOME_RU}
<!-- ENDIF -->
<!-- IF {PHP.usr.lang} == 'en' -->
    {WELCOME_EN}
<!-- ENDIF -->

24.2. Условный вывод категории

Пример из модуля страниц, когда категория зависит от языка:

if ($usr['lang'] != $cfg['defaultlang']) {
    $category = $usr['lang'] . '_news';
} else {
    $category = 'news';
}

24.3. Работа с плагином i18n modified

При использовании плагина i18n modified для контента $usr['lang'] остаётся языком интерфейса, а $i18n_locale — языком контента. В шаблоне доступны оба тега:

<html lang="{HTML_LANG}">
    <body data-lang="{PHP.usr.lang}" data-content-lang="{PHP.i18n_locale}">

24.4. Локализация административной панели

Административная часть использует тот же $usr['lang'] для загрузки языкового файла админки:

if (defined('COT_ADMIN')) {
    require_once cot_langfile('admin', 'core');
}

Языковой файл админки — system/lang/{lang}/admin.{lang}.lang.php.


25. Устранение неполадок

25.1. Язык не переключается

Причина: параметр forcedefaultlang включён, из-за чего личный выбор пользователя игнорируется.

Решение: отключить forcedefaultlang в разделе Панель администрирования → Конфигурация → Локализация.

25.2. Тема не переводится

Причина: отсутствует файл локализации темы themes/{theme}/{theme}.{lang}.lang.php.

Решение: создать языковой файл для нужного языка или использовать английский файл в качестве fallback.

25.3. Плагин не переводится

Причина: отсутствует файл plugins/{plugin}/lang/{plugin}.{lang}.lang.php, и функция cot_langfile() возвращает только английский вариант.

Решение: создать файл локализации или обратиться к разработчику плагина.

25.4. Атрибут lang в <html> содержит короткий код

Причина: в шаблоне используется {PHP.usr.lang} напрямую без преобразования в полный IETF-тег.

Решение: использовать плагин i18n modified, который генерирует {HTML_LANG} в формате uk-UA, en-US и т. п.

25.5. Отличие $usr['lang'] от $i18n_locale

Причина: пользователь ожидает, что при выборе языка контента сменится и язык интерфейса.

Решение: в Cotonti язык интерфейса и язык контента — две независимые переменные. Для смены языка интерфейса пользователь должен выбрать язык в профиле. Плагин i18n modified управляет только языком контента.

25.6. Перевод не отображается на сайте

Причина: категория не входит в настройку cats плагина i18n modified.

Решение: добавить код категории в настройку cats в конфигурации плагина.

25.7. SEO-дружественные URL не работают

Причина: настройка rewrite включена, но .htaccess не обновлён.

Решение: вручную обновить правила перенаправления в .htaccess в соответствии с требованиями плагина.


26. Заключение

Переменная $usr['lang'] — фундаментальный элемент локализации Cotonti CMF. Она определяет, какие языковые файлы загружаются движком, используется в PHP-расширениях и шаблонах XTemplate, связана с глобальной переменной $lang, функцией cot_langfile() и другими инструментами i18n.

Плагин i18n modified расширяет возможности Cotonti, добавляя полноценную систему мультиязычного контента. Он включает:

  • переключатель языков в шапке сайта;
  • перевод структуры (категорий);
  • перевод страниц;
  • поддержку дополнительных полей;
  • SEO-теги hreflang и x-default;
  • интеграцию с плагинами tags и trashcan;
  • гибкие настройки URL и cookie;
  • систему прав доступа.

При разработке тем и расширений необходимо учитывать двойственность: язык интерфейса управляется $usr['lang'], язык контента — плагином i18n modified. Для SEO-корректной разметки рекомендуется использовать полные IETF-теги, формируемые на основе карты соответствий.

Все данные, приведённые в статье, основаны исключительно на анализе исходного кода Cotonti V.1, файлов system/common.php и system/functions.php, а также файлов плагина i18n modified: файла локализации, файла функций, обработчиков страниц, интеграции с дополнительными полями, интеграции с тегами, шаблонных тегов и конфигурации.


27. Полезные ссылки

Комментарии отсутствуют
Добавление комментариев доступно только зарегистрированным пользователям