Расширение
РасширениеПеревод дополнительных блоков Gutenberg

Перевод дополнительных блоков Gutenberg

Gato AI Translations for Polylang умеет переводить записи на основе блоков.

Плагин поставляется с поддержкой многих блоков из коробки. Для всего остального — ваших собственных блоков или блоков из сторонних плагинов, не включающих wpml-config.xml, — вы можете расширить поддержку с помощью хуков PHP.

Перевод строк

Чтобы зарегистрировать дополнительные переводимые атрибуты для блока, используйте фильтр gatompl:gutenberg_block_type_translatable_attribute_regexes.

Зачем нужны регулярные выражения?

Блок Gutenberg сохраняется в post_content в виде HTML-комментария, содержащего JSON-атрибуты блока, за которым следует отрендеренный HTML блока, например:

<!-- wp:my-plugin/my-block {"title":"Hello"} -->
<div class="wp-block-my-plugin-my-block">Hello</div>
<!-- /wp:my-plugin/my-block -->

Перевод блока означает нахождение конкретной подстроки для перевода внутри этой разметки, замену её переводом и сохранение всего остального нетронутым (имя блока, другие атрибуты, структура HTML, окружающие блоки). Регулярные выражения — это то, как плагин точно определяет, какую подстроку нужно заменить: шаблон до и после значения захватывается в группы, а само значение — это часть, которая подставляется.

Стандартные строковые атрибуты (хранящиеся в JSON блока)

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

Например, чтобы перевести атрибуты daysLabel, hoursLabel, minutesLabel и secondsLabel блока kadence/countdown — разметка которого выглядит так:

<!-- wp:kadence/countdown {"uniqueID":"_abc123","date":"2026-12-31 00:00:00","daysLabel":"Days","hoursLabel":"Hours","minutesLabel":"Minutes","secondsLabel":"Seconds"} -->
<div class="wp-block-kadence-countdown">…</div>
<!-- /wp:kadence/countdown -->

…зарегистрируйте атрибуты следующим образом:

add_filter(
    'gatompl:gutenberg_block_type_translatable_attribute_regexes',
    static function (array $regexes): array {
        $regexes['kadence/countdown'] = [
            'daysLabel'    => true,
            'hoursLabel'   => true,
            'minutesLabel' => true,
            'secondsLabel' => true,
        ];
        return $regexes;
    }
);

Значение true внутренне разворачивается в следующее регулярное выражение по умолчанию:

#(<!-- wp:%3$s \{.*?\"%2$s\":\")%1$s(\".*?\}/?-->)#

…где заполнители означают:

  1. %1$s → значение атрибута
  2. %2$s → имя атрибута
  3. %3$s → имя блока

Для атрибута daysLabel блока kadence/countdown заполнители подставляются как %3$skadence/countdown, %2$sdaysLabel, %1$sDays, что даёт:

#(<!-- wp:kadence/countdown \{.*?\"daysLabel\":\")Days(\".*?\}/?-->)#

Заменяется только Days; имя блока, остальные атрибуты и закрывающий комментарий сохраняются благодаря группам захвата.

Структура регулярного выражения:

#(всё до)значение атрибута(всё после)#

Строки, хранящиеся внутри HTML блока

Если значение хранится не в JSON-атрибутах, а внутри отрендеренного HTML, предоставьте собственное регулярное выражение. Вы можете использовать %s (вместо %1$s) на месте значения атрибута, а имя блока и атрибута задать непосредственно в выражении.

Пример — перевод атрибута content блока generateblocks/text. Его разметка выглядит так — обратите внимание, что Hello world не находится в JSON, а располагается между отрендеренными тегами:

<!-- wp:generateblocks/text {"uniqueId":"abc123","tagName":"p"} -->
<p class="gb-text">Hello world</p>
<!-- /wp:generateblocks/text -->

Регулярное выражение по умолчанию никогда не найдёт эту подстроку, поэтому вы предоставляете своё:

add_filter(
    'gatompl:gutenberg_block_type_translatable_attribute_regexes',
    static function (array $regexes): array {
        $regexes['generateblocks/text'] = [
            'content' => '#(<!-- wp:generateblocks/text [^>]*?-->\n?<[a-z0-9]+ ?[^>]*?>)%s(</[a-z0-9]+>\n?<!-- /wp:generateblocks/text -->)#',
        ];
        return $regexes;
    }
);

Когда одно и то же значение встречается в нескольких местах

Если один и тот же атрибут присутствует как в JSON-атрибутах, так и в HTML (или в двух разных местах), передайте массив регулярных выражений — каждое из них должно сработать, чтобы перевести все копии строки.

Например, в блоке generateblocks/media атрибуты alt и title хранятся как внутри htmlAttributes в JSON, так и в виде HTML-атрибутов на отрендеренном <img>:

<!-- wp:generateblocks/media {"mediaId":42,"htmlAttributes":{"alt":"Cat sitting","title":"My cat"}} -->
<figure class="gb-media"><img src="…" alt="Cat sitting" title="My cat"></figure>
<!-- /wp:generateblocks/media -->

Два регулярных выражения на атрибут — одно нацелено на JSON, другое на <img> — гарантируют синхронность обеих копий после перевода:

add_filter(
    'gatompl:gutenberg_block_type_translatable_attribute_regexes',
    static function (array $regexes): array {
        $regexes['generateblocks/media'] = [
            'htmlAttributes.alt' => [
                '#(<!-- wp:generateblocks/media \{.*?\"htmlAttributes\":\{.*?\"alt\":\")%s(\".*?\}.*?\} -->)#',
                '#(<!-- wp:generateblocks/media [^>]*?-->\n?.*<img [^>]*alt=\")%s(\"[^>]*?>.*\n?<!-- /wp:generateblocks/media -->)#',
            ],
            'htmlAttributes.title' => [
                '#(<!-- wp:generateblocks/media \{.*?\"htmlAttributes\":\{.*?\"title\":\")%s(\".*?\}.*?\} -->)#',
                '#(<!-- wp:generateblocks/media [^>]*?-->\n?.*<img [^>]*title=\")%s(\"[^>]*?>.*\n?<!-- /wp:generateblocks/media -->)#',
            ],
        ];
        return $regexes;
    }
);

Если значение атрибута является JSON-объектом, вы можете обратиться к конкретному под-свойству, используя . (точку) в имени атрибута, как показано выше с htmlAttributes.alt и htmlAttributes.title для generateblocks/media.

Отключение перевода для автоматически переводимого атрибута

Передача false или null удаляет перевод для атрибута, который плагин в противном случае переводил бы автоматически. Это полезно, например, чтобы исключить конкретный строковый атрибут из автоматического перевода в блоках, реализованных только на PHP, или для блоков, добавленных из wpml-config.xml, объявленные атрибуты которых вы не хотите переводить:

add_filter(
    'gatompl:gutenberg_block_type_translatable_attribute_regexes',
    static function (array $regexes): array {
        // Disable translation of the `header` attribute on the
        // `my-plugin/duplicate-alert` block (either form works)
        unset($regexes['my-plugin/duplicate-alert']['header']);
        $regexes['my-plugin/duplicate-alert']['implications'] = false;
        return $regexes;
    }
);

Перевод ссылок на сущности

Ссылки на сущности (ID записи/медиафайла/термина/меню, хранящийся в атрибуте блока) могут быть переопределены на соответствующую сущность целевого языка в момент перевода. Используйте один из следующих фильтров в зависимости от типа ссылки:

Тип ссылкиФильтр
Custom posts и медиафайлыgatompl:gutenberg_block_type_custompost_and_media_reference_attribute_regexes
Термины таксономииgatompl:gutenberg_block_type_taxonomy_term_reference_attribute_regexes
Меню по IDgatompl:gutenberg_block_type_menu_reference_by_id_attribute_regexes
Меню по sluggatompl:gutenberg_block_type_menu_reference_by_slug_attribute_regexes

Каждый фильтр принимает ту же структуру, что и фильтр переводимых атрибутов (true для регулярного выражения по умолчанию, строка или массив для пользовательских выражений).

Например, блок woocommerce/single-product хранит связанный продукт как числовой productId:

<!-- wp:woocommerce/single-product {"productId":42} /-->

При переводе записи это значение 42 (продукт на исходном языке) нужно переопределить на его аналог на целевом языке (например, 87). Отметьте productId как ссылку на custom post, чтобы плагин захватил и подставил ID в момент перевода:

add_filter(
    'gatompl:gutenberg_block_type_custompost_and_media_reference_attribute_regexes',
    static function (array $regexes): array {
        $regexes['woocommerce/single-product'] = [
            'productId' => true,
            // …или пользовательское регулярное выражение, если `productId` не хранится в стандартной JSON-форме:
            // 'productId' => '#(<!-- wp:woocommerce/single-product \{.*?\"productId\":)%s([,\}].*? /?-->)#',
        ];
        return $regexes;
    }
);

Используйте тот же шаблон для других типов ссылок. В разметке блока они выглядят одинаково — числовой ID или slug, встроенный в JSON, — разница лишь в том, как плагин преобразует его для целевого языка:

<!-- wp:my-plugin/related-category {"categoryId":17} /-->
<!-- wp:my-plugin/menu-picker {"menuId":5} /-->
<!-- wp:my-plugin/menu-picker {"menuSlug":"main-nav"} /-->
// Taxonomy term reference
add_filter(
    'gatompl:gutenberg_block_type_taxonomy_term_reference_attribute_regexes',
    static function (array $regexes): array {
        $regexes['my-plugin/related-category'] = [
            'categoryId' => true,
        ];
        return $regexes;
    }
);
 
// Menu reference by ID
add_filter(
    'gatompl:gutenberg_block_type_menu_reference_by_id_attribute_regexes',
    static function (array $regexes): array {
        $regexes['my-plugin/menu-picker'] = [
            'menuId' => true,
        ];
        return $regexes;
    }
);
 
// Menu reference by slug
add_filter(
    'gatompl:gutenberg_block_type_menu_reference_by_slug_attribute_regexes',
    static function (array $regexes): array {
        $regexes['my-plugin/menu-picker'] = [
            'menuSlug' => true,
        ];
        return $regexes;
    }
);

Поиск имён атрибутов

Самый быстрый способ узнать имена атрибутов и способ их хранения — выполнить GraphQL-запрос Translate custom posts и изучить поле ответа blockFlattenedDataItems для нужного блока.

Как выполнить этот запрос и прочитать его вывод, описано в руководстве Получение данных конструктора страниц для перевода.

Обработка блоков, атрибуты которых требуют предварительной обработки

Приведённые выше хуки предполагают, что значение атрибута, передаваемое через blockFlattenedDataItems, уже является значением для перевода (скаляр или массив).

Если значение обёрнуто — например, атрибут хранит <li>Some text</li>, а переводить нужно только Some text — его необходимо сначала извлечь с помощью фильтра gatompl:gutenberg_block_flattened_data_item_attributes.

Блок generateblocks/image — реальный пример: его атрибуты alt и title не представлены как самостоятельные атрибуты, они находятся внутри HTML innerContent и должны быть извлечены с помощью регулярного выражения.

add_filter(
    'gatompl:gutenberg_block_flattened_data_item_attributes',
    static function (?\stdClass $attributes, string $blockTypeName, \stdClass $blockDataItem): ?\stdClass {
        if ($attributes === null || $blockTypeName !== 'generateblocks/image') {
            return $attributes;
        }
 
        $innerContent = $blockDataItem->innerContent ?? null;
        if (!is_array($innerContent) || !isset($innerContent[0]) || !is_string($innerContent[0])) {
            return $attributes;
        }
        $html = $innerContent[0];
 
        if (preg_match('#<img [^>]*alt="([^"]*)"[^>]*?>#', $html, $matches) === 1 && $matches[1] !== '') {
            $attributes->alt = $matches[1];
        }
        if (preg_match('#<img [^>]*title="([^"]*)"[^>]*?>#', $html, $matches) === 1 && $matches[1] !== '') {
            $attributes->title = $matches[1];
        }
        return $attributes;
    },
    10,
    3
);

После того как alt и title окажутся в объекте атрибутов, хуки на основе регулярных выражений смогут обращаться к ним так же, как к любому другому атрибуту.

Где найти примеры

Собственные интеграции плагина — отличные примеры из реальной жизни. Изучите следующие файлы внутри установленного плагина:

  • Регулярные выражения атрибутов блоков: wp-content/plugins/gato-ai-translations-for-polylang/src/Constants/BlockTypeAttributeValues.php
  • Предварительная обработка атрибутов блоков: wp-content/plugins/gato-ai-translations-for-polylang/src/ConditionalOnContext/LicenseIsActive/Hooks/CoreBlockFlattenedDataItemAttributesHookSet.php