Как я делал локализацию сайта с помощю Яндекс.Переводчика

Как я делал локализацию сайта с помощю Яндекс.Переводчика

4373
11.02.2016

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

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

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

Для начала была сделана русскоязычная версия сайта (заказчик данную версию выбрал основной). Абсоллютно все кирилические надписи во всех компонентах в обязательном порядке были вынесены в языковые фразы. Очень рекомеую не оставлять в языковых фразах дефолтные заготовки (в стиле "а вдруг пригодится") - не пригодятся! (А если будут нужны - лучше новые создадите, зато себе работу сократите в проверке корректности перевода). Также рекомендую все общие фразы,которые используются в большом количестве шаблонов компонентов, выносить в языковой файл хедера (правда, для страниц, подключаемых без хедера фразы все равно придется дублировать или принудительно подключать языковой файл хедера).

В общем виде подключение языковых фраз выглядит, примерно, так:

use \Bitrix\Main\Localization\Loc as Loc;
Loc::loadMessages(__FILE__);
Loc::getMessage('MSG_KEY');

Начинающие разработчики 1С-Битрикс (хотя и многие уже достаточно опытные разработчики тоже) все оставляют кирилицу прямо в шаблоне, считая, что потом, позже, перенесут. И так и остается вся эта каша на годы в шаблоне. Хорошо, если сайт в одной языковой версии устраивает своих посетителей годами и производить его локализацию нет необходимости, а че если нет, как говорит Семен Слепаков? Поэтому приучайте себя всегда выносить кирилицу в языковые фразы. Не вы, так кто-то другой обязательно выразят вам за это свою благодарность.

Теперь считаем, что вся кирилица хранится в языковых фразах и перевод не будет столь уж сложным. Для того, чтобы вытянуть все языковые фразы для перевода воспользуемся модулем "Перевод" системы 1С-Битрикс (обладателям редакций без данного модуля придется писать скрипт, который за вас вытянет все языковые файлы и сформирует CSV-файл).

Открываем в админке Настройки - Локализация - Просмотр файлов. В поле "Путь" вписываем относительный путь до шаблона сайта. Ниже выбираем вкладку "Выгрузка файла" и на ней - "Выгрузить все переводы". Нажимаем "экспортировать". Таким образом получили CSV файл, состоящий из такого набора колонок:

"file"; "key"; "ru"; "ua"; "en"

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

Файл кладем где-то в системе (я обычно помещаю в upload), а в нашем скрипте-обработчике сразу добавляем первую фразу с указанием полного, абсоллютного пути до файла:

$fileName = $_SERVER["DOCUMENT_ROOT"] . '/upload/bitrix_templates_template.csv';

И файлик, в который запишем результат:

$fileName = $_SERVER["DOCUMENT_ROOT"] . '/upload/bitrix_templates_template_res.csv';

Дальше подключаем битриксовый класс для обработки CSV:

require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/classes/general/csv_data.php");

Битриксовую библиотеку я использую только для записи CSV-файлов. Для чтения уже давно (при использовании битриксового класса около года назад обнаружил проблемы для сложного файла, у которого символ-разделитель присутствовал также и в значениях) использую такую функцию:

function csv_to_array($filename='', $delimiter=',')
{
    if(!file_exists($filename) || !is_readable($filename)) return FALSE;
    $header = NULL;
    $data = array();
    if (($handle = fopen($filename, 'r')) !== FALSE)
    {
        while (($row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
        {
            if(!$header){
                $header = $row;
                foreach ($row as $key=>$value) {
                    if (strlen($value) > 0)$header[$key] = GetTranslitCode($value);
                }
            } else {
                foreach ($row as $key=>$value) {$row[$key]=$value;}
                $data[] = array_combine($header, $row);
            }
        }
        fclose($handle);
    }
    return $data;
}

GetTranslitCode - это обертка вокруг обычной битриксовой функции транслитерации:

function GetTranslitCode($text)
{
    $text = trim($text);
    if (strlen($text) <= 0) return false;
    $params = Array(
        "replace_space" => "-",
        "replace_other" => "-",
        "delete_repeat_replace" => "true",
        "use_google" => "false",
    );
    return CUtil::translit($text, "ru", $params);
}

Прогоняем наш csv-файл через обработку csv-файла и получаем ассоциативный массив из строк файла с заголовками в виде ключей:

$arRows = csv_to_array($fileName, ';');

Создаем с перезапись результирующий файл:

$fp = fopen($resFileName, 'w+');
fclose($fp);

Записываем сразу в результирующий файл заголовки:

$csvFile = new CCSVData();
$fields_type = 'R';
$delimiter = ";";
$csvFile->SetFieldsType($fields_type);
$csvFile->SetDelimiter($delimiter);
$arrHeaderCSV = array("file", "key", "ru", "ua", "en");
$csvFile->SaveFile($resFileName, $arrHeaderCSV);

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

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

$translator = new Yandex_Translate('ключ, полученный тут: api.yandex.ru/key/form.xml?service=trnsl');
$srPresent = array();
foreach ($arRows as $num => $row) {
    if(strlen($row['ru'])<=0) continue;
    if(strlen($row['ua'])<=0){
        if(isset($srPresent[$row['ru']])){
            $row['ua'] = $srPresent[$row['ru']];
        } else {
            $text = json_decode($translator->yandexTranslate('ru', 'uk', $row['ru']))->text;
            $row['ua'] = $text[0];
            $srPresent[$row['ru']] = $row['ua'];
        }
    }

    $csvFile->SaveFile($resFileName, array($row['file'],$row['key'],$row['ru'],$row['ua'],$row['en']));
}

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

Для загрузки языковых фраз снова открываем в админке Настройки - Локализация - Просмотр файлов. В поле "Путь" вписываем относительный путь до шаблона сайта. Ниже выбираем вкладку "Загрузка файла" и на ней выбираем наш файл, выбираем направление перевода "полностью заменить ..." (или только новые, если вы делали частичный перевод) и нажимаем "Импортировать"

Все! Теперь языковые фразы созданы и можно спокойно заниматься реализацией второй локализации сайта.

В процессе проработки данного механизма были использованы материалы:

UPDATE 2016-02-23

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

Для значений свойств этот недостаток можно обойти с помощью свойства типа "Справочник", заполняя UF_NAME основным языком, а другое поле (например, UF_DESCRIPTION) - забираем под второй язык. Но этого нельзя сделать для названий свойств - нет поля, в котором это хранить.

Из всей этой проблемы родилось решение: Создал отдельный языковой файл props.php, который подключил рядом с языковым файлом хедера:

if(LANGUAGE_ID=='ua'){
    Loc::loadMessages($_SERVER['DOCUMENT_ROOT'].SITE_TEMPLATE_PATH.'/props.php');
}

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

Дальше, дописываем функцию получения значений из справочника:

function GetFromVacabulary($filterEntity, $filterValues,$arSelect = array('ID', 'UF_NAME', 'UF_XML_ID','UF_DESCRIPTION'))
{
    if(!is_array($filterEntity) || empty($filterEntity)) return false;

    $cacheParams = array(
        'filterEntity' => $filterEntity,
        'filterValues' => $filterValues,
        'lang' => LANGUAGE_ID
    );
    $cache_id = md5(serialize($cacheParams));
    $cache_dir = __CLASS__ . '/' . $cacheParams['module'];
    $obCache = new CPHPCache;
    if ($obCache->InitCache(36000, $cache_id, $cache_dir)) {
        $arResult = $obCache->GetVars();
        $arResult['CACHE'] = 'Y';
    } elseif (\Bitrix\Main\Loader::includeModule('highloadblock') && $obCache->StartDataCache()) {
        if(LANGUAGE_ID=='ua'){
            $translator = new \Yandex_Translate($this::YANDEX_TRANSLATOR_KEY);
        }
        global $CACHE_MANAGER;
        $CACHE_MANAGER->StartTagCache($cache_dir);

        $hlblock_requests = \Bitrix\Highloadblock\HighloadBlockTable::getList(array("filter" =>$filterEntity))->fetch();
        if (!isset($hlblock_requests['ID'])) return false;
        $entity_requests = HL\HighloadBlockTable::compileEntity($hlblock_requests);
        $entity_requests_data_class = $entity_requests->getDataClass();
        $main_query_requests = new Entity\Query($entity_requests_data_class);
        $main_query_requests->setSelect($arSelect);
        if (!empty($filterValues)){
            $main_query_requests->setFilter($filterValues);
        }
        $result_requests = $main_query_requests->exec();
        $result_requests = new CDBResult($result_requests);

        $arResult['ITEMS'] = array();
        while ($row_requests = $result_requests->Fetch()) {

            if(LANGUAGE_ID=='ua'){
                if(strlen($row_requests['UF_DESCRIPTION'])>0){
                    $row_requests['UF_NAME']  = $row_requests['UF_DESCRIPTION'];
                } else {
                    $text = json_decode($translator->yandexTranslate('ru', 'uk', $row_requests['UF_NAME']))->text;
                    $text['ua'] = $text[0];

                    $row_requests['UF_NAME'] = $text['ua'];
                    $entity_requests_data_class::update($row_requests['ID'],array('UF_DESCRIPTION'=>$text['ua']));
                }
            }

            $arResult['ITEMS'][$row_requests["UF_XML_ID"]] = $row_requests;
            $CACHE_MANAGER->RegisterTag('hl' . $cacheParams['entity_id'] . $row_requests["UF_XML_ID"]);

        }
        $CACHE_MANAGER->RegisterTag('hl' . $cacheParams['entity_id']);
        $CACHE_MANAGER->EndTagCache();
        $obCache->EndDataCache($arResult);
    } else {
        $arResult['ITEMS'] = array();
    }

    return $arResult;
}

Функция получает на входе параметры для выбора, какой справочник взять, и параметры для фильтрации - какие значения нужны. На выходе для украинского языка будем иметь украиноязычные значения (если не заполнены - настроил перевод с помощью Яндекс.Переводчика).

Дальше - займемся названиями свойств. Вот пример для обработки умного фильтра:

$putLinesToLangFile = '';
foreach ($arResult['ITEMS'] as $key=>$arItem) {
    if($arItem['PROPERTY_TYPE']=='S' && $arItem['USER_TYPE']=='directory'){
        $tmp = $mainEntity->GetFromVacabulary(
            array('TABLE_NAME'=>$arItem['USER_TYPE_SETTINGS']['TABLE_NAME']),
            array('UF_XML_ID'=>array_keys($arItem['VALUES']))
        );
        if(!empty($tmp['ITEMS'])){
            foreach ($tmp['ITEMS'] as $hl_code => $hl_val) {
                $arItem['VALUES'][$hl_code]['VALUE'] = $hl_val['UF_NAME'];
            }

        }

    }

    if(strlen(Loc::getMessage($arItem['CODE']))>0){
        $arItem['NAME'] = Loc::getMessage($arItem['CODE']);
    } else {
        $arItem['NAME'] = $mainEntity->TranslateText($arItem['NAME']);
        $putLinesToLangFile .= '$MESS["'.$arItem['CODE'].'"] = "'.$arItem['NAME'].'";'.PHP_EOL;
    }

    $arResult['ITEMS'][$key] = $arItem;
}

if(strlen($putLinesToLangFile)>0){
    $current = file_get_contents($_SERVER['DOCUMENT_ROOT'].SITE_TEMPLATE_PATH.'/lang/ua/props.php');
    $current = substr($current,0,-2).$putLinesToLangFile.'?>';
    file_put_contents($_SERVER['DOCUMENT_ROOT'].SITE_TEMPLATE_PATH.'/lang/ua/props.php', $current);
}

Сначала прогоняем значения справочников через функцию, описанную выше, а затем - занимаемся обработкой названий свойста.

На основании символьного кода свойства проверяем наличие языковой фразы - если фразы нет, то производим автоматический перевод с помощью Яндекс.Переводчика и Записываем полученный результат в заготовку для записи в языковой файл. Далее, когда все свойства перебраны в языковой файл производим запись полученных языковых фраз: считываем наш кастомный языковой файл и дописываем в него новые фразы.

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



Благодарю за внимание! Делитесь вашими замечаниями в комментариях ниже.


P.S. Обращайтесь ко мне за приобретением лицензий и продлений на 1C-Битрикс "Управление сайтом", лицензий на облачную и коробочную версии Битрикс 24 а также за приобретением и внедрением готовых решений на базе 1С-Битрикс от партнеров. За более подробной информацией свяжитесь со мной любым удобным для вас способом


Комментарии

Еще никто не комментировал данную публикацию. Будьте первыми!

Добавить комментарий

captcha

Возврат к списку