การขยาย
การขยายการแปล Gutenberg blocks เพิ่มเติม

การแปล Gutenberg blocks เพิ่มเติม

Gato AI Translations for Polylang สามารถแปลโพสต์ที่ใช้ block-based ได้

ปลั๊กอินมาพร้อมกับการรองรับ blocks หลายประเภทตั้งแต่แรก สำหรับกรณีอื่น ๆ ที่นอกเหนือจากนั้น ไม่ว่าจะเป็น custom blocks ของคุณเอง หรือ blocks จากปลั๊กอินบุคคลที่สามที่ไม่ได้มาพร้อมกับ wpml-config.xml คุณสามารถขยายการรองรับได้ผ่าน PHP hooks

การแปลสตริง

หากต้องการลงทะเบียน attributes ที่แปลได้เพิ่มเติมสำหรับ block ให้ใช้ filter gatompl:gutenberg_block_type_translatable_attribute_regexes

ทำไมต้องใช้ regex?

Gutenberg block ถูกบันทึกลงใน post_content ในรูปแบบ HTML comment ที่บรรจุ JSON attributes ของ block ตามด้วย HTML ที่ render แล้วของ block เช่น:

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

การแปล block หมายถึงการหา substring ที่ต้องการแปลใน markup นั้น แทนที่ด้วยคำแปล และปล่อยให้ส่วนที่เหลือทุกอย่างไม่เปลี่ยนแปลง (ชื่อ block, attributes อื่น ๆ, โครงสร้าง HTML, blocks โดยรอบ) Regex คือวิธีที่ปลั๊กอินระบุ substring ที่จะแทนที่ได้อย่างแม่นยำ: boilerplate ก่อนและหลังค่าถูกจับอยู่ใน capture groups ส่วนค่าเองคือส่วนที่ถูกสลับ

String attributes มาตรฐาน (เก็บไว้ใน JSON ของ block)

หาก property เป็น string ปกติที่เก็บอยู่ใน JSON attributes ของ block ให้ส่ง true แล้วปลั๊กอินจะใช้ regex ค่าเริ่มต้น

ตัวอย่างเช่น หากต้องการแปล attributes daysLabel, hoursLabel, minutesLabel และ secondsLabel ของ block kadence/countdown ซึ่ง markup มีลักษณะดังนี้:

<!-- 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 -->

…ลงทะเบียน attributes ผ่าน:

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 จะถูกขยายภายในเป็น regex ค่าเริ่มต้นนี้:

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

…โดย placeholders มีความหมายดังนี้:

  1. %1$s → ค่าของ attribute
  2. %2$s → ชื่อของ attribute
  3. %3$s → ชื่อของ block

สำหรับ attribute daysLabel บน kadence/countdown placeholders จะถูกแทนที่เป็น %3$skadence/countdown, %2$sdaysLabel, %1$sDays ได้ผลลัพธ์เป็น:

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

เฉพาะ Days เท่านั้นที่ถูกแทนที่ ชื่อ block, attributes อื่น ๆ และ closing comment ถูกรักษาไว้โดย capture groups

รูปแบบของ regex คือ:

#(everything before)attribute value(everything after)#

Strings ที่เก็บอยู่ใน HTML ของ block

หากค่าไม่ได้เก็บอยู่ใน JSON attributes แต่อยู่ภายใน HTML ที่ render แล้ว ให้ระบุ regex ของคุณเอง คุณสามารถใช้ %s (แทน %1$s) ในตำแหน่งที่ค่า attribute จะไป และ hardcode ชื่อ block กับชื่อ attribute ไว้ใน regex

ตัวอย่าง — การแปล attribute content ของ block generateblocks/text markup มีลักษณะดังนี้ — สังเกตว่า Hello world ไม่ได้อยู่ใน JSON แต่อยู่ระหว่าง rendered tags:

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

regex ค่าเริ่มต้นจะไม่พบ substring นั้น จึงต้องระบุ regex ของคุณเอง:

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;
    }
);

เมื่อค่าเดียวกันปรากฏในหลายที่

หาก attribute เดียวกันปรากฏทั้งใน JSON attributes และ ใน HTML (หรือในสองตำแหน่งที่แตกต่างกัน) ให้ส่ง array ของ regexes โดยแต่ละ regex จำเป็นต้องทำงานเพื่อแปลทุก copy ของสตริง

ตัวอย่างเช่น บน block generateblocks/media นั้น alt และ title ถูกเก็บไว้ทั้งใน htmlAttributes ใน JSON และเป็น HTML attributes บน <img> ที่ render แล้ว:

<!-- 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 -->

regex สองตัวต่อ attribute หนึ่งตัวกำหนดเป้าหมายที่ JSON และอีกตัวกำหนดเป้าหมายที่ <img> เพื่อให้มั่นใจว่าทั้งสอง copy ยังคงซิงค์กันหลังการแปล:

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;
    }
);

หากค่า attribute เป็น JSON object คุณสามารถกำหนดเป้าหมายที่ sub-property เฉพาะได้โดยใช้ . (จุด) ในชื่อ attribute ดังที่แสดงด้านบนด้วย htmlAttributes.alt และ htmlAttributes.title บน generateblocks/media

การปิดการแปลสำหรับ attribute ที่ถูกแปลอัตโนมัติ

การส่ง false หรือ null จะลบการแปลสำหรับ attribute ที่ปลั๊กอินจะแปลโดยอัตโนมัติ ซึ่งมีประโยชน์ เช่น เมื่อต้องการยกเว้น string attribute เฉพาะจากการแปลอัตโนมัติใน PHP-only blocks หรือสำหรับ blocks ที่นำเข้าจาก wpml-config.xml ซึ่งมี attributes ที่ประกาศไว้แต่คุณไม่ต้องการให้แปล:

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;
    }
);

การแปล entity references

Entity references (ID ของโพสต์/มีเดีย/taxonomy/เมนูที่เก็บไว้ใน block attribute) สามารถ remap ไปยัง entity ของภาษาเป้าหมายที่สอดคล้องกันในเวลาแปลได้ ใช้ filter ใดตัวหนึ่งต่อไปนี้ขึ้นอยู่กับประเภทของ reference:

ประเภท ReferenceFilter
Custom posts และ mediagatompl:gutenberg_block_type_custompost_and_media_reference_attribute_regexes
Taxonomy termsgatompl: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

แต่ละ filter รับโครงสร้างเดียวกับ translatable-attributes filter (true สำหรับ regex ค่าเริ่มต้น, string/array สำหรับ custom regexes)

ตัวอย่างเช่น block woocommerce/single-product เก็บสินค้าที่เชื่อมโยงไว้เป็นตัวเลข productId:

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

เมื่อโพสต์ถูกแปล 42 นั้น (สินค้าในภาษาต้นทาง) จำเป็นต้อง remap ไปยังสินค้าคู่ขนานในภาษาเป้าหมาย (เช่น 87) ทำเครื่องหมาย productId เป็น custom-post reference เพื่อให้ปลั๊กอินจับและสลับ ID ในเวลาแปล:

add_filter(
    'gatompl:gutenberg_block_type_custompost_and_media_reference_attribute_regexes',
    static function (array $regexes): array {
        $regexes['woocommerce/single-product'] = [
            'productId' => true,
            // …or a custom regex if `productId` is not stored in the standard JSON shape:
            // 'productId' => '#(<!-- wp:woocommerce/single-product \{.*?\"productId\":)%s([,\}].*? /?-->)#',
        ];
        return $regexes;
    }
);

ใช้รูปแบบเดียวกันสำหรับ reference ประเภทอื่น แต่ละประเภทมีลักษณะเหมือนกันใน block markup — เป็น numeric ID หรือ slug ที่ฝังอยู่ใน JSON — สิ่งที่แตกต่างคือวิธีที่ปลั๊กอิน resolve ไปยังภาษาเป้าหมาย:

<!-- 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;
    }
);

การค้นหาชื่อ attribute

วิธีที่เร็วที่สุดในการค้นหาชื่อ attribute และวิธีการเก็บค่าคือการรัน GraphQL query Translate custom posts และดูที่ response field blockFlattenedDataItems สำหรับ block ที่ต้องการ

ดูคู่มือการดึงข้อมูล page builder เพื่อแปล สำหรับวิธีรัน query นั้นและอ่านผลลัพธ์

การแก้ปัญหา blocks ที่ attributes ต้องการการประมวลผล

hooks ข้างต้นถือว่าค่า attribute ที่เปิดเผยผ่าน blockFlattenedDataItems เป็นค่าที่พร้อมแปลอยู่แล้ว (scalar หรือ array)

หากค่าถูก wrap อยู่ เช่น attribute เก็บ <li>Some text</li> และคุณต้องการแปลเฉพาะ Some text คุณจำเป็นต้องแยกออกมาก่อนผ่าน filter gatompl:gutenberg_block_flattened_data_item_attributes

block generateblocks/image เป็นตัวอย่างจากโลกจริง: alt และ title ของมันไม่ถูกเปิดเผยเป็น attributes แบบ standalone แต่อยู่ภายใน HTML ของ innerContent และต้องแยกออกมาด้วย regex

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 มีอยู่บน attributes object แล้ว hooks ที่ใช้ regex ข้างต้นก็สามารถกำหนดเป้าหมายได้เหมือน attribute อื่น ๆ

แหล่งตัวอย่างที่ควรดู

integrations ของปลั๊กอินเองเป็นข้อมูลอ้างอิงที่ดีจากโลกจริง สำรวจไฟล์เหล่านี้ภายในปลั๊กอินที่คุณติดตั้ง:

  • Block attribute regexes: wp-content/plugins/gato-ai-translations-for-polylang/src/Constants/BlockTypeAttributeValues.php
  • การประมวลผลล่วงหน้า block attributes: wp-content/plugins/gato-ai-translations-for-polylang/src/ConditionalOnContext/LicenseIsActive/Hooks/CoreBlockFlattenedDataItemAttributesHookSet.php