Многоязычный сайт на 1C-Bitrix

Про настоящую, удобную мультиязычность на 1С-Битрикс с переводом через GPT и возможностью хардкодить языковые переменные, не создавая их в языковых файлах
Marchenkov Alex, Основатель и разработчик mrlexndr.com
Marchenkov AlexОснователь и разработчик

Многоязычный сайт на битриксе из коробки – боль. Поэтому вы оказались на этой странице. Архитектурно разработчики БУС более-менее сделали поддержку языковых переменных в отдельных файлах, которые вызываются через GetMessage(). Это требует времени и усидчивости, но работает. Переводы инфоблоках создателями не предусмотрены.

Единственным решением было сделать полную физическую копию инфоблоков, привязать к сайтам (s1, s2 и т.д.) и в коде вызывать нужный перевод. Это сложно даже для контентных сайтов, а про интернет-магазины с интеграцией остатков и цен с 1С молчу – много времени разработчика, много скриптов, перегруженные контент-менеджеры. Так дело не пойдет.

Настоящая мультиязычность – это один инфоблок, одно дерево категорий и контент, который адаптируется под пользователя на лету. Да, движок для сайта Битрикс крайне мощный. Но разрабатывать единый двуязычный (трех-, четырех- и т.д.) сайт – значит дублировать инфоблоки, раздувать базу данных, усложнять поддержку разработчиками и контент-менеджерами. Базовое ядро D7 не умеет хранить версии одного элемента даже на двух языках из коробки.

За 16 лет проектирования сайтов на bitrix я устал бороться с ограничениями платформы. Клиенты постоянно спрашивали, как поменять язык в битриксе без мучительного дублирования контента. Поэтому написал собственный архитектурный модуль mrlexndr.translate, который решает задачи мультиязычность 1с элегантно.

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

Боль 1: лавинообразные нагрузки (Cache Stampede)

Как принято: фразы интерфейса хранят в файлах /lang/ и выводят через битрикс GetMessage(). Включают кеширование компонентов, чтобы сервер не упал от тысяч запросов. В мультиязычной среде параметр текущего языка LANGUAGE_ID добавляют в идентификатор кеша.

Если на сайте пять языков, размер кеша увеличивается в пять раз. Менеджер меняет одну запятую, тегированный кеш сбрасывается для всех языков одновременно. Сервер получает шквал запросов к базе на перегенерацию страниц (Cache Stampede).

Стандартный подход (вызывает нагрузку при инвалидации)


// Использование стандартного ядра 
use Bitrix\Main\Localization\Loc; 
Loc::loadMessages(__FILE__);
// Если кеш сбросится, Битрикс пойдет искать файлы на диске 
echo Loc::getMessage('ORDER_BTN_TEXT');

Решение через модуль: PHP Array Generation и OPcache

Я отказался от механизма \Bitrix\Main\Data\Cache для статических словарей. Модуль атомарно генерирует физический файл vocab_ru.php (vocab_любойязык) в виде PHP-массива. Файл сохраняется в OPcache сервера. Доступ к переводу фразы происходит за время O(1) без единого запроса к базе данных или диску.


// Глобальный хелпер, работает напрямую из OPcache 
// 'Оформить заказ' - текст по умолчанию, если фразы еще нет в базе 
echo LANG_VAR('ORDER_BTN_TEXT', 'Оформить заказ');

Боль 2: костыли в шаблонах компонентов

Как принято: чтобы англоязычная версия сайта работала от единого дерева, программисты создают дополнительные свойства инфоблока: NAME_EN, PREVIEW_TEXT_EN. Затем идут в файлы result_modifier.php каждого компонента и пишут костыли. Многосайтовость bitrix здесь мстит за попытку от нее уйти.

Стандартный подход


// result_modifier.php стандартного news.list 
foreach ($arResult['ITEMS'] as &$item) {
   if (LANGUAGE_ID === 'en') {
       if (!empty($item['PROPERTIES']['NAME_EN']['VALUE']) {
           $item['NAME'] = $item['PROPERTIES']['NAME_EN']['VALUE'];
       }
       if (!empty($item['PROPERTIES']['PREVIEW_TEXT_EN']['VALUE'])) {
           $item['PREVIEW_TEXT'] = $item['PROPERTIES']['PREVIEW_TEXT_EN']['VALUE'];
       }
   } elseif (LANGUAGE_ID === 'es') {
       if (!empty($item['PROPERTIES']['NAME_ES']['VALUE']) {
           $item['NAME'] = $item['PROPERTIES']['NAME_ES']['VALUE'];
       }
       if (!empty($item['PROPERTIES']['PREVIEW_TEXT_ES']['VALUE'])) {
           $item['PREVIEW_TEXT'] = $item['PROPERTIES']['PREVIEW_TEXT_ES']['VALUE'];
       }
   }
} 
unset($item);

Решение через модуль: D7 ORM QueryHelper

Правильно делать локализацию не во View (шаблонах), а на уровне Model (запросов). Я вынес логику подмены полей в D7. Если нужен битрикс на английском, класс QueryHelper сам модифицирует массив SELECT до запроса в базу.


foreach ($arResult['ITEMS'] as &$item) {
    IblockHelper::substituteStandardFields($item);
}

Боль 3: ад контент-менеджера и перевод JSON

Как принято: при поддержке четырех языков карточка товара дублируется четыре раз. Заполнять вручную — каторга. Если лендинг собран в визуальном конструкторе (Sprint Editor), где данные лежат в JSON, перевод превращается в хирургическую операцию руками контент-менеджера.

Стандартный подход (мануальный парсинг JSON)


// Чтобы перевести Sprint Editor, программисты пишут такие скрипты:
$data = json_decode($element['PROPERTY_CONTENT_VALUE'], true);
foreach ($data['blocks'] as &$block) {
   if ($block['name'] === 'text') {
       // Переводим текст вручную или отправляем в API
       $block['value'] = my_custom_translate_func($block['value'], LANGUAGE_ID); //какая-то кастомная функция
   }
}

Решение через модуль: пакетный ИИ-переводчик

Фотография
Пример настроек автоматического перевода инфоблока с помощью модуля и GPT

Я встроил в админку асинхронный переводчик на базе YandexGPT. Менеджер заполняет контент только на русском, нажимает кнопку, и скрипт в фоне (AJAX-шагами) переводит свойства. Для Sprint Editor написал сервис JsonTranslator, который обходит JSON-дерево, вытягивает тексты, переводит пачкой через ИИ, раскладывает обратно и сохраняет верстку.


// В админке это делается по кнопке, но под капотом работает так:
$translatedJson = \mrLexndr\Translate\Lang::translateSprintJson(
   $jsonContent,
   'ru',
   'en',
   $apiKey,
   $folderId
);

Боль 4: игнорирование локальной типографики

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

Стандартный подход (хрупкие регулярки)


// Попытки расставить неразрывные пробелы вручную 
$text = preg_replace('/ (\w{1,2}) /u', ' $1 ', $text); 
$text = str_replace(' - ', ' — ', $text);

Решение через модуль: UniversalSpacer

Модуль прогоняет тексты через сервис UniversalSpacer. Он знает специфику языков: привязывает короткие предлоги в кириллице, ставит неразрывный пробел перед знаками пунктуации во французском (?, !, :) и корректно удаляет мусорные пробелы между CJK-иероглифами.


// В шаблоне или при сохранении элемента: 
$cleanText = \mrLexndr\Translate\Lang::typo($text, LANGUAGE_ID); 
// Автоматически маскирует HTML и применяет правила типографики языка

Боль 5: поиск нужной фразы в коде

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

Стандартный подход


# Разработчик ищет фразу через консоль сервера 
grep -r "ORDER_BTN_TEXT" /local/components/

Решение через модуль: Public Editor и CodeScanner

Я вывел управление фразами в публичную часть сайта в режиме Эрмитажа. Редактор наводит курсор на текст (вызванный через LANG_VAR) и кликает — откроется карточка редактирования. А если программист ищет шаблоны bitrix, в которых используется этот ключ, встроенный CodeScanner сам просканирует директории и выведет список файлов в админке.


// При вызове функции в режиме правки: 
echo LANG_VAR('ORDER_BTN_TEXT', 'Оформить'); 

// Она автоматически сгенерирует HTML-обертку для Эрмитажа: 
// // onclick="window.open('/bitrix/admin/...', '_blank');">Оформить

Боль 6: миграция языковых файлов и импорт переводов

Как принято: старый проект переводят на новую архитектуру. Встает задача перенести тысячи фраз из папок lang/ru/ и lang/en/. Разработчики пишут одноразовые скрипты-костыли. Когда дело доходит до работы с профессиональным бюро переводов, программисты выгружают данные в Excel прямыми SQL-запросами.

Стандартный подход (болезненный ручной перенос)


// Программист вынужден писать парсеры для каждого компонента
include '/local/components/my/comp/lang/ru/template.php';
foreach($MESS as $code => $val) {
   // Попытки вставить данные в базу...
}

Решение через модуль: встроенная миграция и экспорт/импорт

Специальная вкладка «Миграция» в админке. Скрипт рекурсивно обходит директорию /local/, находит файлы локализаций, извлекает переменные $MESS и раскладывает в Highload-блок с переводами из параллельных языковых папок.

Для обмена с бюро переводов реализовал нативный CSV экспорт и импорт. Выгрузили базу в один клик, отдали переводчикам, загрузили обратно — проект локализован.


// Модуль сам генерирует корректный CSV со всеми языковыми полями:
fputcsv($out, ['UF_CODE', 'UF_LANG_RU', 'UF_LANG_EN'], ';');
// И так же легко парсит загруженный файл, создавая или обновляя фразы

Боль 7: искажение брендовой терминологии (глоссарий)

Как принято: машинный перевод Yandex или Google по-разному переводит одни и те же термины. На одной странице кнопка называется «Cart», на другой — «Basket». Нейросеть может перевести узкие термины сложного B2B-продукта так, что клиенты не поймут. Программистам приходится писать регулярные выражения для автозамен после перевода.

Решение через модуль: системный глоссарий для GPT

Текстовое поле «Глоссарий» в настройках модуля со списком правил в формате [EN] О компании = About Us. Драйвер YandexGptDriver берет эти правила и инжектит в системный промпт нейросети перед каждым запросом.

Фотография
Использование глоссария с названием компании или уникальными для отрасли терминами

// Драйвер под капотом собирает промпт для YandexGPT:
$systemText .= "ОБЯЗАТЕЛЬНЫЙ ГЛОССАРИЙ (Термины использовать строго):\n";
$systemText .= $this->getGlossaryForLang('en') . "\n";
// В итоге нейросеть понимает контекст и никогда не ошибется в названии бренда

Боль 8: накопление мертвого кода (мусорщик)

Как принято: за годы жизни проекта старые шаблоны удаляются, а фразы в словаре остаются. Разработчики боятся удалять старые ключи, потому что никто не знает: «А вдруг ORDER_BTN_OLD_V2 еще используется?». В результате база пухнет от мусора.

Решение через модуль: тотальная индексация и мусорщик

Инструмент пошаговой индексации. Модуль ищет ключи в PHP-файлах проекта и записывает в поле UF_USAGE_COUNT количество использований фразы в коде.

Фразы со счетчиком 0 автоматически попадают во вкладку «Мусорщик». Список мертвого кода можно безопасно очистить одной кнопкой.


// Мусорщик делает элементарный запрос к HL-блоку
$rsGarbage = $class::getList([
   'filter' => [
       'LOGIC' => 'OR',
       ['=UF_USAGE_COUNT' => 0],
       ['=UF_USAGE_COUNT' => false]
   ]
]);
// И показывает вам все, что давно пора удалить

Боль 9: неграмотный контент на базовом языке (корректор)

Как принято: переводить текст, в котором изначально куча орфографических ошибок. Контент-менеджеры часто лепят прямые кавычки вместо правильных «елочек», используют дефисы вместо тире и допускают опечатки. Обучать типографике — бесполезно.

Решение через модуль: AI-Корректор для русского языка

Модуль прогоняет русский текст через YandexGptCorrector перед переводом на другие языки. Драйвер работает с жестким системным промптом литературного редактора: исправляет орфографию, ставит длинные тире и «елочки», но сохраняет авторский стиль и не ломает верстку внутри HTML-тегов.

Фотография
Исправление орфографических и пунктуационных ошибок с автоматическим типографированием

// Системная инструкция для GPT-корректора:
$systemText = "Ты — литературный корректор. Исправь ошибки и опечатки.\n";
$systemText .= "Заменяй прямые кавычки на елочки «...».\n";
$systemText .= "Не удаляй и не ломай HTML теги (
, )...";
// На выходе получаем кристально чистый текст

Резюме

Сделать мультиязычный Enterprise-проект на едином инфоблоке — сложная задача. Нельзя установить плагин и надеяться, что заработает. Проект должен выдерживать нагрузки и быть удобен в поддержке. Выносим статику в OPcache, динамику перехватываем на уровне ORM, рутинный перевод отдаем нейросетям, а очистку словарей — автоматическим сканерам-мусорщикам. Только так выходим на международный рынок без накопления технического долга.