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 tags. Return only the translated title text. Do not explain, do not ask for more text, and do not include the tags.\n\n<title>[text]"; return $this->ai_engine->translateRequest( $request ); } private function extract_translation_content( $translation_result, &$total_tokens ) { if ( is_array( $translation_result ) ) { $total_tokens += ( isset( $translation_result['tokens'] ) ? $translation_result['tokens'] : 0 ); return ( isset( $translation_result['content'] ) ? $translation_result['content'] : '' ); } return $translation_result; } public static function normalize_translated_title( $title, $source_title, $post_id = null ) { $title = trim( wp_strip_all_tags( (string) $title ) ); $source_title = trim( (string) $source_title ); if ( substr( $title, -1 ) === "." ) { $title = substr( $title, 0, strlen( $title ) - 1 ); } if ( $title === '' || self::is_invalid_title_translation( $title ) ) { polylai_Utils::db_log( 'error', 'translate', 'AI returned an invalid title translation, using source title instead', $post_id, null, $title ); return $source_title; } return $title; } private static function is_invalid_title_translation( $title ) { $normalized = strtolower( $title ); $invalid_fragments = [ 'please provide', 'provide the text', "text you'd like me to translate", 'text you would like me to translate', 'i am ready to translate', "i'm ready to translate", 'i don’t see', "i don't see", 'no content', 'no text', 'à traduire dans votre message', 'texte que vous souhaitez', 'fornire il testo', 'fornisci il testo', 'testo che desideri tradurre' ]; foreach ( $invalid_fragments as $fragment ) { if ( strpos( $normalized, $fragment ) !== false ) { return true; } } return false; } private function translate_links( $html, $locale, $current_locale ) { $pattern = '/href="([^"]*)"/'; if ( preg_match_all( $pattern, $html, $matches ) ) { foreach ( $matches[1] as $href ) { $postID = url_to_postid( $href ); //print_r("POST ID $href $postID\n"); if ( $postID > 0 ) { $translatedPost = pll_get_post( $postID, $locale ); if ( $translatedPost ) { $link = get_permalink( $translatedPost ); if ( $link ) { $html = str_replace( $href, $link, $html ); } } } else { $cat_id = $this->get_category_id_by_url( $href, $current_locale ); if ( $cat_id > 0 ) { $term_id = pll_get_term( $cat_id, $locale ); //print_r("CAT ID $href $term_id $link\n"); if ( $term_id > 0 ) { $link = get_category_link( $term_id ); $html = str_replace( $href, $link, $html ); } } } } } return $html; } private function is_plain_text( $string ) { return strip_tags( $string ) === $string; } private function translate_blocks( $id, $locale, $locale_name ) { $post = get_post( $id ); $current_locale = pll_get_post_language( $id, 'slug' ); $current_locale_name = pll_get_post_language( $id, 'name' ); $blocks = parse_blocks( $post->post_content ); $tot_blocks = count( $blocks ); polylai_Utils::db_log( 'info', "translate", count( $blocks ) . " blocks {$locale}", $id ); $tr_blocks = []; update_post_meta( $id, POLYLAI_KEY, [ "locale" => $locale, "perc" => 0, ] ); foreach ( $blocks as $i => $block ) { $tr_blocks[] = $this->translate_block( $id, $block, $current_locale_name, $locale_name, $tot_blocks, $locale ); if ( count( $blocks ) > 1 ) { update_post_meta( $id, POLYLAI_KEY, [ "locale" => $locale, "perc" => ceil( $i / $tot_blocks * 100 ), ] ); } } $translated_text = serialize_blocks( $tr_blocks ); $translated_text = $this->translate_links( $translated_text, $locale, $current_locale ); try { $title = $this->ai_engine->translate( $post->post_title, $current_locale_name, $locale_name, $id ); if ( substr( $title, -1 ) === "." ) { $title = substr( $title, 0, strlen( $title ) - 1 ); } $meta = $this->translate_meta( $id, $current_locale_name, $locale_name ); $excerpt = get_the_excerpt( $id ); if ( !empty( trim( $excerpt ) ) ) { $excerpt = $this->ai_engine->translate( $excerpt, $current_locale_name, $locale_name, $id ); } $newId = $this->create_translation( $id, $title, $translated_text, $excerpt, $meta, $current_locale, $locale ); $newPost = get_post( $newId ); do_action( 'save_post', $newId, $newPost, false ); if ( class_exists( '\\Elementor\\Plugin' ) ) { $post_css = new \Elementor\Core\Files\CSS\Post($newId); $post_css->update(); \Elementor\Plugin::$instance->files_manager->clear_cache(); polylai_Utils::db_log( 'debug', 'translate', "clear elementor cache" ); } } catch ( Exception $e ) { polylai_Utils::db_log( 'error', 'translate', $e->getMessage() ); } } private function translate_categories() { $italian_language = 'it'; // Ottiene le categorie con il filtro della lingua italiana $args = array( 'taxonomy' => 'category', 'lang' => $italian_language, 'hide_empty' => false, ); $categories = get_terms( $args ); // Ottiene le categorie } private function get_category_id_by_url( $category_url, $locale ) { //print_r(pll_home_url($locale) . "\n"); $path = str_replace( pll_home_url( $locale ), '', $category_url ); $path = trim( $path, '/' ); $slug = array_filter( explode( '/', $path ) ); $category_slug = end( $slug ); $category = get_term_by( 'slug', $category_slug, 'category' ); if ( $category ) { return $category->term_id; } else { return 0; } } }