true], 'names'); if (!empty($public_cpts)) { $post_types = array_unique(array_merge($post_types, array_values($public_cpts))); } foreach ($post_types as $post_type) { // Exclude attachments / internal types if (in_array($post_type, ['attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset'], true)) { continue; } add_meta_box( 'aitrfoel-translation-metabox', __('Meep Translator', 'ai-translator-for-elementor-and-polylang'), [$this, 'render_metabox'], $post_type, 'side', 'high' // High priority places it at the very top of sidebar ); } } /** * Enqueue assets only on post/page edit screens */ public function enqueue_assets($hook) { if (!in_array($hook, ['post.php', 'post-new.php'], true)) { return; } wp_enqueue_style( 'aitrfoel-metabox', AITRFOEL_PLUGIN_URL . 'assets/css/metabox.css', [], AITRFOEL_VERSION ); wp_enqueue_script( 'aitrfoel-metabox', AITRFOEL_PLUGIN_URL . 'assets/js/metabox.js', ['jquery'], AITRFOEL_VERSION, true ); wp_localize_script('aitrfoel-metabox', 'aitrfoelMetabox', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aitrfoel_ajax_nonce'), 'post_id' => get_the_ID(), 'strings' => [ 'translating' => __('Translating...', 'ai-translator-for-elementor-and-polylang'), 'translate' => __('Translate', 'ai-translator-for-elementor-and-polylang'), 'translation_complete' => __('Translation completed!', 'ai-translator-for-elementor-and-polylang'), 'translation_failed' => __('Translation failed.', 'ai-translator-for-elementor-and-polylang'), 'select_language' => __('Please select at least one language.', 'ai-translator-for-elementor-and-polylang'), 'confirm_translate' => __('Start translating to the selected languages?', 'ai-translator-for-elementor-and-polylang'), 'view' => __('View', 'ai-translator-for-elementor-and-polylang'), 'edit' => __('Edit', 'ai-translator-for-elementor-and-polylang'), 'draft' => __('Draft', 'ai-translator-for-elementor-and-polylang'), 'published' => __('Published', 'ai-translator-for-elementor-and-polylang'), 'pending' => __('Pending', 'ai-translator-for-elementor-and-polylang'), 'no_multilingual' => __('Set up a multilingual plugin to enable quick translations.', 'ai-translator-for-elementor-and-polylang'), 'no_content' => __('No translatable content detected.', 'ai-translator-for-elementor-and-polylang'), 'word_limit_reached' => __('Word limit reached. Please upgrade your plan.', 'ai-translator-for-elementor-and-polylang'), ], ]); } /** * Add Quick Translate / Retranslate node to WordPress Admin Bar * (Deprecated: unified into AITRFOEL_Admin_Bar under 'aitrfoel-admin-bar-hub') * * @param WP_Admin_Bar $wp_admin_bar */ public function add_admin_bar_translate_menu($wp_admin_bar) { // Unified into AITRFOEL_Admin_Bar return; } /** * Handle Admin Bar Translate / Retranslate GET requests */ public function handle_admin_bar_translate() { $post_id = isset($_GET['post_id']) ? absint($_GET['post_id']) : 0; $target_lang = isset($_GET['target_lang']) ? sanitize_text_field($_GET['target_lang']) : ''; $redirect_to = isset($_GET['redirect_to']) ? esc_url_raw(rawurldecode($_GET['redirect_to'])) : home_url('/'); if (!$post_id || empty($target_lang)) { wp_die(esc_html__('Invalid translation request parameters.', 'ai-translator-for-elementor-and-polylang')); } check_admin_referer('aitrfoel_ab_trans_' . $post_id); if (!current_user_can('edit_post', $post_id)) { wp_die(esc_html__('You do not have permission to translate this content.', 'ai-translator-for-elementor-and-polylang')); } if (!class_exists('AITRFOEL_Translation_Engine')) { wp_die(esc_html__('Translation engine is not available.', 'ai-translator-for-elementor-and-polylang')); } $engine = AITRFOEL_Translation_Engine::get_instance(); $result = $engine->translate_page($post_id, $target_lang, 'auto'); if (is_wp_error($result)) { $err_msg = $result->get_error_message(); $target_url = add_query_arg('aitrfoel_err', rawurlencode($err_msg), $redirect_to); wp_safe_redirect($target_url); exit; } // Successfully translated: redirect to the translated post permalink (or redirect_to with success notice) $translated_permalink = get_permalink($result); $final_url = $translated_permalink ?: $redirect_to; $final_url = add_query_arg('aitrfoel_msg', rawurlencode(sprintf(__('Page successfully translated to %s!', 'ai-translator-for-elementor-and-polylang'), strtoupper($target_lang))), $final_url); wp_safe_redirect($final_url); exit; } /** * Render toast banner when coming back from admin bar translation */ public function render_admin_bar_toast() { if (!empty($_GET['aitrfoel_msg'])) { $msg = sanitize_text_field(rawurldecode($_GET['aitrfoel_msg'])); echo '
'; echo ' ' . esc_html($msg); echo ''; echo '
'; echo ''; } elseif (!empty($_GET['aitrfoel_err'])) { $err = sanitize_text_field(rawurldecode($_GET['aitrfoel_err'])); echo '
'; echo ' ' . esc_html($err); echo ''; echo '
'; echo ''; } } /** * Render full-screen frosted glass overlay during translation requests */ public function render_admin_bar_loading_overlay() { if (!is_user_logged_in() || !is_admin_bar_showing()) { return; } ?> #wpadminbar #wp-admin-bar-aitrfoel-top-bar-translate > .ab-item { display: flex !important; align-items: center !important; height: 32px !important; color: #fff !important; } @media screen and (max-width: 782px) { #wpadminbar #wp-admin-bar-aitrfoel-top-bar-translate > .ab-item { height: 46px !important; } } #wpadminbar #wp-admin-bar-aitrfoel-top-bar-translate .ab-icon { float: none !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; width: 15px !important; height: 15px !important; min-width: 15px !important; margin: 0 6px 0 0 !important; padding: 0 !important; line-height: 1 !important; } #wpadminbar #wp-admin-bar-aitrfoel-top-bar-translate .ab-icon svg { display: block !important; width: 15px !important; height: 15px !important; margin: 0 !important; padding: 0 !important; } #wpadminbar #wp-admin-bar-aitrfoel-top-bar-translate .ab-label { display: inline-block !important; height: auto !important; line-height: 1.2 !important; margin: 0 !important; padding: 0 !important; } #wpadminbar #wp-admin-bar-aitrfoel-top-bar-translate:hover > .ab-item { background: #1447e6 !important; color: #fff !important; } #wpadminbar #wp-admin-bar-aitrfoel-top-bar-translate .ab-submenu .ab-item { display: flex !important; align-items: center !important; gap: 6px !important; } '; } /** * Render the metabox content */ public function render_metabox($post) { $multilingual = class_exists('AITRFOEL_Multilingual_Manager') ? AITRFOEL_Multilingual_Manager::get_instance() : null; // No multilingual plugin if (!$multilingual || !$multilingual->is_active()) { $dashboard_url = admin_url('admin.php?page=ai-translator-for-elementor-and-polylang'); echo '

'; printf( /* translators: %1$s: opening link tag, %2$s: closing link tag */ esc_html__('Set up your languages for quick translations. %1$sGo to Dashboard%2$s', 'ai-translator-for-elementor-and-polylang'), '', '' ); echo '

'; return; } // Languages must be configured if (!$multilingual->has_languages_configured()) { $detected = $multilingual->get_detected_plugin(); $setup_url = ($detected === 'wpml') ? admin_url('admin.php?page=sitepress-multilingual-cms/menu/languages.php') : admin_url('admin.php?page=mlang'); echo '

'; printf( /* translators: %s: URL to language settings page */ esc_html__('Please %sconfigure your languages%s first.', 'ai-translator-for-elementor-and-polylang'), '', '' ); echo '

'; return; } $post_lang = $multilingual->get_post_language($post->ID); $translations = $multilingual->get_post_translations($post->ID); $available_langs = $multilingual->get_available_languages(); $default_lang = $multilingual->get_default_language(); // Detect content type $content_type = 'unknown'; if (class_exists('AITRFOEL_Content_Detector')) { $detector = AITRFOEL_Content_Detector::get_instance(); $content_type = $detector->detect($post->ID); } // Determine which is the source post for this translation group $source_id = $post->ID; if (!empty($translations)) { // If the default language version exists, that's the source if (isset($translations[$default_lang]) && $translations[$default_lang] != $post->ID) { $source_id = $translations[$default_lang]; } } $is_translation = ($source_id !== $post->ID); ?>
'Elementor', 'gutenberg' => 'Gutenberg', 'classic' => 'Classic', 'woocommerce'=> 'WooCommerce', ]; $type_name = $type_labels[$content_type] ?? ucfirst($content_type); echo '' . esc_html($type_name) . ''; ?>


$lang) { if ($code === $post_lang) { continue; } $lang_name = is_object($lang) ? ($lang->name ?? strtoupper($code)) : (is_array($lang) ? ($lang['name'] ?? strtoupper($code)) : strtoupper($code)); $has_translation = isset($translations[$code]) && $translations[$code]; ?>


is_active()) { $translations = $multilingual->get_post_translations($post_id); $default_lang = $multilingual->get_default_language(); if (!empty($translations[$default_lang]) && (int) $translations[$default_lang] !== $post_id) { $source_id = (int) $translations[$default_lang]; } } // Get translation engine if (!class_exists('AITRFOEL_Translation_Engine')) { throw new Exception(__('Translation engine not available.', 'ai-translator-for-elementor-and-polylang')); } $engine = AITRFOEL_Translation_Engine::get_instance(); $results = []; $errors = []; foreach ($target_langs as $lang) { $result = $engine->translate_page($source_id, $lang, 'auto'); if (is_wp_error($result)) { $errors[$lang] = $result->get_error_message(); } else { $new_post = get_post($result); $results[$lang] = [ 'post_id' => $result, 'title' => $new_post ? $new_post->post_title : '', 'status' => $new_post ? $new_post->post_status : 'draft', 'edit_url' => get_edit_post_link($result, 'raw'), 'view_url' => get_permalink($result), ]; } } if (!empty($errors) && empty($results)) { // All failed wp_send_json_error([ 'message' => __('All translations failed.', 'ai-translator-for-elementor-and-polylang'), 'errors' => $errors, ]); } else { // Check if words are running low and append a nudge $nudge = null; if (class_exists('AITRFOEL_License_Manager_Server')) { $status = AITRFOEL_License_Manager_Server::get_instance()->get_license_status(); $words_left = isset($status['words_left']) ? (int) $status['words_left'] : -1; $word_limit = isset($status['word_limit']) ? (int) $status['word_limit'] : -1; $is_free = isset($status['plan']) && strtolower($status['plan']) === 'free'; if ($is_free && $word_limit > 0 && $words_left !== -1 && $words_left <= 200) { $upgrade_url = (function_exists('eatp_fs') && is_object(eatp_fs()) && method_exists(eatp_fs(), 'is_registered') && eatp_fs()->is_registered()) ? eatp_fs()->get_upgrade_url() : admin_url('admin.php?page=ai-translator-for-elementor-and-polylang-pricing'); $nudge = [ 'words_left' => $words_left, 'upgrade_url' => $upgrade_url, ]; } } wp_send_json_success([ 'message' => sprintf( /* translators: %d: number of successful translations */ __('%d translation(s) completed successfully.', 'ai-translator-for-elementor-and-polylang'), count($results) ), 'translations' => $results, 'errors' => $errors, 'upgrade_nudge' => $nudge, ]); } } catch (Exception $e) { wp_send_json_error(['message' => $e->getMessage()]); } } /** * AJAX: Get translation status for metabox refresh */ public function handle_metabox_status() { try { check_ajax_referer('aitrfoel_ajax_nonce', 'nonce'); $post_id = absint($_POST['post_id'] ?? 0); if (!$post_id) { throw new Exception('Invalid post ID'); } $multilingual = class_exists('AITRFOEL_Multilingual_Manager') ? AITRFOEL_Multilingual_Manager::get_instance() : null; if (!$multilingual || !$multilingual->is_active()) { throw new Exception('Multilingual plugin not active'); } $translations = $multilingual->get_post_translations($post_id); $available_langs = $multilingual->get_available_languages(); $post_lang = $multilingual->get_post_language($post_id); $status_data = []; foreach ($available_langs as $code => $lang) { if ($code === $post_lang) { continue; } $lang_name = is_object($lang) ? ($lang->name ?? strtoupper($code)) : (is_array($lang) ? ($lang['name'] ?? strtoupper($code)) : strtoupper($code)); $has_translation = isset($translations[$code]) && $translations[$code]; $trans_post = $has_translation ? get_post($translations[$code]) : null; $status_data[$code] = [ 'name' => $lang_name, 'translated' => $has_translation, 'status' => $trans_post ? $trans_post->post_status : null, 'post_id' => $trans_post ? $trans_post->ID : null, 'edit_url' => $trans_post ? get_edit_post_link($trans_post->ID, 'raw') : null, 'view_url' => $trans_post ? get_permalink($trans_post->ID) : null, ]; } wp_send_json_success([ 'languages' => $status_data, 'post_lang' => $post_lang, ]); } catch (Exception $e) { wp_send_json_error(['message' => $e->getMessage()]); } } }