# ai-builder/2.2.3/includes/class-translation-handler.php

AI Builder – Generate pages, blocks, images &amp; translate with AI, version 2.2.3. 978 lines.

- Page: https://pluginprobe.com/plugins/ai-builder/2.2.3/code/includes/class-translation-handler.php
- Raw: https://pluginprobe.com/plugins/ai-builder/2.2.3/raw/includes/class-translation-handler.php
- Modified: 2025-11-30T10:16:52+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/ai-builder/2.2.3/code/includes/class-translation-handler.php#L10-L20`.

```php
<?php
/**
 * Translation Handler Class
 * Handles AI-powered translation of WordPress posts and pages
 */

if (!defined('ABSPATH'))
    exit;

class AIBUI_Translation_Handler
{
    const SUPPORTED_LANGUAGES = array(
        // Core popular languages
        'en' => 'English',
        'fr' => 'French',
        'es' => 'Spanish',
        'de' => 'German',
        'it' => 'Italian',
        'pt' => 'Portuguese',
        'pt-br' => 'Portuguese (Brazil)',
        'nl' => 'Dutch',
        'ru' => 'Russian',
        'zh-cn' => 'Chinese (Simplified)',
        'zh-tw' => 'Chinese (Traditional)',
        'ja' => 'Japanese',
        'ko' => 'Korean',
        'ar' => 'Arabic',
        'hi' => 'Hindi',
        'bn' => 'Bengali',
        'tr' => 'Turkish',
        'ur' => 'Urdu',
        'pl' => 'Polish',
        'sv' => 'Swedish',
        'no' => 'Norwegian',
        'da' => 'Danish',
        'fi' => 'Finnish',
        'cs' => 'Czech',
        'el' => 'Greek',
        'he' => 'Hebrew',
        'ro' => 'Romanian',
        'hu' => 'Hungarian',
        'th' => 'Thai',
        'ta' => 'Tamil',
        'mr' => 'Marathi',
        'pa' => 'Punjabi',
        'id' => 'Indonesian',
        'vi' => 'Vietnamese'
    );

    public static function get_supported_languages()
    {
        return self::SUPPORTED_LANGUAGES;
    }

    public function __construct()
    {
        // Add row actions for posts and pages
        add_filter('post_row_actions', array($this, 'add_translate_action'), 10, 2);
        add_filter('page_row_actions', array($this, 'add_translate_action'), 10, 2);

        // Enqueue scripts and styles
        add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_site_editor_scripts'));

        // AJAX handlers
        add_action('wp_ajax_aibui_translate_post', array($this, 'handle_translation'));
        add_action('wp_ajax_aibui_translate_template_part', array($this, 'handle_template_part_translation'));
    }

    /**
     * Add "Translate with AI" action to row actions
     */
    public function add_translate_action($actions, $post)
    {
        // Only show for published posts/pages
        if ($post->post_status !== 'publish' && $post->post_status !== 'draft') {
            return $actions;
        }

        // Check user capabilities
        if (!current_user_can('edit_post', $post->ID)) {
            return $actions;
        }

        $nonce = wp_create_nonce('aibui_translate_' . $post->ID);
        $actions['translate_ai'] = sprintf(
            '<a href="#" class="aibui-translate-link" data-post-id="%d" data-nonce="%s">✨ Translate with AI</a>',
            esc_attr($post->ID),
            esc_attr($nonce)
        );

        return $actions;
    }

    /**
     * Enqueue scripts and styles for translation feature
     */
    public function enqueue_scripts($hook)
    {
        if ($hook !== 'edit.php') {
            return;
        }

        $screen = get_current_screen();
        if (!$screen || !in_array($screen->post_type, array('post', 'page', 'wp_template_part'), true)) {
            return;
        }

        // Enqueue CSS
        wp_enqueue_style(
            'aibui-translation-style',
            plugin_dir_url(dirname(__FILE__)) . 'assets/css/translation.css',
            array(),
            AIBUI_VERSION
        );

        // Enqueue JS
        wp_enqueue_script(
            'aibui-translation-script',
            plugin_dir_url(dirname(__FILE__)) . 'assets/js/translation.js',
            array('jquery'),
            AIBUI_VERSION,
            true
        );

        // Localize script
        wp_localize_script('aibui-translation-script', 'aibuiTranslation', array(
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('aibui_nonce'),
            'languages' => self::SUPPORTED_LANGUAGES,
        ));
    }

    public function enqueue_site_editor_scripts($hook)
    {
        if ($hook !== 'site-editor.php') {
            return;
        }

        wp_enqueue_script(
            'aibui-pattern-translation',
            plugin_dir_url(dirname(__FILE__)) . 'assets/js/pattern-translation.js',
            array('wp-data', 'wp-components', 'wp-element', 'wp-notices', 'wp-edit-site'),
            AIBUI_VERSION,
            true
        );

        wp_localize_script('aibui-pattern-translation', 'aibuiTranslation', array(
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('aibui_nonce'),
            'languages' => self::SUPPORTED_LANGUAGES,
            'messages' => array(
                'success' => __('Translation created. Opening draft...', 'ai-builder'),
                'error' => __('Translation failed. Please try again.', 'ai-builder'),
            ),
        ));
    }

    /**
     * Extract text from Gutenberg blocks recursively
     * Returns a mapped array: key => text
     */
    private function extract_texts_from_blocks($blocks, $prefix = 'block')
    {
        $text_map = array();
        $index = 0;

        foreach ($blocks as $block) {
            if (empty($block['blockName'])) {
                // Skip empty blocks
                continue;
            }

            $block_key = $prefix . '_' . $index;
            $block_name = $block['blockName'];

            // Extract text based on block type
            switch ($block_name) {
                case 'core/paragraph':
                    if (!empty($block['innerHTML'])) {
                        $text_map = array_merge($text_map, $this->extract_text_nodes($block['innerHTML'], $block_key . '_p'));
                    }
                    break;

                case 'core/heading':
                    if (!empty($block['innerHTML'])) {
                        $text_map = array_merge($text_map, $this->extract_text_nodes($block['innerHTML'], $block_key . '_h'));
                    }
                    break;

                case 'core/list':
                    if (!empty($block['innerHTML']) && empty($block['innerBlocks'])) {
                        $text_map = array_merge($text_map, $this->extract_text_nodes($block['innerHTML'], $block_key . '_list'));
                    }
                    break;

                case 'core/list-item':
                    if (!empty($block['innerHTML'])) {
                        $text_map = array_merge($text_map, $this->extract_text_nodes($block['innerHTML'], $block_key . '_li'));
                    }
                    break;

                case 'core/button':
                    if (!empty($block['innerHTML'])) {
                        $text_map = array_merge($text_map, $this->extract_text_nodes($block['innerHTML'], $block_key . '_btn'));
                    }
                    break;

                case 'core/image':
                    // Extract alt text and caption
                    if (!empty($block['attrs']['alt'])) {
                        $text_map[$block_key . '_alt'] = $block['attrs']['alt'];
                    }
                    if (!empty($block['innerHTML'])) {
                        $text_map = array_merge($text_map, $this->extract_text_nodes($block['innerHTML'], $block_key . '_caption'));
                    }
                    break;

                case 'core/quote':
                    if (!empty($block['innerHTML'])) {
                        $text_map = array_merge($text_map, $this->extract_text_nodes($block['innerHTML'], $block_key . '_quote'));
                    }
                    break;

                case 'core/table':
                    if (!empty($block['innerHTML'])) {
                        $text_map = array_merge($text_map, $this->extract_text_nodes($block['innerHTML'], $block_key . '_table'));
                    }
                    break;

                case 'core/navigation':
                    // If navigation has a ref, we need to extract from the referenced wp_navigation post
                    if (!empty($block['attrs']['ref'])) {
                        $nav_post = get_post($block['attrs']['ref']);
                        if ($nav_post && $nav_post->post_type === 'wp_navigation') {
                            $nav_blocks = parse_blocks($nav_post->post_content);
                            $nav_map = $this->extract_texts_from_blocks($nav_blocks, $block_key . '_nav');
                            $text_map = array_merge($text_map, $nav_map);
                        }
                    }
                    // Also process innerBlocks if present (inline navigation)
                    break;

                case 'core/navigation-link':
                case 'core/navigation-submenu':
                    // Extract the label from attrs
                    if (!empty($block['attrs']['label'])) {
                        $text_map[$block_key . '_navlabel'] = $block['attrs']['label'];
                    }
                    break;
            }

            // Recursively process inner blocks
            if (!empty($block['innerBlocks']) && is_array($block['innerBlocks'])) {
                $inner_map = $this->extract_texts_from_blocks($block['innerBlocks'], $block_key);
                $text_map = array_merge($text_map, $inner_map);
            }

            $index++;
        }

        return $text_map;
    }

    /**
     * Extract plain text from HTML, removing tags
     */
    private function extract_text_from_html($html)
    {
        // Remove script and style tags
        $html = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $html);
        $html = preg_replace('#<style(.*?)>(.*?)</style>#is', '', $html);

        // Decode HTML entities
        $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');

        // Remove HTML tags but preserve line breaks for lists
        $text = strip_tags($html);

        // Clean up whitespace
        $text = preg_replace('/\s+/', ' ', $text);
        $text = trim($text);

        return $text;
    }

    /**
     * Extract text nodes preserving HTML structure
     */
    private function extract_text_nodes($html, $key_prefix)
    {
        $result = array();
        if (trim((string)$html) === '') {
            return $result;
        }

        $dom = new DOMDocument('1.0', 'UTF-8');
        libxml_use_internal_errors(true);
        $dom->loadHTML('<?xml encoding="utf-8"?><div>' . $html . '</div>');
        libxml_clear_errors();

        $container = $dom->getElementsByTagName('div')->item(0);
        if (!$container) {
            return $result;
        }

        $index = 0;
        $this->traverse_text_nodes($container, function ($node) use (&$result, $key_prefix, &$index) {
            $text = $node->nodeValue;
            if (trim($text) === '') {
                return;
            }
            $result[$key_prefix . '_text' . $index] = $text;
            $index++;
        });

        return $result;
    }

    /**
     * Replace text nodes with translated values
     */
    private function replace_text_nodes($html, $key_prefix, $translated_map)
    {
        if (trim((string)$html) === '') {
            return $html;
        }

        $dom = new DOMDocument('1.0', 'UTF-8');
        libxml_use_internal_errors(true);
        $dom->loadHTML('<?xml encoding="utf-8"?><div>' . $html . '</div>');
        libxml_clear_errors();

        $container = $dom->getElementsByTagName('div')->item(0);
        if (!$container) {
            return $html;
        }

        $index = 0;
        $this->traverse_text_nodes($container, function ($node) use ($key_prefix, $translated_map, &$index) {
            $text = $node->nodeValue;
            if (trim($text) === '') {
                return;
            }

            $key = $key_prefix . '_text' . $index;
            if (isset($translated_map[$key])) {
                $node->nodeValue = $translated_map[$key];
            }
            $index++;
        });

        $innerHTML = '';
        foreach ($container->childNodes as $child) {
            $innerHTML .= $container->ownerDocument->saveHTML($child);
        }

        return $innerHTML;
    }

    /**
     * Traverse DOM text nodes
     */
    private function traverse_text_nodes($node, $callback)
    {
        if ($node->nodeType === XML_TEXT_NODE) {
            $callback($node);
        }

        if ($node->hasChildNodes()) {
            foreach ($node->childNodes as $child) {
                $this->traverse_text_nodes($child, $callback);
            }
        }
    }

    /**
     * Reconstruct blocks with translated content
     */
    private function reconstruct_blocks($original_blocks, $translated_map, $prefix = 'block')
    {
        $reconstructed = array();
        $index = 0;

        foreach ($original_blocks as $block) {
            if (empty($block['blockName'])) {
                // Preserve empty blocks (spacers, etc.)
                $reconstructed[] = $block;
                continue;
            }

            $block_key = $prefix . '_' . $index;
            $block_name = $block['blockName'];
            $new_block = $block;

            // Replace text based on block type
            switch ($block_name) {
                case 'core/paragraph':
                    if (!empty($block['innerHTML'])) {
                        $updated_html = $this->replace_text_nodes($block['innerHTML'], $block_key . '_p', $translated_map);
                        $new_block['innerHTML'] = $updated_html;
                        $new_block['innerContent'] = array($updated_html);
                    }
                    break;

                case 'core/heading':
                    if (!empty($block['innerHTML'])) {
                        $updated_html = $this->replace_text_nodes($block['innerHTML'], $block_key . '_h', $translated_map);
                        $new_block['innerHTML'] = $updated_html;
                        $new_block['innerContent'] = array($updated_html);
                    }
                    break;

                case 'core/list':
                    if (!empty($block['innerHTML']) && empty($block['innerBlocks'])) {
                        $updated_html = $this->replace_text_nodes($block['innerHTML'], $block_key . '_list', $translated_map);
                        $new_block['innerHTML'] = $updated_html;
                        $new_block['innerContent'] = array($updated_html);
                    }
                    break;

                case 'core/list-item':
                    if (!empty($block['innerHTML'])) {
                        $updated_html = $this->replace_text_nodes($block['innerHTML'], $block_key . '_li', $translated_map);
                        $new_block['innerHTML'] = $updated_html;
                        $new_block['innerContent'] = array($updated_html);
                    }
                    break;

                case 'core/button':
                    if (!empty($block['innerHTML'])) {
                        $updated_html = $this->replace_text_nodes($block['innerHTML'], $block_key . '_btn', $translated_map);
                        $new_block['innerHTML'] = $updated_html;
                        $new_block['innerContent'] = array($updated_html);
                    }
                    break;

                case 'core/image':
                    if (isset($translated_map[$block_key . '_alt'])) {
                        $new_block['attrs']['alt'] = sanitize_text_field($translated_map[$block_key . '_alt']);
                    }
                    if (!empty($block['innerHTML'])) {
                        $updated_html = $this->replace_text_nodes($block['innerHTML'], $block_key . '_caption', $translated_map);
                        $new_block['innerHTML'] = $updated_html;
                        if (isset($new_block['innerContent'])) {
                            $new_block['innerContent'] = array($updated_html);
                        }
                    }
                    break;

                case 'core/quote':
                    if (!empty($block['innerHTML'])) {
                        $updated_html = $this->replace_text_nodes($block['innerHTML'], $block_key . '_quote', $translated_map);
                        $new_block['innerHTML'] = $updated_html;
                        $new_block['innerContent'] = array($updated_html);
                    }
                    break;

                case 'core/table':
                    if (!empty($block['innerHTML'])) {
                        $updated_html = $this->replace_text_nodes($block['innerHTML'], $block_key . '_table', $translated_map);
                        $new_block['innerHTML'] = $updated_html;
                        $new_block['innerContent'] = array($updated_html);
                    }
                    break;

                case 'core/navigation':
                    // If navigation has a ref, duplicate the wp_navigation post with translated content
                    if (!empty($block['attrs']['ref'])) {
                        $new_nav_id = $this->duplicate_and_translate_navigation($block['attrs']['ref'], $translated_map, $block_key . '_nav');
                        if ($new_nav_id) {
                            $new_block['attrs']['ref'] = $new_nav_id;
                        }
                    }
                    break;

                case 'core/navigation-link':
                case 'core/navigation-submenu':
                    // Replace the label in attrs
                    if (isset($translated_map[$block_key . '_navlabel'])) {
                        $new_block['attrs']['label'] = $translated_map[$block_key . '_navlabel'];
                    }
                    break;
            }

            // Recursively process inner blocks
            if (!empty($block['innerBlocks']) && is_array($block['innerBlocks'])) {
                $new_block['innerBlocks'] = $this->reconstruct_blocks($block['innerBlocks'], $translated_map, $block_key);
            }

            $reconstructed[] = $new_block;
            $index++;
        }

        return $reconstructed;
    }

    /**
     * Duplicate a wp_navigation post and translate its content
     */
    private function duplicate_and_translate_navigation($nav_id, $translated_map, $prefix)
    {
        $nav_post = get_post($nav_id);
        if (!$nav_post || $nav_post->post_type !== 'wp_navigation') {
            return null;
        }

        // Parse the navigation blocks
        $nav_blocks = parse_blocks($nav_post->post_content);

        // Reconstruct with translated labels
        $translated_nav_blocks = $this->reconstruct_blocks($nav_blocks, $translated_map, $prefix);

        // Serialize back to block content
        $translated_nav_content = serialize_blocks($translated_nav_blocks);

        // Get target language from the current translation context
        $target_lang = isset($_POST['target_lang']) ? sanitize_text_field($_POST['target_lang']) : 'translated';

        // Create a new wp_navigation post
        $new_nav_data = array(
            'post_title' => $nav_post->post_title . ' (' . strtoupper($target_lang) . ')',
            'post_content' => $translated_nav_content,
            'post_status' => 'publish',
            'post_type' => 'wp_navigation',
            'post_author' => get_current_user_id(),
        );

        $new_nav_id = wp_insert_post($new_nav_data);

        if (is_wp_error($new_nav_id)) {
            return null;
        }

        // Copy language meta
        update_post_meta($new_nav_id, '_ai_lang', $target_lang);
        update_post_meta($new_nav_id, '_ai_translation_source', $nav_id);

        // Link to same translation group
        $translation_group = get_post_meta($nav_id, '_ai_translation_group', true);
        if (empty($translation_group)) {
            $translation_group = 'nav_' . time() . '_' . wp_generate_password(8, false);
            update_post_meta($nav_id, '_ai_translation_group', $translation_group);
        }
        update_post_meta($new_nav_id, '_ai_translation_group', $translation_group);

        return $new_nav_id;
    }

    /**
     * Get meta description with fallback logic
     */
    private function get_meta_description($post_id)
    {
        // Check _ai_builder_seo_desc first (as specified by user)
        $meta_desc = get_post_meta($post_id, '_ai_builder_seo_desc', true);
        if (!empty($meta_desc)) {
            return $meta_desc;
        }

        // Check plugin's own meta key
        $meta_desc = get_post_meta($post_id, 'aibui_meta_description', true);
        if (!empty($meta_desc)) {
            return $meta_desc;
        }

        // Check Yoast SEO meta
        $meta_desc = get_post_meta($post_id, '_yoast_wpseo_metadesc', true);
        if (!empty($meta_desc)) {
            return $meta_desc;
        }

        // Fallback to excerpt
        $post = get_post($post_id);
        return $post ? $post->post_excerpt : '';
    }

    /**
     * AJAX handler for translation
     */
    public function handle_translation()
    {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'aibui_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed'));
        }

        // Get and validate post ID
        if (!isset($_POST['post_id'])) {
            wp_send_json_error(array('message' => 'Post ID is required'));
        }

        $post_id = intval($_POST['post_id']);
        if (!$post_id) {
            wp_send_json_error(array('message' => 'Invalid post ID'));
        }

        // Check user capabilities
        if (!current_user_can('edit_post', $post_id)) {
            wp_send_json_error(array('message' => 'Insufficient permissions'));
        }

        // Get target language
        if (!isset($_POST['target_lang']) || !array_key_exists($_POST['target_lang'], self::SUPPORTED_LANGUAGES)) {
            wp_send_json_error(array('message' => 'Invalid target language'));
        }

        $target_lang = sanitize_text_field($_POST['target_lang']);
        $target_lang_name = strtolower(self::SUPPORTED_LANGUAGES[$target_lang]);

        // Get original post
        $original_post = get_post($post_id);
        if (!$original_post) {
            wp_send_json_error(array('message' => 'Post not found'));
        }

        // Extract data
        $title = $original_post->post_title;
        $meta_desc = $this->get_meta_description($post_id);
        $content = $original_post->post_content;

        // Parse blocks
        $blocks = parse_blocks($content);
        $content_map = $this->extract_texts_from_blocks($blocks);

        // Prepare data for API
        $api_data = array(
            'title' => $title,
            'meta_desc' => $meta_desc,
            'content_map' => $content_map,
            'target_lang' => $target_lang_name
        );
        $this->log_translation_debug($post_id, 'request_payload', $api_data);

        // Get JWT token
        $jwt_token = get_option('aibui_jwt_token', '');
        if (empty($jwt_token)) {
            wp_send_json_error(array('message' => 'Authentication required. Please sign in.'));
        }

        // Call translation API
        $api_url = 'https://api.wordpress-ai-builder.com/api' . '/ai-transform-page/translate-post';
        
        $response = wp_remote_post($api_url, array(
            'timeout' => 180, // 5 minutes - AI translation can take longer for large pages
            'headers' => array(
                'Authorization' => 'Bearer ' . $jwt_token,
                'Content-Type' => 'application/json',
            ),
            'body' => json_encode($api_data),
        ));

        if (is_wp_error($response)) {
            wp_send_json_error(array('message' => 'API request failed: ' . $response->get_error_message()));
        }

        $response_code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        $this->log_translation_debug($post_id, 'api_response', array(
            'status' => $response_code,
            'body' => $body
        ));

        if ($response_code === 402) {
            $error_message = 'You do not have enough credits to translate this page. Each translation costs 40 credits.';

            // Try to merge API error message if available
            $decoded = json_decode($body, true);
            if (is_array($decoded) && !empty($decoded['message'])) {
                $error_message .= ' ' . $decoded['message'];
            }

            wp_send_json_error(array(
                'message' => $error_message,
                'code'    => 'not_enough_credits',
            ));
        }

        if ($response_code !== 200) {
            wp_send_json_error(array('message' => 'Translation failed. Status: ' . $response_code));
        }

        $translated_data = json_decode($body, true);
        $this->log_translation_debug($post_id, 'translated_payload', $translated_data);
        if (!$translated_data || !isset($translated_data['title']) || !isset($translated_data['content_map'])) {
            wp_send_json_error(array('message' => 'Invalid response from translation API'));
        }

        // Reconstruct blocks with translated content
        $translated_blocks = $this->reconstruct_blocks($blocks, $translated_data['content_map']);
        $translated_content = serialize_blocks($translated_blocks);
        $this->log_translation_debug($post_id, 'final_content', array(
            'content_preview' => mb_substr($translated_content, 0, 1000)
        ));

        // For template parts, generate a unique slug with language suffix
        $new_post_name = '';
        if ($original_post->post_type === 'wp_template_part') {
            $new_post_name = $original_post->post_name . '-' . $target_lang;
            // Template parts need to be published for Site Editor to find them
            $new_post_status = 'publish';
        } else {
            $new_post_status = 'draft';
        }

        // Create new post
        $new_post_data = array(
            'post_title' => $translated_data['title'],
            'post_content' => $translated_content,
            'post_status' => $new_post_status,
            'post_type' => $original_post->post_type,
            'post_author' => get_current_user_id(),
        );

        // Set explicit slug for template parts
        if (!empty($new_post_name)) {
            $new_post_data['post_name'] = $new_post_name;
        }

        $new_post_id = wp_insert_post($new_post_data);

        if (is_wp_error($new_post_id)) {
            wp_send_json_error(array('message' => 'Failed to create translated post: ' . $new_post_id->get_error_message()));
        }

        // Save meta description across all supported meta keys
        if (!empty($translated_data['meta_desc'])) {
            update_post_meta($new_post_id, '_ai_builder_seo_desc', $translated_data['meta_desc']);
            update_post_meta($new_post_id, '_yoast_wpseo_metadesc', $translated_data['meta_desc']);
            update_post_meta($new_post_id, 'aibui_meta_description', $translated_data['meta_desc']);
            update_post_meta($new_post_id, '_ai_translation_meta_desc', $translated_data['meta_desc']);
        }

        // Copy custom CSS meta if present
        $css_meta_keys = array(
            'ai_builder_page_css_content',
            'ai_builder_block_css_content',
            'ai_builder_css_content'
        );

        foreach ($css_meta_keys as $css_key) {
            $css_value = get_post_meta($post_id, $css_key, true);
            if (!empty($css_value)) {
                update_post_meta($new_post_id, $css_key, $css_value);
            }
        }

        // Save language meta
        update_post_meta($new_post_id, '_ai_lang', $target_lang);
        update_post_meta($new_post_id, '_ai_translation_source', $post_id);
        if (!get_post_meta($post_id, '_ai_translation_source', true)) {
            update_post_meta($post_id, '_ai_translation_source', $post_id);
        }

        // Copy template part taxonomy/meta if needed
        if ($original_post->post_type === 'wp_template_part') {
            // Assign to current theme
            $theme_slug = wp_get_theme()->get_stylesheet();
            wp_set_object_terms($new_post_id, $theme_slug, 'wp_theme', false);

            // Copy template part area taxonomy
            $area_terms = wp_get_object_terms($post_id, 'wp_template_part_area', array('fields' => 'slugs'));
            if (!is_wp_error($area_terms) && !empty($area_terms)) {
                wp_set_object_terms($new_post_id, $area_terms, 'wp_template_part_area', false);
            }

            update_post_meta($new_post_id, '_wp_template_part_area', get_post_meta($post_id, '_wp_template_part_area', true));
            update_post_meta($new_post_id, '_ai_template_part_slug', $original_post->post_name);
        }

        // Handle translation group
        $translation_group = get_post_meta($post_id, '_ai_translation_group', true);
        if (empty($translation_group)) {
            $translation_group = 'trans_' . time() . '_' . wp_generate_password(8, false);
            update_post_meta($post_id, '_ai_translation_group', $translation_group);
        }
        update_post_meta($new_post_id, '_ai_translation_group', $translation_group);

        // Copy other relevant meta
        $original_lang = get_post_meta($post_id, '_ai_lang', true);
        if (empty($original_lang)) {
            update_post_meta($post_id, '_ai_lang', 'en'); // Default to English if not set
        }

        // Preserve AI-created flag to keep hide-title CSS behavior
        if (get_post_meta($post_id, '_aibui_created_by_ai', true) === '1') {
            update_post_meta($new_post_id, '_aibui_created_by_ai', '1');
        }

        // If the original page is the front page, mark the translation as homepage for its language
        $front_page_id = get_option('page_on_front');
        if ($front_page_id && (int) $front_page_id === (int) $post_id) {
            update_post_meta($new_post_id, '_ai_is_homepage', '1');
        }

        // Also copy the homepage flag if it exists on the original
        if (get_post_meta($post_id, '_ai_is_homepage', true) === '1') {
            update_post_meta($new_post_id, '_ai_is_homepage', '1');
        }

        // Copy featured image if exists
        $thumbnail_id = get_post_thumbnail_id($post_id);
        if ($thumbnail_id) {
            set_post_thumbnail($new_post_id, $thumbnail_id);
        }

        // Return success with new post URL
        // For template parts, use Site Editor URL instead of post.php
        if ($original_post->post_type === 'wp_template_part') {
            $new_post = get_post($new_post_id);
            $theme = wp_get_theme()->get_stylesheet();
            // Site Editor URL format: /wp-admin/site-editor.php?postType=wp_template_part&postId=theme//slug
            $edit_url = admin_url('site-editor.php?postType=wp_template_part&postId=' . urlencode($theme . '//' . $new_post->post_name) . '&canvas=edit');
        } else {
            $edit_url = admin_url('post.php?post=' . $new_post_id . '&action=edit');
        }

        wp_send_json_success(array(
            'message' => 'Translation created successfully!',
            'post_id' => $new_post_id,
            'edit_url' => $edit_url,
        ));
    }

    private function log_translation_debug($post_id, $stage, $data)
    {
        return;

        // $log_file = plugin_dir_path(__FILE__) . '../debug-unescape.log';
        // if (is_array($data) || is_object($data)) {
        //     $data = wp_json_encode($data);
        // }
        // $line = sprintf("[%s][AI Builder Translation][Post %d][%s] %s\n", date('Y-m-d H:i:s'), $post_id, $stage, $data);
        // file_put_contents($log_file, $line, FILE_APPEND);
    }

    /**
     * AJAX handler for template part translation
     * Template parts use slugs like "theme//slug" instead of numeric IDs
     */
    public function handle_template_part_translation()
    {
        $this->log_template_part_debug('start', array(
            'POST' => $_POST,
        ));

        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'aibui_nonce')) {
            $this->log_template_part_debug('error', 'Nonce verification failed');
            wp_send_json_error(array('message' => 'Security check failed'));
        }

        // Get template part ID (slug format: "theme//slug")
        if (!isset($_POST['template_part_id']) || empty($_POST['template_part_id'])) {
            $this->log_template_part_debug('error', 'Template part ID is required');
            wp_send_json_error(array('message' => 'Template part ID is required'));
        }

        $template_part_id = sanitize_text_field($_POST['template_part_id']);
        $this->log_template_part_debug('template_part_id', $template_part_id);

        // Get target language
        if (!isset($_POST['target_lang']) || !array_key_exists($_POST['target_lang'], self::SUPPORTED_LANGUAGES)) {
            $this->log_template_part_debug('error', 'Invalid target language: ' . ($_POST['target_lang'] ?? 'not set'));
            wp_send_json_error(array('message' => 'Invalid target language'));
        }

        $target_lang = sanitize_text_field($_POST['target_lang']);
        $target_lang_name = strtolower(self::SUPPORTED_LANGUAGES[$target_lang]);

        // Parse the template part ID to get theme and slug
        // Format: "theme//slug" e.g., "twentytwentyfive//header"
        $parts = explode('//', $template_part_id);
        if (count($parts) !== 2) {
            $this->log_template_part_debug('error', 'Invalid template part ID format: ' . $template_part_id);
            wp_send_json_error(array('message' => 'Invalid template part ID format'));
        }

        $theme = $parts[0];
        $slug = $parts[1];
        $this->log_template_part_debug('parsed', array('theme' => $theme, 'slug' => $slug));

        // Find the template part post by slug
        $post = $this->get_template_part_post($theme, $slug);

        if (!$post) {
            $this->log_template_part_debug('error', 'Template part not found for theme: ' . $theme . ', slug: ' . $slug);
            wp_send_json_error(array('message' => 'Template part not found. Make sure it has been customized (not a theme default).'));
        }

        $post_id = $post->ID;
        $this->log_template_part_debug('found_post', array('post_id' => $post_id, 'post_title' => $post->post_title));

        // Check user capabilities
        if (!current_user_can('edit_post', $post_id)) {
            $this->log_template_part_debug('error', 'Insufficient permissions for post: ' . $post_id);
            wp_send_json_error(array('message' => 'Insufficient permissions'));
        }

        // Now translate using the numeric post ID
        $_POST['post_id'] = $post_id;
        $_POST['target_lang'] = $target_lang;
        
        $this->log_template_part_debug('calling_handle_translation', array(
            'post_id' => $post_id,
            'target_lang' => $target_lang,
        ));

        // Call the main translation handler
        $this->handle_translation();
    }

    /**
     * Get template part post by theme and slug
     */
    private function get_template_part_post($theme, $slug)
    {
        // Query for the template part
        $query = new WP_Query(array(
            'post_type' => 'wp_template_part',
            'post_status' => array('publish', 'draft', 'auto-draft'),
            'name' => $slug,
            'posts_per_page' => 1,
            'no_found_rows' => true,
            'tax_query' => array(
                array(
                    'taxonomy' => 'wp_theme',
                    'field' => 'slug',
                    'terms' => $theme,
                ),
            ),
        ));

        $this->log_template_part_debug('query_result', array(
            'found_posts' => $query->found_posts,
            'post_count' => $query->post_count,
        ));

        if ($query->have_posts()) {
            return $query->posts[0];
        }

        // Try without theme filter (for child themes, etc.)
        $query2 = new WP_Query(array(
            'post_type' => 'wp_template_part',
            'post_status' => array('publish', 'draft', 'auto-draft'),
            'name' => $slug,
            'posts_per_page' => 1,
            'no_found_rows' => true,
        ));

        $this->log_template_part_debug('query2_result', array(
            'found_posts' => $query2->found_posts,
            'post_count' => $query2->post_count,
        ));

        if ($query2->have_posts()) {
            return $query2->posts[0];
        }

        return null;
    }

    /**
     * Debug logging for template part translation
     */
    private function log_template_part_debug($stage, $data)
    {
        $log_file = plugin_dir_path(__FILE__) . '../debug-template-part.log';
        if (is_array($data) || is_object($data)) {
            $data = wp_json_encode($data);
        }
        $line = sprintf("[%s][Template Part Translation][%s] %s\n", date('Y-m-d H:i:s'), $stage, $data);
        file_put_contents($log_file, $line, FILE_APPEND);
    }
}


```
