id, 'ai-translator-for-elementor') === false) return; echo ''; } public function add_admin_menu() { // Create main menu page // Meep "m" mark (white, from assets/meep-icon.svg) for the dark admin sidebar. $menu_icon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NC42OCAyOS4zIj48cGF0aCBmaWxsPSIjRkVGRUZFIiBkPSJNMCAyOS4zbDcuODQgMCAwIC0xNi41NmMwLC01LjAyIDMuMzksLTUuOSA1LjIxLC01LjkgMS44MiwwIDUuMzQsMC44OCA1LjM0LDUuOWwwIDE2LjU2IDcuODQgMCAwIC0xNi41NmMwLC01LjAyIDMuNDUsLTUuOSA1LjIxLC01LjkgMS44MiwwIDUuMzksMC44OCA1LjM5LDUuOWwwIDE2LjU2IDcuODUgMCAwIC0xNy42M2MwLC05LjA0IC01LjcxLC0xMS42NyAtMTAuOCwtMTEuNjcgLTMuMiwwIC03LjAyLDEuMDcgLTkuNDcsNC4zOSAtMi4wNywtMy4yNiAtNS41OCwtNC4zOSAtOC45MSwtNC4zOSAtMi42NCwwIC01LjUyLDEuMDEgLTcuNjYsMy4zM2wwIC0yLjg5IC03Ljg0IDAgMCAyOC44NiAwIDB6Ii8+PC9zdmc+'; add_menu_page( __('Meep AI Translator', 'ai-translator-for-elementor-and-polylang'), // page title (heading / browser tab) __('Meep Translator', 'ai-translator-for-elementor-and-polylang'), // sidebar menu label 'manage_options', 'ai-translator-for-elementor-and-polylang', [$this, 'render_dashboard_page'], $menu_icon, 30 ); // Rename the auto-created first submenu item from the menu label to "Dashboard". add_submenu_page( 'ai-translator-for-elementor-and-polylang', __('Dashboard', 'ai-translator-for-elementor-and-polylang'), __('Dashboard', 'ai-translator-for-elementor-and-polylang'), 'manage_options', 'ai-translator-for-elementor-and-polylang', [$this, 'render_dashboard_page'] ); // Add "Translate All" submenu (2nd item) add_submenu_page( 'ai-translator-for-elementor-and-polylang', __('Translate All', 'ai-translator-for-elementor-and-polylang'), __('Translate All', 'ai-translator-for-elementor-and-polylang'), 'manage_options', 'ai-translator-for-elementor-and-polylang-translate-all', [$this, 'render_translate_all_page'] ); // Add Settings submenu (3rd item) add_submenu_page( 'ai-translator-for-elementor-and-polylang', __('Settings', 'ai-translator-for-elementor-and-polylang'), __('Settings', 'ai-translator-for-elementor-and-polylang'), 'manage_options', 'ai-translator-for-elementor-and-polylang-settings', ['AITRFOEL_Settings', 'render_page'] ); // Add History submenu add_submenu_page( 'ai-translator-for-elementor-and-polylang', __('History', 'ai-translator-for-elementor-and-polylang'), __('History', 'ai-translator-for-elementor-and-polylang'), 'manage_options', 'ai-translator-for-elementor-and-polylang-history', [$this, 'render_history_page'] ); // Add "Activate License" submenu for PRO version (always visible, even before opt-in) if (defined('AITRFOEL_VERSION_TYPE') && AITRFOEL_VERSION_TYPE === 'pro') { add_submenu_page( 'ai-translator-for-elementor-and-polylang', __('Activate License', 'ai-translator-for-elementor-and-polylang'), __('Activate License', 'ai-translator-for-elementor-and-polylang'), 'manage_options', 'ai-translator-for-elementor-and-polylang-activate-license', [$this, 'render_activate_license_page'] ); } // Freemius will automatically add its Account, Pricing, etc. submenus after this } /** * Render History page (delegates to dedicated class) */ public function render_history_page() { if (class_exists('AITRFOEL_Translation_History')) { AITRFOEL_Translation_History::get_instance()->render_page(); } } /** * Render Translate All page (delegates to dedicated class) */ public function render_translate_all_page() { if (class_exists('AITRFOEL_Translate_All')) { AITRFOEL_Translate_All::get_instance()->render_page(); } } /** * Render license activation page * This provides a way to enter license key without reactivating the plugin */ public function render_activate_license_page() { // Handle license activation form submission if (isset($_POST['aitrfoel_activate_license']) && isset($_POST['license_key'])) { check_admin_referer('aitrfoel_activate_license_nonce'); $license_key = sanitize_text_field($_POST['license_key']); if (!empty($license_key) && function_exists('eatp_fs') && is_object(eatp_fs())) { // If this license is already active in Freemius, sync with server directly $current_license = method_exists(eatp_fs(), '_get_license') ? eatp_fs()->_get_license() : null; $already_active = ($current_license && isset($current_license->secret_key) && $current_license->secret_key === $license_key); if ($already_active) { if (class_exists('AITRFOEL_License_Manager_Server')) { AITRFOEL_License_Manager_Server::get_instance()->clear_status_cache(); AITRFOEL_License_Manager_Server::get_instance()->sync_plan_to_server(); } $success_message = __('Your license is already active and up to date! Unlimited translations are enabled.', 'ai-translator-for-elementor-and-polylang'); } else { // Try to activate the license using Freemius try { $result = eatp_fs()->activate_migrated_license($license_key); // activate_migrated_license returns an array, not an object if (is_array($result) && isset($result['error'])) { $error_message = is_string($result['error']) ? $result['error'] : __('License activation failed.', 'ai-translator-for-elementor-and-polylang'); } elseif (is_object($result) && isset($result->error)) { $error_message = isset($result->error->message) ? $result->error->message : __('License activation failed.', 'ai-translator-for-elementor-and-polylang'); } else { // Clear status cache and force fresh status if (class_exists('AITRFOEL_License_Manager_Server')) { AITRFOEL_License_Manager_Server::get_instance()->clear_status_cache(); delete_option('aitrfoel_cached_license_key'); } // Check if pending email confirmation if (method_exists(eatp_fs(), 'is_pending_activation') && eatp_fs()->is_pending_activation()) { $redirect_url = admin_url('admin.php?page=ai-translator-for-elementor-and-polylang&license_pending=true'); } else { $redirect_url = admin_url('admin.php?page=ai-translator-for-elementor-and-polylang&activated=true'); if (is_array($result) && !empty($result['next_page'])) { $redirect_url = $result['next_page']; } } // Render JS redirect rather than wp_redirect() + exit, because admin headers have already been sent by WordPress echo ''; echo ''; echo '

' . esc_html__('License activated successfully! Redirecting...', 'ai-translator-for-elementor-and-polylang') . ' ' . esc_html__('Click here if not redirected automatically.', 'ai-translator-for-elementor-and-polylang') . '

'; return; } } catch (Exception $e) { $error_message = $e->getMessage(); } } } else { $error_message = __('Please enter a valid license key.', 'ai-translator-for-elementor-and-polylang'); } } // Check current license status $current_license = null; $is_paying = false; if (function_exists('eatp_fs') && is_object(eatp_fs())) { $current_license = method_exists(eatp_fs(), '_get_license') ? eatp_fs()->_get_license() : null; $is_paying = method_exists(eatp_fs(), 'is_paying') ? eatp_fs()->is_paying() : false; } ?>
render_plugin_header(__('Activate License', 'ai-translator-for-elementor-and-polylang')); ?>

is_expired()): ?>

expiration)): ?>

expiration))); ?>

is_expired()): ?>

is_registered()): ?>

is_registered()): ?>

render_setup_notice(); return; } // Get pages with Elementor data $pages = $this->get_elementor_pages(); $languages = $this->get_languages(); // Get stats from OpenAI API $api = AITRFOEL_OpenAI_API::get_instance(); $stats = $api->get_usage_stats(); ?>

render_quick_tips(); ?>
render_plugin_header(__('Setup', 'ai-translator-for-elementor-and-polylang')); ?>

  1. OpenAI API Keys
['page', 'post'], 'posts_per_page' => -1, 'post_status' => 'publish', 'meta_query' => [ [ 'key' => '_elementor_data', 'compare' => 'EXISTS' ] ], 'orderby' => 'title', 'order' => 'ASC' ]; return get_posts($args); } /** * Get available languages */ private function get_languages() { return [ 'English' => __('English', 'ai-translator-for-elementor-and-polylang'), 'Spanish' => __('Spanish', 'ai-translator-for-elementor-and-polylang'), 'French' => __('French', 'ai-translator-for-elementor-and-polylang'), 'German' => __('German', 'ai-translator-for-elementor-and-polylang'), 'Italian' => __('Italian', 'ai-translator-for-elementor-and-polylang'), 'Portuguese' => __('Portuguese', 'ai-translator-for-elementor-and-polylang'), 'Russian' => __('Russian', 'ai-translator-for-elementor-and-polylang'), 'Ukrainian' => __('Ukrainian', 'ai-translator-for-elementor-and-polylang'), 'Belarusian' => __('Belarusian', 'ai-translator-for-elementor-and-polylang'), 'Czech' => __('Czech', 'ai-translator-for-elementor-and-polylang'), 'Polish' => __('Polish', 'ai-translator-for-elementor-and-polylang'), 'Vietnamese' => __('Vietnamese', 'ai-translator-for-elementor-and-polylang'), 'Chinese' => __('Chinese (Simplified)', 'ai-translator-for-elementor-and-polylang'), 'Japanese' => __('Japanese', 'ai-translator-for-elementor-and-polylang'), 'Korean' => __('Korean', 'ai-translator-for-elementor-and-polylang'), 'Arabic' => __('Arabic', 'ai-translator-for-elementor-and-polylang'), 'Hebrew' => __('Hebrew', 'ai-translator-for-elementor-and-polylang'), 'Hindi' => __('Hindi', 'ai-translator-for-elementor-and-polylang'), 'Turkish' => __('Turkish', 'ai-translator-for-elementor-and-polylang'), 'Dutch' => __('Dutch', 'ai-translator-for-elementor-and-polylang'), 'Swedish' => __('Swedish', 'ai-translator-for-elementor-and-polylang'), 'Norwegian' => __('Norwegian', 'ai-translator-for-elementor-and-polylang'), 'Danish' => __('Danish', 'ai-translator-for-elementor-and-polylang'), 'Finnish' => __('Finnish', 'ai-translator-for-elementor-and-polylang'), ]; } /** * Render modern multi-step onboarding wizard * Steps: Welcome → Installing → Add Languages → Finish */ private function render_multilingual_onboarding_screen() { // Enqueue wizard CSS (meep-admin is already loaded globally) wp_enqueue_style( 'aitrfoel-onboarding-wizard', AITRFOEL_PLUGIN_URL . 'assets/css/onboarding-wizard.css', ['aitrfoel-meep-admin'], defined('AITRFOEL_VERSION') ? AITRFOEL_VERSION : '1.3.2' ); // Get available languages from language mapper $available_languages = []; if (class_exists('AITRFOEL_Language_Mapper')) { $mappings = AITRFOEL_Language_Mapper::get_all_mappings(); foreach ($mappings as $code => $name) { $available_languages[] = ['code' => $code, 'name' => $name]; } } else { $available_languages = [ ['code' => 'en', 'name' => 'English'], ['code' => 'es', 'name' => 'Spanish'], ['code' => 'fr', 'name' => 'French'], ['code' => 'de', 'name' => 'German'], ['code' => 'it', 'name' => 'Italian'], ['code' => 'pt', 'name' => 'Portuguese'], ['code' => 'nl', 'name' => 'Dutch'], ['code' => 'pl', 'name' => 'Polish'], ['code' => 'ru', 'name' => 'Russian'], ['code' => 'ja', 'name' => 'Japanese'], ['code' => 'zh', 'name' => 'Chinese'], ['code' => 'ko', 'name' => 'Korean'], ['code' => 'ar', 'name' => 'Arabic'], ['code' => 'tr', 'name' => 'Turkish'], ['code' => 'hi', 'name' => 'Hindi'], ]; } usort($available_languages, function($a, $b) { return strcmp($a['name'], $b['name']); }); // Popular languages with SVG flag data (ISO 3166-1 alpha-2 country codes for flags) $popular_languages = [ ['code' => 'es', 'name' => 'Spanish', 'flag' => 'es'], ['code' => 'fr', 'name' => 'French', 'flag' => 'fr'], ['code' => 'de', 'name' => 'German', 'flag' => 'de'], ['code' => 'it', 'name' => 'Italian', 'flag' => 'it'], ['code' => 'pt', 'name' => 'Portuguese', 'flag' => 'pt'], ['code' => 'nl', 'name' => 'Dutch', 'flag' => 'nl'], ['code' => 'pl', 'name' => 'Polish', 'flag' => 'pl'], ['code' => 'ru', 'name' => 'Russian', 'flag' => 'ru'], ['code' => 'ja', 'name' => 'Japanese', 'flag' => 'jp'], ['code' => 'zh', 'name' => 'Chinese', 'flag' => 'cn'], ['code' => 'ko', 'name' => 'Korean', 'flag' => 'kr'], ['code' => 'ar', 'name' => 'Arabic', 'flag' => 'sa'], ['code' => 'tr', 'name' => 'Turkish', 'flag' => 'tr'], ['code' => 'hi', 'name' => 'Hindi', 'flag' => 'in'], ['code' => 'sv', 'name' => 'Swedish', 'flag' => 'se'], ['code' => 'uk', 'name' => 'Ukrainian', 'flag' => 'ua'], ]; // Check if Polylang or WPML was previously installed (offer import notice) $polylang_was_active = get_option('polylang', false) || get_option('polylang_wpml_strings', false); $wpml_was_active = get_option('icl_sitepress_settings', false); $had_previous_plugin = $polylang_was_active || $wpml_was_active; $previous_plugin_name = $polylang_was_active ? 'Polylang' : ($wpml_was_active ? 'WPML' : ''); // Detect languages from Polylang/WPML for auto-import $detected_languages = []; $detected_default_lang = ''; // Try Polylang (active — functions available) if (function_exists('pll_languages_list') && function_exists('pll_default_language')) { $pll_codes = pll_languages_list(['fields' => 'slug']); $detected_default_lang = pll_default_language('slug') ?: ''; if (is_array($pll_codes)) { foreach ($pll_codes as $code) { $name = ''; if (class_exists('AITRFOEL_Language_Mapper')) { $name = AITRFOEL_Language_Mapper::get_language_name($code, false); } $detected_languages[] = [ 'code' => $code, 'name' => $name ?: strtoupper($code), ]; } } if (!$had_previous_plugin) { $had_previous_plugin = !empty($detected_languages); $previous_plugin_name = 'Polylang'; } } // Try WPML (active — globals available) elseif (defined('ICL_SITEPRESS_VERSION')) { global $sitepress; if (isset($sitepress) && is_object($sitepress) && method_exists($sitepress, 'get_active_languages')) { $active_langs = $sitepress->get_active_languages(); $detected_default_lang = method_exists($sitepress, 'get_default_language') ? $sitepress->get_default_language() : ''; if (is_array($active_langs)) { foreach ($active_langs as $code => $lang_info) { $name = ''; if (class_exists('AITRFOEL_Language_Mapper')) { $name = AITRFOEL_Language_Mapper::get_language_name($code, false); } if (!$name && isset($lang_info['english_name'])) { $name = $lang_info['english_name']; } $detected_languages[] = [ 'code' => $code, 'name' => $name ?: strtoupper($code), ]; } } if (!$had_previous_plugin) { $had_previous_plugin = !empty($detected_languages); $previous_plugin_name = 'WPML'; } } } // Try Polylang DB options (inactive but data remains) elseif ($polylang_was_active) { $pll_option = get_option('polylang', []); if (is_array($pll_option) && !empty($pll_option['default_lang'])) { $detected_default_lang = $pll_option['default_lang']; } // Polylang stores language data in wp_terms / taxonomy 'language' // When Polylang is deactivated, the taxonomy is not registered, // so get_terms() returns WP_Error. Use direct DB query instead. if (taxonomy_exists('language')) { $pll_terms = get_terms([ 'taxonomy' => 'language', 'hide_empty' => false, ]); if (!is_wp_error($pll_terms) && is_array($pll_terms)) { foreach ($pll_terms as $term) { $code = $term->slug; $name = ''; if (class_exists('AITRFOEL_Language_Mapper')) { $name = AITRFOEL_Language_Mapper::get_language_name($code, false); } $detected_languages[] = [ 'code' => $code, 'name' => $name ?: $term->name, ]; } } } else { // Taxonomy not registered (Polylang deactivated) — query DB directly global $wpdb; $db_terms = $wpdb->get_results( "SELECT t.slug, t.name FROM {$wpdb->terms} t " . "INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id " . "WHERE tt.taxonomy = 'language'" ); if (is_array($db_terms)) { foreach ($db_terms as $term) { $code = $term->slug; $name = ''; if (class_exists('AITRFOEL_Language_Mapper')) { $name = AITRFOEL_Language_Mapper::get_language_name($code, false); } $detected_languages[] = [ 'code' => $code, 'name' => $name ?: $term->name, ]; } } } } // Detect plugin install/active states for system choice screen if (!function_exists('is_plugin_active')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $polylang_active = (function_exists('is_plugin_active') && is_plugin_active('polylang/polylang.php')) || function_exists('pll_languages_list'); $wpml_active = (function_exists('is_plugin_active') && is_plugin_active('sitepress-multilingual-cms/sitepress.php')) || (defined('ICL_SITEPRESS_VERSION') && class_exists('SitePress')); $polylang_installed = $polylang_active || file_exists(WP_PLUGIN_DIR . '/polylang/polylang.php'); $wpml_installed = $wpml_active || file_exists(WP_PLUGIN_DIR . '/sitepress-multilingual-cms/sitepress.php'); ?>
01
02
03
04

.

.

// 0 selected
// click to add
// search

✓0.4s

.

0
~1 min

get_detected_plugin() : ''; // If Polylang is active, add language directly into Polylang if ($detected_plugin === 'polylang' && function_exists('PLL') && is_object(PLL()) && isset(PLL()->model)) { $pll_locales = [ 'en' => ['en_US', 'us', 0, 'English'], 'es' => ['es_ES', 'es', 0, 'Español'], 'fr' => ['fr_FR', 'fr', 0, 'Français'], 'de' => ['de_DE', 'de', 0, 'Deutsch'], 'it' => ['it_IT', 'it', 0, 'Italiano'], 'nl' => ['nl_NL', 'nl', 0, 'Nederlands'], 'pl' => ['pl_PL', 'pl', 0, 'Polski'], 'pt' => ['pt_PT', 'pt', 0, 'Português'], 'ru' => ['ru_RU', 'ru', 0, 'Русский'], 'uk' => ['uk', 'ua', 0, 'Українська'], 'ar' => ['ar', 'sa', 1, 'العربية'], 'zh' => ['zh_CN', 'cn', 0, '中文'], 'ja' => ['ja', 'jp', 0, '日本語'], 'ro' => ['ro_RO', 'ro', 0, 'Română'], 'cs' => ['cs_CZ', 'cz', 0, 'Čeština'], 'sv' => ['sv_SE', 'se', 0, 'Svenska'], 'da' => ['da_DK', 'dk', 0, 'Dansk'], 'fi' => ['fi', 'fi', 0, 'Suomi'], 'no' => ['nb_NO', 'no', 0, 'Norsk'], 'tr' => ['tr_TR', 'tr', 0, 'Türkçe'], 'el' => ['el', 'gr', 0, 'Ελληνικά'], 'hu' => ['hu_HU', 'hu', 0, 'Magyar'], 'hr' => ['hr', 'hr', 0, 'Hrvatski'], 'bg' => ['bg_BG', 'bg', 0, 'Български'], 'sk' => ['sk_SK', 'sk', 0, 'Slovenčina'], 'sl' => ['sl_SI', 'si', 0, 'Slovenščina'], 'he' => ['he_IL', 'il', 1, 'עברית'], 'hi' => ['hi_IN', 'in', 0, 'हिन्दी'], 'id' => ['id_ID', 'id', 0, 'Bahasa Indonesia'], 'ko' => ['ko_KR', 'kr', 0, '한국어'], 'vi' => ['vi', 'vn', 0, 'Tiếng Việt'], 'th' => ['th', 'th', 0, 'ไทย'], ]; $existing_pll = PLL()->model->get_languages_list(); if (empty($existing_pll)) { $wp_locale = get_locale(); $wp_short = strtolower(substr($wp_locale, 0, 2)) ?: 'en'; if ($wp_short === $code) { $wp_short = ($code === 'en') ? 'es' : 'en'; } $def_info = $pll_locales[$wp_short] ?? [$wp_locale ?: 'en_US', $wp_short, 0, strtoupper($wp_short)]; PLL()->model->add_language([ 'name' => $def_info[3], 'slug' => $wp_short, 'locale' => $def_info[0], 'rtl' => $def_info[2], 'term_group' => 0, 'flag' => $def_info[1], ]); } $target_info = $pll_locales[$code] ?? [$code . '_' . strtoupper($code), $code, 0, $name]; $pll_res = PLL()->model->add_language([ 'name' => $name, 'slug' => $code, 'locale' => $target_info[0], 'rtl' => $target_info[2], 'term_group' => 1, 'flag' => $target_info[1], ]); if (!is_wp_error($pll_res) && $pll_res) { delete_option('aitrfoel_dependencies_status'); flush_rewrite_rules(); wp_send_json_success(['message' => __('Language added.', 'ai-translator-for-elementor-and-polylang')]); } } // Built-in language system path if (!$builtin->is_enabled()) { $builtin->enable(); $builtin->create_table(); } $existing_builtin = $builtin->get_available_languages(); if (empty($existing_builtin)) { $wp_locale = get_locale(); $wp_short = strtolower(substr($wp_locale, 0, 2)) ?: 'en'; $lang_names = [ 'en' => 'English', 'es' => 'Español', 'fr' => 'Français', 'de' => 'Deutsch', 'it' => 'Italiano', 'nl' => 'Nederlands', 'pl' => 'Polski', 'pt' => 'Português', 'ru' => 'Русский', 'uk' => 'Українська', ]; if ($wp_short !== $code) { $def_name = $lang_names[$wp_short] ?? strtoupper($wp_short); $builtin->add_language($wp_short, $def_name, $def_name, true); } } $result = $builtin->add_language($code, $name, $name); if ($result) { delete_option('aitrfoel_dependencies_status'); delete_option('aitrfoel_rewrite_rules_version'); flush_rewrite_rules(); wp_send_json_success(['message' => __('Language added.', 'ai-translator-for-elementor-and-polylang')]); } else { wp_send_json_error(__('Language already exists or could not be added.', 'ai-translator-for-elementor-and-polylang')); } break; case 'remove': $code = sanitize_text_field($_POST['language_code'] ?? ''); $result = $builtin->remove_language($code); if ($result) { flush_rewrite_rules(); wp_send_json_success(['message' => __('Language removed.', 'ai-translator-for-elementor-and-polylang')]); } else { wp_send_json_error(__('Cannot remove this language.', 'ai-translator-for-elementor-and-polylang')); } break; case 'set_default': $code = sanitize_text_field($_POST['language_code'] ?? ''); $result = $builtin->set_default_language($code); if ($result) { wp_send_json_success(['message' => __('Default language updated.', 'ai-translator-for-elementor-and-polylang')]); } else { wp_send_json_error(__('Could not set default language.', 'ai-translator-for-elementor-and-polylang')); } break; case 'disable': $builtin->disable(); delete_option('aitrfoel_dependencies_status'); wp_send_json_success(['message' => __('Built-in language system disabled.', 'ai-translator-for-elementor-and-polylang')]); break; default: wp_send_json_error(__('Invalid operation.', 'ai-translator-for-elementor-and-polylang')); } } /** * AJAX handler: Save analytics (PostHog) opt-in preference from onboarding wizard */ public function handle_save_analytics_optin() { check_ajax_referer('aitrfoel_builtin_setup', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Insufficient permissions.'); } $optin = sanitize_text_field(wp_unslash($_POST['optin'] ?? '0')); // Must match the option read by AITRFOEL_Analytics::is_enabled(). update_option('aitrfoel_analytics_enabled', $optin === '1'); wp_send_json_success(['message' => 'Preference saved.']); } /** * Render a 1-click "Pick the language you want to translate your site into" card * when 0 target languages are configured (works on both Dashboard and Translate All). */ public function render_quick_add_language_card($multilingual = null) { if (!$multilingual && class_exists('AITRFOEL_Multilingual_Manager')) { $multilingual = AITRFOEL_Multilingual_Manager::get_instance(); } $existing_langs = ($multilingual && method_exists($multilingual, 'get_available_languages')) ? $multilingual->get_available_languages() : []; $default_lang = ($multilingual && method_exists($multilingual, 'get_default_language')) ? $multilingual->get_default_language() : strtolower(substr(get_locale(), 0, 2)); if (empty($default_lang)) { $default_lang = 'en'; } $popular_langs = [ 'es' => ['name' => 'Spanish (Español)', 'short' => 'ES'], 'fr' => ['name' => 'French (Français)', 'short' => 'FR'], 'de' => ['name' => 'German (Deutsch)', 'short' => 'DE'], 'it' => ['name' => 'Italian (Italiano)', 'short' => 'IT'], 'nl' => ['name' => 'Dutch (Nederlands)', 'short' => 'NL'], 'pl' => ['name' => 'Polish (Polski)', 'short' => 'PL'], 'pt' => ['name' => 'Portuguese (Português)','short' => 'PT'], 'uk' => ['name' => 'Ukrainian (Українська)','short' => 'UK'], 'ru' => ['name' => 'Russian (Русский)', 'short' => 'RU'], 'ar' => ['name' => 'Arabic (العربية)', 'short' => 'AR'], 'zh' => ['name' => 'Chinese (中文)', 'short' => 'ZH'], 'ja' => ['name' => 'Japanese (日本語)', 'short' => 'JA'], 'en' => ['name' => 'English', 'short' => 'EN'], ]; $more_langs = [ 'ro' => 'Romanian (Română)', 'cs' => 'Czech (Čeština)', 'sv' => 'Swedish (Svenska)', 'da' => 'Danish (Dansk)', 'fi' => 'Finnish (Suomi)', 'no' => 'Norwegian (Norsk)', 'tr' => 'Turkish (Türkçe)', 'el' => 'Greek (Ελληνικά)', 'hu' => 'Hungarian (Magyar)', 'hr' => 'Croatian (Hrvatski)', 'bg' => 'Bulgarian (Български)', 'sk' => 'Slovak (Slovenčina)', 'sl' => 'Slovenian (Slovenščina)', 'he' => 'Hebrew (עברית)', 'hi' => 'Hindi (हिन्दी)', 'id' => 'Indonesian (Bahasa Indonesia)', 'ko' => 'Korean (한국어)', 'vi' => 'Vietnamese (Tiếng Việt)', 'th' => 'Thai (ไทย)', ]; $ajax_nonce = wp_create_nonce('aitrfoel_ajax_nonce'); ?>

' . esc_html(strtoupper($default_lang)) . '' ); ?>

$info) : if ($code === $default_lang || isset($existing_langs[$code])) continue; ?>
clear_status_cache(); } } // Show license activation notices if (isset($_GET['activated']) && $_GET['activated'] === 'true') { echo '

' . esc_html__('License activated successfully!', 'ai-translator-for-elementor-and-polylang') . ' ' . esc_html__('Your plan has been upgraded.', 'ai-translator-for-elementor-and-polylang') . '

'; } if (isset($_GET['license_pending']) && $_GET['license_pending'] === 'true') { echo '

' . esc_html__('Almost there!', 'ai-translator-for-elementor-and-polylang') . ' ' . esc_html__('Please check your email and click the confirmation link to complete the license activation.', 'ai-translator-for-elementor-and-polylang') . '

'; } // Check for missing dependencies first $deps_status = get_option('aitrfoel_dependencies_status', ['status' => true, 'missing' => []]); // Check if only multilingual plugin is missing (show friendly onboarding screen) $only_multilingual_missing = !empty($deps_status['missing']) && count($deps_status['missing']) === 1 && in_array('multilingual', $deps_status['missing']); if ($only_multilingual_missing) { $this->render_multilingual_onboarding_screen(); return; } if (!$deps_status['status'] && !empty($deps_status['missing'])) { ?>

is_active()) { // Show the onboarding/installation screen instead of an error $this->render_multilingual_onboarding_screen(); return; } // Check if languages are configured (need at least 2 for translation) $has_languages = method_exists($multilingual, 'has_languages_configured') && $multilingual->has_languages_configured(); $language_count = method_exists($multilingual, 'get_language_count') ? $multilingual->get_language_count() : 0; $plugin_display_name = $multilingual->get_plugin_display_name(); $detected_plugin = $multilingual->get_detected_plugin(); if (!$has_languages) { ?>
render_plugin_header(__('Dashboard', 'ai-translator-for-elementor-and-polylang')); ?> render_quick_add_language_card($multilingual); ?>
get_pages_with_translations($current_sort); // Determine whether any translation has ever been completed $has_any_translation = false; foreach ($pages_data as $group) { if (!empty($group['translations'])) { $has_any_translation = true; break; } } ?>
render_plugin_header(__('Dashboard', 'ai-translator-for-elementor-and-polylang')); ?> render_compact_stats_bar($pages_data); ?> get_license_status(); $u_used = isset($lic_status['words_used']) ? (int) $lic_status['words_used'] : (int) ($lic_status['words_translated'] ?? 0); $u_limit = isset($lic_status['word_limit']) ? (int) $lic_status['word_limit'] : (int) ($lic_status['total_words'] ?? 7200); $u_unlimited = ($u_limit === -1 || !empty($lic_status['unlimited']) || !empty($lic_status['is_unlimited']) || strtolower($lic_status['plan'] ?? '') === 'agency lifetime'); $u_pct = ($u_limit > 0 && !$u_unlimited) ? min(100, round(($u_used / $u_limit) * 100)) : 0; $is_cooldown = !$u_unlimited && (!empty($lic_status['cooldown_active']) || ($u_pct >= 100)); $bonus_claimed = (bool) get_option('aitrfoel_share_bonus_claimed', false); $pricing_url = 'https://meeptech.com/products/ai-translator-for-elementor-polylang/?utm_source=wp-plugin&utm_medium=admin-dashboard&utm_campaign=pro-upgrade#pricing'; $upg_dismissed = (bool) get_option('aitrfoel_upgrade_banner_dismissed', false); ?>
= 80 && !$upg_dismissed): ?>

render_setup_checklist(); ?>
post_type; if ($pt === 'page') { $pages_only[] = $group; } elseif ($pt === 'product' || $pt === 'sc_product') { $products_only[] = $group; } elseif ($pt === 'post') { $posts_only[] = $group; } elseif (in_array($pt, $form_types, true)) { $forms_only[] = $group; } elseif (in_array($pt, $element_types, true)) { $elements_only[] = $group; } else { $custom_only[] = $group; } } $pages_count = count($pages_only); $posts_count = count($posts_only); $products_count = count($products_only); $forms_count = count($forms_only); $elements_count = count($elements_only); $custom_count = count($custom_only); // Build visible tabs list (hide tabs with 0 items) $visible_tabs = []; if ($pages_count > 0) { $visible_tabs['pages'] = [ 'label' => __('Pages', 'ai-translator-for-elementor-and-polylang'), 'count' => $pages_count, 'icon' => '' ]; } if ($posts_count > 0) { $visible_tabs['posts'] = [ 'label' => __('Posts', 'ai-translator-for-elementor-and-polylang'), 'count' => $posts_count, 'icon' => '' ]; } if ($products_count > 0) { $visible_tabs['products'] = [ 'label' => __('Products', 'ai-translator-for-elementor-and-polylang'), 'count' => $products_count, 'icon' => '' ]; } if ($forms_count > 0) { $visible_tabs['forms'] = [ 'label' => __('Forms', 'ai-translator-for-elementor-and-polylang'), 'count' => $forms_count, 'icon' => '' ]; } if ($elements_count > 0) { $visible_tabs['elements'] = [ 'label' => __('Elements & Templates', 'ai-translator-for-elementor-and-polylang'), 'count' => $elements_count, 'icon' => '' ]; } if ($custom_count > 0) { $visible_tabs['custom'] = [ 'label' => __('Custom Post Types', 'ai-translator-for-elementor-and-polylang'), 'count' => $custom_count, 'icon' => '' ]; } // Fallback: if everything is 0, show Pages tab as empty default if (empty($visible_tabs)) { $visible_tabs['pages'] = [ 'label' => __('Pages', 'ai-translator-for-elementor-and-polylang'), 'count' => 0, 'icon' => '' ]; } $active_tab_key = array_key_first($visible_tabs); ?>
render_quick_tips(); ?>
enqueue_dashboard_assets(); } /** * Backward compatibility alias for get_pages_with_translations */ public function get_all_pages_with_translations() { return $this->get_pages_with_translations(); } /** * Get pages with their translations * * @param string $sort Sort mode: 'date_desc', 'date_asc', 'title_asc', 'title_desc' */ public function get_pages_with_translations($sort = 'date_desc') { $multilingual = class_exists('AITRFOEL_Multilingual_Manager') ? AITRFOEL_Multilingual_Manager::get_instance() : null; if (!$multilingual || !$multilingual->is_active()) { return []; } if (!in_array($sort, ['date_desc', 'date_asc', 'title_asc', 'title_desc'], true)) { $sort = 'date_desc'; } $query_orderby = 'date'; $query_order = 'DESC'; if ($sort === 'date_asc') { $query_orderby = 'date'; $query_order = 'ASC'; } elseif ($sort === 'title_asc') { $query_orderby = 'title'; $query_order = 'ASC'; } elseif ($sort === 'title_desc') { $query_orderby = 'title'; $query_order = 'DESC'; } // Include all content post types that have an admin UI (pages, posts, products, // and custom post types like Testimonials, Employees, Portfolios, JetEngine, etc.) $exclude_system_types = [ 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block', 'wp_navigation', 'wp_template', 'wp_template_part', 'wp_global_styles', 'action_scheduler' ]; $dashboard_post_types = []; foreach (get_post_types(['show_ui' => true], 'objects') as $pt_name => $pt_obj) { if (in_array($pt_name, $exclude_system_types, true)) { continue; } $dashboard_post_types[] = $pt_name; } /** * Filter the post types listed on the translation dashboard. * * @param string[] $dashboard_post_types */ $dashboard_post_types = apply_filters('aitrfoel_dashboard_post_types', $dashboard_post_types); if (empty($dashboard_post_types)) { $dashboard_post_types = ['page', 'post', 'product']; } $element_post_types = ['elementor_library', 'elementor-hf', 'jet-theme-core', 'jet-engine', 'wpcf7_contact_form', 'sureforms_form', 'sf_form', 'wpforms', 'jet-form-builder', 'fluentform', 'formidable', 'ninja_forms']; foreach ($element_post_types as $ept) { if (post_type_exists($ept) && !in_array($ept, $dashboard_post_types, true)) { $dashboard_post_types[] = $ept; } } $all_pages = get_posts([ 'post_type' => $dashboard_post_types, 'post_status' => ['publish', 'draft'], 'numberposts' => -1, 'orderby' => $query_orderby, 'order' => $query_order, 'suppress_filters' => true // Get posts in all languages for WPML ]); $translation_groups = []; $processed_pages = []; // Track all post IDs already assigned to a group $source_id_to_group = []; // Map source page ID to group index for merging $default_lang = $multilingual->get_default_language(); foreach ($all_pages as $page) { // Skip if already processed as part of another translation group if (in_array($page->ID, $processed_pages)) { continue; } // Check if this is a translated post created by our plugin — skip it // (it will be displayed as a translation under its source post) $source_page_meta = get_post_meta($page->ID, '_aitrfoel_source_page', true); if ($source_page_meta && (int) $source_page_meta !== $page->ID) { $source_id = (int) $source_page_meta; // If the source is already in a group, add this as a translation if (isset($source_id_to_group[$source_id])) { $gi = $source_id_to_group[$source_id]; $lang = $multilingual->get_post_language($page->ID); if ($lang && !isset($translation_groups[$gi]['translations'][$lang])) { $translation_groups[$gi]['translations'][$lang] = $page; } $processed_pages[] = $page->ID; continue; } // Source not yet processed — skip; meta query will pick this up // when the source post creates its group $source_post = get_post($source_id); if ($source_post && $source_post->post_status !== 'trash') { continue; } // Source is gone — fall through to create own group } $page_lang = $multilingual->get_post_language($page->ID); $translations = $multilingual->get_post_translations($page->ID); // Check if ANY post in this translation group is already in a processed group $existing_group_index = null; $all_group_ids = array_values(array_filter((array) $translations)); $all_group_ids[] = $page->ID; foreach ($all_group_ids as $check_id) { if (in_array($check_id, $processed_pages)) { // Find which group this belongs to foreach ($translation_groups as $gi => $existing_group) { if ($existing_group['source']->ID === $check_id) { $existing_group_index = $gi; break; } foreach ($existing_group['translations'] as $existing_trans) { if ($existing_trans->ID === $check_id) { $existing_group_index = $gi; break 2; } } } break; } } // If this page overlaps with an existing group, merge into it and skip if ($existing_group_index !== null) { // Mark all IDs in this translation group as processed foreach ($all_group_ids as $gid) { if (!in_array($gid, $processed_pages)) { $processed_pages[] = $gid; // Add as translation to existing group if not the source if ($gid !== $translation_groups[$existing_group_index]['source']->ID) { $lang = $multilingual->get_post_language($gid); if ($lang && !isset($translation_groups[$existing_group_index]['translations'][$lang])) { $post_obj = get_post($gid); if ($post_obj) { $translation_groups[$existing_group_index]['translations'][$lang] = $post_obj; } } } } } continue; } // Find the canonical source page for this translation group // Priority 1: Check if any post in the group is explicitly referenced as the original source by another post via _aitrfoel_source_page // Priority 2: Post in the site's default language (canonical source for Polylang/WPML/Built-in groups) // Priority 3: First post that has no _aitrfoel_source_page (created naturally, not as a translation) // Priority 4: Oldest post in the group $source_page = null; $all_group_ids = array_unique(array_filter(array_merge([$page->ID], array_values((array) $translations)))); // Priority 1: Explicit source reference foreach ($all_group_ids as $cid) { $cmeta = get_post_meta($cid, '_aitrfoel_source_page', true); if ($cmeta && (int) $cmeta !== $cid && in_array((int) $cmeta, $all_group_ids, true)) { $cpost = get_post((int) $cmeta); if ($cpost && $cpost->post_status !== 'trash') { $source_page = $cpost; break; } } } // Priority 2: Post in default language if (!$source_page && $default_lang) { if (isset($translations[$default_lang])) { $cpost = get_post($translations[$default_lang]); if ($cpost && $cpost->post_status !== 'trash') { $source_page = $cpost; } } elseif ($page_lang === $default_lang && $page->post_status !== 'trash') { $source_page = $page; } else { foreach ($all_group_ids as $cid) { if ($multilingual->get_post_language($cid) === $default_lang) { $cpost = ($cid === $page->ID) ? $page : get_post($cid); if ($cpost && $cpost->post_status !== 'trash') { $source_page = $cpost; break; } } } } } // Priority 3: First post that has no _aitrfoel_source_page (created naturally, not as a translation) if (!$source_page) { foreach ($all_group_ids as $cid) { $cmeta = get_post_meta($cid, '_aitrfoel_source_page', true); if (!$cmeta || (int) $cmeta === $cid) { $cpost = ($cid === $page->ID) ? $page : get_post($cid); if ($cpost && $cpost->post_status !== 'trash') { $source_page = $cpost; break; } } } } // Priority 4: Oldest post in the group if (!$source_page) { $oldest_page = $page; $oldest_date = $page->post_date; foreach ($all_group_ids as $cid) { $cpost = ($cid === $page->ID) ? $page : get_post($cid); if ($cpost && $cpost->post_status !== 'trash' && $cpost->post_date < $oldest_date) { $oldest_page = $cpost; $oldest_date = $cpost->post_date; } } $source_page = $oldest_page; } if ($source_page) { // Deduplicate: if a group with the same source ID already exists, skip if (isset($source_id_to_group[$source_page->ID])) { // Mark all translation IDs as processed foreach ($translations as $lang_code => $post_id) { if ($post_id && !in_array($post_id, $processed_pages)) { $processed_pages[] = $post_id; } } continue; } $group = [ 'source' => $source_page, 'translations' => [] ]; // Add all translations to the group foreach ($translations as $lang_code => $post_id) { if ($post_id && $post_id !== $source_page->ID) { $translation_post = get_post($post_id); if ($translation_post) { $group['translations'][$lang_code] = $translation_post; $processed_pages[] = $post_id; } } } // Fallback: find translations via our meta key // (covers cases where Polylang/WPML don't track the relationship) $meta_translations = get_posts([ 'post_type' => $source_page->post_type, 'meta_key' => '_aitrfoel_source_page', 'meta_value' => (string) $source_page->ID, 'numberposts' => -1, 'post_status' => 'any', 'suppress_filters' => true, ]); foreach ($meta_translations as $meta_trans) { if ($meta_trans->ID !== $source_page->ID && !in_array($meta_trans->ID, $processed_pages)) { $lang = $multilingual->get_post_language($meta_trans->ID); if ($lang && !isset($group['translations'][$lang])) { $group['translations'][$lang] = $meta_trans; $processed_pages[] = $meta_trans->ID; } } } $group_index = count($translation_groups); $translation_groups[] = $group; $processed_pages[] = $source_page->ID; $source_id_to_group[$source_page->ID] = $group_index; } } // Sort translation groups according to the requested criteria usort($translation_groups, function ($a, $b) use ($sort) { $src_a = $a['source']; $src_b = $b['source']; switch ($sort) { case 'date_asc': return strcmp($src_a->post_date, $src_b->post_date); case 'title_asc': return strcasecmp($src_a->post_title, $src_b->post_title); case 'title_desc': return strcasecmp($src_b->post_title, $src_a->post_title); case 'date_desc': default: return strcmp($src_b->post_date, $src_a->post_date); } }); return $translation_groups; } /** * Get proper display name for post status */ private function get_status_display_name($status) { $status_names = [ 'publish' => __('Published', 'ai-translator-for-elementor-and-polylang'), 'draft' => __('Draft', 'ai-translator-for-elementor-and-polylang'), 'private' => __('Private', 'ai-translator-for-elementor-and-polylang'), 'pending' => __('Pending Review', 'ai-translator-for-elementor-and-polylang'), 'future' => __('Scheduled', 'ai-translator-for-elementor-and-polylang') ]; return isset($status_names[$status]) ? $status_names[$status] : ucfirst($status); } /** * Get page type (Elementor or Standard) */ private function get_page_type($page_id) { // Use content detector if available if (class_exists('AITRFOEL_Content_Detector')) { $detector = AITRFOEL_Content_Detector::get_instance(); $content_type = $detector->detect($page_id); switch ($content_type) { case AITRFOEL_Content_Detector::TYPE_ELEMENTOR: return [ 'type' => 'elementor', 'label' => __('Elementor', 'ai-translator-for-elementor-and-polylang'), 'class' => 'aitrfoel-page-elementor', 'tooltip' => __('Built with Elementor - full translation support', 'ai-translator-for-elementor-and-polylang') ]; case AITRFOEL_Content_Detector::TYPE_GUTENBERG: return [ 'type' => 'gutenberg', 'label' => __('Gutenberg', 'ai-translator-for-elementor-and-polylang'), 'class' => 'aitrfoel-page-gutenberg', 'tooltip' => __('Built with Block Editor - can be translated', 'ai-translator-for-elementor-and-polylang') ]; case AITRFOEL_Content_Detector::TYPE_CLASSIC: return [ 'type' => 'classic', 'label' => __('Classic', 'ai-translator-for-elementor-and-polylang'), 'class' => 'aitrfoel-page-classic', 'tooltip' => __('Classic Editor content - can be translated', 'ai-translator-for-elementor-and-polylang') ]; case AITRFOEL_Content_Detector::TYPE_WOOCOMMERCE: return [ 'type' => 'woocommerce', 'label' => __('WooCommerce', 'ai-translator-for-elementor-and-polylang'), 'class' => 'aitrfoel-page-woocommerce', 'tooltip' => __('WooCommerce product - title, description, attributes and meta are translated', 'ai-translator-for-elementor-and-polylang') ]; case AITRFOEL_Content_Detector::TYPE_FORM: return [ 'type' => 'form', 'label' => __('Form', 'ai-translator-for-elementor-and-polylang'), 'class' => 'aitrfoel-page-form', 'tooltip' => __('Form - fields, options, and messages are translated', 'ai-translator-for-elementor-and-polylang') ]; default: return [ 'type' => 'unknown', 'label' => __('Unknown', 'ai-translator-for-elementor-and-polylang'), 'class' => 'aitrfoel-page-unknown', 'tooltip' => __('No translatable content detected', 'ai-translator-for-elementor-and-polylang') ]; } } // Fallback to original Elementor-only detection $elementor_data = get_post_meta($page_id, '_elementor_data', true); $elementor_edit_mode = get_post_meta($page_id, '_elementor_edit_mode', true); if (!empty($elementor_data) || $elementor_edit_mode === 'builder') { return [ 'type' => 'elementor', 'label' => __('Elementor', 'ai-translator-for-elementor-and-polylang'), 'class' => 'aitrfoel-page-elementor', 'tooltip' => __('Built with Elementor - can be translated', 'ai-translator-for-elementor-and-polylang') ]; } else { return [ 'type' => 'standard', 'label' => __('Standard', 'ai-translator-for-elementor-and-polylang'), 'class' => 'aitrfoel-page-standard', 'tooltip' => __('Standard WordPress page - limited translation support', 'ai-translator-for-elementor-and-polylang') ]; } } /** * Render table rows for pages */ public function render_pages_rows($pages_data) { if (empty($pages_data)) { echo '' . esc_html__('No pages or posts found.', 'ai-translator-for-elementor-and-polylang') . ''; return; } // Get multilingual manager instance $multilingual = class_exists('AITRFOEL_Multilingual_Manager') ? AITRFOEL_Multilingual_Manager::get_instance() : null; if (!$multilingual || !$multilingual->is_active()) { echo '' . esc_html__('Multilingual plugin not active.', 'ai-translator-for-elementor-and-polylang') . ''; return; } foreach ($pages_data as $group) { $source = $group['source']; $translations = $group['translations']; $translation_count = count($translations); $available_languages = $multilingual->get_available_languages(); $available_lang_codes = array_keys($available_languages); $total_languages = count($available_languages); // Render source page row $is_source = true; $checkbox_disabled = false; // Calculate missing languages for this page $source_lang = $multilingual->get_post_language($source->ID); $missing_languages = []; $existing_languages = []; foreach ($available_lang_codes as $lang_code) { if ($lang_code === $source_lang) { $existing_languages[] = $lang_code; // Source language exists } elseif (isset($translations[$lang_code])) { $existing_languages[] = $lang_code; // Translation exists } else { $missing_languages[] = $lang_code; // Translation missing } } // Get the correct permalink using the multilingual manager $source_permalink = $multilingual->get_post_permalink($source->ID); $post_type_icon_class = 'aitrfoel-title-icon-page'; $post_type_label = __('Page', 'ai-translator-for-elementor-and-polylang'); if ($source->post_type === 'post') { $post_type_icon_class = 'aitrfoel-title-icon-post'; $post_type_label = __('Post', 'ai-translator-for-elementor-and-polylang'); } elseif ($source->post_type === 'product' || $source->post_type === 'sc_product') { $post_type_icon_class = 'aitrfoel-title-icon-product'; $post_type_label = __('Product', 'ai-translator-for-elementor-and-polylang'); } elseif ($source->post_type === 'elementor-hf') { $post_type_icon_class = 'aitrfoel-title-icon-template'; $post_type_label = __('Header/Footer', 'ai-translator-for-elementor-and-polylang'); } elseif ($source->post_type === 'elementor_library' || $source->post_type === 'jet-theme-core') { $post_type_icon_class = 'aitrfoel-title-icon-template'; $post_type_label = __('Template', 'ai-translator-for-elementor-and-polylang'); } elseif (strpos($source->post_type, 'form') !== false) { $post_type_icon_class = 'aitrfoel-title-icon-form'; $post_type_label = __('Form', 'ai-translator-for-elementor-and-polylang'); } $cached_words = get_post_meta($source->ID, '_aitrfoel_word_count', true); $has_cached_words = ($cached_words !== '' && is_numeric($cached_words)); $words_val = $has_cached_words ? (int) $cached_words : 0; ?> >
post_title); ?> (get_post_language($source->ID) ?: '?')); ?>)
get_status_display_name($source->post_status)); ?> ' . esc_html__('Calculating...', 'ai-translator-for-elementor-and-polylang'); ?>
get_post_language($source->ID); if (!empty($available_langs)) { foreach ($available_langs as $lang_code) { // Check if translation exists OR if this is the source language $has_translation = isset($translations[$lang_code]) || ($lang_code === $source_lang); $class = $has_translation ? 'aitrfoel-lang-exists' : 'aitrfoel-lang-missing'; echo '' . esc_html(strtoupper($lang_code)) . ' '; } } ?> get_page_type($source->ID); ?>
$translation) { // Get the correct permalink using the multilingual manager $translation_permalink = $multilingual->get_post_permalink($translation->ID); ?>
post_title); ?> ()
get_status_display_name($translation->post_status)); ?>
: post_title); ?> get_page_type($translation->ID); ?> 'ai-translator-for-elementor-and-polylang-review', 'source_id' => absint($source->ID), 'post_id' => absint($translation->ID), 'lang' => rawurlencode($lang_code), ], admin_url('admin.php') ); ?>
post_status, ['draft', 'pending'], true)) : ?>
is_active()) { $available_langs = $multilingual_manager->get_available_languages(); foreach ($available_langs as $code => $lang) { $name = is_object($lang) ? ($lang->name ?? strtoupper($code)) : (is_array($lang) ? ($lang['name'] ?? strtoupper($code)) : strtoupper($code)); $multilingual_languages[$code] = $name; } } // Review eligibility $rev_dismissed = (bool) get_option('aitrfoel_review_dismissed', false); $rev_snooze = (int) get_option('aitrfoel_review_snoozed_until', 0); $rev_count = (int) get_option('aitrfoel_successful_translations_count', 0); $review_eligible = (!$rev_dismissed && time() > $rev_snooze && $rev_count >= 3); $license_info = $this->get_license_info(); wp_localize_script('aitrfoel-dashboard', 'aitrfoelDashboard', [ 'ajax_url' => esc_url(admin_url('admin-ajax.php')), 'nonce' => wp_create_nonce('aitrfoel_ajax_nonce'), 'upgrade_url' => esc_url($upgrade_url), 'is_pro' => (defined('AITRFOEL_VERSION_TYPE') && AITRFOEL_VERSION_TYPE === 'pro'), 'review_eligible' => $review_eligible, 'success_count' => $rev_count, 'languages' => $multilingual_languages, 'default_language' => ($multilingual_manager && method_exists($multilingual_manager, 'get_default_language')) ? $multilingual_manager->get_default_language() : 'en', 'multilingual_plugin' => $multilingual_manager ? $multilingual_manager->get_plugin_display_name() : 'None', 'word_limit' => $license_info['word_limit'] ?? 7200, 'words_used' => $license_info['words_used'] ?? 0, 'words_left' => $license_info['words_left'] ?? 7200, 'plan' => $license_info['plan'] ?? 'Free', 'bonus_claimed' => (bool) get_option('aitrfoel_share_bonus_claimed', false), 'server_usage_factor' => 2.0, 'strings' => [ /* translators: %d: Number of pages */ 'pages_selected' => __('%d pages selected', 'ai-translator-for-elementor-and-polylang'), /* translators: %d: Estimated word count */ 'estimated_words' => __('Est: ~%d words', 'ai-translator-for-elementor-and-polylang'), 'select_languages' => __('Select languages', 'ai-translator-for-elementor-and-polylang'), /* translators: %d: Number of languages */ 'languages_selected' => __('%d languages', 'ai-translator-for-elementor-and-polylang'), // Translation process strings 'start_translation' => __('Start Translation', 'ai-translator-for-elementor-and-polylang'), 'translation_in_progress' => __('Translation in Progress', 'ai-translator-for-elementor-and-polylang'), 'translation_completed' => __('Translation completed successfully!', 'ai-translator-for-elementor-and-polylang'), 'translation_failed' => __('Translation failed. Please try again.', 'ai-translator-for-elementor-and-polylang'), 'translation_queued' => __('Translation queued, waiting to start', 'ai-translator-for-elementor-and-polylang'), 'translation_paused_limit' => __('Translation paused: word limit reached', 'ai-translator-for-elementor-and-polylang'), 'translation_failed_start' => __('Translation failed to start.', 'ai-translator-for-elementor-and-polylang'), 'translation_already_in_progress' => __('Translation is already in progress. Please wait for completion.', 'ai-translator-for-elementor-and-polylang'), 'translating' => __('Translating...', 'ai-translator-for-elementor-and-polylang'), // Status strings 'checking_status' => __('Checking active translation status...', 'ai-translator-for-elementor-and-polylang'), 'processing_translation' => __('Processing translation', 'ai-translator-for-elementor-and-polylang'), 'status_completed' => __('Completed', 'ai-translator-for-elementor-and-polylang'), 'status_error' => __('Error', 'ai-translator-for-elementor-and-polylang'), // Warning/validation strings 'select_page_warning' => __('Please select at least one page to translate.', 'ai-translator-for-elementor-and-polylang'), 'select_language_warning' => __('Please select at least one target language.', 'ai-translator-for-elementor-and-polylang'), 'select_page_tooltip' => __('Select at least one page to start translation', 'ai-translator-for-elementor-and-polylang'), 'select_lang_tooltip' => __('Select target languages to start translation', 'ai-translator-for-elementor-and-polylang'), // Error strings 'network_error_analysis' => __('Network error during pre-analysis', 'ai-translator-for-elementor-and-polylang'), 'pre_analysis_failed' => __('Pre-analysis failed', 'ai-translator-for-elementor-and-polylang'), // Progress messages 'waiting_for_ai' => __('AI is preparing your translations', 'ai-translator-for-elementor-and-polylang'), 'sending_content' => __('Sending content for translation', 'ai-translator-for-elementor-and-polylang'), 'processing_data' => __('Processing the data', 'ai-translator-for-elementor-and-polylang'), 'analyzing_structure' => __('Analyzing page structure', 'ai-translator-for-elementor-and-polylang'), 'preparing_request' => __('Preparing translation request', 'ai-translator-for-elementor-and-polylang'), 'processing_results' => __('Processing translation results', 'ai-translator-for-elementor-and-polylang'), 'applying_translations' => __('Applying translations to content', 'ai-translator-for-elementor-and-polylang'), 'finalizing_creation' => __('Finalizing page creation', 'ai-translator-for-elementor-and-polylang'), 'optimizing_content' => __('Optimizing translated content', 'ai-translator-for-elementor-and-polylang'), 'completing_process' => __('Completing translation process', 'ai-translator-for-elementor-and-polylang'), // Settings 'settings' => __('Settings', 'ai-translator-for-elementor-and-polylang'), // Insufficient words modal 'not_enough_words' => __('Not Enough Words', 'ai-translator-for-elementor-and-polylang'), 'insufficient_words_message' => __('Insufficient words for this translation.', 'ai-translator-for-elementor-and-polylang'), 'required' => __('Required', 'ai-translator-for-elementor-and-polylang'), 'available' => __('Available', 'ai-translator-for-elementor-and-polylang'), 'missing' => __('Missing', 'ai-translator-for-elementor-and-polylang'), 'words' => __('words', 'ai-translator-for-elementor-and-polylang'), 'reduce_or_upgrade' => __('Reduce your selection or upgrade your plan to proceed.', 'ai-translator-for-elementor-and-polylang'), 'upgrade_plan' => __('Upgrade Plan', 'ai-translator-for-elementor-and-polylang'), 'close' => __('Close', 'ai-translator-for-elementor-and-polylang'), // Review prompt 'enjoying_plugin' => __('Enjoying Meep AI Translator? A quick review helps us a lot!', 'ai-translator-for-elementor-and-polylang'), 'leave_review' => __('Leave a Review', 'ai-translator-for-elementor-and-polylang'), 'no_thanks' => __('No thanks', 'ai-translator-for-elementor-and-polylang'), // Dashboard word estimate / selection state 'choose_pages' => __('Choose pages to translate', 'ai-translator-for-elementor-and-polylang'), /* translators: %d: number of pages selected */ 'selected_pages' => __('Selected: %d page', 'ai-translator-for-elementor-and-polylang'), /* translators: %d: number of pages selected (plural) */ 'selected_pages_plural' => __('Selected: %d pages', 'ai-translator-for-elementor-and-polylang'), /* translators: %d: number of translation jobs */ 'ready_jobs' => __('Ready: %d translation job', 'ai-translator-for-elementor-and-polylang'), /* translators: %d: number of translation jobs (plural) */ 'ready_jobs_plural' => __('Ready: %d translation jobs', 'ai-translator-for-elementor-and-polylang'), 'all_fully_translated' => __('All selected pages are fully translated', 'ai-translator-for-elementor-and-polylang'), 'words_label' => __('words', 'ai-translator-for-elementor-and-polylang'), 'quota_exceeds_warning'=> __('Needs ~%1$s words (%2$s over limit)', 'ai-translator-for-elementor-and-polylang'), 'quota_fits_info' => __('Selected: %1$s · ~%2$s words (%3$s left)', 'ai-translator-for-elementor-and-polylang'), 'calculating_words' => __('Calculating...', 'ai-translator-for-elementor-and-polylang'), ] ]); // Localize settings modal script wp_localize_script('aitrfoel-settings-modal', 'aitrfoel_ajax', [ 'ajax_url' => esc_url(admin_url('admin-ajax.php')), 'nonce' => wp_create_nonce('aitrfoel_ajax_nonce'), ]); } /** * Get license information and word limits */ private function get_license_info() { // Get license status from server (authoritative source) $license_manager = class_exists('AITRFOEL_License_Manager_Server') ? AITRFOEL_License_Manager_Server::get_instance() : null; $server_status = $license_manager ? $license_manager->get_license_status() : []; $license_status = $server_status['plan'] ?? 'Free'; $word_limit = $server_status['word_limit'] ?? 7200; $words_used = $server_status['words_used'] ?? 0; $words_left = $server_status['words_left'] ?? max(0, $word_limit - $words_used); $license_expired = isset($server_status['license_expired']) ? $server_status['license_expired'] : false; // Fallback: If server status still says 'Free' but Freemius SDK is paying, use Freemius plan if (strtolower($license_status) === 'free' && function_exists('eatp_fs') && is_object(eatp_fs()) && method_exists(eatp_fs(), 'is_paying') && eatp_fs()->is_paying()) { if ($license_manager && method_exists($license_manager, 'get_freemius_plan_name')) { $fs_plan = $license_manager->get_freemius_plan_name(); if (!empty($fs_plan) && strtolower($fs_plan) !== 'free') { $license_status = $fs_plan; if (strtolower($license_status) === 'agency lifetime') { $word_limit = -1; $words_left = -1; } } } } // Capitalize for display (preserve multi-word plan names like 'Agency Lifetime') $display_name = ucwords(strtolower($license_status)); $is_pro = (defined('AITRFOEL_VERSION_TYPE') && AITRFOEL_VERSION_TYPE === 'pro') || (strtolower($license_status) !== 'free' && !empty($license_status)) || (function_exists('eatp_fs') && is_object(eatp_fs()) && method_exists(eatp_fs(), 'is_paying') && eatp_fs()->is_paying()); return [ 'type' => strtolower($license_status), 'limit' => $word_limit, 'name' => $display_name, 'plan' => $license_status, 'word_limit' => $word_limit, 'words_used' => $words_used, 'words_left' => $words_left, 'is_active' => (function_exists('eatp_fs') && is_object(eatp_fs()) && method_exists(eatp_fs(), 'is_registered') && eatp_fs()->is_registered()), 'is_pro' => $is_pro, 'license_expired' => $license_expired, 'freemius_validated' => isset($server_status['freemius_validated']) ? $server_status['freemius_validated'] : false, 'addon_active' => isset($server_status['addon_active']) ? $server_status['addon_active'] : false, 'addon_words' => isset($server_status['addon_words']) ? $server_status['addon_words'] : 0, ]; } /** * Get total word count from all pages (estimate for words available to translate) */ private function get_total_words_available() { global $wpdb; // Get all pages with Elementor content $pages = $this->get_elementor_pages(); $total_words = 0; foreach ($pages as $page) { // Get basic word count from post content and title $content = $page->post_content . ' ' . $page->post_title; $basic_words = AITRFOEL_Translation_Engine::count_words(wp_strip_all_tags($content)); // Try to get more accurate count from Elementor data if available $elementor_data = get_post_meta($page->ID, '_elementor_data', true); if (!empty($elementor_data)) { // This is a simplified estimation - in reality you'd want to parse Elementor data $elementor_words = AITRFOEL_Translation_Engine::count_words(wp_strip_all_tags($elementor_data)); $total_words += max($basic_words, $elementor_words); } else { $total_words += $basic_words; } } return $total_words; } /** * Render compact stats bar for top of dashboard */ private function render_compact_stats_bar($pages_data = []) { // 1. Calculate Content and Translation Completeness $multilingual = class_exists('AITRFOEL_Multilingual_Manager') ? AITRFOEL_Multilingual_Manager::get_instance() : null; $all_langs = ($multilingual && $multilingual->is_active()) ? $multilingual->get_available_languages() : []; $default_lang = ($multilingual && $multilingual->is_active()) ? $multilingual->get_default_language() : 'en'; $target_langs = []; foreach ($all_langs as $code => $lang) { if ($code !== $default_lang) { $name = is_object($lang) ? ($lang->name ?? strtoupper($code)) : (is_array($lang) ? ($lang['name'] ?? strtoupper($code)) : strtoupper($code)); $target_langs[$code] = $name; } } $target_lang_count = count($target_langs); $total_items = !empty($pages_data) ? count($pages_data) : count($this->get_elementor_pages()); $total_slots = max(1, $total_items * max(1, $target_lang_count)); $completed_slots = 0; $untranslated_items = 0; if (!empty($pages_data) && $target_lang_count > 0) { foreach ($pages_data as $group) { $source = $group['source']; $source_lang = $multilingual ? $multilingual->get_post_language($source->ID) : $default_lang; $present_langs = []; if ($source_lang) { $present_langs[$source_lang] = true; } if (!empty($group['translations']) && is_array($group['translations'])) { foreach ($group['translations'] as $t_lang => $t_post) { if (!empty($t_post)) { $present_langs[$t_lang] = true; } } } $item_trans_count = 0; foreach ($target_langs as $code => $name) { if (!empty($group['translations'][$code]) || (!empty($present_langs[$code]) && $code !== $default_lang)) { $completed_slots++; $item_trans_count++; } } if ($item_trans_count < $target_lang_count) { $untranslated_items++; } } } $health_pct = ($total_slots > 0) ? min(100, round(($completed_slots / $total_slots) * 100)) : 100; if (empty($pages_data) || $target_lang_count === 0) { $health_pct = 100; } // 2. Word usage & license status $license_manager = AITRFOEL_License_Manager_Server::get_instance(); delete_option('aitrfoel_words_used_this_month'); delete_transient('aitrfoel_license_usage_cache'); $license_status = $license_manager->get_license_status(); $words_translated = isset($license_status['words_used']) ? (int) $license_status['words_used'] : (int) ($license_status['words_translated'] ?? 0); $license_info = $this->get_license_info(); $total_words = $license_info['limit']; $is_unlimited = ($total_words === -1 || strtolower($license_info['plan'] ?? '') === 'agency lifetime' || !empty($license_status['unlimited']) || !empty($license_status['is_unlimited'])); $cooldown_end = get_option('aitrfoel_cooldown_end_date', 0); $is_cooldown = (!$is_unlimited && strtolower($license_info['plan'] ?? '') === 'free' && $cooldown_end > 0 && $cooldown_end > time()); if ($is_unlimited || strtolower($license_info['plan'] ?? '') !== 'free') { delete_option('aitrfoel_cooldown_end_date'); } $pct = (!$is_unlimited && $total_words > 0) ? min(100, (int) round(($words_translated / $total_words) * 100)) : 0; $reset_label = $is_cooldown ? date_i18n('j M', $cooldown_end) : ''; $bar_color = $is_unlimited ? '#10b981' : (($pct >= 100 || $is_cooldown) ? '#ef4444' : (($pct >= 80) ? '#f59e0b' : '#3b82f6')); $health_color = ($health_pct >= 95) ? '#10b981' : (($health_pct >= 60) ? '#3b82f6' : '#f59e0b'); ?>
%
:
: 0): ?>
: +
is_active()) { return; } $all_langs = $multilingual->get_available_languages(); $default_lang = $multilingual->get_default_language(); // Calculate target languages (all active languages minus default) $target_langs = []; foreach ($all_langs as $code => $lang) { if ($code !== $default_lang) { $name = is_object($lang) ? ($lang->name ?? strtoupper($code)) : (is_array($lang) ? ($lang['name'] ?? strtoupper($code)) : strtoupper($code)); $target_langs[$code] = $name; } } $target_lang_count = count($target_langs); if ($target_lang_count === 0 || empty($pages_data)) { return; } $total_items = count($pages_data); $total_slots = $total_items * $target_lang_count; $completed_slots = 0; $untranslated_items = 0; // Language stats $lang_stats = []; foreach ($target_langs as $code => $name) { $lang_stats[$code] = [ 'name' => $name, 'total' => $total_items, 'completed' => 0, 'pct' => 0, ]; } // Category stats $form_types = ['wpcf7_contact_form', 'sureforms_form', 'sf_form', 'wpforms', 'jet-form-builder', 'fluentform', 'formidable', 'ninja_forms']; $element_types = ['elementor_library', 'elementor-hf', 'jet-theme-core', 'jet-engine']; $cat_data = [ 'pages' => ['label' => __('Pages', 'ai-translator-for-elementor-and-polylang'), 'total' => 0, 'completed' => 0], 'posts' => ['label' => __('Posts', 'ai-translator-for-elementor-and-polylang'), 'total' => 0, 'completed' => 0], 'products' => ['label' => __('Products', 'ai-translator-for-elementor-and-polylang'), 'total' => 0, 'completed' => 0], 'forms' => ['label' => __('Forms', 'ai-translator-for-elementor-and-polylang'), 'total' => 0, 'completed' => 0], 'templates' => ['label' => __('Templates', 'ai-translator-for-elementor-and-polylang'), 'total' => 0, 'completed' => 0], ]; foreach ($pages_data as $group) { $source = $group['source']; $pt = $source->post_type; $cat_key = 'pages'; if ($pt === 'post') { $cat_key = 'posts'; } elseif ($pt === 'product' || $pt === 'sc_product') { $cat_key = 'products'; } elseif (in_array($pt, $form_types, true)) { $cat_key = 'forms'; } elseif (in_array($pt, $element_types, true)) { $cat_key = 'templates'; } $cat_data[$cat_key]['total']++; $source_lang = $multilingual->get_post_language($source->ID); // Group all language codes present in this translation family $present_langs = []; if ($source_lang) { $present_langs[$source_lang] = true; } if (!empty($group['translations']) && is_array($group['translations'])) { foreach ($group['translations'] as $t_lang => $t_post) { if (!empty($t_post)) { $present_langs[$t_lang] = true; } } } $item_translations_count = 0; foreach ($target_langs as $code => $name) { if (!empty($group['translations'][$code]) || (!empty($present_langs[$code]) && $code !== $default_lang)) { $completed_slots++; $item_translations_count++; $lang_stats[$code]['completed']++; $cat_data[$cat_key]['completed']++; } } if ($item_translations_count < $target_lang_count) { $untranslated_items++; } } foreach ($lang_stats as $code => &$stats) { $stats['pct'] = ($stats['total'] > 0) ? round(($stats['completed'] / $stats['total']) * 100) : 0; } unset($stats); $health_pct = ($total_slots > 0) ? min(100, round(($completed_slots / $total_slots) * 100)) : 100; // Determine score theme if ($health_pct >= 95) { $score_theme = 'green'; $score_label = __('Site Fully Translated', 'ai-translator-for-elementor-and-polylang'); $score_desc = __('All pages, posts, templates and forms are synchronized.', 'ai-translator-for-elementor-and-polylang'); } elseif ($health_pct >= 70) { $score_theme = 'blue'; $score_label = __('Good Translation Health', 'ai-translator-for-elementor-and-polylang'); $score_desc = sprintf( /* translators: %d: number of items */ __('%d items still need translation across your target languages.', 'ai-translator-for-elementor-and-polylang'), $untranslated_items ); } elseif ($health_pct >= 40) { $score_theme = 'amber'; $score_label = __('Translations In Progress', 'ai-translator-for-elementor-and-polylang'); $score_desc = sprintf( /* translators: %d: number of items */ __('%d items waiting for translation.', 'ai-translator-for-elementor-and-polylang'), $untranslated_items ); } else { $score_theme = 'orange'; $score_label = __('Content Needs Translation', 'ai-translator-for-elementor-and-polylang'); $score_desc = sprintf( /* translators: %d: number of items */ __('%d items waiting to be translated.', 'ai-translator-for-elementor-and-polylang'), $untranslated_items ); } $flag_map = [ 'en' => 'gb', 'es' => 'es', 'fr' => 'fr', 'de' => 'de', 'it' => 'it', 'pt' => 'pt', 'nl' => 'nl', 'pl' => 'pl', 'ru' => 'ru', 'ja' => 'jp', 'zh' => 'cn', 'ko' => 'kr', 'ar' => 'sa', 'tr' => 'tr', 'hi' => 'in', 'sv' => 'se', 'uk' => 'ua', 'da' => 'dk', 'fi' => 'fi', 'nb' => 'no', 'no' => 'no', 'cs' => 'cz', 'el' => 'gr', 'he' => 'il', 'ro' => 'ro', 'hu' => 'hu', 'bg' => 'bg', 'sk' => 'sk', 'hr' => 'hr', 'sr' => 'rs', ]; ?>
%

0): ?>
$lstat): $flag_code = $flag_map[$code] ?? substr($code, 0, 2); ?>
<?php echo esc_attr($code); ?>
%
$cinfo): if ($cinfo['total'] === 0) continue; $cat_slots = $cinfo['total'] * $target_lang_count; $cat_pct = ($cat_slots > 0) ? round(($cinfo['completed'] / $cat_slots) * 100) : 100; ?>
%
get_license_info(); $is_pro_active = !empty($license_info['is_pro']); $plugin_name = $is_pro_active ? __('Meep AI Translator', 'ai-translator-for-elementor-and-polylang') : __('Meep AI Translator', 'ai-translator-for-elementor-and-polylang'); $normalized_page = strtolower(trim(wp_strip_all_tags((string) $current_page))); $default_title = sprintf( /* translators: %1$s: opening span tag, %2$s: closing span tag */ __('Translate your site, %1$spage by page%2$s.', 'ai-translator-for-elementor-and-polylang'), '', '' ); $default_subtitle = __('Manage translations for your pages and posts. Select source content to create bulk translations.', 'ai-translator-for-elementor-and-polylang'); $hero_text_map = [ 'dashboard' => [ 'eyebrow' => __('dashboard', 'ai-translator-for-elementor-and-polylang'), 'title' => $default_title, 'subtitle' => $default_subtitle, ], 'settings' => [ 'eyebrow' => __('settings', 'ai-translator-for-elementor-and-polylang'), 'title' => 'Configure your translation workspace.', 'subtitle' => __('Set up languages, switcher behavior, SEO translation, WooCommerce options, glossary, and advanced rules.', 'ai-translator-for-elementor-and-polylang'), ], 'translation history' => [ 'eyebrow' => __('translation history', 'ai-translator-for-elementor-and-polylang'), 'title' => 'Review your translation activity.', 'subtitle' => __('Track completed translations, monitor usage trends, and inspect your recent translation records.', 'ai-translator-for-elementor-and-polylang'), ], 'translate all' => [ 'eyebrow' => __('translate all', 'ai-translator-for-elementor-and-polylang'), 'title' => 'Translate your full site in bulk.', 'subtitle' => __('Select post types and target languages, estimate words, and launch or schedule large translation batches.', 'ai-translator-for-elementor-and-polylang'), ], 'review' => [ 'eyebrow' => __('review translation', 'ai-translator-for-elementor-and-polylang'), 'title' => 'Review and refine your translated content.', 'subtitle' => __('Inspect each translated segment side by side with the source. Click any cell to edit and save changes directly.', 'ai-translator-for-elementor-and-polylang'), ], 'activate license' => [ 'eyebrow' => __('activate license', 'ai-translator-for-elementor-and-polylang'), 'title' => 'Activate your license key.', 'subtitle' => __('Enter your license key to unlock unlimited translation words, premium features, and automatic updates.', 'ai-translator-for-elementor-and-polylang'), ], 'clear history' => [ 'eyebrow' => __('clear history', 'ai-translator-for-elementor-and-polylang'), 'title' => 'Reset and clear translation data.', 'subtitle' => __('Clear translation cache, usage statistics, and temporary plugin options.', 'ai-translator-for-elementor-and-polylang'), ], ]; $hero_text = isset($hero_text_map[$normalized_page]) ? $hero_text_map[$normalized_page] : [ 'eyebrow' => $normalized_page, 'title' => $default_title, 'subtitle' => $default_subtitle, ]; ?>
//

get_total_words_translated(); } // Fallback to direct query global $wpdb; $table_name = $wpdb->prefix . 'aitrfoel_usage_stats'; $result = $wpdb->get_var( "SELECT SUM(word_count) FROM $table_name" ); return $result ? (int) $result : 0; } /** * Render information card for display on dashboard pages */ public function render_quick_tips() { ?>
prefix . 'aitrfoel_translation_cache'; $deleted_cache = $wpdb->query("TRUNCATE TABLE $cache_table"); /* translators: %d: Number of cache entries cleared */ $results[] = sprintf(__('Translation cache: %d entries cleared', 'ai-translator-for-elementor-and-polylang'), $deleted_cache === false ? 0 : $deleted_cache); // 2. Clear usage stats table $stats_table = $wpdb->prefix . 'aitrfoel_usage_stats'; $deleted_stats = $wpdb->query("TRUNCATE TABLE $stats_table"); /* translators: %d: Number of statistics entries cleared */ $results[] = sprintf(__('Usage statistics: %d entries cleared', 'ai-translator-for-elementor-and-polylang'), $deleted_stats === false ? 0 : $deleted_stats); // 3. Clear WordPress options delete_option('aitrfoel_words_used_this_month'); delete_option('aitrfoel_last_usage_reset'); delete_option('aitrfoel_cooldown_expiry'); delete_option('aitrfoel_limit_reached_timestamp'); delete_option('aitrfoel_words_since_verification'); delete_option('aitrfoel_last_license_verification'); $results[] = __('WordPress options cleared', 'ai-translator-for-elementor-and-polylang'); // 4. Clear transients delete_transient('aitrfoel_debug_logs_' . get_current_user_id()); $results[] = __('Debug logs cleared', 'ai-translator-for-elementor-and-polylang'); // Show success message echo '

' . esc_html__('Translation History Cleared!', 'ai-translator-for-elementor-and-polylang') . '

'; // Log the action if (class_exists('AITRFOEL_Logger')) { AITRFOEL_Logger::log('Translation history manually cleared by user', 'info'); } } // Get current statistics global $wpdb; $cache_table = $wpdb->prefix . 'aitrfoel_translation_cache'; $stats_table = $wpdb->prefix . 'aitrfoel_usage_stats'; $cache_count = $wpdb->get_var("SELECT COUNT(*) FROM $cache_table"); $stats_count = $wpdb->get_var("SELECT COUNT(*) FROM $stats_table"); $words_used = get_option('aitrfoel_words_used_this_month', 0); $cooldown_expiry = get_option('aitrfoel_cooldown_expiry', 0); ?>
render_plugin_header(__('Clear History', 'ai-translator-for-elementor-and-polylang')); ?>

' . sprintf(esc_html__('Active (%s remaining)', 'ai-translator-for-elementor-and-polylang'), esc_html($remaining)) . ''; } else { echo ' ' . esc_html__('Not active', 'ai-translator-for-elementor-and-polylang') . ''; } ?>


get_setup_checklist_items(); // Don't show if all items are complete $all_complete = true; foreach ($checklist as $item) { if (!$item['complete']) { $all_complete = false; break; } } if ($all_complete) { return; } $completed_count = count(array_filter($checklist, function($item) { return $item['complete']; })); $total_count = count($checklist); $progress_pct = round(($completed_count / $total_count) * 100); ?>
is_active() && method_exists($multilingual, 'has_languages_configured') && $multilingual->has_languages_configured(); $detected_plugin = $multilingual ? $multilingual->get_detected_plugin() : ''; $lang_settings_url = admin_url('admin.php?page=ai-translator-for-elementor-and-polylang-settings'); if ($detected_plugin === 'polylang') { $lang_settings_url = admin_url('admin.php?page=mlang'); } elseif ($detected_plugin === 'wpml') { $lang_settings_url = admin_url('admin.php?page=sitepress-multilingual-cms/menu/languages.php'); } $items[] = [ 'key' => 'languages', 'title' => __('Languages Configured', 'ai-translator-for-elementor-and-polylang'), 'description' => __('At least two languages are set up for translation.', 'ai-translator-for-elementor-and-polylang'), 'complete' => $has_languages || in_array('languages', $skipped), 'skipped' => in_array('languages', $skipped), 'action_url' => $lang_settings_url, 'action_label'=> __('Configure Languages', 'ai-translator-for-elementor-and-polylang'), ]; // 3. First translation completed $has_translation = false; if ($multilingual && $multilingual->is_active()) { $pages = get_posts([ 'post_type' => ['page', 'post'], 'post_status' => ['publish', 'draft'], 'posts_per_page' => 50, 'suppress_filters' => true, 'meta_query' => [['key' => '_elementor_data', 'compare' => 'EXISTS']], ]); foreach ($pages as $page) { $translations = $multilingual->get_post_translations($page->ID); if (count($translations) > 1) { $has_translation = true; break; } } } $items[] = [ 'key' => 'first_translation', 'title' => __('First Translation', 'ai-translator-for-elementor-and-polylang'), 'description' => __('Translate your first page or post into another language.', 'ai-translator-for-elementor-and-polylang'), 'complete' => $has_translation || in_array('first_translation', $skipped), 'skipped' => in_array('first_translation', $skipped), 'action_url' => '', 'action_label'=> '', ]; // 4. Language switcher configured $switcher_configured = false; if ($detected_plugin === 'builtin') { $switcher_configured = (bool) get_option('aitrfoel_nav_menu_switcher', false); if (!$switcher_configured) { // Check if shortcode is used in any widget or content $switcher_configured = (bool) get_option('aitrfoel_switcher_style', ''); } } else { // For Polylang/WPML the switcher is managed by those plugins; mark complete $switcher_configured = true; } $items[] = [ 'key' => 'switcher', 'title' => __('Language Switcher', 'ai-translator-for-elementor-and-polylang'), 'description' => __('Let visitors switch between languages on the front end.', 'ai-translator-for-elementor-and-polylang'), 'complete' => $switcher_configured || in_array('switcher', $skipped), 'skipped' => in_array('switcher', $skipped), 'action_url' => admin_url('admin.php?page=ai-translator-for-elementor-and-polylang-settings#tab-languages'), 'action_label'=> __('Set Up Switcher', 'ai-translator-for-elementor-and-polylang'), ]; // 5. Smart Translation Memory $tm_enabled = (bool) get_option('aitrfoel_enable_translation_memory', true); $items[] = [ 'key' => 'translation_memory', 'title' => __('Smart Translation Memory', 'ai-translator-for-elementor-and-polylang'), 'description' => __('Automatically reuses repeated phrases to reduce API load and save word quota.', 'ai-translator-for-elementor-and-polylang'), 'complete' => $tm_enabled || in_array('translation_memory', $skipped), 'skipped' => in_array('translation_memory', $skipped), 'action_url' => admin_url('admin.php?page=ai-translator-for-elementor-and-polylang-settings#tab-glossary'), 'action_label'=> __('View Memory', 'ai-translator-for-elementor-and-polylang'), ]; return $items; } /** * AJAX handler: dismiss the setup checklist */ public function handle_dismiss_setup_checklist() { check_ajax_referer('aitrfoel_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized', 403); } update_user_meta(get_current_user_id(), 'aitrfoel_setup_checklist_dismissed', true); wp_send_json_success(); } /** * AJAX handler: skip a single setup checklist step */ public function handle_skip_setup_step() { check_ajax_referer('aitrfoel_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized', 403); } $step = isset($_POST['step']) ? sanitize_key($_POST['step']) : ''; $allowed = ['languages', 'first_translation', 'switcher']; if (!in_array($step, $allowed, true)) { wp_send_json_error('Invalid step', 400); } $skipped = (array) get_user_meta(get_current_user_id(), 'aitrfoel_setup_checklist_skipped', true); if (!in_array($step, $skipped, true)) { $skipped[] = $step; update_user_meta(get_current_user_id(), 'aitrfoel_setup_checklist_skipped', $skipped); } wp_send_json_success(); } /** * Register setup checklist as a WordPress dashboard widget */ public function register_dashboard_widget() { if (!current_user_can('manage_options')) { return; } // Don't register if dismissed or all complete if (get_user_meta(get_current_user_id(), 'aitrfoel_setup_checklist_dismissed', true)) { return; } $checklist = $this->get_setup_checklist_items(); $all_complete = true; foreach ($checklist as $item) { if (!$item['complete']) { $all_complete = false; break; } } if ($all_complete) { return; } wp_add_dashboard_widget( 'aitrfoel_setup_checklist_widget', __('Meep Translator - Setup Checklist', 'ai-translator-for-elementor-and-polylang'), [$this, 'render_wp_dashboard_widget'] ); } /** * Render the WordPress admin dashboard widget content */ public function render_wp_dashboard_widget() { // Enqueue our dashboard CSS on the WP dashboard wp_enqueue_style( 'aitrfoel-dashboard', AITRFOEL_PLUGIN_URL . 'assets/css/dashboard.css', [], defined('AITRFOEL_VERSION') ? AITRFOEL_VERSION : '1.0.0' ); $checklist = $this->get_setup_checklist_items(); $completed_count = count(array_filter($checklist, function($item) { return $item['complete']; })); $total_count = count($checklist); $progress_pct = round(($completed_count / $total_count) * 100); ?>