[ 'text' ], 'core/image' => [ 'alt', 'caption' ], 'core/cover' => [ 'alt' ], 'core/quote' => [ 'citation' ], 'core/pullquote' => [ 'citation' ], 'core/audio' => [ 'caption' ], 'core/video' => [ 'caption' ], 'core/embed' => [ 'caption' ], 'core/file' => [ 'downloadButtonText' ], 'core/search' => [ 'label', 'buttonText', 'placeholder' ], 'core/post-title' => [ 'placeholder' ], 'core/site-title' => [ 'placeholder' ], 'core/site-tagline' => [ 'placeholder' ], ]; /** * Translate a post_content string. * * @return string|WP_Error Translated HTML/block markup on success. */ public static function translate_content( string $content, string $target_lang, ?string $source_lang ) { if ( '' === trim( $content ) ) { return ''; } if ( ! has_blocks( $content ) ) { $result = Adara_Translate_Client::translate( $content, $target_lang, $source_lang ); if ( is_wp_error( $result ) ) { return $result; } return (string) $result['translatedText']; } $blocks = parse_blocks( $content ); $out = []; foreach ( $blocks as $block ) { $translated = self::translate_block( $block, $target_lang, $source_lang ); if ( is_wp_error( $translated ) ) { return $translated; } $out[] = $translated; } return serialize_blocks( $out ); } /** * Translate a single block (recursively for innerBlocks). * * @return array|WP_Error */ private static function translate_block( array $block, string $target_lang, ?string $source_lang ) { $name = isset( $block['blockName'] ) ? (string) $block['blockName'] : ''; // Translate curated text attributes first. These exist regardless of // whether the block has innerBlocks. if ( '' !== $name && isset( self::TRANSLATABLE_ATTRS[ $name ] ) && ! empty( $block['attrs'] ) && is_array( $block['attrs'] ) ) { foreach ( self::TRANSLATABLE_ATTRS[ $name ] as $attr_key ) { if ( ! isset( $block['attrs'][ $attr_key ] ) || ! is_string( $block['attrs'][ $attr_key ] ) ) { continue; } if ( '' === trim( $block['attrs'][ $attr_key ] ) ) { continue; } $attr_result = Adara_Translate_Client::translate( $block['attrs'][ $attr_key ], $target_lang, $source_lang ); if ( is_wp_error( $attr_result ) ) { return $attr_result; } $block['attrs'][ $attr_key ] = (string) $attr_result['translatedText']; } } $content_locked = in_array( $name, self::SKIP_CONTENT, true ); $has_children = ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ); if ( $has_children ) { $translated_children = []; foreach ( $block['innerBlocks'] as $child ) { $result = self::translate_block( $child, $target_lang, $source_lang ); if ( is_wp_error( $result ) ) { return $result; } $translated_children[] = $result; } $block['innerBlocks'] = $translated_children; // Parent blocks store wrapping markup in innerContent[] interleaved // with null placeholders for child positions. Translate only // fragments that contain visible text — most wrappers are pure // markup (e.g. "