ai_engine = new polylai_ChatGPT(); } public function make_post( $from_post_id, $title, $text, $excerpt, $meta, $status ) { $oldpost = get_post( $from_post_id ); $post = [ 'post_author' => $oldpost->post_author, 'post_date' => $oldpost->post_date, 'post_date_gmt' => $oldpost->post_date_gmt, 'post_content' => $text, 'post_title' => $title, 'post_excerpt' => $excerpt, 'post_status' => $status, 'comment_status' => $oldpost->comment_status, 'ping_status' => $oldpost->ping_status, 'post_password' => $oldpost->post_password, 'post_name' => sanitize_title( $title ), 'to_ping' => $oldpost->to_ping, 'post_type' => $oldpost->post_type, ]; $new_post_id = wp_insert_post( $post ); $all_meta = get_post_meta( $from_post_id ); foreach ( $all_meta as $key => $item ) { if ( substr( $key, 0, strlen( '_ez-toc' ) ) == '_ez-toc' || substr( $key, 0, strlen( '_yoast' ) ) == '_yoast' ) { foreach ( $item as $value ) { update_post_meta( $new_post_id, $key, maybe_unserialize( $value ) ); } } } foreach ( $meta as $key => $value ) { update_post_meta( $new_post_id, $key, $value ); } return $new_post_id; } public function create_translation( $from_post_id, $new_post_id, $locale, $meta = [] ) { pll_set_post_language( $new_post_id, $locale ); $translations = pll_get_post_translations( $from_post_id ); $translations[$locale] = $new_post_id; pll_save_post_translations( $translations ); // Get locale names for translation - using consistent mapping $language_mapping = array( 'en' => 'English', 'es' => 'Spanish', 'it' => 'Italian', 'fr' => 'French', 'de' => 'German', 'pt' => 'Portuguese', 'ru' => 'Russian', 'ja' => 'Japanese', 'zh' => 'Chinese', 'ar' => 'Arabic', 'nl' => 'Dutch', 'pl' => 'Polish', 'tr' => 'Turkish', ); // Get current locale slug for mapping $current_locale_slug = pll_get_post_language( $from_post_id, 'slug' ); $current_locale_name = ( isset( $language_mapping[$current_locale_slug] ) ? $language_mapping[$current_locale_slug] : $current_locale_slug ); $locale_name = ( isset( $language_mapping[$locale] ) ? $language_mapping[$locale] : $locale ); $taxonomies = get_post_taxonomies( $from_post_id ); if ( $taxonomies ) { foreach ( $taxonomies as $taxonomy ) { if ( $taxonomy == 'language' ) { continue; } if ( $taxonomy == 'post_translations' ) { continue; } $terms = wp_get_object_terms( $from_post_id, $taxonomy, [ 'fields' => 'ids', ] ); if ( count( $terms ) == 0 ) { continue; } foreach ( $terms as $k => $term_id ) { $new_tx_id = pll_get_term( $terms[$k], $locale ); if ( $new_tx_id == $term_id ) { // Same term, skip unset($terms[$k]); continue; } if ( $new_tx_id == 0 ) { // No translation found, create one $new_tx_id = $this->create_translated_term( $terms[$k], $taxonomy, $locale, $current_locale_name, $locale_name ); if ( $new_tx_id > 0 ) { $terms[$k] = $new_tx_id; } else { unset($terms[$k]); } continue; } $terms[$k] = $new_tx_id; } wp_set_object_terms( $new_post_id, $terms, $taxonomy ); } } $acf_fields = []; if ( isset( $meta[polylai_ACF::META_KEY] ) && is_array( $meta[polylai_ACF::META_KEY] ) ) { $acf_fields = $meta[polylai_ACF::META_KEY]; unset($meta[polylai_ACF::META_KEY]); } foreach ( $meta as $key => $value ) { add_post_meta( $new_post_id, $key, $value ); } if ( function_exists( 'update_field' ) ) { foreach ( $acf_fields as $field ) { if ( !is_array( $field ) || !isset( $field['selector'] ) ) { continue; } $updated = update_field( $field['selector'], $field['value'], $new_post_id ); if ( $updated === false && isset( $field['name'] ) && $field['name'] !== $field['selector'] ) { $updated = update_field( $field['name'], $field['value'], $new_post_id ); } if ( $updated === false ) { $field_name = ( isset( $field['name'] ) ? $field['name'] : $field['selector'] ); polylai_Utils::db_log( 'error', 'ACF translate', 'Unable to update ACF/SCF field ' . $field_name, $new_post_id ); } } } else { if ( count( $acf_fields ) > 0 ) { polylai_Utils::db_log( 'error', 'ACF translate', 'update_field() is not available while saving translated ACF/SCF fields', $new_post_id ); } } return $new_post_id; } /** * Create a translated term (category or tag) when no translation exists * * @param int $original_term_id Original term ID * @param string $taxonomy Taxonomy type (category, post_tag, etc.) * @param string $target_locale Target locale code * @param string $source_locale_name Source locale name * @param string $target_locale_name Target locale name * @return int New translated term ID or 0 if failed */ private function create_translated_term( $original_term_id, $taxonomy, $target_locale, $source_locale_name, $target_locale_name ) { // Get original term data $original_term = get_term( $original_term_id, $taxonomy ); if ( is_wp_error( $original_term ) || !$original_term ) { polylai_Utils::db_log( 'error', 'create_translated_term', 'Failed to get original term: ' . $original_term_id ); return 0; } try { // Translate term name $translated_name_result = $this->ai_engine->translate( $original_term->name, $source_locale_name, $target_locale_name, null, true ); // Handle the new return format (array with content and tokens) if ( is_array( $translated_name_result ) ) { $translated_name = $translated_name_result['content']; } else { // Fallback for backward compatibility $translated_name = $translated_name_result; } // Translate description if it exists $translated_description = ''; if ( !empty( $original_term->description ) ) { $translated_desc_result = $this->ai_engine->translate( $original_term->description, $source_locale_name, $target_locale_name, null, true ); // Handle the new return format if ( is_array( $translated_desc_result ) ) { $translated_description = $translated_desc_result['content']; } else { $translated_description = $translated_desc_result; } } // Create new term $new_term_args = array( 'description' => $translated_description, 'slug' => sanitize_title( $translated_name ), ); // Handle parent for hierarchical taxonomies (categories) if ( is_taxonomy_hierarchical( $taxonomy ) && $original_term->parent > 0 ) { $parent_term_translation = pll_get_term( $original_term->parent, $target_locale ); if ( $parent_term_translation > 0 ) { $new_term_args['parent'] = $parent_term_translation; } } $new_term = wp_insert_term( $translated_name, $taxonomy, $new_term_args ); if ( is_wp_error( $new_term ) ) { polylai_Utils::db_log( 'error', 'create_translated_term', 'Failed to create term: ' . $new_term->get_error_message() ); return 0; } $new_term_id = $new_term['term_id']; // Set language for the new term pll_set_term_language( $new_term_id, $target_locale ); // Link the translation $translations = pll_get_term_translations( $original_term_id ); if ( !$translations ) { $translations = array(); } $translations[$target_locale] = $new_term_id; pll_save_term_translations( $translations ); polylai_Utils::db_log( 'info', 'create_translated_term', "Created translated term: '{$translated_name}' (ID: {$new_term_id}) for taxonomy '{$taxonomy}' in locale '{$target_locale}'" ); return $new_term_id; } catch ( Exception $e ) { polylai_Utils::db_log( 'error', 'create_translated_term', 'Exception: ' . $e->getMessage() ); return 0; } } private function translate_block( $id, $block, $locale_from, $locale_to, $tot_blocks, $locale ) { foreach ( $block['innerBlocks'] as $i => $innerBlock ) { $block['innerBlocks'][$i] = $this->translate_block( $id, $block['innerBlocks'][$i], $locale_from, $locale_to, $tot_blocks, $locale ); } $html = ( $block['innerHTML'] == null ? '' : $block['innerHTML'] ); $cleanText = trim( strip_tags( $html ) ); // cannot use wp_strip_all_tags here $total_tokens = 0; if ( strlen( $cleanText ) > 0 ) { foreach ( $block['innerContent'] as $i => $c ) { $splitted_text = polylai_Utils::split_text( $block['innerContent'][$i], 500 ); $block['innerContent'][$i] = ''; foreach ( $splitted_text as $ii => $s ) { $translation_result = $this->ai_engine->translate( $s, $locale_from, $locale_to, null ); // Handle the new return format (array with content and tokens) if ( is_array( $translation_result ) ) { $translated_text = $translation_result['content']; $total_tokens += $translation_result['tokens']; } else { // Fallback for backward compatibility $translated_text = $translation_result; } $block['innerContent'][$i] .= $translated_text . " "; if ( $tot_blocks == 1 ) { // update_post_meta($id, POLYLAI_KEY, ["locale" => $locale, "perc" => ceil($ii / count($splitted_text) * 100)]); } } // $block['innerContent'][$i] = $this->ai_engine->translate($block['innerContent'][$i], $locale_from, $locale_to, null); } } return $block; } private function translate_meta( $id, $current_locale_name, $locale_name ) { $meta_to_translate = []; $meta = []; $total_tokens = 0; $meta_yoast = polylai_Yoast::translate_meta( $id, $current_locale_name, $locale_name, $this->ai_engine ); $meta_elementor = polylai_Elementor::translate_meta( $id, $current_locale_name, $locale_name, $this->ai_engine ); $meta_acf = polylai_ACF::translate_meta( $id, $current_locale_name, $locale_name, $this->ai_engine ); $meta = array_merge( $meta, $meta_yoast ); $meta = array_merge( $meta, $meta_elementor ); $meta = array_merge( $meta, $meta_acf ); foreach ( $meta_to_translate as $key ) { $item = get_post_meta( $id, $key, true ); if ( $item ) { $translation_result = $this->ai_engine->translate( $item, $current_locale_name, $locale_name, $id, true ); // Handle the new return format (array with content and tokens) if ( is_array( $translation_result ) ) { $meta[$key] = $translation_result['content']; $total_tokens += $translation_result['tokens']; } else { // Fallback for backward compatibility $meta[$key] = $translation_result; } } } return $meta; } public function translate( $id, $locale, $locale_name, $status ) { if ( $this->ai_engine instanceof polylai_ChatGPT ) { $this->ai_engine->resetTranslationError(); } $post = get_post( $id ); $current_locale = pll_get_post_language( $id, 'slug' ); $current_locale_name = pll_get_post_language( $id, 'name' ); $total_tokens = 0; $content = $post->post_content; $translation_result = $this->ai_engine->translate( $content, $current_locale, $locale, $id, $this->is_plain_text( $content ) ); // Handle the new return format (array with content and tokens) if ( is_array( $translation_result ) ) { $translated_text = $translation_result['content']; $total_tokens += $translation_result['tokens']; } else { // Fallback for backward compatibility $translated_text = $translation_result; } $translated_text = $this->translate_links( $translated_text, $locale, $current_locale ); try { $title = $this->translate_title( $post->post_title, $current_locale_name, $locale_name, $id, $total_tokens ); $meta = $this->translate_meta( $id, $current_locale_name, $locale_name ); $excerpt = get_the_excerpt( $id ); if ( trim( $excerpt ) ) { $excerpt_result = $this->ai_engine->translate( $excerpt, $current_locale_name, $locale_name, $id, $this->is_plain_text( $excerpt ) ); // Handle the new return format for excerpt if ( is_array( $excerpt_result ) ) { $excerpt = $excerpt_result['content']; $total_tokens += $excerpt_result['tokens']; } else { // Fallback for backward compatibility $excerpt = $excerpt_result; } } $elementor_data = polylai_Elementor::translate_data( $id, $current_locale_name, $locale_name, $this->ai_engine ); if ( $elementor_data ) { $meta['_elementor_data'] = $elementor_data; } $duplicator = new polylai_Duplicator(); // ACF/RankMath may catch provider exceptions internally. if ( $this->ai_engine instanceof polylai_ChatGPT ) { $this->ai_engine->assertTranslationSucceeded(); } $new_post_id = $duplicator->duplicate( $id, $title, $translated_text, $excerpt, $status, $meta ); //$new_post_id = $this->create_translation($id, $title, $translated_text, $excerpt, $meta, $current_locale, $locale, $status); $this->create_translation( $id, $new_post_id, $locale, $meta ); if ( $this->ai_engine instanceof polylai_ChatGPT ) { $this->ai_engine->assertTranslationSucceeded(); } // Get the translation ID from Manager and update with token count $translations = polylai_Manager::getInstance()->list_pending_translating(); foreach ( $translations as $translation ) { if ( $translation->source_post_id == $id && $translation->locale_to == $locale ) { polylai_Manager::getInstance()->set_status( $translation->id, 'complete', null, $total_tokens ); polylai_Utils::db_log( 'info', "translate", "complete {$locale}", $id ); break; } } } catch ( Exception $e ) { polylai_Utils::db_log( 'error', 'translate', $e->getMessage() ); if ( $this->ai_engine instanceof polylai_ChatGPT ) { $this->ai_engine->assertTranslationSucceeded(); } } } protected function translate_title( $source_title, $locale_from, $locale_to, $post_id, &$total_tokens ) { $title_result = $this->ai_engine->translate( $source_title, $locale_from, $locale_to, $post_id, true ); $title = $this->extract_translation_content( $title_result, $total_tokens ); if ( self::is_invalid_title_translation( $title ) ) { $retry_result = $this->translate_title_with_strict_prompt( $source_title, $locale_from, $locale_to, $post_id ); $retry_title = $this->extract_translation_content( $retry_result, $total_tokens ); if ( !self::is_invalid_title_translation( $retry_title ) ) { $title = $retry_title; } } return self::normalize_translated_title( $title, $source_title, $post_id ); } private function translate_title_with_strict_prompt( $source_title, $locale_from, $locale_to, $post_id ) { $request = new polylai_TranslationRequest(); $request->text = $source_title; $request->locale_from = $locale_from; $request->locale_to = $locale_to; $request->post_id = $post_id; $request->plain_text = true; $request->custom_prompt = "Translate this exact WordPress post title from {$locale_from} to {$locale_to}. The title is inside