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 '
';
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()): ?>
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_plugin_header(__('Setup', 'ai-translator-for-elementor-and-polylang')); ?>
['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');
?>
.
>
()
// 0 selected
// click to add
// search
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;
?>
+
$label) :
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'])) {
?>
Elementor -
Elementor 3.0+ -
-
PHP 7.4+ -
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);
?>
0
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;
?>
>
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_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')
);
?>
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');
?>
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',
];
?>
$lstat):
$flag_code = $flag_map[$code] ?? substr($code, 0, 2);
?>
%
$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') . '
';
foreach ($results as $result) {
echo '' . esc_html($result) . ' ';
}
echo ' ';
// 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);
?>