Многоязычный каталог - второй сайт, переводы товаров и корзина
Собираем английскую версию каталога: второй сайт, переводы товаров, свои подписи, формат валюты и одна корзина.
Механика
Многоязычности контента в платформе нет, и это первое, что стоит принять. Языковые файлы переводят интерфейс: подписи компонентов, кнопки форм, тексты писем. Название товара, его описание и характеристики к языку интерфейса не привязаны вообще никак. Остальное собирается руками из мультисайтовости, дополнительных свойств инфоблока и собственного кода проекта.
Рабочая схема при этом одна: язык - это отдельный сайт платформы. У сайта есть
поле языка, и платформа берёт фразы из папки именно этого языка. Второй сайт
живёт в папке вида /en/ или на собственном домене со своим шаблоном.
Хранение переводов выбирают из двух вариантов, и рабочими оказываются оба, но по-разному. Копия инфоблока на каждый язык даёт простую выборку и обычные шаблоны без единой правки. Один инфоблок со свойствами-переводами держит цены и остатки в единственном месте. Копии удобнее контент-менеджеру, свойства - разработчику, а переиграть выбор через полгода стоит дорого.
Связь между языковыми карточками товара обязательна при любом из двух вариантов хранения. Без неё переключатель языка уводит покупателя на главную вместо той же самой страницы. Связку хранят свойством-привязкой или общим внешним кодом, а не совпадением названий.
Корзина привязана к сайту, и об этом на проекте узнают довольно поздно. Позиция корзины хранит идентификатор сайта, поэтому у посетителя выходят две независимые корзины. Заказ из товаров разных сайтов платформа собрать не даст, и снятие фильтра эту задачу не решает.
Цены при этом общие, а вот их внешний вид зависит от языка. Формат валюты хранится отдельно для каждого языка интерфейса, вместе со знаком и разделителями. Незаполненный английский формат - и на английской витрине появляется цена «1 990 руб.».
Обмен с учётной системой перетирает переводы при первой же полной выгрузке. Названия приезжают из справочника номенклатуры, и правки в админке живут ровно до ночи. Переводы поэтому хранят на стороне учётной системы и выгружают дополнительными реквизитами.
Шаги
- Завести второй сайт с нужным языком интерфейса, своей папкой или доменом.
- Выбрать хранение переводов: копия инфоблока на язык или свойства одного.
- Связать карточки языков ключом, чтобы переключатель вёл на ту же страницу.
- Вынести все подписи шаблона в языковые файлы и проверить каждую форму.
- Настроить формат валюты, заголовки страниц и почтовые шаблоны второго языка.
- Согласовать обмен так, чтобы полная выгрузка не стирала переводы.
Код
Читаем язык текущего сайта:
$site = \Bitrix\Main\SiteTable::getRow([ 'filter' => ['=LID' => SITE_ID], 'select' => ['LID', 'LANGUAGE_ID', 'DIR', 'SERVER_NAME'],]);$lang = $site['LANGUAGE_ID']; // из него платформа берёт папку языковых фраз// в консоли и в агенте текущего сайта нет: язык там задают явноЯзык - это поле сайта, а не пользователя и не значение в куках. Переключение языка на лету, в пределах одного сайта, штатными средствами не делается.
Заводим инфоблок второго языка:
$iblockId = CIBlock::Add([ 'IBLOCK_TYPE_ID' => 'catalog', 'NAME' => 'Catalog EN', 'CODE' => 'catalog_en', 'SITE_ID' => ['s2'], // инфоблок виден только английскому сайту 'LIST_PAGE_URL' => '/en/catalog/', 'DETAIL_PAGE_URL' => '/en/catalog/#SECTION_CODE#/#ELEMENT_CODE#/',]);// у копии инфоблока обязательна своя запись в торговом каталогеАдреса страниц живут в настройках инфоблока, и у второго языка они свои. Копия инфоблока при этом требует своей записи в торговом каталоге, иначе английская карточка останется без цены.
Связываем карточки двух языков:
$rs = CIBlockElement::GetList([], ['IBLOCK_ID' => $enIblockId, '=PROPERTY_TRANSLATION_OF' => $ruElementId], // привязка к русской карточке false, false, ['ID', 'NAME', 'DETAIL_PAGE_URL']);$translated = $rs->GetNext();// свойство заводят у карточек обоих языков, иначе связка односторонняя// общий внешний код тоже годится, но его перетирает обмен с учётной системойСвязка нужна не только переключателю языка. По ней же ищут позицию второго языка при переносе корзины и сверяют полноту перевода перед выкладкой.
Переводим подписи шаблона:
$MESS['SHOP_CART'] = 'Cart';
// /local/templates/shop/header.php\Bitrix\Main\Localization\Loc::loadMessages(__FILE__);echo Loc::getMessage('SHOP_CART'); // папка lang повторяет путь самого файла// у компонента фразы лежат в его собственной папке lang, рядом с шаблономПапка с переводом зеркалит путь файла шаблона, и в этом вся механика. Тиражные шаблоны иногда держат тексты мимо языковых файлов: в параметрах компонента или в отдельной папке с обработчиками форм.
Ставим переключатель на ту же страницу:
$url = $translated ? $translated['DETAIL_PAGE_URL'] : '/en/catalog/';echo '<a hreflang="en" href="' . $url . '">English</a>';// ту же связку отдают поисковику в шапке страницыecho '<link rel="alternate" hreflang="en" href="https://example.org' . $url . '">';// адрес в alternate обязан быть абсолютным, иначе поисковик его не учтётПеревода у части товаров не будет никогда, и это нормальное состояние каталога. Переключатель в таком случае ведёт в раздел второго языка, а не на главную и не в ошибку 404.
Кладём перевод в кэш компонента:
// template.php шаблона карточки товара$arResult['NAME_EN'] = $arResult['PROPERTIES']['NAME_EN']['VALUE'];$this->__component->setResultCacheKeys(['NAME', 'NAME_EN', 'IPROPERTY_VALUES']);// без ключа кэша перевод пропадёт при первом же попадании страницы в кэшСтавим заголовки второго языка:
// component_epilog.php выполняется мимо кэша компонентаif (LANGUAGE_ID === 'en' && $arResult['NAME_EN'] !== '') { $APPLICATION->SetTitle($arResult['NAME_EN']); $APPLICATION->SetPageProperty('title', $arResult['NAME_EN'] . ' | Shop'); $APPLICATION->SetPageProperty('description', $arResult['DESC_EN']);}SEO-шаблоны инфоблока одни на все сайты и вдобавок кэшируются. Поэтому заголовок и описание второго языка ставят кодом, а нужные поля заранее объявляют ключами кэша в шаблоне.
Задаём формат валюты для языка:
\Bitrix\Main\Loader::includeModule('currency');CCurrencyLang::Update('RUB', 'en', [ 'FORMAT_STRING' => '# RUB', // на русском сайте здесь стоит «# руб.» 'DEC_POINT' => '.', 'THOUSANDS_SEP' => ' ', 'DECIMALS' => 2,]);echo CCurrencyLang::CurrencyFormat(1990, 'RUB', true); // формат берётся по языку// курс валюты при этом общий: он не зависит ни от сайта, ни от языкаКурс валюты один на всю установку, а формат вывода - свой у каждого языка. Это разные настройки, и путают их регулярно, потому что лежат они на соседних вкладках.
Переносим корзину между сайтами:
\Bitrix\Main\Loader::includeModule('sale');$fuserId = \Bitrix\Sale\Fuser::getId();$from = \Bitrix\Sale\Basket::loadItemsForFUser($fuserId, 's1');$to = \Bitrix\Sale\Basket::loadItemsForFUser($fuserId, 's2');foreach ($from as $item) { $copy = $to->createItem('catalog', $item->getProductId()); // товар второго языка $copy->setFields(['QUANTITY' => $item->getQuantity(), 'CURRENCY' => 'RUB', 'LID' => 's2', 'PRODUCT_PROVIDER_CLASS' => 'CCatalogProductProvider']);}$to->save();// цену и количество пересчитывают заново: у второго сайта бывает свой тип ценыПри двух инфоблоках позицию подменяют по той же связке переводов: в корзину второго сайта кладут карточку второго языка, а не исходную. Перенос вешают на сам переключатель языка, иначе покупатель теряет собранную корзину на первом же клике и уходит.
Ограничения
Полноценного перевода контента в продукте нет, и обойти это нечем. Всё описанное выше - сборка из мультисайтовости, своих свойств и своего кода, поэтому сроки такой задачи считают как сроки своей разработки, а не как настройку.
Второй сайт расходует лицензию на дополнительный сайт, и решают это до старта работ. Вариант с дополнительными свойствами в одном инфоблоке обходится без этого, но требует правки каждого места вывода, включая чужие шаблоны компонентов.
Административный раздел остаётся на языке интерфейса пользователя, а не на языке сайта. Контент-менеджер редактирует две карточки товара в одной и той же админке, и полноту перевода за него никто не проверяет.
Двуязычным становится не только каталог, но и почти вся остальная витрина. Почтовые шаблоны привязаны к сайту, названия статусов заказа хранятся по языкам, а поиск индексирует каждый сайт отдельно и требует своего ограничения в параметрах.
Пустой перевод обходится дороже, чем честно отсутствующая страница второго языка. Английская карточка с русским текстом попадает в индекс и остаётся там надолго, поэтому непереведённые позиции лучше не публиковать вовсе.
Обновление платформы иногда задевает языковые настройки, и замечают это не сразу. Страницы второго языка падают с ошибкой про отсутствие языка, а лечится это восстановлением записей в языковых и региональных параметрах.
Типичные проблемы
На английской версии цена выводится как «1 990 руб.».
Формат валюты задан только для русского языка интерфейса, а английский остался пустым. Сам формат хранится отдельно для каждого языка интерфейса, вместе со знаком валюты.
После полной выгрузки английские названия стали русскими.
Переводы правили в админке сайта, а обмен перетирает эти поля данными справочника. Переводы поэтому хранят в учётной системе и выгружают дополнительными реквизитами номенклатуры.
У покупателя две корзины: русская и английская.
Позиция корзины привязана к сайту отдельным полем, и вторая версия заводит свою. Заказ, собранный из товаров сразу двух разных сайтов, платформа сохранить не даст вовсе.
Кнопка формы осталась на русском, хотя перевод есть.
Текст задан параметром компонента или лежит в файле мимо папки языковых фраз. Такое встречается в тиражных шаблонах, где формы вынесены в отдельную папку.
Страницы второго языка упали с ошибкой про язык.
После обновления платформы пропали записи самого языка и связанных с ним региональных настроек. Их заводят заново вручную в языковых параметрах и в региональных настройках продукта.
Заголовок английской карточки собран из русского шаблона.
SEO-шаблоны инфоблока задаются общими для всех сайтов установки и вдобавок попадают в кэш. Заголовок второго языка ставят своим кодом в эпилоге шаблона компонента, мимо кэша.
Частые вопросы
Как реализовать мультиязычность встроенными средствами Битрикс?
Интерфейс переводится языковыми файлами, а контент выносится на второй сайт со своим языком. Полноценного перевода пользовательского контента в коробке нет, и эту часть всегда дописывают.
Один инфоблок на два языка или два инфоблока?
Два инфоблока проще в выводе и понятнее контент-менеджеру, один со свойствами-переводами - надёжнее по ценам и остаткам. Выбор делают до наполнения каталога: переехать потом дороже, чем написать заново.
Нужна ли отдельная лицензия на второй сайт?
Второй сайт расходует лицензию на дополнительный сайт. Схема с дополнительными свойствами в одном инфоблоке лицензии не требует, но требует правок в каждом шаблоне.
Как объединить корзины двух языковых версий?
Переносить позиции при переключении языка, подставляя карточку нужного языка. Снимать фильтр по сайту в компоненте корзины бесполезно: заказ из товаров двух сайтов всё равно не создаётся.
Как сделать многоязычные категории при полной выгрузке из 1С?
Хранить наименования на нужных языках в самой учётной системе и выгружать их дополнительными реквизитами. Ручное переименование разделов в админке переживает только частичную выгрузку.
Смежное
-
Каталог товаров - оглавление подтемы
-
Данные на двух сайтах: каталог, цены, заказы, письма - как устроены данные двух витрин
-
Код для двух сайтов: текущий сайт, настройки, общие данные - как писать код под две версии
-
Второй сайт на одном ядре: домен, папка, шаблон, общие данные - настройка второй витрины по шагам
-
Мультисайтовость - устройство второго сайта
-
Обмен каталогом с несколькими сайтами и конфигурациями - обмен на две версии каталога
-
Валюты и курсы: несколько валют в магазине и пересчёт цен - курс и формат вывода цены
-
Карточка товара: вывод свойств, картинок и характеристик - куда вставлять переводы свойств
-
Каталог и продажи - устройство интернет-магазина целиком, от каталога и цен до заказа
-
Второй язык сайта: языки и сайты, тексты шаблона, переключатель - языковая версия самого сайта