Changeset 3761123
- Timestamp:
- 10/23/2026 07:44:09 AM (45 hours ago)
- Location:
- ait-multilingual-translate/trunk
- Files:
-
- 6 added
- 9 edited
-
CHANGELOG.md (modified) (1 diff)
-
README.md (modified) (2 diffs)
-
ait-multilingual-translate.php (modified) (87 diffs)
-
assets/css/admin.css (modified) (1 diff)
-
assets/js/admin.js (modified) (2 diffs)
-
includes/class-aitmt-interface-language.php (added)
-
includes/class-aitmt-locales.php (modified) (1 diff)
-
includes/class-aitmt-runtime-maintenance.php (modified) (14 diffs)
-
includes/class-aitmt-scanner.php (added)
-
languages/ait-multilingual-translate-ka_GE.mo (added)
-
languages/ait-multilingual-translate-ka_GE.po (added)
-
languages/ait-multilingual-translate-ru_RU.mo (added)
-
languages/ait-multilingual-translate-ru_RU.po (added)
-
languages/ait-multilingual-translate.pot (modified) (1 diff)
-
readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ait-multilingual-translate/trunk/CHANGELOG.md
r3665107 r3761123 4 4 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). 6 7 ## [0.4.2] - 2026-09-03 8 9 ### Fixed 10 - Detect Elementor documents from metadata/document APIs and skip their flattened post_content when structured collection is enabled, while keeping page titles and excerpts. 11 - Preserve scanner unit ownership separately from deduplicated Translation Memory hashes. Elementor-only scanning no longer depends on the Pages switch. 12 - Block old flattened Elementor queue rows and excluded units before enqueue/processing without deleting historical translations. 13 14 ### Added 15 - Persistent field and whole-page/object exclusions, Include again, and separately scoped Remove from queue actions. 16 - Bulk selection/actions and server-side Preview filters for inclusion, source type, object, translation status and search, with pagination across the entire manifest. 17 - Stable repeater identities and collision-resistant long field paths; active widget fields retain their own scanner identities. 18 - Regression and real-WordPress integration tests for six distinct Elementor services, normal pages, rebuilds, exclusions, locks and Translation Memory preservation. 19 20 ## [0.4.1] - 2026-09-01 21 22 ### Added 23 24 - Scanner Sources controls with Frontend, Shared Frontend, Template/Backend, Technical, and Metadata classifications, per-source counts, dynamic public post types, and conservative defaults. 25 - Scanner Preview with page, source type, widget/field context, character count, translation status, local filters, and per-unit include/exclude controls. 26 - Structured Elementor scanning that creates one translation unit per visible widget field, including nested repeaters, instead of aggregated JSON strings. 27 - Explicit WooCommerce Products scope for published titles, descriptions, and visible product attributes without technical product metadata. 28 - Persistent background Translate All worker with pause, resume, stop, progress, cooldown state, configurable Cron interval, batch count, and runtime budget. 29 - CSV, PO, and MO translation import/export in the Translation Matrix. 30 - Independent plugin interface language selector with Russian and Georgian PO/MO packs; website language and translation targets remain unchanged. 31 - Dashboard overview for provider state, queue/worker state, usage limits, Translation Memory hits, last translation, and next background event. 32 - Safe Free, Recommended, and Fast Bulk presets plus RPM, TPM, RPD, daily, monthly, per-request, and estimated-cost safeguards. 33 - Manual database maintenance for queued/failed jobs, source-specific queue jobs, stale runtime rows, orphaned rows, and obsolete scanner-derived translations. 34 - Regression coverage for locale/script validation, Elementor unit separation, interface locale isolation, and Translation Memory preservation. 35 36 ### Changed 37 38 - Provider prompts now resolve source and target languages through one locale service and explicitly request the target locale/script. 39 - Georgian output is rejected before saving when it contains no Georgian script; Russian/Cyrillic output is no longer accepted for Georgian jobs. 40 - Published/manual/import translations are used on the frontend; drafts prevent repeat billing but are not displayed publicly. 41 - Translation Memory hits, skipped obsolete rows, and failed rows now advance worker progress without unnecessary provider calls. 42 - Manual worker continuation and recurring Cron use separate events while sharing the same TTL queue lock. 43 - Setup Wizard now includes scanner scope and cost-safety controls before the first scan or translation run. 44 - Updated POT and shipped Russian/Georgian interface language packs. 45 46 ### Safety 47 48 - Upgrades never automatically delete published translations. 49 - Obsolete scanner cleanup requires a dry run, explicit confirmation, and bounded batches; manual/import/current Translation Memory entries are preserved. 50 - Unpublished, excluded, technical, unknown, and obsolete queue rows are skipped before provider calls. 51 - Import files require capability and nonce checks, a matching CSV/PO/MO extension, and a 10 MB size limit. 52 53 ### Fixed 54 55 - Prevented recurring Cron from delaying a manually started Translate All worker. 56 - Fixed worker stalls when a batch is satisfied entirely from Translation Memory or consists only of skipped rows. 57 - Fixed worker recovery states so provider cooldown, local quota exhaustion, queue-lock contention, and fatal failures are handled separately. 58 - Fixed wrong-language provider wrapper arguments that could mix Russian and Georgian targets. 59 - Rejected short wrong-script provider responses, including Cyrillic text returned for Georgian and Georgian text returned for Russian. 60 - Fixed debug-log download buffering and response headers. 61 - Removed the unreachable 0.4.0 aggregated Elementor scanner path so future scans use only structured field extraction. 62 - Fixed template and frontend-editor source-type validation after Scanner Sources were introduced. 6 63 7 64 ## [0.4.0] - 2026-08-20 -
ait-multilingual-translate/trunk/README.md
r3665107 r3761123 4 4 [](https://www.php.net/) 5 5 [](LICENSE) 6 [](CHANGELOG.md)6 [](CHANGELOG.md) 7 7 [](https://wordpress.org/plugins/ait-multilingual-translate/) 8 8 … … 20 20 - Translation queue for controlled, batch-based translation workflows. 21 21 - Scanner for collecting translatable strings before processing. 22 - Scanner Sources and Preview with usage classifications, source counts, structured Elementor fields, filters, and per-unit include/exclude controls. 23 - Persistent background Translate All worker with pause, resume, stop, progress, configurable Cron, and a shared queue lock. 24 - CSV, PO, and MO import/export from the Translation Matrix. 25 - Independent Russian and Georgian plugin interface packs that do not change website or target languages. 22 26 - Translation Matrix for searching, reviewing, editing, saving, importing, and exporting translations. 23 27 - Frontend Editor for administrator-only visual translation edits on the live site. -
ait-multilingual-translate/trunk/ait-multilingual-translate.php
r3665107 r3761123 4 4 * Description: AI-powered multilingual translation plugin compatible with WooCommerce and Elementor, with SEO-friendly URLs and frontend editing support. 5 5 * Plugin URI: https://wp-ai.itdesign.biz 6 * Version: 0.4. 06 * Version: 0.4.2 7 7 * Author: sotter IT Design 8 8 * Author URI: https://wp-ai.itdesign.biz … … 19 19 } 20 20 21 define('AITMT_VERSION', '0.4. 0');21 define('AITMT_VERSION', '0.4.2'); 22 22 define('AITMT_PLUGIN_FILE', __FILE__); 23 23 define('AITMT_PUBLIC_NAME', 'AIT Multilingual Translate'); … … 35 35 require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-protected-content.php'; 36 36 require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-locales.php'; 37 require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-interface-language.php'; 38 require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-scanner.php'; 37 39 require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-provider-registry.php'; 38 40 require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-runtime-maintenance.php'; 39 41 AITMT_Runtime_Maintenance::bootstrap(); 42 AITMT_Interface_Language::bootstrap(); 40 43 41 44 if (!defined('AITMT_USE_LEGACY_FULL_ENGINE') || !AITMT_USE_LEGACY_FULL_ENGINE) { … … 96 99 add_action('admin_post_aitmt_onboarding_finish', 'aitmt_fallback_onboarding_finish_handler'); 97 100 add_action('admin_post_aitmt_scan_site', 'aitmt_fallback_scan_site_handler'); 101 add_action('admin_post_aitmt_save_scanner_sources', array('AITMT_Scanner', 'handle_save')); 102 add_action('admin_post_aitmt_save_scanner_exclusions', array('AITMT_Scanner', 'handle_preview_exclusions')); 103 add_action('admin_post_aitmt_manage_scanner_preview', array('AITMT_Scanner', 'handle_preview_management')); 98 104 add_action('admin_post_aitmt_save_manual_translation', 'aitmt_fallback_save_manual_translation_handler'); 99 105 add_action('admin_post_aitmt_save_translation_matrix', 'aitmt_fallback_save_translation_matrix_handler'); … … 102 108 add_action('admin_post_aitmt_process_queue', 'aitmt_fallback_process_queue_handler'); 103 109 add_action('admin_post_aitmt_translate_all_queue', 'aitmt_fallback_translate_all_queue_handler'); 110 add_action('admin_post_aitmt_worker_control', 'aitmt_fallback_worker_control_handler'); 111 add_action('admin_post_aitmt_save_background_settings', 'aitmt_fallback_save_background_settings_handler'); 104 112 add_action('admin_post_aitmt_clear_queue', 'aitmt_fallback_clear_queue_handler'); 105 113 add_action('admin_post_aitmt_clear_unknown_queue', 'aitmt_fallback_clear_unknown_queue_handler'); … … 134 142 add_action('save_post', 'aitmt_fallback_scan_saved_post', 20, 3); 135 143 add_action('aitmt_fallback_process_queue_event', 'aitmt_fallback_cron_process_queue'); 144 add_action('aitmt_fallback_worker_event', 'aitmt_fallback_cron_process_queue'); 136 145 add_filter('cron_schedules', 'aitmt_fallback_cron_schedules'); 137 146 add_filter('wp_nav_menu_objects', 'aitmt_fallback_nav_menu_objects', 20, 2); … … 590 599 $input = is_array($raw_input) ? $raw_input : array(); 591 600 $old_options = aitmt_fallback_options(); 601 $preset = isset($_POST['aitmt_apply_preset']) ? sanitize_key(wp_unslash((string) $_POST['aitmt_apply_preset'])) : ''; 602 $presets = aitmt_fallback_safety_presets(); 603 if (isset($presets[$preset])) { 604 $input = array_merge($input, $presets[$preset]); 605 } 592 606 $options = aitmt_fallback_sanitize($input); 593 607 … … 775 789 { 776 790 wp_clear_scheduled_hook('aitmt_fallback_process_queue_event'); 791 wp_clear_scheduled_hook('aitmt_fallback_worker_event'); 777 792 AITMT_Runtime_Maintenance::deactivate(); 778 793 flush_rewrite_rules(false); … … 1490 1505 $text = (string) $text; 1491 1506 $patterns = array( 1507 '/authorization\s*:\s*bearer\s+[^,\s"\']+/i', 1492 1508 '/sk-[A-Za-z0-9_\-]{8,}/i', 1493 1509 '/xai-[A-Za-z0-9_\-]{8,}/i', … … 1638 1654 $options = aitmt_fallback_sanitize(wp_parse_args($input, $old_options)); 1639 1655 update_option('aitmt_options', $options, false); 1656 aitmt_fallback_sync_cron_schedule(); 1640 1657 aitmt_fallback_register_rewrites(); 1641 1658 flush_rewrite_rules(false); … … 1705 1722 $url_mode = isset($input['url_mode']) && 'query' === $input['url_mode'] ? 'query' : 'directory'; 1706 1723 $admin_mode = isset($input['admin_mode']) && 'advanced' === sanitize_key((string) $input['admin_mode']) ? 'advanced' : 'basic'; 1724 $interface_language = AITMT_Interface_Language::sanitize($input['interface_language'] ?? 'auto'); 1707 1725 $allowed_providers = AITMT_Provider_Registry::ids(); 1708 1726 $provider = isset($input['provider']) ? sanitize_key((string) $input['provider']) : 'openai'; … … 1717 1735 $retry_limit = isset($input['retry_limit']) ? absint($input['retry_limit']) : 3; 1718 1736 $retry_limit = min(10, max(1, $retry_limit)); 1737 $cron_interval = isset($input['cron_interval']) ? absint($input['cron_interval']) : 300; 1738 $cron_interval = in_array($cron_interval, array(60, 120, 300, 600, 900, 1800, 3600), true) ? $cron_interval : 300; 1739 $cron_max_batches = isset($input['cron_max_batches']) ? absint($input['cron_max_batches']) : 2; 1740 $cron_max_batches = min(10, max(1, $cron_max_batches)); 1741 $cron_max_runtime = isset($input['cron_max_runtime']) ? absint($input['cron_max_runtime']) : 20; 1742 $cron_max_runtime = min(120, max(5, $cron_max_runtime)); 1719 1743 $daily_limit = isset($input['quota_daily_chars']) ? absint($input['quota_daily_chars']) : 5000; 1720 1744 $monthly_limit = isset($input['quota_monthly_chars']) ? absint($input['quota_monthly_chars']) : 50000; 1745 $requests_per_minute = max(0, min(1000, absint($input['quota_requests_per_minute'] ?? 5))); 1746 $tokens_per_minute = max(0, min(10000000, absint($input['quota_tokens_per_minute'] ?? 10000))); 1747 $requests_per_day = max(0, min(1000000, absint($input['quota_requests_per_day'] ?? 100))); 1721 1748 $max_chars_per_request = isset($input['max_chars_per_request']) ? absint($input['max_chars_per_request']) : 2000; 1722 1749 $estimated_cost_limit = isset($input['estimated_cost_limit']) ? (float) str_replace(',', '.', sanitize_text_field((string) $input['estimated_cost_limit'])) : 0.02; … … 1754 1781 'enabled_languages' => array_values(array_unique($languages)), 1755 1782 'admin_mode' => $admin_mode, 1783 'interface_language' => $interface_language, 1756 1784 'provider' => $provider, 1757 1785 'openai_api_key' => aitmt_fallback_sanitize_secret($input, 'openai_api_key', $stored_options), … … 1782 1810 'quota_daily_chars' => $daily_limit, 1783 1811 'quota_monthly_chars' => $monthly_limit, 1812 'quota_requests_per_minute' => $requests_per_minute, 1813 'quota_tokens_per_minute' => $tokens_per_minute, 1814 'quota_requests_per_day' => $requests_per_day, 1784 1815 'max_chars_per_request' => $max_chars_per_request, 1785 1816 'estimated_cost_limit' => $estimated_cost_limit, … … 1804 1835 'runtime_excluded_source_types' => $runtime_excluded_source_types, 1805 1836 'runtime_translate_woocommerce_private' => empty($input['runtime_translate_woocommerce_private']) ? '0' : '1', 1837 'scanner_sources' => AITMT_Scanner::sanitize(isset($input['scanner_sources']) && is_array($input['scanner_sources']) ? $input['scanner_sources'] : (isset($stored_options['scanner_sources']) && is_array($stored_options['scanner_sources']) ? $stored_options['scanner_sources'] : array())), 1838 'scanner_exclusions' => AITMT_Scanner::sanitize_exclusions($input['scanner_exclusions'] ?? $stored_options['scanner_exclusions'] ?? array()), 1839 'scanner_object_exclusions' => AITMT_Scanner::sanitize_exclusions($input['scanner_object_exclusions'] ?? $stored_options['scanner_object_exclusions'] ?? array(), true), 1806 1840 'scan_on_save' => empty($input['scan_on_save']) ? '0' : '1', 1807 1841 'cron_enabled' => empty($input['cron_enabled']) ? '0' : '1', 1842 'cron_interval' => $cron_interval, 1843 'cron_max_batches' => $cron_max_batches, 1844 'cron_max_runtime' => $cron_max_runtime, 1808 1845 'draft_mode' => empty($input['draft_mode']) ? '0' : '1', 1809 1846 'translate_attributes' => empty($input['translate_attributes']) ? '0' : '1', … … 2002 2039 'enabled_languages' => array(), 2003 2040 'admin_mode' => 'basic', 2041 'interface_language' => 'auto', 2004 2042 'provider' => 'openai', 2005 2043 'openai_api_key' => '', … … 2030 2068 'quota_daily_chars' => 5000, 2031 2069 'quota_monthly_chars' => 50000, 2070 'quota_requests_per_minute' => 5, 2071 'quota_tokens_per_minute' => 10000, 2072 'quota_requests_per_day' => 100, 2032 2073 'max_chars_per_request' => 2000, 2033 2074 'estimated_cost_limit' => 0.02, … … 2052 2093 'runtime_excluded_source_types' => '', 2053 2094 'runtime_translate_woocommerce_private' => '0', 2095 'scanner_sources' => AITMT_Scanner::defaults(), 2096 'scanner_exclusions' => array(), 2097 'scanner_object_exclusions' => array(), 2054 2098 'scan_on_save' => '0', 2055 2099 'cron_enabled' => '0', 2100 'cron_interval' => 300, 2101 'cron_max_batches' => 2, 2102 'cron_max_runtime' => 20, 2056 2103 'draft_mode' => '0', 2057 2104 'translate_attributes' => '1', … … 2092 2139 'draft_mode' => '0', 2093 2140 'translate_attributes' => '1', 2141 ); 2142 } 2143 2144 function aitmt_fallback_safety_presets() 2145 { 2146 return array( 2147 'safe_free' => array( 2148 'quality_mode' => 'cheap', 2149 'translation_temperature' => 0.1, 2150 'quota_daily_chars' => 5000, 2151 'quota_monthly_chars' => 50000, 2152 'quota_requests_per_minute' => 5, 2153 'quota_tokens_per_minute' => 10000, 2154 'quota_requests_per_day' => 100, 2155 'max_chars_per_request' => 2000, 2156 'estimated_cost_limit' => 0.02, 2157 'max_segments_per_request' => 5, 2158 'cron_enabled' => '1', 2159 'cron_interval' => 300, 2160 'cron_max_batches' => 1, 2161 'cron_max_runtime' => 10, 2162 'translate_on_page_load' => '0', 2163 'scan_on_save' => '0', 2164 ), 2165 'recommended' => array( 2166 'quality_mode' => 'balanced', 2167 'translation_temperature' => 0.1, 2168 'quota_daily_chars' => 25000, 2169 'quota_monthly_chars' => 250000, 2170 'quota_requests_per_minute' => 10, 2171 'quota_tokens_per_minute' => 30000, 2172 'quota_requests_per_day' => 500, 2173 'max_chars_per_request' => 4000, 2174 'estimated_cost_limit' => 0.10, 2175 'max_segments_per_request' => 10, 2176 'cron_enabled' => '1', 2177 'cron_interval' => 300, 2178 'cron_max_batches' => 2, 2179 'cron_max_runtime' => 20, 2180 'translate_on_page_load' => '0', 2181 'scan_on_save' => '0', 2182 ), 2183 'fast_bulk' => array( 2184 'quality_mode' => 'balanced', 2185 'translation_temperature' => 0.1, 2186 'quota_daily_chars' => 250000, 2187 'quota_monthly_chars' => 2000000, 2188 'quota_requests_per_minute' => 30, 2189 'quota_tokens_per_minute' => 120000, 2190 'quota_requests_per_day' => 5000, 2191 'max_chars_per_request' => 10000, 2192 'estimated_cost_limit' => 1.00, 2193 'max_segments_per_request' => 50, 2194 'cron_enabled' => '1', 2195 'cron_interval' => 60, 2196 'cron_max_batches' => 5, 2197 'cron_max_runtime' => 60, 2198 'translate_on_page_load' => '0', 2199 'scan_on_save' => '0', 2200 ), 2094 2201 ); 2095 2202 } … … 2947 3054 function aitmt_fallback_translate_with_provider($segments, $source_language, $target_language, $context = array()) 2948 3055 { 3056 $target_info = AITMT_Locales::resolve($target_language, aitmt_fallback_languages()); 3057 if ('1' !== ($target_info['known'] ?? '0')) { 3058 aitmt_fallback_log_debug_event('locale', 'Blocked provider request for an unknown target locale.', array( 3059 'target_language' => sanitize_text_field((string) $target_language), 3060 )); 3061 return new WP_Error('aitmt_unknown_target_language', __('The target language is not recognized. Select a supported language before translating.', 'ait-multilingual-translate')); 3062 } 3063 2949 3064 $provider = aitmt_fallback_active_provider(); 2950 3065 $deduped = aitmt_fallback_dedupe_segments((array) $segments); … … 2980 3095 ? $adapter->translate_batch($segments_for_provider, $source_language, $target_language) 2981 3096 : new WP_Error('aitmt_provider_unavailable', 'The selected translation provider is not available.'); 3097 3098 if (!is_wp_error($result)) { 3099 foreach ((array) $result as $source_hash => $translated_text) { 3100 if (!AITMT_Locales::matches_target_script($translated_text, $target_language)) { 3101 $result = new WP_Error( 3102 'aitmt_wrong_target_language', 3103 sprintf( 3104 /* translators: %s: requested target locale. */ 3105 __('The provider response does not appear to use the requested target language (%s). The translation was not saved.', 'ait-multilingual-translate'), 3106 $target_info['api_locale'] 3107 ) 3108 ); 3109 break; 3110 } 3111 } 3112 } 2982 3113 2983 3114 if (!is_wp_error($result)) { … … 3182 3313 $monthly_limit = isset($options['quota_monthly_chars']) ? absint($options['quota_monthly_chars']) : 0; 3183 3314 $request_char_limit = isset($options['max_chars_per_request']) ? absint($options['max_chars_per_request']) : 0; 3315 $requests_per_minute = absint($options['quota_requests_per_minute'] ?? 0); 3316 $tokens_per_minute = absint($options['quota_tokens_per_minute'] ?? 0); 3317 $requests_per_day = absint($options['quota_requests_per_day'] ?? 0); 3184 3318 $cost_limit = isset($options['estimated_cost_limit']) ? (float) $options['estimated_cost_limit'] : 0.0; 3185 3319 $day = gmdate('Ymd'); 3186 3320 $month = gmdate('Ym'); 3321 3322 $now_timestamp = current_time('timestamp', true); 3323 $minute_start = $now_timestamp - MINUTE_IN_SECONDS; 3324 $day_start = strtotime(gmdate('Y-m-d 00:00:00', $now_timestamp) . ' UTC'); 3325 $recent_requests = 0; 3326 $recent_tokens = 0; 3327 $today_requests = 0; 3328 foreach (aitmt_fallback_provider_request_log_entries() as $entry) { 3329 if (sanitize_key((string) ($entry['provider'] ?? '')) !== sanitize_key((string) $provider) || 'blocked' === sanitize_key((string) ($entry['status'] ?? ''))) { 3330 continue; 3331 } 3332 $timestamp = strtotime((string) ($entry['created_at'] ?? '') . ' UTC'); 3333 if (!$timestamp) { 3334 continue; 3335 } 3336 if ($timestamp >= $minute_start) { 3337 $recent_requests++; 3338 $recent_tokens += absint($entry['tokens'] ?? ceil(absint($entry['characters'] ?? 0) / 4)); 3339 } 3340 if ($timestamp >= $day_start) { 3341 $today_requests++; 3342 } 3343 } 3344 $request_tokens = aitmt_fallback_estimate_tokens_for_segments($segments); 3345 3346 if ($requests_per_minute > 0 && $recent_requests + 1 > $requests_per_minute) { 3347 return new WP_Error('aitmt_requests_per_minute', sprintf('Local requests-per-minute limit reached for %s.', aitmt_fallback_provider_label($provider)), array('status' => 429)); 3348 } 3349 if ($tokens_per_minute > 0 && $recent_tokens + $request_tokens > $tokens_per_minute) { 3350 return new WP_Error('aitmt_tokens_per_minute', sprintf('Local tokens-per-minute limit reached for %s.', aitmt_fallback_provider_label($provider)), array('status' => 429)); 3351 } 3352 if ($requests_per_day > 0 && $today_requests + 1 > $requests_per_day) { 3353 return new WP_Error('aitmt_requests_per_day', sprintf('Local requests-per-day limit reached for %s.', aitmt_fallback_provider_label($provider)), array('status' => 429)); 3354 } 3187 3355 3188 3356 if ($request_char_limit > 0 && $chars > $request_char_limit) { … … 3261 3429 { 3262 3430 $entry = array( 3263 'created_at' => current_time('mysql' ),3431 'created_at' => current_time('mysql', true), 3264 3432 'provider' => sanitize_key((string) $provider), 3265 3433 'source_type' => sanitize_key((string) $source_type), … … 3267 3435 'target_language' => aitmt_fallback_normalize_language($target_language), 3268 3436 'characters' => aitmt_fallback_provider_char_count((array) $segments), 3437 'tokens' => aitmt_fallback_estimate_tokens_for_segments((array) $segments), 3269 3438 'batch_id' => sanitize_key((string) $batch_id), 3270 3439 'status' => sanitize_key((string) $status), … … 5550 5719 } 5551 5720 5552 $existing = aitmt_fallback_get_existing_translations($clean_segments, $source_language, $target_language); 5553 if (!empty($existing)) { 5554 aitmt_fallback_provider_stats_record_cache_hits(aitmt_fallback_active_provider(), count($existing)); 5555 } 5556 $missing = array_diff_key($clean_segments, $existing); 5721 $existing = aitmt_fallback_get_existing_translations($clean_segments, $source_language, $target_language, true, false); 5722 $known = aitmt_fallback_get_existing_translations($clean_segments, $source_language, $target_language, true, true); 5723 if (!empty($known)) { 5724 aitmt_fallback_provider_stats_record_cache_hits(aitmt_fallback_active_provider(), count($known)); 5725 } 5726 $missing = array_diff_key($clean_segments, $known); 5557 5727 5558 5728 if (empty($missing)) { … … 5598 5768 5599 5769 try { 5600 $after_lock = aitmt_fallback_get_existing_translations($locked_missing, $source_language, $target_language); 5601 if (!empty($after_lock)) { 5770 $after_lock = aitmt_fallback_get_existing_translations($locked_missing, $source_language, $target_language, true, false); 5771 $after_lock_known = aitmt_fallback_get_existing_translations($locked_missing, $source_language, $target_language, true, true); 5772 if (!empty($after_lock_known)) { 5602 5773 $existing = array_merge($existing, $after_lock); 5603 $locked_missing = array_diff_key($locked_missing, $after_lock );5774 $locked_missing = array_diff_key($locked_missing, $after_lock_known); 5604 5775 } 5605 5776 … … 5680 5851 'temperature' => aitmt_fallback_translation_temperature(), 5681 5852 'instructions' => sprintf( 5682 'Translate to %s (%s). Preserve formatting. Return only translated text for each segment. %s Preserve placeholders, numbers, emails, URLs, shortcodes, HTML entities, and brand names. Return only valid JSON that matches the schema.', 5683 $target_name, 5684 strtoupper($target_language), 5853 '%s Preserve formatting. Return only translated text for each segment. %s Preserve placeholders, numbers, emails, URLs, shortcodes, HTML entities, and brand names. Return only valid JSON that matches the schema.', 5854 AITMT_Locales::translation_instruction($source_language, $target_language, $languages), 5685 5855 $translation_instruction 5686 5856 ), … … 5821 5991 5822 5992 $prompt = sprintf( 5823 "Translate to %s (%s). Preserve formatting. Return only translated text for each segment.\n%s\nPreserve placeholders, numbers, emails, URLs, shortcodes, HTML entities, and brand names.\nReturn only JSON in this exact shape: {\"translations\":[{\"id\":\"same id\",\"text\":\"translated text\"}]}.\n\nSegments:\n%s", 5824 $target_name, 5825 strtoupper($target_language), 5993 "%s Preserve formatting. Return only translated text for each segment.\n%s\nPreserve placeholders, numbers, emails, URLs, shortcodes, HTML entities, and brand names.\nReturn only JSON in this exact shape: {\"translations\":[{\"id\":\"same id\",\"text\":\"translated text\"}]}.\n\nSegments:\n%s", 5994 AITMT_Locales::translation_instruction($source_language, $target_language, $languages), 5826 5995 $translation_instruction, 5827 5996 wp_json_encode($items, JSON_UNESCAPED_UNICODE) … … 5940 6109 'body' => wp_json_encode(array( 5941 6110 'q' => $texts, 5942 'source' => $source_language,5943 'target' => $target_language,6111 'source' => AITMT_Locales::provider_code($source_language, 'google_translate', true), 6112 'target' => AITMT_Locales::provider_code($target_language, 'google_translate'), 5944 6113 'format' => 'text', 5945 6114 )), … … 6050 6219 function aitmt_fallback_deepl_language_code($language, $is_source = false) 6051 6220 { 6052 $language = strtoupper(aitmt_fallback_normalize_language($language)); 6053 6054 if ('EN' === $language && !$is_source) { 6055 return 'EN-US'; 6056 } 6057 6058 return $language; 6221 return AITMT_Locales::provider_code($language, 'deepl', $is_source); 6059 6222 } 6060 6223 6061 6224 function aitmt_fallback_grok_translate_batch($segments, $source_language, $target_language) 6062 6225 { 6063 unset($source_language); 6064 6065 return aitmt_fallback_openai_compatible_translate_batch('grok', 'https://api.x.ai/v1/chat/completions', $segments, $target_language); 6226 return aitmt_fallback_openai_compatible_translate_batch('grok', 'https://api.x.ai/v1/chat/completions', $segments, $source_language, $target_language); 6066 6227 } 6067 6228 … … 6080 6241 } 6081 6242 6082 function aitmt_fallback_provider_prompt($segments, $ target_language)6243 function aitmt_fallback_provider_prompt($segments, $source_language, $target_language) 6083 6244 { 6084 6245 $instruction = aitmt_fallback_translation_instruction(); 6246 $locale_instruction = AITMT_Locales::translation_instruction($source_language, $target_language, aitmt_fallback_languages()); 6085 6247 6086 6248 return sprintf( 6087 ' Translate to %1$s.Preserve formatting and placeholders. %2$s Return only JSON in this exact shape: {"translations":[{"id":"same id","text":"translated text"}]}. Segments: %3$s',6088 strtoupper((string) $target_language),6249 '%1$s Preserve formatting and placeholders. %2$s Return only JSON in this exact shape: {"translations":[{"id":"same id","text":"translated text"}]}. Segments: %3$s', 6250 $locale_instruction, 6089 6251 $instruction, 6090 6252 wp_json_encode(aitmt_fallback_provider_prompt_items($segments), JSON_UNESCAPED_UNICODE) … … 6124 6286 } 6125 6287 6126 function aitmt_fallback_openai_compatible_translate_batch($provider, $endpoint, $segments, $ target_language)6288 function aitmt_fallback_openai_compatible_translate_batch($provider, $endpoint, $segments, $source_language, $target_language) 6127 6289 { 6128 6290 $provider = sanitize_key((string) $provider); … … 6146 6308 array( 6147 6309 'role' => 'user', 6148 'content' => aitmt_fallback_provider_prompt($segments, $ target_language),6310 'content' => aitmt_fallback_provider_prompt($segments, $source_language, $target_language), 6149 6311 ), 6150 6312 ), … … 6191 6353 function aitmt_fallback_mistral_translate_batch($segments, $source_language, $target_language) 6192 6354 { 6193 unset($source_language); 6194 6195 return aitmt_fallback_openai_compatible_translate_batch('mistral', 'https://api.mistral.ai/v1/chat/completions', $segments, $target_language); 6355 return aitmt_fallback_openai_compatible_translate_batch('mistral', 'https://api.mistral.ai/v1/chat/completions', $segments, $source_language, $target_language); 6196 6356 } 6197 6357 6198 6358 function aitmt_fallback_deepseek_translate_batch($segments, $source_language, $target_language) 6199 6359 { 6200 unset($source_language); 6201 6202 return aitmt_fallback_openai_compatible_translate_batch('deepseek', 'https://api.deepseek.com/chat/completions', $segments, $target_language); 6360 return aitmt_fallback_openai_compatible_translate_batch('deepseek', 'https://api.deepseek.com/chat/completions', $segments, $source_language, $target_language); 6203 6361 } 6204 6362 6205 6363 function aitmt_fallback_qwen_translate_batch($segments, $source_language, $target_language) 6206 6364 { 6207 unset($source_language);6208 6365 $region = aitmt_fallback_provider_setting('qwen', 'region', 'us'); 6209 6366 $endpoints = array( … … 6214 6371 $endpoint = isset($endpoints[$region]) ? $endpoints[$region] : $endpoints['us']; 6215 6372 6216 return aitmt_fallback_openai_compatible_translate_batch('qwen', $endpoint, $segments, $ target_language);6373 return aitmt_fallback_openai_compatible_translate_batch('qwen', $endpoint, $segments, $source_language, $target_language); 6217 6374 } 6218 6375 6219 6376 function aitmt_fallback_baidu_translate_batch($segments, $source_language, $target_language) 6220 6377 { 6221 unset($source_language); 6222 6223 return aitmt_fallback_openai_compatible_translate_batch('baidu', 'https://api.baiduqianfan.ai/v1/chat/completions', $segments, $target_language); 6378 return aitmt_fallback_openai_compatible_translate_batch('baidu', 'https://api.baiduqianfan.ai/v1/chat/completions', $segments, $source_language, $target_language); 6224 6379 } 6225 6380 6226 6381 function aitmt_fallback_claude_translate_batch($segments, $source_language, $target_language) 6227 6382 { 6228 unset($source_language);6229 6383 $api_key = aitmt_fallback_provider_key('claude'); 6230 6384 if (empty($api_key)) { … … 6249 6403 array( 6250 6404 'role' => 'user', 6251 'content' => aitmt_fallback_provider_prompt($segments, $ target_language),6405 'content' => aitmt_fallback_provider_prompt($segments, $source_language, $target_language), 6252 6406 ), 6253 6407 ), … … 6296 6450 array( 6297 6451 'folderId' => $folder_id, 6298 'sourceLanguageCode' => AITMT_Locales:: base($source_language),6299 'targetLanguageCode' => AITMT_Locales:: base($target_language),6452 'sourceLanguageCode' => AITMT_Locales::provider_code($source_language, 'yandex_translate', true), 6453 'targetLanguageCode' => AITMT_Locales::provider_code($target_language, 'yandex_translate'), 6300 6454 'format' => 'PLAIN_TEXT', 6301 6455 'texts' => array_values($segments), … … 6338 6492 function aitmt_fallback_yandexgpt_translate_batch($segments, $source_language, $target_language) 6339 6493 { 6340 unset($source_language);6341 6494 $api_key = aitmt_fallback_provider_key('yandexgpt'); 6342 6495 $folder_id = aitmt_fallback_provider_setting('yandexgpt', 'folder_id'); … … 6364 6517 array( 6365 6518 'role' => 'user', 6366 'text' => aitmt_fallback_provider_prompt($segments, $ target_language),6519 'text' => aitmt_fallback_provider_prompt($segments, $source_language, $target_language), 6367 6520 ), 6368 6521 ), … … 6461 6614 } 6462 6615 6463 function aitmt_fallback_get_existing_translations($segments, $source_language, $target_language, $allow_locale_fallback = true )6616 function aitmt_fallback_get_existing_translations($segments, $source_language, $target_language, $allow_locale_fallback = true, $include_drafts = true) 6464 6617 { 6465 6618 global $wpdb; … … 6487 6640 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Dynamic IN placeholders and table name are controlled above; all values are prepared. 6488 6641 $prepared = $wpdb->prepare( 6489 "SELECT id, source_hash, source_text, translated_text, target_language, context, source_type FROM {$table} WHERE source_hash IN ({$placeholders}) AND source_language = %s AND target_language = %s AND context = %s AND status IN ('published', 'manual', 'import', 'draft')",6642 "SELECT id, source_hash, source_text, translated_text, target_language, context, source_type, status FROM {$table} WHERE source_hash IN ({$placeholders}) AND source_language = %s AND target_language = %s AND context = %s AND status IN ('published', 'manual', 'import', 'draft')", 6490 6643 ...$args 6491 6644 ); … … 6495 6648 6496 6649 foreach ((array) $rows as $row) { 6650 if (!$include_drafts && 'draft' === (string) ($row['status'] ?? '')) { 6651 continue; 6652 } 6497 6653 $current_hash = aitmt_fallback_translation_hash((string) ($row['source_text'] ?? '')); 6498 6654 if (isset($segments[$current_hash])) { … … 6518 6674 $source_language, 6519 6675 $base_language, 6520 false 6676 false, 6677 $include_drafts 6521 6678 ); 6522 6679 $map = array_replace($base_translations, $map); … … 6684 6841 $hash = aitmt_fallback_translation_hash($source_text); 6685 6842 6843 $scanner_options = aitmt_fallback_options(); 6844 if (AITMT_Scanner::is_excluded($source_type, absint($object_id), $field_key, $scanner_options) 6845 || AITMT_Scanner::is_flattened_elementor_row(array('source_type' => $source_type, 'object_id' => $object_id, 'field_key' => $field_key), $scanner_options)) { 6846 return false; 6847 } 6848 6686 6849 $legacy_hash = aitmt_fallback_legacy_translation_hash($source_text); 6687 6850 if ($legacy_hash !== $hash) { … … 6721 6884 $result = $wpdb->query($query); 6722 6885 6886 // Shared text has one job. A new included owner may replace an excluded legacy owner, never saved memory. 6887 if (0 === $result) { 6888 $existing = $wpdb->get_row($wpdb->prepare( 6889 "SELECT id, source_type, object_id, field_key FROM {$table} WHERE source_hash = %s AND source_language = %s AND target_language = %s AND context = 'html' AND status = 'queued' AND translated_text = '' AND provider NOT IN ('manual', 'import') LIMIT 1", 6890 $hash, aitmt_fallback_normalize_language($source_language), aitmt_fallback_normalize_language($target_language) 6891 ), ARRAY_A); 6892 if ($existing && !AITMT_Scanner::queue_row_enabled($existing, $scanner_options)) { 6893 $wpdb->update($table, array('source_type' => $source_type, 'object_id' => absint($object_id), 'field_key' => aitmt_fallback_normalize_field_key($field_key)), 6894 array('id' => absint($existing['id']), 'status' => 'queued', 'translated_text' => ''), array('%s', '%d', '%s'), array('%d', '%s', '%s')); 6895 } 6896 } 6897 6723 6898 if (1 === (int) $result && 'page_load' === $source_type) { 6724 6899 AITMT_Runtime_Maintenance::increment_runtime_count($target_language); … … 6789 6964 'widget', 6790 6965 'elementor', 6966 'elementor_library', 6967 'sectionbuilder', 6968 'litho-mega-menu', 6969 'page_meta', 6791 6970 'wpfd_file', 6792 6971 'page_load', … … 6796 6975 'manual', 6797 6976 'import', 6977 'provider', 6978 'frontend_editor', 6979 'shortcode', 6980 'runtime', 6798 6981 'legacy_runtime', 6799 6982 ); … … 6849 7032 function aitmt_fallback_normalize_field_key($field_key) 6850 7033 { 6851 $field_key = sanitize_key((string) $field_key); 6852 6853 return substr($field_key, 0, 120); 7034 $field_key = preg_replace('/[^a-z0-9_.:\-]/', '', strtolower((string) $field_key)); 7035 return strlen($field_key) <= 120 ? $field_key : substr($field_key, 0, 79) . ':' . sha1($field_key); 6854 7036 } 6855 7037 … … 7062 7244 } 7063 7245 7246 function aitmt_fallback_mark_processing_rows_skipped($table, $rows, $reason) 7247 { 7248 global $wpdb; 7249 7250 $ids = array(); 7251 foreach ((array) $rows as $row) { 7252 $id = absint($row['id'] ?? 0); 7253 if ($id) { 7254 $ids[] = $id; 7255 } 7256 } 7257 $ids = array_values(array_filter(array_unique($ids))); 7258 if (empty($ids)) { 7259 return 0; 7260 } 7261 7262 $reason = function_exists('mb_substr') ? mb_substr(wp_strip_all_tags((string) $reason), 0, 500, 'UTF-8') : substr(wp_strip_all_tags((string) $reason), 0, 500); 7263 $now = current_time('mysql'); 7264 $placeholders = implode(',', array_fill(0, count($ids), '%d')); 7265 $args = array_merge(array('skipped', $reason, '', $now, 'processing'), $ids); 7266 7267 return (int) $wpdb->query( 7268 $wpdb->prepare( 7269 "UPDATE {$table} SET status = %s, failed_reason = %s, batch_id = %s, updated_at = %s WHERE status = %s AND id IN ({$placeholders})", 7270 ...$args 7271 ) 7272 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name and placeholders are controlled; values are prepared above. 7273 } 7274 7064 7275 function aitmt_fallback_mark_processing_rows_after_failure($table, $rows, $reason) 7065 7276 { … … 7107 7318 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name and placeholders are controlled; values are prepared above. 7108 7319 } 7320 7321 return array( 7322 'failed' => count($failed_ids), 7323 'requeued' => count($retry_ids), 7324 ); 7109 7325 } 7110 7326 … … 7446 7662 'processed' => 0, 7447 7663 'locked' => true, 7664 'error_code' => $lock->get_error_code(), 7665 'error_status' => absint($lock->get_error_data('status')), 7448 7666 ); 7449 7667 } … … 7509 7727 $options = aitmt_fallback_options(); 7510 7728 $processed = 0; 7729 $skipped = 0; 7730 $cached = 0; 7731 $failed = 0; 7511 7732 $routes = array(); 7512 7733 $now = current_time('mysql'); … … 7539 7760 } 7540 7761 7762 if (!AITMT_Scanner::queue_row_enabled($row, $options)) { 7763 aitmt_fallback_record_rejected_string('disabled_or_unpublished_source', $row['source_text'] ?? '', array( 7764 'source_type' => $row['source_type'] ?? '', 7765 'object_id' => isset($row['object_id']) ? absint($row['object_id']) : 0, 7766 'field_key' => isset($row['field_key']) ? $row['field_key'] : '', 7767 )); 7768 $invalid_rows[] = $row; 7769 continue; 7770 } 7771 7541 7772 if (!aitmt_fallback_is_translatable_text($row['source_text'] ?? '', true, array( 7542 7773 'source_type' => $row['source_type'] ?? '', … … 7556 7787 7557 7788 if (!empty($invalid_rows)) { 7558 aitmt_fallback_mark_processing_rows_failed($table, $invalid_rows, 'Skipped unsafe or unknownqueue item before provider request.');7789 $skipped += aitmt_fallback_mark_processing_rows_skipped($table, $invalid_rows, 'Skipped unsafe, excluded, obsolete, or unpublished queue item before provider request.'); 7559 7790 } 7560 7791 7561 $existing = aitmt_fallback_get_existing_translations($segments, $source_language, $target_language); 7562 $segments = array_diff_key($segments, $existing); 7563 7564 if (empty($segments)) { 7792 $published_existing = aitmt_fallback_get_existing_translations($segments, $source_language, $target_language, true, false); 7793 $known_existing = aitmt_fallback_get_existing_translations($segments, $source_language, $target_language, true, true); 7794 $cached_hashes = array_intersect_key($segments, $known_existing); 7795 $segments = array_diff_key($segments, $known_existing); 7796 7797 if (!empty($cached_hashes)) { 7565 7798 foreach ($valid_claimed_rows as $row) { 7566 $wpdb->update( 7799 $hash = (string) $row['source_hash']; 7800 if (!isset($cached_hashes[$hash], $known_existing[$hash])) { 7801 continue; 7802 } 7803 $cached_status = isset($published_existing[$hash]) ? 'published' : 'draft'; 7804 $updated = $wpdb->update( 7567 7805 $table, 7568 7806 array( 7569 'status' => 'published', 7807 'translated_text' => (string) $known_existing[$hash], 7808 'status' => $cached_status, 7809 'provider' => 'translation_memory', 7570 7810 'failed_reason' => '', 7571 7811 'batch_id' => '', … … 7573 7813 ), 7574 7814 array('id' => (int) $row['id']), 7575 array('%s', '%s', '%s', '%s' ),7815 array('%s', '%s', '%s', '%s', '%s', '%s'), 7576 7816 array('%d') 7577 7817 ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is validated by aitmt_fallback_translation_table_sql(). 7818 if (false !== $updated) { 7819 $cached++; 7820 } 7578 7821 } 7822 } 7823 7824 if (empty($segments)) { 7579 7825 continue; 7580 7826 } … … 7587 7833 if (is_wp_error($translated)) { 7588 7834 aitmt_fallback_log($translated->get_error_message()); 7589 aitmt_fallback_mark_processing_rows_after_failure($table, $valid_claimed_rows, $translated->get_error_message());7835 $failure_counts = aitmt_fallback_mark_processing_rows_after_failure($table, $valid_claimed_rows, $translated->get_error_message()); 7590 7836 7591 7837 return array( … … 7597 7843 'routes' => implode(', ', $routes), 7598 7844 'batch_id' => $batch_id, 7845 'skipped' => $skipped, 7846 'cached' => $cached, 7847 'failed' => $failed + absint($failure_counts['failed'] ?? 0), 7848 'requeued' => absint($failure_counts['requeued'] ?? 0), 7849 'error_code' => $translated->get_error_code(), 7850 'error_status' => absint($translated->get_error_data('status')), 7599 7851 ); 7600 7852 } … … 7607 7859 if (!isset($translated[$row['source_hash']])) { 7608 7860 aitmt_fallback_mark_processing_rows_after_failure($table, array($row), 'Provider did not return a translation for this string.'); 7861 $failed++; 7609 7862 continue; 7610 7863 } … … 7635 7888 'message' => sprintf('Processed %d queued translation(s) across %d route(s).', $processed, count($groups)), 7636 7889 'processed' => $processed, 7890 'skipped' => $skipped, 7891 'cached' => $cached, 7892 'failed' => $failed, 7637 7893 'routes' => implode(', ', $routes), 7638 7894 'stale_requeued' => absint($stale['requeued'] ?? 0), … … 7649 7905 $post_id = absint($post_id); 7650 7906 $post = get_post($post_id); 7651 7652 7907 if (!$post || 'publish' !== $post->post_status) { 7653 return array( 7654 'strings' => 0, 7655 'queued' => 0, 7656 ); 7908 return array('strings' => 0, 'queued' => 0); 7909 } 7910 7911 $options = aitmt_fallback_options(); 7912 $source_buckets = AITMT_Scanner::collect_post($post_id, $options); 7913 if (empty($source_buckets)) { 7914 return array('strings' => 0, 'queued' => 0); 7657 7915 } 7658 7916 7659 7917 $source_language = aitmt_fallback_source_language(); 7660 7918 $target_languages = array_values(array_diff(aitmt_fallback_enabled_languages(), array($source_language))); 7661 $source_type = sanitize_key((string) get_post_type($post_id)); 7662 $source_type = '' === $source_type ? 'post' : $source_type; 7663 $strings = array(); 7664 7665 aitmt_fallback_add_scan_text($strings, get_the_title($post_id)); 7666 aitmt_fallback_add_scan_text($strings, get_post_field('post_excerpt', $post_id)); 7667 7668 foreach (aitmt_fallback_extract_strings_from_html(get_post_field('post_content', $post_id)) as $text) { 7669 $strings[aitmt_fallback_translation_hash($text)] = $text; 7670 } 7671 7672 foreach (aitmt_fallback_scan_post_meta($post_id) as $text) { 7673 $strings[aitmt_fallback_translation_hash($text)] = $text; 7674 } 7675 7919 $unique = array(); 7676 7920 $queued = 0; 7677 foreach ($strings as $text) { 7678 foreach ($target_languages as $target_language) { 7679 if (aitmt_fallback_enqueue_translation($text, $source_language, $target_language, $source_type, $post_id, 'auto_save')) { 7680 $queued++; 7921 foreach ($source_buckets as $source_type => $items) { 7922 foreach ((array) $items as $hash => $item) { 7923 $text = is_array($item) ? (string) ($item['text'] ?? '') : (string) $item; 7924 $object_id = is_array($item) ? absint($item['object_id'] ?? $post_id) : $post_id; 7925 $field_key = is_array($item) ? aitmt_fallback_normalize_field_key($item['field_key'] ?? '') : ''; 7926 if ('' === $text) { 7927 continue; 7681 7928 } 7929 $unique[aitmt_fallback_translation_hash($text)] = true; 7930 foreach ($target_languages as $target_language) { 7931 if (aitmt_fallback_enqueue_translation($text, $source_language, $target_language, $source_type, $object_id, $field_key)) { 7932 $queued++; 7933 } 7934 } 7682 7935 } 7683 7936 } 7684 7937 7685 7938 return array( 7686 'strings' => count($ strings),7939 'strings' => count($unique), 7687 7940 'queued' => $queued, 7688 7941 ); … … 7726 7979 } 7727 7980 7981 function aitmt_fallback_worker_state() 7982 { 7983 $state = get_option('aitmt_worker_state', array()); 7984 $state = wp_parse_args(is_array($state) ? $state : array(), array( 7985 'status' => 'stopped', 7986 'started_at' => '', 7987 'completed_at' => '', 7988 'initial' => 0, 7989 'processed' => 0, 7990 'remaining' => aitmt_fallback_queued_count(), 7991 'failed' => 0, 7992 'skipped' => 0, 7993 'cached' => 0, 7994 'last_batch' => '', 7995 'last_message' => '', 7996 )); 7997 if ('cooldown' === (string) $state['status']) { 7998 $state['status'] = 'provider_cooldown'; 7999 } 8000 return $state; 8001 } 8002 8003 function aitmt_fallback_worker_error_status($error_code, $error_status = 0, $locked = false) 8004 { 8005 $error_code = sanitize_key((string) $error_code); 8006 if ($locked || 'aitmt_queue_locked' === $error_code) { 8007 return 'waiting_for_lock'; 8008 } 8009 8010 $quota_codes = array( 8011 'aitmt_requests_per_minute', 8012 'aitmt_tokens_per_minute', 8013 'aitmt_requests_per_day', 8014 'aitmt_request_char_limit', 8015 'aitmt_estimated_cost_limit', 8016 'aitmt_daily_quota', 8017 'aitmt_monthly_quota', 8018 ); 8019 if (in_array($error_code, $quota_codes, true)) { 8020 return 'quota_reached'; 8021 } 8022 if ('aitmt_provider_cooldown' === $error_code || 429 === absint($error_status)) { 8023 return 'provider_cooldown'; 8024 } 8025 8026 return 'failed'; 8027 } 8028 8029 function aitmt_fallback_start_worker() 8030 { 8031 $queued = aitmt_fallback_queued_count(); 8032 $estimate = aitmt_fallback_queue_estimate(); 8033 if (!empty($estimate['unknown_queued']) || !empty($estimate['technical_queued'])) { 8034 $state = aitmt_fallback_worker_state(); 8035 $state['status'] = 'blocked'; 8036 $state['remaining'] = $queued; 8037 $state['last_message'] = __('Background translation is blocked until unknown and technical queue rows are reviewed or cleared.', 'ait-multilingual-translate'); 8038 update_option('aitmt_worker_state', $state, false); 8039 return $state; 8040 } 8041 $state = array( 8042 'status' => $queued > 0 ? 'running' : 'completed', 8043 'started_at' => current_time('mysql'), 8044 'completed_at' => $queued > 0 ? '' : current_time('mysql'), 8045 'initial' => $queued, 8046 'processed' => 0, 8047 'remaining' => $queued, 8048 'failed' => 0, 8049 'skipped' => 0, 8050 'cached' => 0, 8051 'last_batch' => '', 8052 'last_message' => $queued > 0 ? __('Background translation worker started.', 'ait-multilingual-translate') : __('Queue is empty.', 'ait-multilingual-translate'), 8053 ); 8054 update_option('aitmt_worker_state', $state, false); 8055 if ($queued > 0 && !wp_next_scheduled('aitmt_fallback_worker_event')) { 8056 wp_schedule_single_event(time() + 1, 'aitmt_fallback_worker_event'); 8057 } 8058 return $state; 8059 } 8060 8061 function aitmt_fallback_worker_control_handler() 8062 { 8063 if (!current_user_can('manage_options')) { 8064 wp_die(esc_html__('Permission denied.', 'ait-multilingual-translate')); 8065 } 8066 check_admin_referer('aitmt_worker_control'); 8067 $operation = isset($_POST['operation']) ? sanitize_key(wp_unslash((string) $_POST['operation'])) : ''; 8068 $state = aitmt_fallback_worker_state(); 8069 8070 if ('pause' === $operation) { 8071 $state['status'] = 'paused'; 8072 $state['last_message'] = __('Worker paused by an administrator.', 'ait-multilingual-translate'); 8073 } elseif ('resume' === $operation) { 8074 $state['status'] = 'running'; 8075 $state['last_message'] = __('Worker resumed.', 'ait-multilingual-translate'); 8076 if (!wp_next_scheduled('aitmt_fallback_worker_event')) { 8077 wp_schedule_single_event(time() + 1, 'aitmt_fallback_worker_event'); 8078 } 8079 } elseif ('stop' === $operation) { 8080 $state['status'] = 'stopped'; 8081 $state['last_message'] = __('Worker stopped by an administrator. Queued rows were preserved.', 'ait-multilingual-translate'); 8082 wp_clear_scheduled_hook('aitmt_fallback_worker_event'); 8083 } elseif ('run' === $operation) { 8084 $state = aitmt_fallback_start_worker(); 8085 } elseif ('reschedule' === $operation) { 8086 wp_clear_scheduled_hook('aitmt_fallback_process_queue_event'); 8087 aitmt_fallback_sync_cron_schedule(); 8088 $state['last_message'] = __('Background schedule was rebuilt.', 'ait-multilingual-translate'); 8089 } elseif ('clear_lock' === $operation) { 8090 $lock = aitmt_fallback_queue_lock_status(); 8091 if (empty($lock['active'])) { 8092 delete_option('aitmt_queue_lock'); 8093 $state['last_message'] = __('Stale queue lock cleared.', 'ait-multilingual-translate'); 8094 } else { 8095 $state['last_message'] = __('The queue lock is active and was not cleared.', 'ait-multilingual-translate'); 8096 } 8097 } 8098 update_option('aitmt_worker_state', $state, false); 8099 wp_safe_redirect(admin_url('admin.php?page=ait-multilingual-translate-scanner')); 8100 exit; 8101 } 8102 8103 function aitmt_fallback_save_background_settings_handler() 8104 { 8105 if (!current_user_can('manage_options')) { 8106 wp_die(esc_html__('Permission denied.', 'ait-multilingual-translate')); 8107 } 8108 check_admin_referer('aitmt_save_background_settings'); 8109 $options = get_option('aitmt_options', array()); 8110 $options = is_array($options) ? $options : array(); 8111 $interval = isset($_POST['cron_interval']) ? absint($_POST['cron_interval']) : 300; 8112 $options['cron_enabled'] = empty($_POST['cron_enabled']) ? '0' : '1'; 8113 $options['cron_interval'] = in_array($interval, array(60, 120, 300, 600, 900, 1800, 3600), true) ? $interval : 300; 8114 $options['cron_max_batches'] = max(1, min(10, absint($_POST['cron_max_batches'] ?? 2))); 8115 $options['cron_max_runtime'] = max(5, min(120, absint($_POST['cron_max_runtime'] ?? 20))); 8116 $options['max_segments_per_request'] = max(1, min(100, absint($_POST['max_segments_per_request'] ?? 5))); 8117 update_option('aitmt_options', $options, false); 8118 wp_clear_scheduled_hook('aitmt_fallback_process_queue_event'); 8119 aitmt_fallback_sync_cron_schedule(); 8120 if ('1' === $options['cron_enabled'] && aitmt_fallback_queued_count() > 0) { 8121 aitmt_fallback_start_worker(); 8122 } 8123 wp_safe_redirect(admin_url('admin.php?page=ait-multilingual-translate-scanner')); 8124 exit; 8125 } 8126 7728 8127 function aitmt_fallback_cron_schedules($schedules) 7729 8128 { 7730 if (!isset($schedules['aitmt_five_minutes'])) { 7731 $schedules['aitmt_five_minutes'] = array( 7732 'interval' => 5 * MINUTE_IN_SECONDS, 7733 'display' => __('Every 5 minutes', 'ait-multilingual-translate'), 8129 $intervals = array( 8130 60 => __('Every 1 minute', 'ait-multilingual-translate'), 8131 120 => __('Every 2 minutes', 'ait-multilingual-translate'), 8132 300 => __('Every 5 minutes', 'ait-multilingual-translate'), 8133 600 => __('Every 10 minutes', 'ait-multilingual-translate'), 8134 900 => __('Every 15 minutes', 'ait-multilingual-translate'), 8135 1800 => __('Every 30 minutes', 'ait-multilingual-translate'), 8136 3600 => __('Hourly', 'ait-multilingual-translate'), 8137 ); 8138 foreach ($intervals as $seconds => $display) { 8139 $schedules['aitmt_' . $seconds] = array( 8140 'interval' => $seconds, 8141 'display' => $display, 7734 8142 ); 7735 8143 } 7736 7737 8144 return $schedules; 7738 8145 } … … 7743 8150 $hook = 'aitmt_fallback_process_queue_event'; 7744 8151 $scheduled = wp_next_scheduled($hook); 8152 $interval = absint($options['cron_interval'] ?? 300); 8153 $schedule = 'aitmt_' . $interval; 8154 $event = function_exists('wp_get_scheduled_event') ? wp_get_scheduled_event($hook) : false; 7745 8155 7746 8156 if ('1' === $options['cron_enabled']) { 8157 if ($event && isset($event->schedule) && $schedule !== $event->schedule) { 8158 wp_clear_scheduled_hook($hook); 8159 $scheduled = false; 8160 } 7747 8161 if (!$scheduled) { 7748 $result = wp_schedule_event(time() + 120, 'aitmt_five_minutes', $hook, array(), true);8162 $result = wp_schedule_event(time() + min(60, $interval), $schedule, $hook, array(), true); 7749 8163 if (is_wp_error($result)) { 7750 8164 aitmt_fallback_set_last_error($result->get_error_message()); … … 7767 8181 { 7768 8182 $options = aitmt_fallback_options(); 7769 7770 if ('1' !== $options['cron_enabled']) { 8183 $worker = aitmt_fallback_worker_state(); 8184 8185 if ('provider_cooldown' === (string) $worker['status']) { 8186 $cooldown = aitmt_fallback_provider_cooldown_remaining(); 8187 if ($cooldown > 0) { 8188 if (!wp_next_scheduled('aitmt_fallback_worker_event')) { 8189 wp_schedule_single_event(time() + $cooldown + 1, 'aitmt_fallback_worker_event'); 8190 } 8191 return; 8192 } 8193 $worker['status'] = 'running'; 8194 update_option('aitmt_worker_state', $worker, false); 8195 } elseif ('waiting_for_lock' === (string) $worker['status']) { 8196 $worker['status'] = 'running'; 8197 update_option('aitmt_worker_state', $worker, false); 8198 } 8199 8200 if ('1' === $options['cron_enabled'] && 'completed' === (string) $worker['status'] && aitmt_fallback_queued_count() > 0) { 8201 $worker = aitmt_fallback_start_worker(); 8202 } 8203 if (in_array((string) $worker['status'], array('paused', 'stopped', 'blocked', 'completed', 'quota_reached', 'failed'), true)) { 7771 8204 return; 7772 8205 } 7773 7774 $limit = isset($options['max_segments_per_request']) ? absint($options['max_segments_per_request']) : 10; 7775 $limit = max(1, min(25, $limit)); 7776 $result = aitmt_fallback_process_queue($limit); 7777 update_option('aitmt_cron_queue_result', array_merge($result, array('created_at' => current_time('mysql'))), false); 8206 if ('1' !== $options['cron_enabled'] && 'running' !== $worker['status']) { 8207 return; 8208 } 8209 8210 $limit = max(1, min(100, absint($options['max_segments_per_request'] ?? 5))); 8211 $max_batches = max(1, min(10, absint($options['cron_max_batches'] ?? 2))); 8212 $deadline = time() + max(5, min(120, absint($options['cron_max_runtime'] ?? 20))); 8213 $processed = 0; 8214 $skipped = 0; 8215 $cached = 0; 8216 $failed = 0; 8217 $batches = 0; 8218 $last = array('ok' => true, 'message' => __('Queue is empty.', 'ait-multilingual-translate'), 'processed' => 0); 8219 while ($batches < $max_batches && time() < $deadline && aitmt_fallback_queued_count() > 0) { 8220 $last = aitmt_fallback_process_queue($limit); 8221 $processed += absint($last['processed'] ?? 0); 8222 $skipped += absint($last['skipped'] ?? 0); 8223 $cached += absint($last['cached'] ?? 0); 8224 $failed += absint($last['failed'] ?? 0); 8225 $handled = absint($last['processed'] ?? 0) + absint($last['skipped'] ?? 0) + absint($last['cached'] ?? 0) + absint($last['failed'] ?? 0); 8226 $batches++; 8227 if (empty($last['ok']) || $handled < 1) { 8228 break; 8229 } 8230 } 8231 $remaining = aitmt_fallback_queued_count(); 8232 $result = array_merge($last, array( 8233 'processed' => $processed, 8234 'skipped' => $skipped, 8235 'cached' => $cached, 8236 'failed' => $failed, 8237 'batches' => $batches, 8238 'remaining' => $remaining, 8239 'created_at' => current_time('mysql'), 8240 )); 8241 update_option('aitmt_cron_queue_result', $result, false); 8242 8243 if ('running' === $worker['status']) { 8244 $worker['processed'] = absint($worker['processed'] ?? 0) + $processed; 8245 $worker['skipped'] = absint($worker['skipped'] ?? 0) + $skipped; 8246 $worker['cached'] = absint($worker['cached'] ?? 0) + $cached; 8247 $worker['failed'] = absint($worker['failed'] ?? 0) + $failed; 8248 $worker['remaining'] = $remaining; 8249 $worker['last_batch'] = current_time('mysql'); 8250 $worker['last_message'] = (string) ($last['message'] ?? ''); 8251 if (empty($last['ok'])) { 8252 $worker['status'] = aitmt_fallback_worker_error_status( 8253 (string) ($last['error_code'] ?? ''), 8254 absint($last['error_status'] ?? 0), 8255 !empty($last['locked']) 8256 ); 8257 if ('waiting_for_lock' === $worker['status'] && !wp_next_scheduled('aitmt_fallback_worker_event')) { 8258 wp_schedule_single_event(time() + 10, 'aitmt_fallback_worker_event'); 8259 } elseif ('provider_cooldown' === $worker['status'] && !wp_next_scheduled('aitmt_fallback_worker_event')) { 8260 wp_schedule_single_event(time() + max(60, aitmt_fallback_provider_cooldown_remaining() + 1), 'aitmt_fallback_worker_event'); 8261 } 8262 } elseif ($remaining < 1) { 8263 $worker['status'] = 'completed'; 8264 $worker['completed_at'] = current_time('mysql'); 8265 } else { 8266 if (!wp_next_scheduled('aitmt_fallback_worker_event')) { 8267 wp_schedule_single_event(time() + 10, 'aitmt_fallback_worker_event'); 8268 } 8269 } 8270 update_option('aitmt_worker_state', $worker, false); 8271 } 7778 8272 } 7779 8273 … … 8354 8848 } 8355 8849 8356 $source_buckets[$source_type][aitmt_fallback_translation_hash($text)] = array( 8850 $unit_key = aitmt_fallback_translation_hash($text) . '|' . AITMT_Scanner::context_key($source_type, $object_id, $field_key); 8851 $source_buckets[$source_type][$unit_key] = array( 8357 8852 'text' => $text, 8358 8853 'object_id' => absint($object_id), … … 8363 8858 function aitmt_fallback_add_scan_source_html(&$source_buckets, $source_type, $html, $object_id = 0, $field_key = '') 8364 8859 { 8365 foreach (aitmt_fallback_extract_strings_from_html($html) as $text) { 8366 aitmt_fallback_add_scan_source_text($source_buckets, $source_type, $text, $object_id, $field_key); 8367 } 8368 } 8369 8370 function aitmt_fallback_scan_post_elementor_meta($post_id) 8371 { 8372 $strings = array(); 8373 8374 foreach (array('_elementor_data', '_elementor_page_settings') as $meta_key) { 8375 foreach ((array) get_post_meta($post_id, $meta_key, false) as $raw_value) { 8376 if (!is_scalar($raw_value)) { 8377 continue; 8378 } 8379 8380 $decoded = json_decode((string) $raw_value, true); 8381 if (is_array($decoded)) { 8382 aitmt_fallback_collect_elementor_scan_strings($strings, $decoded); 8383 } 8384 } 8385 } 8386 8387 return array_values($strings); 8388 } 8860 foreach (array_values(aitmt_fallback_extract_strings_from_html($html)) as $index => $text) { 8861 aitmt_fallback_add_scan_source_text($source_buckets, $source_type, $text, $object_id, $field_key . '.text:' . $index); 8862 } 8863 } 8864 8389 8865 function aitmt_fallback_scan_site_handler() 8390 8866 { … … 8416 8892 } 8417 8893 8418 $source_buckets = array(); 8419 aitmt_fallback_add_scan_source_text($source_buckets, 'site', get_bloginfo('name'), 0, 'blogname'); 8420 aitmt_fallback_add_scan_source_text($source_buckets, 'site', get_bloginfo('description'), 0, 'description'); 8421 8422 $post_types = get_post_types(array('public' => true), 'names'); 8423 unset($post_types['attachment']); 8424 8425 $posts = get_posts(array( 8426 'post_type' => array_values($post_types), 8427 'post_status' => array('publish'), 8428 'posts_per_page' => -1, 8429 'fields' => 'ids', 8430 'no_found_rows' => true, 8431 'update_post_meta_cache' => false, 8432 'update_post_term_cache' => false, 8433 )); 8434 8435 foreach ((array) $posts as $post_id) { 8436 $source_type = sanitize_key((string) get_post_type($post_id)); 8437 $source_type = '' === $source_type ? 'post' : $source_type; 8438 aitmt_fallback_add_scan_source_text($source_buckets, $source_type, get_the_title($post_id), $post_id, 'post_title'); 8439 aitmt_fallback_add_scan_source_text($source_buckets, $source_type, get_post_field('post_excerpt', $post_id), $post_id, 'post_excerpt'); 8440 aitmt_fallback_add_scan_source_html($source_buckets, $source_type, get_post_field('post_content', $post_id), $post_id, 'post_content'); 8441 8442 foreach (aitmt_fallback_scan_post_meta($post_id, true) as $text) { 8443 aitmt_fallback_add_scan_source_text($source_buckets, $source_type . '_meta', $text, $post_id, 'post_meta'); 8444 } 8445 8446 foreach (aitmt_fallback_scan_post_elementor_meta($post_id) as $text) { 8447 aitmt_fallback_add_scan_source_text($source_buckets, 'elementor', $text, $post_id, 'elementor'); 8448 } 8449 } 8450 8451 $taxonomies = get_taxonomies(array('public' => true), 'names'); 8452 foreach ((array) $taxonomies as $taxonomy) { 8453 $terms = get_terms(array('taxonomy' => $taxonomy, 'hide_empty' => false)); 8454 8455 if (is_wp_error($terms)) { 8456 continue; 8457 } 8458 8459 foreach ((array) $terms as $term) { 8460 aitmt_fallback_add_scan_source_text($source_buckets, 'taxonomy', $term->name, $term->term_id, 'term_name'); 8461 aitmt_fallback_add_scan_source_html($source_buckets, 'taxonomy', $term->description, $term->term_id, 'term_description'); 8462 } 8463 } 8464 8465 foreach ((array) wp_get_nav_menus() as $menu) { 8466 $items = wp_get_nav_menu_items($menu->term_id); 8467 8468 foreach ((array) $items as $item) { 8469 aitmt_fallback_add_scan_source_text($source_buckets, 'menu', $item->title, $item->ID, 'menu_title'); 8470 aitmt_fallback_add_scan_source_text($source_buckets, 'menu', $item->attr_title, $item->ID, 'menu_attr_title'); 8471 aitmt_fallback_add_scan_source_text($source_buckets, 'menu', $item->description, $item->ID, 'menu_description'); 8472 } 8473 } 8474 8475 foreach (aitmt_fallback_scan_widget_options() as $text) { 8476 aitmt_fallback_add_scan_source_text($source_buckets, 'widget', $text, 0, 'widget'); 8477 } 8894 $source_buckets = aitmt_fallback_collect_site_scan_sources(); 8478 8895 8479 8896 $unique_strings = array(); … … 8488 8905 } 8489 8906 8490 $unique_strings[ $hash] = $text;8907 $unique_strings[aitmt_fallback_translation_hash($text)] = $text; 8491 8908 foreach ($target_languages as $target_language) { 8492 8909 if (aitmt_fallback_enqueue_translation($text, $source_language, $target_language, $source_type, $object_id, $field_key)) { … … 8506 8923 } 8507 8924 8508 function aitmt_fallback_scan_post_meta($post_id, $exclude_elementor = false) 8509 { 8510 $allowed_private_keys = array( 8511 '_yoast_wpseo_title', 8512 '_yoast_wpseo_metadesc', 8513 '_aioseo_title', 8514 '_aioseo_description', 8515 'rank_math_title', 8516 'rank_math_description', 8517 '_elementor_data', 8518 '_elementor_page_settings', 8519 ); 8520 $meta = get_post_meta($post_id); 8521 $strings = array(); 8522 8523 foreach ((array) $meta as $key => $values) { 8524 if ($exclude_elementor && in_array($key, array('_elementor_data', '_elementor_page_settings'), true)) { 8525 continue; 8526 } 8527 8528 if (0 === strpos((string) $key, '_') && !in_array($key, $allowed_private_keys, true)) { 8529 continue; 8530 } 8531 8532 foreach ((array) $values as $value) { 8533 if (!is_scalar($value) || is_serialized($value)) { 8925 /** 8926 * Collect the current scanner manifest without changing the translation table. 8927 * 8928 * Maintenance uses the same collector as a normal scan so that a still-valid 8929 * Translation Memory entry is never classified as obsolete merely because two 8930 * different scanner implementations disagreed. 8931 * 8932 * @return array<string,array<string,array<string,mixed>>> 8933 */ 8934 function aitmt_fallback_collect_site_scan_sources() 8935 { 8936 return AITMT_Scanner::collect(aitmt_fallback_options()); 8937 } 8938 8939 /** 8940 * Return hashes and source types represented by the current scanner output. 8941 * 8942 * @return array{hashes:array<string,bool>,source_types:array<int,string>,strings:int} 8943 */ 8944 function aitmt_fallback_current_scanner_manifest() 8945 { 8946 $buckets = aitmt_fallback_collect_site_scan_sources(); 8947 $hashes = array(); 8948 $types = array(); 8949 8950 foreach ($buckets as $source_type => $items) { 8951 $source_type = aitmt_fallback_normalize_source_type($source_type); 8952 if ('' !== $source_type) { 8953 $types[$source_type] = true; 8954 } 8955 foreach ((array) $items as $hash => $item) { 8956 $text = is_array($item) ? (string) ($item['text'] ?? '') : (string) $item; 8957 if ('' === $text) { 8534 8958 continue; 8535 8959 } 8536 8537 $raw = (string) $value; 8538 $decoded = json_decode($raw, true); 8539 8540 if (is_array($decoded)) { 8541 if (in_array($key, array('_elementor_data', '_elementor_page_settings'), true)) { 8542 aitmt_fallback_collect_elementor_scan_strings($strings, $decoded); 8543 } 8544 continue; 8545 } else { 8546 foreach (aitmt_fallback_extract_strings_from_html($raw) as $text) { 8547 $strings[aitmt_fallback_translation_hash($text)] = $text; 8548 } 8549 } 8550 } 8551 } 8552 8553 return array_values($strings); 8960 $hashes[aitmt_fallback_translation_hash($text)] = true; 8961 } 8962 } 8963 8964 return array( 8965 'hashes' => $hashes, 8966 'source_types' => array_keys($types), 8967 'strings' => count($hashes), 8968 ); 8554 8969 } 8555 8970 … … 8575 8990 } 8576 8991 8577 function aitmt_fallback_collect_elementor_scan_strings(&$strings, $value, $depth = 0, $current_key = '')8578 {8579 if ($depth > 8 || null === $value || is_bool($value)) {8580 return;8581 }8582 8583 if (is_array($value) || is_object($value)) {8584 foreach ((array) $value as $key => $child) {8585 aitmt_fallback_collect_elementor_scan_strings($strings, $child, $depth + 1, (string) $key);8586 }8587 8588 return;8589 }8590 8591 if (!is_scalar($value)) {8592 return;8593 }8594 8595 $allowed_keys = array(8596 'title',8597 'text',8598 'editor',8599 'description',8600 'subtitle',8601 'heading',8602 'caption',8603 'alt',8604 'label',8605 'placeholder',8606 'button_text',8607 'link_text',8608 'tab_title',8609 'accordion_title',8610 'item_title',8611 'testimonial_content',8612 'html',8613 );8614 8615 if (!in_array($current_key, $allowed_keys, true)) {8616 return;8617 }8618 8619 foreach (aitmt_fallback_extract_strings_from_html((string) $value) as $text) {8620 $strings[aitmt_fallback_translation_hash($text)] = $text;8621 }8622 }8623 8992 function aitmt_fallback_collect_scalar_scan_strings(&$strings, $value, $depth = 0, $current_key = '') 8624 8993 { … … 8862 9231 8863 9232 $uploaded_tmp_name = isset($_FILES['translation_file']['tmp_name']) ? sanitize_text_field(wp_unslash((string) $_FILES['translation_file']['tmp_name'])) : ''; 8864 if ('' === $uploaded_tmp_name || !is_uploaded_file($uploaded_tmp_name)) { 8865 wp_die(esc_html__('No import file uploaded.', 'ait-multilingual-translate')); 9233 $uploaded_name = isset($_FILES['translation_file']['name']) ? sanitize_file_name(wp_unslash((string) $_FILES['translation_file']['name'])) : ''; 9234 $uploaded_size = isset($_FILES['translation_file']['size']) ? absint($_FILES['translation_file']['size']) : 0; 9235 $uploaded_error = isset($_FILES['translation_file']['error']) ? absint($_FILES['translation_file']['error']) : UPLOAD_ERR_NO_FILE; 9236 if (UPLOAD_ERR_OK !== $uploaded_error || '' === $uploaded_tmp_name || !is_uploaded_file($uploaded_tmp_name)) { 9237 wp_die(esc_html__('No valid import file was uploaded.', 'ait-multilingual-translate')); 9238 } 9239 if ($uploaded_size < 1 || $uploaded_size > 10 * MB_IN_BYTES) { 9240 wp_die(esc_html__('Import file must be smaller than 10 MB.', 'ait-multilingual-translate')); 8866 9241 } 8867 9242 … … 8869 9244 $format = isset($_POST['import_format']) ? sanitize_key(wp_unslash((string) $_POST['import_format'])) : 'csv'; 8870 9245 $format = in_array($format, array('csv', 'po', 'mo'), true) ? $format : 'csv'; 9246 $uploaded_extension = strtolower((string) pathinfo($uploaded_name, PATHINFO_EXTENSION)); 9247 if ($uploaded_extension !== $format) { 9248 wp_die(esc_html__('The uploaded file extension must match the selected import format.', 'ait-multilingual-translate')); 9249 } 8871 9250 8872 9251 if (!$language || !in_array($language, aitmt_fallback_enabled_languages(), true)) { … … 8939 9318 } 8940 9319 9320 function aitmt_fallback_plural_forms($target_language) 9321 { 9322 $base = AITMT_Locales::base($target_language); 9323 $forms = array( 9324 'ar' => 'nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);', 9325 'cs' => 'nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', 9326 'fr' => 'nplurals=2; plural=(n > 1);', 9327 'ja' => 'nplurals=1; plural=0;', 9328 'ka' => 'nplurals=1; plural=0;', 9329 'ko' => 'nplurals=1; plural=0;', 9330 'pl' => 'nplurals=3; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4 && (n%100<12 || n%100>14)) ? 1 : 2);', 9331 'ru' => 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);', 9332 'sk' => 'nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', 9333 'uk' => 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);', 9334 'zh' => 'nplurals=1; plural=0;', 9335 ); 9336 9337 return $forms[$base] ?? 'nplurals=2; plural=(n != 1);'; 9338 } 9339 8941 9340 function aitmt_fallback_build_po($entries, $target_language) 8942 9341 { … … 8946 9345 '"Project-Id-Version: AIT Multilingual Translate ' . AITMT_VERSION . '\n"', 8947 9346 '"Language: ' . $target_language . '\n"', 9347 '"MIME-Version: 1.0\n"', 8948 9348 '"Content-Type: text/plain; charset=UTF-8\n"', 8949 9349 '"Content-Transfer-Encoding: 8bit\n"', 9350 '"Plural-Forms: ' . aitmt_fallback_plural_forms($target_language) . '\n"', 9351 '"X-Generator: AIT Multilingual Translate ' . AITMT_VERSION . '\n"', 8950 9352 '', 8951 9353 ); … … 8971 9373 function aitmt_fallback_build_mo($entries, $target_language) 8972 9374 { 8973 $pairs = array('' => "Project-Id-Version: AIT Multilingual Translate " . AITMT_VERSION . "\nLanguage: " . $target_language . "\n Content-Type: text/plain; charset=UTF-8\n");9375 $pairs = array('' => "Project-Id-Version: AIT Multilingual Translate " . AITMT_VERSION . "\nLanguage: " . $target_language . "\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: " . aitmt_fallback_plural_forms($target_language) . "\nX-Generator: AIT Multilingual Translate " . AITMT_VERSION . "\n"); 8974 9376 8975 9377 foreach ($entries as $entry) { … … 9604 10006 $queue_result = get_option('aitmt_queue_result', array()); 9605 10007 $queue_result = is_array($queue_result) ? $queue_result : array(); 10008 $scanner_selected = AITMT_Scanner::selection($options); 10009 $scanner_catalog = AITMT_Scanner::catalog(); 9606 10010 9607 10011 aitmt_fallback_admin_page_start( … … 9629 10033 <span class="aitmt-step-number">2</span> 9630 10034 <h3><?php esc_html_e('Languages', 'ait-multilingual-translate'); ?></h3> 10035 <label> 10036 <span><?php esc_html_e('Plugin Interface Language', 'ait-multilingual-translate'); ?></span> 10037 <select name="aitmt_options[interface_language]"> 10038 <?php foreach (AITMT_Interface_Language::choices() as $locale => $label) : ?> 10039 <option value="<?php echo esc_attr($locale); ?>" <?php selected($options['interface_language'], $locale); ?>><?php echo esc_html($label); ?></option> 10040 <?php endforeach; ?> 10041 </select> 10042 </label> 10043 <p class="description"><?php esc_html_e('Changes only this plugin interface; website languages and translation targets stay unchanged.', 'ait-multilingual-translate'); ?></p> 9631 10044 <label> 9632 10045 <span><?php esc_html_e('Source language', 'ait-multilingual-translate'); ?></span> … … 9674 10087 <h3><?php esc_html_e('API Keys', 'ait-multilingual-translate'); ?></h3> 9675 10088 <?php aitmt_fallback_render_provider_fields($options, true); ?> 10089 </section> 10090 <section class="aitmt-onboarding-step aitmt-onboarding-step-wide"> 10091 <span class="aitmt-step-number">5</span> 10092 <h3><?php esc_html_e('Scanner Sources', 'ait-multilingual-translate'); ?></h3> 10093 <p class="description"><?php esc_html_e('Select only published, visitor-facing sources that should create translation jobs. Technical content is off by default.', 'ait-multilingual-translate'); ?></p> 10094 <div class="aitmt-onboarding-source-grid"> 10095 <?php foreach ($scanner_catalog as $scanner_item) : ?> 10096 <label class="aitmt-checkbox-row"> 10097 <input type="checkbox" name="aitmt_options[scanner_sources][<?php echo esc_attr($scanner_item['key']); ?>]" value="1" <?php checked('1', $scanner_selected[$scanner_item['key']] ?? '0'); ?>> 10098 <span> 10099 <strong><?php echo esc_html($scanner_item['label']); ?></strong> 10100 <small><?php echo esc_html(AITMT_Scanner::classification_label($scanner_item['classification']) . ' | ' . number_format_i18n($scanner_item['count'])); ?></small> 10101 </span> 10102 </label> 10103 <?php endforeach; ?> 10104 </div> 10105 </section> 10106 <section class="aitmt-onboarding-step aitmt-onboarding-step-wide"> 10107 <span class="aitmt-step-number">6</span> 10108 <h3><?php esc_html_e('Cost Safety', 'ait-multilingual-translate'); ?></h3> 10109 <p class="description"><?php esc_html_e('Plugin limits are checked before provider calls. Also configure hard budgets and quotas in the provider account.', 'ait-multilingual-translate'); ?></p> 10110 <div class="aitmt-onboarding-limit-grid"> 10111 <label><span><?php esc_html_e('Batch size', 'ait-multilingual-translate'); ?></span><input type="number" min="1" max="100" name="aitmt_options[max_segments_per_request]" value="<?php echo esc_attr((string) $options['max_segments_per_request']); ?>"></label> 10112 <label><span><?php esc_html_e('Daily character limit', 'ait-multilingual-translate'); ?></span><input type="number" min="0" name="aitmt_options[quota_daily_chars]" value="<?php echo esc_attr((string) $options['quota_daily_chars']); ?>"></label> 10113 <label><span><?php esc_html_e('Monthly character limit', 'ait-multilingual-translate'); ?></span><input type="number" min="0" name="aitmt_options[quota_monthly_chars]" value="<?php echo esc_attr((string) $options['quota_monthly_chars']); ?>"></label> 10114 <label><span><?php esc_html_e('Requests per minute', 'ait-multilingual-translate'); ?></span><input type="number" min="0" name="aitmt_options[quota_requests_per_minute]" value="<?php echo esc_attr((string) $options['quota_requests_per_minute']); ?>"></label> 10115 <label><span><?php esc_html_e('Requests per day', 'ait-multilingual-translate'); ?></span><input type="number" min="0" name="aitmt_options[quota_requests_per_day]" value="<?php echo esc_attr((string) $options['quota_requests_per_day']); ?>"></label> 10116 </div> 10117 <input type="hidden" name="aitmt_options[cron_enabled]" value="0"> 10118 <label class="aitmt-checkbox-row"> 10119 <input type="checkbox" name="aitmt_options[cron_enabled]" value="1" <?php checked($options['cron_enabled'], '1'); ?>> 10120 <span><?php esc_html_e('Enable background queue processing after setup', 'ait-multilingual-translate'); ?></span> 10121 </label> 9676 10122 </section> 9677 10123 <section class="aitmt-onboarding-step"> … … 10019 10465 $page_load_queued = absint($source_stats['page_load']['queued'] ?? 0); 10020 10466 $page_load_queued_characters = absint($source_stats['page_load']['queued_characters'] ?? 0); 10467 $worker_state = aitmt_fallback_worker_state(); 10468 $worker_initial = max(0, absint($worker_state['initial'] ?? 0)); 10469 $worker_processed = max(0, absint($worker_state['processed'] ?? 0)); 10470 $worker_handled = $worker_processed 10471 + max(0, absint($worker_state['cached'] ?? 0)) 10472 + max(0, absint($worker_state['skipped'] ?? 0)) 10473 + max(0, absint($worker_state['failed'] ?? 0)); 10474 $worker_progress = $worker_initial > 0 ? min(100, (int) round(($worker_handled / $worker_initial) * 100)) : 0; 10475 $next_worker_event = wp_next_scheduled('aitmt_fallback_worker_event'); 10476 $next_cron_event = wp_next_scheduled('aitmt_fallback_process_queue_event'); 10477 $next_queue_event = $next_worker_event && $next_cron_event 10478 ? min($next_worker_event, $next_cron_event) 10479 : ($next_worker_event ?: $next_cron_event); 10021 10480 10022 10481 aitmt_fallback_admin_page_start( … … 10027 10486 <div class="aitmt-wide-card"> 10028 10487 <h2><?php esc_html_e('Scanner and Queue', 'ait-multilingual-translate'); ?></h2> 10488 <?php AITMT_Scanner::render_ui($options); ?> 10029 10489 <p> 10030 10490 <?php esc_html_e('Auto scan on save:', 'ait-multilingual-translate'); ?> … … 10044 10504 </p> 10045 10505 <?php endif; ?> 10506 <details class="aitmt-debug-result" open> 10507 <summary><strong><?php esc_html_e('Background Translate All', 'ait-multilingual-translate'); ?></strong></summary> 10508 <p> 10509 <?php esc_html_e('Status:', 'ait-multilingual-translate'); ?> <code><?php echo esc_html((string) ($worker_state['status'] ?? 'stopped')); ?></code> 10510 <?php esc_html_e('Processed:', 'ait-multilingual-translate'); ?> <code><?php echo esc_html(number_format_i18n($worker_processed)); ?></code> 10511 <?php esc_html_e('From cache:', 'ait-multilingual-translate'); ?> <code><?php echo esc_html(number_format_i18n(absint($worker_state['cached'] ?? 0))); ?></code> 10512 <?php esc_html_e('Skipped:', 'ait-multilingual-translate'); ?> <code><?php echo esc_html(number_format_i18n(absint($worker_state['skipped'] ?? 0))); ?></code> 10513 <?php esc_html_e('Failed:', 'ait-multilingual-translate'); ?> <code><?php echo esc_html(number_format_i18n(absint($worker_state['failed'] ?? 0))); ?></code> 10514 <?php esc_html_e('Remaining:', 'ait-multilingual-translate'); ?> <code><?php echo esc_html(number_format_i18n(absint($worker_state['remaining'] ?? 0))); ?></code> 10515 <?php esc_html_e('Progress:', 'ait-multilingual-translate'); ?> <code><?php echo esc_html((string) $worker_progress); ?>%</code> 10516 </p> 10517 <progress value="<?php echo esc_attr((string) $worker_progress); ?>" max="100" style="width:100%;max-width:720px"></progress> 10518 <?php if (!empty($worker_state['last_message'])) : ?> 10519 <p><?php echo esc_html((string) $worker_state['last_message']); ?></p> 10520 <?php endif; ?> 10521 <p> 10522 <?php esc_html_e('Next queue event:', 'ait-multilingual-translate'); ?> 10523 <code><?php echo $next_queue_event ? esc_html(wp_date('Y-m-d H:i:s', $next_queue_event)) : esc_html__('not scheduled', 'ait-multilingual-translate'); ?></code> 10524 </p> 10525 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px"> 10526 <input type="hidden" name="action" value="aitmt_worker_control"> 10527 <?php wp_nonce_field('aitmt_worker_control'); ?> 10528 <button class="button button-primary" name="operation" value="run"><?php esc_html_e('Start / Restart', 'ait-multilingual-translate'); ?></button> 10529 <button class="button" name="operation" value="pause"><?php esc_html_e('Pause', 'ait-multilingual-translate'); ?></button> 10530 <button class="button" name="operation" value="resume"><?php esc_html_e('Resume', 'ait-multilingual-translate'); ?></button> 10531 <button class="button" name="operation" value="stop"><?php esc_html_e('Stop worker', 'ait-multilingual-translate'); ?></button> 10532 <button class="button" name="operation" value="reschedule"><?php esc_html_e('Rebuild schedule', 'ait-multilingual-translate'); ?></button> 10533 <?php if (empty($lock_status['active'])) : ?> 10534 <button class="button" name="operation" value="clear_lock"><?php esc_html_e('Clear stale lock', 'ait-multilingual-translate'); ?></button> 10535 <?php endif; ?> 10536 </form> 10537 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 10538 <input type="hidden" name="action" value="aitmt_save_background_settings"> 10539 <?php wp_nonce_field('aitmt_save_background_settings'); ?> 10540 <table class="form-table"><tbody> 10541 <tr><th><?php esc_html_e('Recurring background queue', 'ait-multilingual-translate'); ?></th><td><label><input type="checkbox" name="cron_enabled" value="1" <?php checked('1', $options['cron_enabled']); ?>> <?php esc_html_e('Enable recurring WP-Cron processing', 'ait-multilingual-translate'); ?></label></td></tr> 10542 <tr><th><label for="aitmt-cron-interval"><?php esc_html_e('Interval', 'ait-multilingual-translate'); ?></label></th><td><select id="aitmt-cron-interval" name="cron_interval"> 10543 <?php foreach (array(60 => __('Every 1 minute', 'ait-multilingual-translate'), 120 => __('Every 2 minutes', 'ait-multilingual-translate'), 300 => __('Every 5 minutes', 'ait-multilingual-translate'), 600 => __('Every 10 minutes', 'ait-multilingual-translate'), 900 => __('Every 15 minutes', 'ait-multilingual-translate'), 1800 => __('Every 30 minutes', 'ait-multilingual-translate'), 3600 => __('Hourly', 'ait-multilingual-translate')) as $seconds => $label) : ?> 10544 <option value="<?php echo esc_attr((string) $seconds); ?>" <?php selected(absint($options['cron_interval'] ?? 300), $seconds); ?>><?php echo esc_html($label); ?></option> 10545 <?php endforeach; ?> 10546 </select></td></tr> 10547 <tr><th><label for="aitmt-cron-batches"><?php esc_html_e('Batches per event', 'ait-multilingual-translate'); ?></label></th><td><input id="aitmt-cron-batches" type="number" min="1" max="10" name="cron_max_batches" value="<?php echo esc_attr((string) absint($options['cron_max_batches'] ?? 2)); ?>"></td></tr> 10548 <tr><th><label for="aitmt-cron-runtime"><?php esc_html_e('Runtime per event (seconds)', 'ait-multilingual-translate'); ?></label></th><td><input id="aitmt-cron-runtime" type="number" min="5" max="120" name="cron_max_runtime" value="<?php echo esc_attr((string) absint($options['cron_max_runtime'] ?? 20)); ?>"></td></tr> 10549 <tr><th><label for="aitmt-cron-batch-size"><?php esc_html_e('Rows per provider batch', 'ait-multilingual-translate'); ?></label></th><td><input id="aitmt-cron-batch-size" type="number" min="1" max="100" name="max_segments_per_request" value="<?php echo esc_attr((string) absint($options['max_segments_per_request'] ?? 5)); ?>"><p class="description"><?php esc_html_e('Use 3-5 for free/test quotas. Increase only after confirming provider limits.', 'ait-multilingual-translate'); ?></p></td></tr> 10550 </tbody></table> 10551 <?php submit_button(__('Save background settings', 'ait-multilingual-translate'), 'secondary', 'submit', false); ?> 10552 </form> 10553 </details> 10046 10554 <p class="description"> 10047 10555 <?php esc_html_e('Cost optimization:', 'ait-multilingual-translate'); ?> … … 10679 11187 nocache_headers(); 10680 11188 header('Content-Type: text/plain; charset=utf-8'); 11189 header('X-Content-Type-Options: nosniff'); 10681 11190 header('Content-Disposition: attachment; filename="ait-multilingual-translate-debug-' . gmdate('Ymd-His') . '.log"'); 10682 11191 $contents = aitmt_fallback_read_local_file($log_path); 10683 header('Content-Length: ' . (string) strlen($contents)); 10684 echo aitmt_fallback_redact_sensitive_text($contents); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain-text log download is sanitized/redacted above after nonce/capability checks. 11192 $download = aitmt_fallback_redact_sensitive_text($contents); 11193 header('Content-Length: ' . (string) strlen($download)); 11194 echo $download; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain-text log download is sanitized/redacted above after nonce/capability checks. 10685 11195 exit; 10686 11196 } … … 10732 11242 wp_die(esc_html__('Permission denied.', 'ait-multilingual-translate')); 10733 11243 } 10734 10735 11244 check_admin_referer('aitmt_translate_all_queue'); 10736 10737 11245 $confirmed = !empty($_POST['aitmt_confirm_translate_all']) && '1' === sanitize_text_field(wp_unslash((string) $_POST['aitmt_confirm_translate_all'])); 11246 $estimate = aitmt_fallback_queue_estimate(); 11247 10738 11248 if (!$confirmed) { 10739 update_option('aitmt_queue_result', array( 10740 'ok' => false, 10741 'message' => __('Translate All was not started because the cost confirmation checkbox was not selected.', 'ait-multilingual-translate'), 10742 'processed' => 0, 10743 'created_at' => current_time('mysql'), 10744 ), false); 10745 wp_safe_redirect(aitmt_fallback_admin_redirect_url_from_post('ait-multilingual-translate-scanner')); 10746 exit; 10747 } 10748 10749 $estimate = aitmt_fallback_queue_estimate(); 10750 if (!empty($estimate['unknown_queued']) || !empty($estimate['technical_queued'])) { 10751 update_option('aitmt_queue_result', array( 10752 'ok' => false, 10753 'message' => sprintf( 10754 'Translate All was blocked because the queue contains %1$d unknown and %2$d technical/non-translatable queued item(s). Clear these rows before sending text to the provider.', 10755 absint($estimate['unknown_queued'] ?? 0), 10756 absint($estimate['technical_queued'] ?? 0) 10757 ), 10758 'processed' => 0, 10759 'created_at' => current_time('mysql'), 10760 ), false); 10761 wp_safe_redirect(aitmt_fallback_admin_redirect_url_from_post('ait-multilingual-translate-scanner')); 10762 exit; 10763 } 10764 10765 $options = aitmt_fallback_options(); 10766 $limit = isset($options['max_segments_per_request']) ? absint($options['max_segments_per_request']) : 40; 10767 $limit = max(1, min(100, $limit)); 10768 $queued_before = aitmt_fallback_queued_count(); 10769 $max_batches = (int) apply_filters('aitmt_translate_all_max_batches', 10); 10770 $max_batches = max(1, min(25, $max_batches)); 10771 $deadline = time() + (int) apply_filters('aitmt_translate_all_time_budget', 25); 10772 $processed = 0; 10773 $batches = 0; 10774 $routes = array(); 10775 $lock = aitmt_fallback_acquire_queue_lock('translate_all'); 10776 if (is_wp_error($lock)) { 10777 update_option('aitmt_queue_result', array( 10778 'ok' => false, 10779 'message' => $lock->get_error_message(), 10780 'processed' => 0, 10781 'locked' => true, 10782 'created_at' => current_time('mysql'), 10783 ), false); 10784 wp_safe_redirect(aitmt_fallback_admin_redirect_url_from_post('ait-multilingual-translate-scanner')); 10785 exit; 10786 } 10787 10788 $last_result = array( 10789 'ok' => true, 10790 'message' => 'Queue is empty.', 11249 $message = __('Translate All was not started because the cost confirmation checkbox was not selected.', 'ait-multilingual-translate'); 11250 } elseif (!empty($estimate['unknown_queued']) || !empty($estimate['technical_queued'])) { 11251 $message = __('Translate All is blocked until unknown and technical queue rows are reviewed or cleared.', 'ait-multilingual-translate'); 11252 } else { 11253 $state = aitmt_fallback_start_worker(); 11254 $message = (string) $state['last_message']; 11255 } 11256 11257 update_option('aitmt_queue_result', array( 11258 'ok' => $confirmed && empty($estimate['unknown_queued']) && empty($estimate['technical_queued']), 11259 'message' => $message, 10791 11260 'processed' => 0, 10792 ); 10793 10794 try { 10795 while ($queued_before > 0 && $batches < $max_batches && time() < $deadline) { 10796 $result = aitmt_fallback_process_queue($limit, $lock); 10797 $last_result = is_array($result) ? $result : $last_result; 10798 $batches++; 10799 10800 if (!empty($last_result['routes'])) { 10801 $routes[] = (string) $last_result['routes']; 10802 } 10803 10804 $batch_processed = isset($last_result['processed']) ? absint($last_result['processed']) : 0; 10805 $processed += $batch_processed; 10806 10807 if (empty($last_result['ok']) || $batch_processed < 1 || aitmt_fallback_queued_count() < 1) { 10808 break; 10809 } 10810 } 10811 } finally { 10812 aitmt_fallback_release_queue_lock($lock); 10813 } 10814 10815 $remaining = aitmt_fallback_queued_count(); 10816 $ok = !empty($last_result['ok']); 10817 10818 if ($queued_before < 1) { 10819 $message = __('Queue is empty.', 'ait-multilingual-translate'); 10820 } elseif (!$ok) { 10821 $message = sprintf( 10822 /* translators: 1: processed count, 2: original queued count, 3: error message. */ 10823 __('Translate All stopped after processing %1$d of %2$d queued translation(s): %3$s', 'ait-multilingual-translate'), 10824 $processed, 10825 $queued_before, 10826 isset($last_result['message']) ? (string) $last_result['message'] : __('Provider error.', 'ait-multilingual-translate') 10827 ); 10828 } elseif ($remaining > 0 && $processed < 1 && !empty($last_result['message'])) { 10829 $message = (string) $last_result['message']; 10830 } elseif ($remaining > 0) { 10831 $message = sprintf( 10832 /* translators: 1: processed count, 2: original queued count, 3: remaining count, 4: batch count. */ 10833 __('Translate All processed %1$d of %2$d queued translation(s) in %4$d safe batch(es). %3$d item(s) remain. Run it again or enable background processing; provider quota or server time limits may stop long runs.', 'ait-multilingual-translate'), 10834 $processed, 10835 $queued_before, 10836 $remaining, 10837 $batches 10838 ); 10839 } else { 10840 $message = sprintf( 10841 /* translators: 1: processed count, 2: batch count. */ 10842 __('Translate All finished. Processed %1$d queued translation(s) in %2$d safe batch(es).', 'ait-multilingual-translate'), 10843 $processed, 10844 $batches 10845 ); 10846 } 10847 10848 $routes = array_values(array_unique(array_filter($routes))); 10849 $queue_result = array_merge($last_result, array( 10850 'ok' => $ok, 10851 'message' => $message, 10852 'processed' => $processed, 10853 'routes' => implode(', ', $routes), 10854 'queued_before' => $queued_before, 10855 'queued_remaining' => $remaining, 10856 'batches' => $batches, 10857 'translate_all' => true, 11261 'background' => true, 10858 11262 'created_at' => current_time('mysql'), 10859 )); 10860 10861 update_option('aitmt_queue_result', $queue_result, false); 10862 10863 if (!$ok && !empty($message)) { 10864 update_option('aitmt_last_error', current_time('mysql') . ' - ' . wp_strip_all_tags($message), false); 10865 } 10866 11263 ), false); 10867 11264 wp_safe_redirect(aitmt_fallback_admin_redirect_url_from_post('ait-multilingual-translate-scanner')); 10868 11265 exit; … … 11208 11605 $api_key_source = aitmt_fallback_provider_key_source($active_provider); 11209 11606 $cost_safety_issues = aitmt_fallback_cost_safety_issues($options); 11607 $dashboard_queue = aitmt_fallback_queue_estimate(); 11608 $dashboard_worker = aitmt_fallback_worker_state(); 11609 $dashboard_provider_stats = aitmt_fallback_provider_stats_for($active_provider); 11610 $dashboard_day_chars = aitmt_fallback_provider_chars_used($active_provider, gmdate('Ymd')); 11611 $dashboard_month_chars = aitmt_fallback_provider_chars_used($active_provider, gmdate('Ym')); 11612 $dashboard_next_worker = wp_next_scheduled('aitmt_fallback_worker_event'); 11613 $dashboard_next_cron = wp_next_scheduled('aitmt_fallback_process_queue_event'); 11614 $dashboard_next_event = $dashboard_next_worker && $dashboard_next_cron ? min($dashboard_next_worker, $dashboard_next_cron) : ($dashboard_next_worker ?: $dashboard_next_cron); 11615 $dashboard_last_translation = !empty($recent_translations[0]) && is_array($recent_translations[0]) ? $recent_translations[0] : array(); 11210 11616 ?> 11211 11617 <div class="wrap aitmt-admin-page aitmt-mode-<?php echo esc_attr($admin_mode); ?>"> … … 11237 11643 <?php aitmt_fallback_public_beta_notice(); ?> 11238 11644 11645 <section class="aitmt-dashboard-overview" aria-labelledby="aitmt-dashboard-overview-title"> 11646 <h2 id="aitmt-dashboard-overview-title"><?php esc_html_e('Dashboard Overview', 'ait-multilingual-translate'); ?></h2> 11647 <div class="aitmt-dashboard-grid"> 11648 <div class="aitmt-dashboard-metric"> 11649 <h3><?php esc_html_e('Provider', 'ait-multilingual-translate'); ?></h3> 11650 <strong><?php echo esc_html(aitmt_fallback_provider_label($active_provider)); ?></strong> 11651 <span><?php echo esc_html('' !== $api_key ? __('API key configured', 'ait-multilingual-translate') : __('API key missing', 'ait-multilingual-translate')); ?></span> 11652 </div> 11653 <div class="aitmt-dashboard-metric"> 11654 <h3><?php esc_html_e('Queue / Worker', 'ait-multilingual-translate'); ?></h3> 11655 <strong><?php echo esc_html(number_format_i18n(absint($dashboard_queue['queued'] ?? 0))); ?></strong> 11656 <span><?php echo esc_html((string) ($dashboard_worker['status'] ?? 'stopped')); ?></span> 11657 </div> 11658 <div class="aitmt-dashboard-metric"> 11659 <h3><?php esc_html_e('Usage Today', 'ait-multilingual-translate'); ?></h3> 11660 <strong><?php echo esc_html(number_format_i18n($dashboard_day_chars)); ?></strong> 11661 <?php /* translators: %s: configured daily character limit. */ ?> 11662 <span><?php echo esc_html(sprintf(__('Limit: %s characters', 'ait-multilingual-translate'), number_format_i18n(absint($options['quota_daily_chars'])))); ?></span> 11663 </div> 11664 <div class="aitmt-dashboard-metric"> 11665 <h3><?php esc_html_e('Usage This Month', 'ait-multilingual-translate'); ?></h3> 11666 <strong><?php echo esc_html(number_format_i18n($dashboard_month_chars)); ?></strong> 11667 <?php /* translators: %s: configured monthly character limit. */ ?> 11668 <span><?php echo esc_html(sprintf(__('Limit: %s characters', 'ait-multilingual-translate'), number_format_i18n(absint($options['quota_monthly_chars'])))); ?></span> 11669 </div> 11670 <div class="aitmt-dashboard-metric"> 11671 <h3><?php esc_html_e('Translation Memory', 'ait-multilingual-translate'); ?></h3> 11672 <strong><?php echo esc_html(number_format_i18n(absint($dashboard_provider_stats['cache_hits'] ?? 0))); ?></strong> 11673 <span><?php esc_html_e('API requests avoided by local reuse', 'ait-multilingual-translate'); ?></span> 11674 </div> 11675 <div class="aitmt-dashboard-metric"> 11676 <h3><?php esc_html_e('Last Translation', 'ait-multilingual-translate'); ?></h3> 11677 <strong><?php echo esc_html(!empty($dashboard_last_translation['target_language']) ? strtoupper((string) $dashboard_last_translation['target_language']) : __('None yet', 'ait-multilingual-translate')); ?></strong> 11678 <span><?php echo esc_html((string) ($dashboard_last_translation['updated_at'] ?? '')); ?></span> 11679 </div> 11680 <div class="aitmt-dashboard-metric"> 11681 <h3><?php esc_html_e('Next Background Event', 'ait-multilingual-translate'); ?></h3> 11682 <strong><?php echo esc_html($dashboard_next_event ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $dashboard_next_event) : __('Not scheduled', 'ait-multilingual-translate')); ?></strong> 11683 <span><?php echo esc_html('1' === $options['cron_enabled'] ? __('Cron enabled', 'ait-multilingual-translate') : __('Cron disabled', 'ait-multilingual-translate')); ?></span> 11684 </div> 11685 </div> 11686 </section> 11687 11239 11688 <?php if (!empty($cost_safety_issues)) : ?> 11240 11689 <div class="notice notice-warning inline aitmt-cost-warning"> … … 11295 11744 </fieldset> 11296 11745 <p class="description"><?php esc_html_e('Basic mode shows the setup needed for everyday translation. Advanced mode reveals queue diagnostics, provider internals, scanner details, routes, and logs.', 'ait-multilingual-translate'); ?></p> 11746 </td> 11747 </tr> 11748 <tr> 11749 <th scope="row"><label for="aitmt-interface-language"><?php esc_html_e('Plugin Interface Language', 'ait-multilingual-translate'); ?></label></th> 11750 <td> 11751 <select id="aitmt-interface-language" name="aitmt_options[interface_language]"> 11752 <?php foreach (AITMT_Interface_Language::choices() as $locale => $label) : ?> 11753 <option value="<?php echo esc_attr($locale); ?>" <?php selected($options['interface_language'], $locale); ?>><?php echo esc_html($label); ?></option> 11754 <?php endforeach; ?> 11755 </select> 11756 <p class="description"><?php esc_html_e('This changes only the AIT Multilingual Translate interface. It does not change the WordPress site language, website source language, default frontend language, or translation targets.', 'ait-multilingual-translate'); ?></p> 11297 11757 </td> 11298 11758 </tr> … … 11445 11905 <h3 class="aitmt-section-heading"><?php esc_html_e('AI Cost Optimization', 'ait-multilingual-translate'); ?></h3> 11446 11906 <p class="description"><?php esc_html_e('Use cheap models by default, deduplicate strings before provider calls, reuse translation memory, and keep safety limits enabled while testing provider quota.', 'ait-multilingual-translate'); ?></p> 11907 <div class="aitmt-safety-presets"> 11908 <p><strong><?php esc_html_e('Safety presets', 'ait-multilingual-translate'); ?></strong></p> 11909 <p class="description"><?php esc_html_e('Choose a profile only when you want it to replace the related local limits and worker settings. Provider-side billing limits remain required.', 'ait-multilingual-translate'); ?></p> 11910 <button type="submit" class="button button-primary" name="aitmt_apply_preset" value="safe_free"><?php esc_html_e('Apply Safe Free Mode', 'ait-multilingual-translate'); ?></button> 11911 <button type="submit" class="button" name="aitmt_apply_preset" value="recommended"><?php esc_html_e('Apply Recommended', 'ait-multilingual-translate'); ?></button> 11912 <button type="submit" class="button" name="aitmt_apply_preset" value="fast_bulk" onclick="return confirm('<?php echo esc_js(__('Fast Bulk can consume substantial paid API quota. Continue only after setting provider billing limits.', 'ait-multilingual-translate')); ?>');"><?php esc_html_e('Apply Fast Bulk Translation', 'ait-multilingual-translate'); ?></button> 11913 </div> 11447 11914 <?php aitmt_fallback_render_cost_estimator_panel($options); ?> 11448 11915 </th> … … 11495 11962 <input id="aitmt-quota-monthly" type="number" min="0" step="1000" name="aitmt_options[quota_monthly_chars]" value="<?php echo esc_attr((string) $options['quota_monthly_chars']); ?>"> 11496 11963 <p class="description"><?php esc_html_e('Local safety limit for the active provider. Provider billing limits still need to be configured in OpenAI/Google/DeepL/xAI accounts.', 'ait-multilingual-translate'); ?></p> 11964 <label for="aitmt-quota-rpm"><?php esc_html_e('Requests per minute', 'ait-multilingual-translate'); ?></label><br> 11965 <input id="aitmt-quota-rpm" type="number" min="0" step="1" name="aitmt_options[quota_requests_per_minute]" value="<?php echo esc_attr((string) $options['quota_requests_per_minute']); ?>"> 11966 <p class="description"><?php esc_html_e('Local request-rate ceiling checked before contacting the provider. Recommended: 5 for free/test plans.', 'ait-multilingual-translate'); ?></p> 11967 <label for="aitmt-quota-tpm"><?php esc_html_e('Estimated tokens per minute', 'ait-multilingual-translate'); ?></label><br> 11968 <input id="aitmt-quota-tpm" type="number" min="0" step="1000" name="aitmt_options[quota_tokens_per_minute]" value="<?php echo esc_attr((string) $options['quota_tokens_per_minute']); ?>"> 11969 <p class="description"><?php esc_html_e('Conservative local estimate based on source characters. Provider token accounting remains authoritative.', 'ait-multilingual-translate'); ?></p> 11970 <label for="aitmt-quota-rpd"><?php esc_html_e('Requests per day', 'ait-multilingual-translate'); ?></label><br> 11971 <input id="aitmt-quota-rpd" type="number" min="0" step="10" name="aitmt_options[quota_requests_per_day]" value="<?php echo esc_attr((string) $options['quota_requests_per_day']); ?>"> 11972 <p class="description"><?php esc_html_e('Optional local daily request ceiling. 0 disables only this local check.', 'ait-multilingual-translate'); ?></p> 11497 11973 <label for="aitmt-max-chars-per-request"><?php esc_html_e('Max characters per request', 'ait-multilingual-translate'); ?></label><br> 11498 11974 <input id="aitmt-max-chars-per-request" type="number" min="0" step="500" name="aitmt_options[max_chars_per_request]" value="<?php echo esc_attr((string) $options['max_chars_per_request']); ?>"> -
ait-multilingual-translate/trunk/assets/css/admin.css
r3665107 r3761123 890 890 } 891 891 } 892 893 .aitmt-scanner-preview-filters { 894 display: flex; 895 flex-wrap: wrap; 896 align-items: end; 897 gap: 12px; 898 margin: 12px 0; 899 } 900 901 .aitmt-scanner-preview-filters label { 902 display: grid; 903 gap: 4px; 904 font-weight: 600; 905 } 906 907 .aitmt-scanner-preview-filters select, 908 .aitmt-scanner-preview-filters input { 909 min-width: 180px; 910 } 911 912 .aitmt-scanner-preview-filters [data-aitmt-preview-count] { 913 padding-bottom: 7px; 914 } 915 916 .aitmt-onboarding-step-wide { 917 grid-column: span 2; 918 } 919 920 .aitmt-onboarding-source-grid, 921 .aitmt-onboarding-limit-grid { 922 display: grid; 923 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); 924 gap: 10px 16px; 925 } 926 927 .aitmt-onboarding-source-grid .aitmt-checkbox-row { 928 align-items: flex-start; 929 padding: 8px; 930 border: 1px solid #dcdcde; 931 } 932 933 .aitmt-onboarding-source-grid small { 934 display: block; 935 margin-top: 3px; 936 color: #646970; 937 } 938 939 .aitmt-onboarding-limit-grid label { 940 display: grid; 941 gap: 4px; 942 } 943 944 .aitmt-onboarding-limit-grid input { 945 width: 100%; 946 } 947 948 @media (max-width: 782px) { 949 .aitmt-onboarding-step-wide { 950 grid-column: auto; 951 } 952 } 953 954 .aitmt-dashboard-overview { 955 margin: 16px 0; 956 } 957 958 .aitmt-dashboard-grid { 959 display: grid; 960 grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); 961 gap: 10px; 962 } 963 964 .aitmt-dashboard-metric { 965 min-height: 104px; 966 padding: 14px; 967 border: 1px solid #dcdcde; 968 border-radius: 4px; 969 background: #fff; 970 } 971 972 .aitmt-dashboard-metric h3 { 973 margin: 0 0 8px; 974 font-size: 13px; 975 } 976 977 .aitmt-dashboard-metric strong, 978 .aitmt-dashboard-metric span { 979 display: block; 980 } 981 982 .aitmt-dashboard-metric strong { 983 margin-bottom: 5px; 984 font-size: 20px; 985 } 986 987 .aitmt-dashboard-metric span { 988 color: #646970; 989 } 990 .aitmt-preview-actions { min-width: 140px; } 991 .aitmt-preview-actions .button-link { display: block; margin-bottom: 6px; text-align: left; white-space: normal; } -
ait-multilingual-translate/trunk/assets/js/admin.js
r3665107 r3761123 351 351 } 352 352 353 function initScannerPreviewFilters() { 354 var form = document.querySelector('[data-aitmt-preview-management]'); 355 if (!form) { return; } 356 var rows = Array.prototype.slice.call(form.querySelectorAll('[data-aitmt-preview-row]')); 357 form.querySelector('[data-aitmt-select-visible]').addEventListener('click', function () { 358 rows.forEach(function (row) { 359 if (!row.hidden) { row.querySelector('input[type="checkbox"]').checked = true; } 360 }); 361 }); 362 form.querySelector('[data-aitmt-select-none]').addEventListener('click', function () { 363 rows.forEach(function (row) { row.querySelector('input[type="checkbox"]').checked = false; }); 364 }); 365 form.addEventListener('submit', function (event) { 366 var action = event.submitter ? event.submitter.value.split('|')[0] : ''; 367 if (action === 'bulk') { action = form.querySelector('[name="bulk_operation"]').value; } 368 if (action === 'remove' && !window.confirm(form.getAttribute('data-remove-confirm'))) { 369 event.preventDefault(); 370 } 371 }); 372 } 373 353 374 function initAdmin() { 354 375 initLanguageSearch(); … … 357 378 initAdminMode(); 358 379 initMenuSwitcherMetabox(); 380 initScannerPreviewFilters(); 359 381 } 360 382 -
ait-multilingual-translate/trunk/includes/class-aitmt-locales.php
r3665107 r3761123 14 14 */ 15 15 final class AITMT_Locales { 16 /** 17 * Resolve a stored language code to provider-safe metadata. 18 * 19 * @param string $locale Locale or language code. 20 * @param array<string,string> $registry Optional code-to-name registry. 21 * @return array<string,string> 22 */ 23 public static function resolve( $locale, $registry = array() ) { 24 $normalized = self::normalize( $locale ); 25 $base = self::base( $normalized ); 26 $variants = self::regional_variants(); 27 $names = array_change_key_case( (array) $registry, CASE_LOWER ); 28 $defaults = self::default_locales(); 29 $english = ''; 30 $native = ''; 31 $known = false; 32 33 if ( isset( $variants[ $normalized ] ) ) { 34 $english = $variants[ $normalized ]['english_name']; 35 $native = $variants[ $normalized ]['native_name']; 36 $known = true; 37 } elseif ( isset( $names[ strtolower( $normalized ) ] ) ) { 38 $english = (string) $names[ strtolower( $normalized ) ]; 39 $known = true; 40 } elseif ( isset( $names[ $base ] ) ) { 41 $english = (string) $names[ $base ]; 42 $known = true; 43 } 44 45 if ( '' === $english ) { 46 $english = '' !== $normalized ? strtoupper( $normalized ) : strtoupper( (string) $locale ); 47 } 48 49 $provider_locale = isset( $defaults[ $base ] ) && false === strpos( $normalized, '-' ) 50 ? $defaults[ $base ] 51 : $normalized; 52 53 return array( 54 'input' => (string) $locale, 55 'locale' => $normalized, 56 'code' => $base, 57 'base' => $base, 58 'bcp47' => $provider_locale, 59 'english_name' => $english, 60 'native_name' => '' !== $native ? $native : $english, 61 'api_locale' => $provider_locale, 62 'default' => $provider_locale, 63 'known' => $known ? '1' : '0', 64 ); 65 } 66 67 public static function translation_instruction( $source_locale, $target_locale, $registry = array() ) { 68 $source = self::resolve( $source_locale, $registry ); 69 $target = self::resolve( $target_locale, $registry ); 70 if ( '1' !== $target['known'] || '' === $target['code'] ) { 71 return ''; 72 } 73 return sprintf( 74 'Translate each segment from %1$s (%2$s) to %3$s (%4$s). Use natural modern %3$s. Do not transliterate. Do not use another language. Do not leave source-language text untranslated unless it is a brand name, URL, email, placeholder, technical identifier, or code.', 75 $source['english_name'], 76 $source['api_locale'], 77 $target['english_name'], 78 $target['api_locale'] 79 ); 80 } 81 82 public static function provider_code( $locale, $provider, $source = false ) { 83 $info = self::resolve( $locale ); 84 $provider = sanitize_key( (string) $provider ); 85 $base = $info['base']; 86 if ( in_array( $provider, array( 'google_translate', 'yandex_translate' ), true ) ) { 87 return $base; 88 } 89 if ( 'deepl' === $provider ) { 90 $code = strtoupper( str_replace( '_', '-', $info['api_locale'] ) ); 91 if ( $source ) { 92 return $base ? strtoupper( $base ) : ''; 93 } 94 if ( in_array( $code, array( 'EN-US', 'EN-GB', 'PT-BR', 'PT-PT', 'ZH-HANS', 'ZH-HANT' ), true ) ) { 95 return $code; 96 } 97 return $base ? strtoupper( $base ) : ''; 98 } 99 return $info['api_locale']; 100 } 101 102 /** 103 * Check whether a provider response uses the expected script. 104 * 105 * Short labels and brand names are allowed to remain in Latin script. The 106 * guard targets longer responses where a wrong provider locale would create 107 * visibly mixed Translation Memory entries. 108 * 109 * @param string $text Provider response. 110 * @param string $target_locale Requested target locale. 111 * @return bool 112 */ 113 public static function matches_target_script( $text, $target_locale ) { 114 $text = trim( wp_strip_all_tags( (string) $text ) ); 115 $base = self::base( $target_locale ); 116 $patterns = array( 117 'ka' => '/\p{Georgian}/u', 118 'ru' => '/\p{Cyrillic}/u', 119 'uk' => '/\p{Cyrillic}/u', 120 'ar' => '/\p{Arabic}/u', 121 'he' => '/\p{Hebrew}/u', 122 'zh' => '/\p{Han}/u', 123 'ja' => '/[\p{Hiragana}\p{Katakana}\p{Han}]/u', 124 'ko' => '/\p{Hangul}/u', 125 ); 126 if ( ! isset( $patterns[ $base ] ) ) { 127 return true; 128 } 129 130 if ( 1 === preg_match( $patterns[ $base ], $text ) ) { 131 return true; 132 } 133 134 $foreign_scripts = array( 135 'ka' => '/[\p{Cyrillic}\p{Thai}\p{Arabic}\p{Hebrew}\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}]/u', 136 'ru' => '/[\p{Georgian}\p{Thai}\p{Arabic}\p{Hebrew}\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}]/u', 137 'uk' => '/[\p{Georgian}\p{Thai}\p{Arabic}\p{Hebrew}\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}]/u', 138 'ar' => '/[\p{Georgian}\p{Cyrillic}\p{Thai}\p{Hebrew}\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}]/u', 139 'he' => '/[\p{Georgian}\p{Cyrillic}\p{Thai}\p{Arabic}\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}]/u', 140 'zh' => '/[\p{Georgian}\p{Cyrillic}\p{Thai}\p{Arabic}\p{Hebrew}\p{Hangul}]/u', 141 'ja' => '/[\p{Georgian}\p{Cyrillic}\p{Thai}\p{Arabic}\p{Hebrew}\p{Hangul}]/u', 142 'ko' => '/[\p{Georgian}\p{Cyrillic}\p{Thai}\p{Arabic}\p{Hebrew}\p{Han}\p{Hiragana}\p{Katakana}]/u', 143 ); 144 145 if ( isset( $foreign_scripts[ $base ] ) && 1 === preg_match( $foreign_scripts[ $base ], $text ) ) { 146 return false; 147 } 148 149 // Short Latin brand names and technical labels may remain unchanged. 150 return self::text_length( $text ) < 12; 151 } 152 153 /** 154 * UTF-8-safe length helper without a hard mbstring dependency. 155 * 156 * @param string $text Text. 157 * @return int 158 */ 159 private static function text_length( $text ) { 160 return function_exists( 'mb_strlen' ) ? mb_strlen( (string) $text, 'UTF-8' ) : strlen( (string) $text ); 161 } 162 /** 163 * Default provider locales for languages whose region matters. 164 * 165 * @return array<string,string> 166 */ 167 public static function default_locales() { 168 return array( 169 'ar' => 'ar-SA', 170 'de' => 'de-DE', 171 'en' => 'en-US', 172 'es' => 'es-ES', 173 'fr' => 'fr-FR', 174 'it' => 'it-IT', 175 'ja' => 'ja-JP', 176 'ka' => 'ka-GE', 177 'ko' => 'ko-KR', 178 'pt' => 'pt-PT', 179 'ru' => 'ru-RU', 180 'uk' => 'uk-UA', 181 'zh' => 'zh-CN', 182 'cs' => 'cs-CZ', 183 'et' => 'et-EE', 184 'he' => 'he-IL', 185 'lt' => 'lt-LT', 186 'lv' => 'lv-LV', 187 'pl' => 'pl-PL', 188 'sk' => 'sk-SK', 189 'tr' => 'tr-TR', 190 ); 191 } 16 192 /** 17 193 * Normalize a locale without discarding script or region subtags. -
ait-multilingual-translate/trunk/includes/class-aitmt-runtime-maintenance.php
r3665107 r3761123 17 17 { 18 18 const USAGE_SCHEMA_VERSION = 1; 19 const SETTINGS_MIGRATION_VERSION = 3;19 const SETTINGS_MIGRATION_VERSION = 4; 20 20 const CRON_HOOK = 'aitmt_runtime_cache_cleanup_event'; 21 21 … … 29 29 add_action('admin_menu', array(__CLASS__, 'register_admin_page'), 99); 30 30 add_action('admin_post_aitmt_database_maintenance', array(__CLASS__, 'handle_admin_action')); 31 add_action('admin_post_aitmt_rebuild_scanner_queue', array(__CLASS__, 'handle_rebuild_scanner_queue')); 31 32 add_action('admin_notices', array(__CLASS__, 'render_migration_notice')); 32 33 add_action(self::CRON_HOOK, array(__CLASS__, 'scheduled_cleanup')); … … 43 44 public static function maybe_install() 44 45 { 46 self::maybe_mark_scanner_maintenance(); 47 45 48 if ((int) get_option('aitmt_runtime_usage_schema_version', 0) < self::USAGE_SCHEMA_VERSION) { 46 49 self::create_usage_table(); … … 57 60 public static function activate() 58 61 { 62 self::maybe_mark_scanner_maintenance(); 59 63 self::create_usage_table(); 60 64 self::migrate_safe_settings(); 61 65 self::sync_cleanup_schedule(); 66 } 67 68 /** 69 * Record an upgrade notice without modifying translation rows. 70 * 71 * The 0.4.1 scanner can identify obsolete aggregated scanner records, but 72 * cleanup remains an explicit administrator action with a dry run. Fresh 73 * installs do not receive an upgrade notice. 74 */ 75 private static function maybe_mark_scanner_maintenance() 76 { 77 $current = defined('AITMT_VERSION') ? (string) AITMT_VERSION : ''; 78 if ('' === $current) { 79 return; 80 } 81 82 $recorded = (string) get_option('aitmt_runtime_release_version', ''); 83 $legacy = (string) get_option('aitmt_rewrite_version', ''); 84 $previous = '' !== $recorded ? $recorded : $legacy; 85 86 if ('' !== $previous && version_compare($previous, '0.4.1', '<') && version_compare($current, '0.4.1', '>=')) { 87 update_option('aitmt_scanner_maintenance_notice', '1', false); 88 } 89 90 update_option('aitmt_runtime_release_version', $current, false); 62 91 } 63 92 … … 163 192 $message = get_option('aitmt_migration_error', ''); 164 193 if ('' === (string) $message) { 194 if ('1' !== (string) get_option('aitmt_scanner_maintenance_notice', '0')) { 195 return; 196 } 197 ?> 198 <div class="notice notice-info"> 199 <p> 200 <?php esc_html_e('AIT Multilingual Translate preserved all existing translations during the update. A manual scanner maintenance tool is available to preview obsolete aggregated scanner records and rebuild only missing queue items.', 'ait-multilingual-translate'); ?> 201 <a href="<?php echo esc_url(admin_url('admin.php?page=ait-multilingual-translate-database')); ?>"><?php esc_html_e('Review scanner maintenance', 'ait-multilingual-translate'); ?></a> 202 </p> 203 </div> 204 <?php 165 205 return; 166 206 } … … 370 410 } 371 411 412 delete_option('aitmt_scanner_maintenance_notice'); 413 372 414 $result = get_transient('aitmt_maintenance_result_' . get_current_user_id()); 373 415 $summary = self::database_summary(); 374 416 $operations = self::operations(); 417 $obsolete_preview = self::obsolete_scanner_candidates(20); 375 418 ?> 376 419 <div class="wrap aitmt-admin-wrap"> … … 442 485 </form> 443 486 487 <h2><?php esc_html_e('Scanner-derived Translation Memory', 'ait-multilingual-translate'); ?></h2> 488 <p style="max-width:1100px"> 489 <?php esc_html_e('Use Obsolete scanner-derived translations in the maintenance selector to preview records that no longer match any current scanner source. Manual/imported translations and every still-valid Translation Memory entry are always preserved. Nothing is removed during a plugin update.', 'ait-multilingual-translate'); ?> 490 </p> 491 <p><strong><?php echo esc_html(sprintf( 492 /* translators: 1: rows, 2: characters. */ 493 __('Detected obsolete scanner candidates: %1$s row(s), %2$s character(s).', 'ait-multilingual-translate'), 494 number_format_i18n(absint($obsolete_preview['count'] ?? 0)), 495 number_format_i18n(absint($obsolete_preview['characters'] ?? 0)) 496 )); ?></strong></p> 497 <?php if (!empty($obsolete_preview['rows'])) : ?> 498 <div style="overflow:auto;max-width:1100px;max-height:360px"> 499 <table class="widefat striped"> 500 <thead><tr> 501 <th><?php esc_html_e('Source type', 'ait-multilingual-translate'); ?></th> 502 <th><?php esc_html_e('Object', 'ait-multilingual-translate'); ?></th> 503 <th><?php esc_html_e('Field', 'ait-multilingual-translate'); ?></th> 504 <th><?php esc_html_e('Original text preview', 'ait-multilingual-translate'); ?></th> 505 <th><?php esc_html_e('Status', 'ait-multilingual-translate'); ?></th> 506 </tr></thead> 507 <tbody> 508 <?php foreach ($obsolete_preview['rows'] as $obsolete_row) : ?> 509 <tr> 510 <td><code><?php echo esc_html((string) ($obsolete_row['source_type'] ?? '')); ?></code></td> 511 <td><?php echo esc_html(number_format_i18n(absint($obsolete_row['object_id'] ?? 0))); ?></td> 512 <td><code><?php echo esc_html((string) ($obsolete_row['field_key'] ?? '')); ?></code></td> 513 <td><?php echo esc_html(wp_html_excerpt((string) ($obsolete_row['source_text'] ?? ''), 180, '...')); ?></td> 514 <td><code><?php echo esc_html((string) ($obsolete_row['status'] ?? '')); ?></code></td> 515 </tr> 516 <?php endforeach; ?> 517 </tbody> 518 </table> 519 </div> 520 <?php endif; ?> 521 522 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="max-width:1100px"> 523 <input type="hidden" name="action" value="aitmt_rebuild_scanner_queue"> 524 <?php wp_nonce_field('aitmt_rebuild_scanner_queue'); ?> 525 <p><?php esc_html_e('Rebuild scans the current site and queues only missing translations. It does not delete or overwrite published, draft, manual, or imported translations.', 'ait-multilingual-translate'); ?></p> 526 <button class="button" type="submit"><?php esc_html_e('Rebuild scanner queue', 'ait-multilingual-translate'); ?></button> 527 </form> 528 444 529 <h2><?php esc_html_e('Runtime diagnostics (last 14 days)', 'ait-multilingual-translate'); ?></h2> 445 530 <pre style="max-width:1100px;overflow:auto;background:#fff;padding:12px;border:1px solid #ccd0d4"><?php echo esc_html(wp_json_encode(get_option('aitmt_runtime_diagnostics', array()), JSON_PRETTY_PRINT)); ?></pre> … … 459 544 private static function operations() 460 545 { 461 return array( 546 $operations = array( 547 'queued' => __('Clear all queued jobs (translations are preserved)', 'ait-multilingual-translate'), 548 'failed' => __('Clear all failed jobs (published/manual/import translations are preserved)', 'ait-multilingual-translate'), 462 549 'stale-page-load' => __('Delete stale page-load cache rows', 'ait-multilingual-translate'), 463 550 'all-runtime' => __('Clear generated runtime translation cache (page-load and legacy rows)', 'ait-multilingual-translate'), … … 466 553 'empty-source-type' => __('Delete old unclassified runtime candidates', 'ait-multilingual-translate'), 467 554 'orphaned' => __('Delete rows linked to missing posts', 'ait-multilingual-translate'), 555 'obsolete-scanner' => __('Delete obsolete scanner-derived translations', 'ait-multilingual-translate'), 468 556 'classify' => __('Classify high-confidence legacy rows', 'ait-multilingual-translate'), 469 557 'analyze' => __('Analyze translation table', 'ait-multilingual-translate'), 470 558 'optimize' => __('Optimize translation table (manual only)', 'ait-multilingual-translate'), 471 559 ); 560 foreach (self::scanner_source_types() as $source_type) { 561 $operations['queued-source-' . $source_type] = sprintf( 562 /* translators: %s: scanner source type. */ 563 __('Clear queued jobs from source: %s', 'ait-multilingual-translate'), 564 $source_type 565 ); 566 } 567 568 return $operations; 472 569 } 473 570 … … 518 615 519 616 /** 617 * Re-run the current scanner without deleting or overwriting translations. 618 */ 619 public static function handle_rebuild_scanner_queue() 620 { 621 if (!current_user_can('manage_options')) { 622 wp_die(esc_html__('Permission denied.', 'ait-multilingual-translate')); 623 } 624 625 check_admin_referer('aitmt_rebuild_scanner_queue'); 626 if (!function_exists('aitmt_fallback_scan_site')) { 627 $result = array('ok' => false, 'message' => __('The scanner is unavailable.', 'ait-multilingual-translate')); 628 } else { 629 $scan = aitmt_fallback_scan_site(); 630 $result = array( 631 'ok' => !empty($scan['ok']), 632 'message' => (string) ($scan['message'] ?? __('Scanner rebuild finished.', 'ait-multilingual-translate')), 633 ); 634 } 635 636 set_transient('aitmt_maintenance_result_' . get_current_user_id(), $result, 5 * MINUTE_IN_SECONDS); 637 wp_safe_redirect(admin_url('admin.php?page=ait-multilingual-translate-database')); 638 exit; 639 } 640 641 /** 520 642 * Execute a controlled maintenance operation. 521 643 */ … … 606 728 $cutoff = gmdate('Y-m-d H:i:s', time() - (max(1, absint($age_days)) * DAY_IN_SECONDS)); 607 729 730 if (0 === strpos($operation, 'queued-source-')) { 731 $source_type = sanitize_key(substr($operation, strlen('queued-source-'))); 732 if (in_array($source_type, self::scanner_source_types(), true)) { 733 return array("status = %s AND source_type = %s", array('queued', $source_type)); 734 } 735 return array('1 = 0', array()); 736 } 737 608 738 switch ($operation) { 739 case 'queued': 740 return array("status = %s", array('queued')); 741 case 'failed': 742 return array("status = %s", array('failed')); 609 743 case 'stale-page-load': 610 744 return array("source_type = %s AND updated_at < %s", array('page_load', $cutoff)); … … 645 779 return self::orphaned_stats($age_days); 646 780 } 781 if ('obsolete-scanner' === $operation) { 782 return self::obsolete_scanner_stats(); 783 } 647 784 648 785 list($where, $args) = self::operation_condition($operation, $age_days); … … 673 810 } elseif ('orphaned' === $operation) { 674 811 $rows = self::orphaned_rows($age_days, $batch_size); 812 } elseif ('obsolete-scanner' === $operation) { 813 $rows = self::obsolete_scanner_rows($batch_size); 675 814 } else { 676 815 list($where, $args) = self::operation_condition($operation, $age_days); … … 697 836 698 837 return (int) $wpdb->query($query); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- IDs are prepared and table is validated. 838 } 839 840 /** 841 * Return scanner source types, including auto-detected public post types. 842 * 843 * @return array<int,string> 844 */ 845 private static function scanner_source_types() 846 { 847 $types = array('site', 'post', 'page', 'product', 'elementor', 'elementor_library', 'sectionbuilder', 'litho-mega-menu', 'taxonomy', 'menu', 'widget', 'page_meta'); 848 foreach ((array) get_post_types(array('public' => true), 'names') as $post_type) { 849 $post_type = sanitize_key((string) $post_type); 850 if ('' === $post_type || 'attachment' === $post_type) { 851 continue; 852 } 853 $types[] = $post_type; 854 $types[] = $post_type . '_meta'; 855 } 856 if (function_exists('aitmt_fallback_current_scanner_manifest')) { 857 $manifest = aitmt_fallback_current_scanner_manifest(); 858 $types = array_merge($types, (array) ($manifest['source_types'] ?? array())); 859 } 860 861 return array_values(array_unique(array_filter(array_map('sanitize_key', $types)))); 862 } 863 864 /** 865 * Build the current source hash manifest without writing queue rows. 866 * 867 * @return array<string,bool> 868 */ 869 private static function current_scanner_hashes() 870 { 871 if (!function_exists('aitmt_fallback_current_scanner_manifest')) { 872 return array(); 873 } 874 $manifest = aitmt_fallback_current_scanner_manifest(); 875 876 return is_array($manifest['hashes'] ?? null) ? $manifest['hashes'] : array(); 877 } 878 879 /** 880 * Iterate scanner-derived rows that are no longer present in the manifest. 881 * 882 * Manual/imported rows are excluded in SQL and still-valid hashes are 883 * excluded in PHP. This method never modifies data. 884 * 885 * @param int $result_limit Maximum obsolete rows to return; zero counts all. 886 * @return array{rows:array<int,array<string,mixed>>,count:int,characters:int} 887 */ 888 private static function obsolete_scanner_candidates($result_limit = 0) 889 { 890 global $wpdb; 891 892 $table = self::translation_table_sql(); 893 $types = self::scanner_source_types(); 894 $current_hashes = self::current_scanner_hashes(); 895 $result = array('rows' => array(), 'count' => 0, 'characters' => 0); 896 if ('' === $table || empty($types)) { 897 return $result; 898 } 899 900 $type_placeholders = implode(',', array_fill(0, count($types), '%s')); 901 $last_id = 0; 902 $page_size = 1000; 903 904 do { 905 $args = array_merge($types, array('manual', 'import', 'manual', 'import', $last_id, $page_size)); 906 $sql = "SELECT id, source_hash, source_text, source_type, object_id, field_key, status, provider FROM {$table} 907 WHERE source_type IN ({$type_placeholders}) 908 AND status NOT IN (%s, %s) 909 AND provider NOT IN (%s, %s) 910 AND id > %d 911 ORDER BY id ASC LIMIT %d"; 912 $rows = $wpdb->get_results($wpdb->prepare($sql, ...$args), ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Placeholders and table are controlled; all values are prepared. 913 914 foreach ((array) $rows as $row) { 915 $last_id = max($last_id, absint($row['id'] ?? 0)); 916 if (!self::is_obsolete_scanner_row($row, $current_hashes, $types)) { 917 continue; 918 } 919 $result['count']++; 920 $result['characters'] += function_exists('mb_strlen') 921 ? mb_strlen((string) ($row['source_text'] ?? ''), 'UTF-8') 922 : strlen((string) ($row['source_text'] ?? '')); 923 if (0 === $result_limit || count($result['rows']) < $result_limit) { 924 $result['rows'][] = $row; 925 } 926 if ($result_limit > 0 && count($result['rows']) >= $result_limit) { 927 break 2; 928 } 929 } 930 } while (count($rows) === $page_size); 931 932 return $result; 933 } 934 935 /** 936 * Decide whether a scanner row is safe to offer for explicit cleanup. 937 * 938 * This guard is intentionally stricter than the SQL prefilter so the 939 * preservation rules remain testable and cannot be bypassed by a future 940 * query change. 941 * 942 * @param array $row Translation row. 943 * @param array<string,bool> $current_hashes Hashes in the current scan. 944 * @param array<int,string> $scanner_types Scanner-owned source types. 945 * @return bool 946 */ 947 public static function is_obsolete_scanner_row($row, $current_hashes, $scanner_types) 948 { 949 $row = is_array($row) ? $row : array(); 950 $current_hashes = is_array($current_hashes) ? $current_hashes : array(); 951 $scanner_types = is_array($scanner_types) ? $scanner_types : array(); 952 $source_hash = (string) ($row['source_hash'] ?? ''); 953 $source_type = sanitize_key((string) ($row['source_type'] ?? '')); 954 $status = sanitize_key((string) ($row['status'] ?? '')); 955 $provider = sanitize_key((string) ($row['provider'] ?? '')); 956 957 if ('' === $source_hash || !in_array($source_type, $scanner_types, true)) { 958 return false; 959 } 960 961 if (in_array($status, array('manual', 'import'), true) || in_array($provider, array('manual', 'import'), true)) { 962 return false; 963 } 964 965 return !isset($current_hashes[$source_hash]); 966 } 967 968 /** 969 * Preview obsolete scanner-derived rows. 970 * 971 * @return array{rows:int,characters:int} 972 */ 973 private static function obsolete_scanner_stats() 974 { 975 $candidates = self::obsolete_scanner_candidates(0); 976 977 return array( 978 'rows' => absint($candidates['count'] ?? 0), 979 'characters' => absint($candidates['characters'] ?? 0), 980 ); 981 } 982 983 /** 984 * Select one bounded batch of obsolete scanner-derived rows. 985 * 986 * @param int $batch_size Batch size. 987 * @return array<int,array<string,mixed>> 988 */ 989 private static function obsolete_scanner_rows($batch_size) 990 { 991 $candidates = self::obsolete_scanner_candidates(max(1, absint($batch_size))); 992 993 return (array) ($candidates['rows'] ?? array()); 699 994 } 700 995 -
ait-multilingual-translate/trunk/languages/ait-multilingual-translate.pot
r3665107 r3761123 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: AIT Multilingual Translate 0.4.0\n" 6 "Report-Msgid-Bugs-To: https://wp-ai.itdesign.biz/\n" 7 "POT-Creation-Date: 2026-05-20 13:30+04:00\n" 5 "Project-Id-Version: AIT Multilingual Translate 0.4.2\n" 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-ai-translate\n" 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 8 "Language-Team: LANGUAGE <LL@li.org>\n" 8 9 "MIME-Version: 1.0\n" 9 10 "Content-Type: text/plain; charset=UTF-8\n" 10 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026-09-03T08:26:18+00:00\n" 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.12.0\n" 11 15 "X-Domain: ait-multilingual-translate\n" 12 16 13 #: includes/class-wpait-frontend-editor.php 17 #. Plugin Name of the plugin 18 #: ait-multilingual-translate.php 19 #: ait-multilingual-translate.php:278 20 #: includes/class-aitmt-settings.php:88 21 #: includes/class-aitmt-settings.php:89 22 #: includes/class-aitmt-settings.php:200 23 msgid "AIT Multilingual Translate" 24 msgstr "" 25 26 #. Plugin URI of the plugin 27 #. Author URI of the plugin 28 #: ait-multilingual-translate.php 29 msgid "https://wp-ai.itdesign.biz" 30 msgstr "" 31 32 #. Description of the plugin 33 #: ait-multilingual-translate.php 34 msgid "AI-powered multilingual translation plugin compatible with WooCommerce and Elementor, with SEO-friendly URLs and frontend editing support." 35 msgstr "" 36 37 #. Author of the plugin 38 #: ait-multilingual-translate.php 39 msgid "sotter IT Design" 40 msgstr "" 41 42 #: ait-multilingual-translate.php:267 43 #: ait-multilingual-translate.php:268 44 #: ait-multilingual-translate.php:11621 45 msgid "AI Translate" 46 msgstr "" 47 48 #: ait-multilingual-translate.php:279 49 msgid "Dashboard" 50 msgstr "" 51 52 #: ait-multilingual-translate.php:287 53 #: ait-multilingual-translate.php:288 54 #: ait-multilingual-translate.php:11728 55 msgid "Translations" 56 msgstr "" 57 58 #: ait-multilingual-translate.php:296 59 #: ait-multilingual-translate.php:297 60 #: ait-multilingual-translate.php:11724 61 msgid "Scanner" 62 msgstr "" 63 64 #: ait-multilingual-translate.php:305 65 #: ait-multilingual-translate.php:306 66 #: includes/class-aitmt-scanner.php:1016 67 #: includes/class-aitmt-scanner.php:1079 68 msgid "Usage" 69 msgstr "" 70 71 #: ait-multilingual-translate.php:314 72 #: ait-multilingual-translate.php:315 73 #: ait-multilingual-translate.php:1492 74 #: ait-multilingual-translate.php:9834 75 #: ait-multilingual-translate.php:9973 76 #: ait-multilingual-translate.php:12148 77 msgid "Report Bug" 78 msgstr "" 79 80 #: ait-multilingual-translate.php:323 81 #: ait-multilingual-translate.php:324 82 msgid "Feedback" 83 msgstr "" 84 85 #: ait-multilingual-translate.php:332 86 #: ait-multilingual-translate.php:333 87 #: ait-multilingual-translate.php:10022 88 msgid "Setup Wizard" 89 msgstr "" 90 91 #: ait-multilingual-translate.php:341 92 #: ait-multilingual-translate.php:342 93 #: ait-multilingual-translate.php:11727 94 #: ait-multilingual-translate.php:12135 95 msgid "Support" 96 msgstr "" 97 98 #: ait-multilingual-translate.php:350 99 #: ait-multilingual-translate.php:351 100 #: ait-multilingual-translate.php:10944 101 #: ait-multilingual-translate.php:12259 102 msgid "Debugger" 103 msgstr "" 104 105 #: ait-multilingual-translate.php:440 106 msgid "Public Beta Build" 107 msgstr "" 108 109 #: ait-multilingual-translate.php:541 110 msgid "Provider Pricing Notice" 111 msgstr "" 112 113 #: ait-multilingual-translate.php:542 114 msgid "This provider may require a paid API account. Before translating your website, configure a free tier if available, daily quota, monthly budget, and billing alerts. AIT Multilingual Translate minimizes API usage with Translation Memory, cache reuse, duplicate detection, and small batches." 115 msgstr "" 116 117 #: ait-multilingual-translate.php:543 118 msgid "Continue" 119 msgstr "" 120 121 #: ait-multilingual-translate.php:544 122 #: ait-multilingual-translate.php:5447 123 msgid "Cancel" 124 msgstr "" 125 126 #: ait-multilingual-translate.php:545 127 #: ait-multilingual-translate.php:2434 128 msgid "Test connection" 129 msgstr "" 130 131 #: ait-multilingual-translate.php:546 132 msgid "Testing connection..." 133 msgstr "" 134 135 #: ait-multilingual-translate.php:547 136 msgid "Provider test failed." 137 msgstr "" 138 139 #: ait-multilingual-translate.php:592 140 #: ait-multilingual-translate.php:631 141 #: ait-multilingual-translate.php:651 142 #: ait-multilingual-translate.php:707 143 #: ait-multilingual-translate.php:1592 144 #: ait-multilingual-translate.php:1620 145 #: ait-multilingual-translate.php:1645 146 #: ait-multilingual-translate.php:1668 147 #: ait-multilingual-translate.php:8064 148 #: ait-multilingual-translate.php:8106 149 #: ait-multilingual-translate.php:8716 150 #: ait-multilingual-translate.php:8742 151 #: ait-multilingual-translate.php:8868 152 #: ait-multilingual-translate.php:9073 153 #: ait-multilingual-translate.php:9109 154 #: ait-multilingual-translate.php:9174 155 #: ait-multilingual-translate.php:9227 156 #: ait-multilingual-translate.php:11108 157 #: ait-multilingual-translate.php:11129 158 #: ait-multilingual-translate.php:11153 159 #: ait-multilingual-translate.php:11173 160 #: ait-multilingual-translate.php:11201 161 #: ait-multilingual-translate.php:11222 162 #: ait-multilingual-translate.php:11242 163 #: ait-multilingual-translate.php:11271 164 #: ait-multilingual-translate.php:11322 165 #: ait-multilingual-translate.php:11358 166 #: ait-multilingual-translate.php:11398 167 #: includes/class-aitmt-frontend-editor.php:54 168 #: includes/class-aitmt-frontend-editor.php:79 169 #: includes/class-aitmt-runtime-maintenance.php:600 170 #: includes/class-aitmt-runtime-maintenance.php:622 171 #: includes/class-aitmt-scanner.php:578 172 #: includes/class-aitmt-scanner.php:686 173 #: includes/class-aitmt-scanner.php:937 174 msgid "Permission denied." 175 msgstr "" 176 177 #: ait-multilingual-translate.php:656 178 #: ait-multilingual-translate.php:713 179 msgid "Invalid provider." 180 msgstr "" 181 182 #: ait-multilingual-translate.php:750 183 msgid "The provider returned no translation." 184 msgstr "" 185 186 #: ait-multilingual-translate.php:755 187 msgid "Connection test succeeded. The settings were tested without being saved." 188 msgstr "" 189 190 #: ait-multilingual-translate.php:1342 191 #: includes/class-aitmt-plugin.php:44 192 msgid "Settings" 193 msgstr "" 194 195 #: ait-multilingual-translate.php:1430 196 msgid "Support Development" 197 msgstr "" 198 199 #: ait-multilingual-translate.php:1473 200 #: ait-multilingual-translate.php:9956 201 #: ait-multilingual-translate.php:9959 202 #: ait-multilingual-translate.php:12138 203 msgid "Support AIT Multilingual Translate" 204 msgstr "" 205 206 #: ait-multilingual-translate.php:1474 207 #: ait-multilingual-translate.php:9960 208 #: ait-multilingual-translate.php:12139 209 msgid "AIT Multilingual Translate is currently in Public Beta and includes temporary full feature access while the platform is actively tested and improved." 210 msgstr "" 211 212 #: ait-multilingual-translate.php:1475 213 #: ait-multilingual-translate.php:9961 214 #: ait-multilingual-translate.php:12140 215 msgid "Users who support the project with a donation during the Public Beta period may receive a significant discount or special early-supporter offer for the future commercial release of AIT Multilingual Translate." 216 msgstr "" 217 218 #: ait-multilingual-translate.php:1476 219 #: ait-multilingual-translate.php:9962 220 #: ait-multilingual-translate.php:12141 221 msgid "Your support helps improve the plugin, optimize AI providers, expand language support, and accelerate development." 222 msgstr "" 223 224 #: ait-multilingual-translate.php:1490 225 msgid "AIT Multilingual Translate is currently in Public Beta. Please make a backup before bulk translating production websites." 226 msgstr "" 227 228 #: ait-multilingual-translate.php:1493 229 #: ait-multilingual-translate.php:9974 230 #: ait-multilingual-translate.php:12147 231 msgid "Send Feedback" 232 msgstr "" 233 234 #: ait-multilingual-translate.php:2262 235 #: ait-multilingual-translate.php:2268 236 #: ait-multilingual-translate.php:2274 237 #: ait-multilingual-translate.php:2280 238 #: ait-multilingual-translate.php:2285 239 #: ait-multilingual-translate.php:2298 240 #: ait-multilingual-translate.php:2304 241 #: ait-multilingual-translate.php:2310 242 #: ait-multilingual-translate.php:2317 243 #: ait-multilingual-translate.php:2323 244 #: ait-multilingual-translate.php:2329 245 #: ait-multilingual-translate.php:2344 246 msgid "API key" 247 msgstr "" 248 249 #: ait-multilingual-translate.php:2263 250 #: ait-multilingual-translate.php:2269 251 #: ait-multilingual-translate.php:2275 252 #: ait-multilingual-translate.php:2299 253 #: ait-multilingual-translate.php:2312 254 #: ait-multilingual-translate.php:2318 255 #: ait-multilingual-translate.php:2324 256 #: ait-multilingual-translate.php:2339 257 #: ait-multilingual-translate.php:2345 258 msgid "Model" 259 msgstr "" 260 261 #: ait-multilingual-translate.php:2288 262 msgid "Endpoint plan" 263 msgstr "" 264 265 #: ait-multilingual-translate.php:2290 266 msgid "DeepL API Free" 267 msgstr "" 268 269 #: ait-multilingual-translate.php:2291 270 msgid "DeepL API Pro" 271 msgstr "" 272 273 #: ait-multilingual-translate.php:2305 274 #: ait-multilingual-translate.php:2311 275 msgid "Folder ID" 276 msgstr "" 277 278 #: ait-multilingual-translate.php:2332 279 msgid "API region" 280 msgstr "" 281 282 #: ait-multilingual-translate.php:2334 283 msgid "United States" 284 msgstr "" 285 286 #: ait-multilingual-translate.php:2335 287 msgid "Singapore / international" 288 msgstr "" 289 290 #: ait-multilingual-translate.php:2336 291 msgid "China" 292 msgstr "" 293 294 #: ait-multilingual-translate.php:2408 295 msgid "Configured - enter a new value to replace it" 296 msgstr "" 297 298 #: ait-multilingual-translate.php:2408 299 msgid "Not configured" 300 msgstr "" 301 302 #: ait-multilingual-translate.php:2410 303 msgid "Clear saved key" 304 msgstr "" 305 306 #: ait-multilingual-translate.php:2427 307 msgid "Test target language" 308 msgstr "" 309 310 #: ait-multilingual-translate.php:2439 311 msgid "Keys are never printed back into the page. Leave a key field empty to keep the saved value, or use Clear saved key to remove it." 312 msgstr "" 313 314 #: ait-multilingual-translate.php:2449 315 #: ait-multilingual-translate.php:2501 316 #: ait-multilingual-translate.php:2535 317 #: ait-multilingual-translate.php:2551 318 #: ait-multilingual-translate.php:2567 319 #: ait-multilingual-translate.php:2583 320 #: ait-multilingual-translate.php:2599 321 #: ait-multilingual-translate.php:2615 322 #: ait-multilingual-translate.php:2631 323 #: ait-multilingual-translate.php:2655 324 msgid "Usage Based" 325 msgstr "" 326 327 #: ait-multilingual-translate.php:2450 328 msgid "OpenAI API usage is billed by your OpenAI account. Start with a small budget and the cheapest recommended translation model." 329 msgstr "" 330 331 #: ait-multilingual-translate.php:2452 332 msgid "Monthly budget: $5 while testing" 333 msgstr "" 334 335 #: ait-multilingual-translate.php:2453 336 msgid "Soft alert: $3, hard limit: $5 where available" 337 msgstr "" 338 339 #: ait-multilingual-translate.php:2454 340 msgid "Use gpt-4o-mini with temperature 0.1" 341 msgstr "" 342 343 #: ait-multilingual-translate.php:2455 344 msgid "Keep Translation Memory, duplicate detection, and queue mode enabled" 345 msgstr "" 346 347 #: ait-multilingual-translate.php:2456 348 msgid "Use small batches of 3-5 strings until quota is confirmed" 349 msgstr "" 350 351 #: ait-multilingual-translate.php:2459 352 #: ait-multilingual-translate.php:2476 353 #: ait-multilingual-translate.php:2493 354 #: ait-multilingual-translate.php:2510 355 #: ait-multilingual-translate.php:2527 356 #: ait-multilingual-translate.php:2543 357 #: ait-multilingual-translate.php:2559 358 #: ait-multilingual-translate.php:2575 359 #: ait-multilingual-translate.php:2591 360 #: ait-multilingual-translate.php:2607 361 #: ait-multilingual-translate.php:2623 362 #: ait-multilingual-translate.php:2639 363 #: ait-multilingual-translate.php:9975 364 msgid "Documentation" 365 msgstr "" 366 367 #: ait-multilingual-translate.php:2460 368 #: ait-multilingual-translate.php:2477 369 #: ait-multilingual-translate.php:2494 370 #: ait-multilingual-translate.php:2511 371 #: ait-multilingual-translate.php:2528 372 #: ait-multilingual-translate.php:2544 373 #: ait-multilingual-translate.php:2560 374 #: ait-multilingual-translate.php:2576 375 #: ait-multilingual-translate.php:2592 376 #: ait-multilingual-translate.php:2608 377 #: ait-multilingual-translate.php:2624 378 #: ait-multilingual-translate.php:2640 379 msgid "Pricing" 380 msgstr "" 381 382 #: ait-multilingual-translate.php:2461 383 #: ait-multilingual-translate.php:2478 384 #: ait-multilingual-translate.php:2495 385 #: ait-multilingual-translate.php:2512 386 #: ait-multilingual-translate.php:2529 387 #: ait-multilingual-translate.php:2545 388 #: ait-multilingual-translate.php:2561 389 #: ait-multilingual-translate.php:2577 390 #: ait-multilingual-translate.php:2593 391 #: ait-multilingual-translate.php:2609 392 #: ait-multilingual-translate.php:2625 393 #: ait-multilingual-translate.php:2641 394 msgid "Quota settings" 395 msgstr "" 396 397 #: ait-multilingual-translate.php:2462 398 #: ait-multilingual-translate.php:2479 399 #: ait-multilingual-translate.php:2496 400 #: ait-multilingual-translate.php:2513 401 #: ait-multilingual-translate.php:2530 402 #: ait-multilingual-translate.php:2546 403 #: ait-multilingual-translate.php:2562 404 #: ait-multilingual-translate.php:2578 405 #: ait-multilingual-translate.php:2594 406 #: ait-multilingual-translate.php:2610 407 #: ait-multilingual-translate.php:2626 408 #: ait-multilingual-translate.php:2642 409 msgid "Budget settings" 410 msgstr "" 411 412 #: ait-multilingual-translate.php:2467 413 #: ait-multilingual-translate.php:2518 414 msgid "Free Tier Available" 415 msgstr "" 416 417 #: ait-multilingual-translate.php:2468 418 msgid "Gemini may offer a free tier, but rate limits can be low and paid billing may apply depending on your Google AI/GCP configuration." 419 msgstr "" 420 421 #: ait-multilingual-translate.php:2470 422 msgid "Use the free tier where available" 423 msgstr "" 424 425 #: ait-multilingual-translate.php:2471 426 msgid "Use a Flash model for testing" 427 msgstr "" 428 429 #: ait-multilingual-translate.php:2472 430 msgid "Enable quota limits and billing alerts in Google tools" 431 msgstr "" 432 433 #: ait-multilingual-translate.php:2473 434 msgid "Keep batches small and process the queue manually first" 435 msgstr "" 436 437 #: ait-multilingual-translate.php:2484 438 msgid "Paid API" 439 msgstr "" 440 441 #: ait-multilingual-translate.php:2485 442 msgid "xAI API access usually requires an active paid account or credits. Use the lowest-cost model available to your account." 443 msgstr "" 444 445 #: ait-multilingual-translate.php:2487 446 msgid "Confirm account credits before testing" 447 msgstr "" 448 449 #: ait-multilingual-translate.php:2488 450 msgid "Use the lowest-cost mini/lightweight model where available" 451 msgstr "" 452 453 #: ait-multilingual-translate.php:2489 454 msgid "Set usage alerts in xAI Console if available" 455 msgstr "" 456 457 #: ait-multilingual-translate.php:2490 458 msgid "Run one small manual queue batch before Translate All" 459 msgstr "" 460 461 #: ait-multilingual-translate.php:2502 462 msgid "Google Cloud Translation API is billed by characters. Set Google Cloud quotas and billing alerts before bulk translation." 463 msgstr "" 464 465 #: ait-multilingual-translate.php:2504 466 msgid "Enable Cloud Translation API only for the intended project" 467 msgstr "" 468 469 #: ait-multilingual-translate.php:2505 470 msgid "Set daily quota and request limits" 471 msgstr "" 472 473 #: ait-multilingual-translate.php:2506 474 msgid "Create billing alerts and budgets in Google Cloud Billing" 475 msgstr "" 476 477 #: ait-multilingual-translate.php:2507 478 msgid "Keep plugin monthly and per-request character limits enabled" 479 msgstr "" 480 481 #: ait-multilingual-translate.php:2519 482 msgid "DeepL API can be used with a free endpoint when your key supports it. Monitor monthly character usage in your DeepL account." 483 msgstr "" 484 485 #: ait-multilingual-translate.php:2521 486 msgid "Use DeepL Free endpoint when possible" 487 msgstr "" 488 489 #: ait-multilingual-translate.php:2522 490 msgid "Monitor monthly character usage in DeepL account" 491 msgstr "" 492 493 #: ait-multilingual-translate.php:2523 494 msgid "Use plugin character limits as an extra safety stop" 495 msgstr "" 496 497 #: ait-multilingual-translate.php:2524 498 msgid "Run small manual batches before translating the full queue" 499 msgstr "" 500 501 #: ait-multilingual-translate.php:2536 502 msgid "Anthropic API usage is billed by your Anthropic account. Start with a Haiku model and a small workspace spending limit." 503 msgstr "" 504 505 #: ait-multilingual-translate.php:2538 506 msgid "Use the configured Haiku model for initial tests" 507 msgstr "" 508 509 #: ait-multilingual-translate.php:2539 510 msgid "Set a workspace spending limit before bulk translation" 511 msgstr "" 512 513 #: ait-multilingual-translate.php:2540 514 msgid "Keep batches small until provider limits are confirmed" 515 msgstr "" 516 517 #: ait-multilingual-translate.php:2552 518 msgid "Yandex Cloud Translate usage depends on your Yandex Cloud billing account and quotas." 519 msgstr "" 520 521 #: ait-multilingual-translate.php:2554 522 msgid "Use a dedicated service account and folder" 523 msgstr "" 524 525 #: ait-multilingual-translate.php:2555 526 msgid "Set provider-side quotas before processing a large queue" 527 msgstr "" 528 529 #: ait-multilingual-translate.php:2556 530 msgid "Test with one small manual batch first" 531 msgstr "" 532 533 #: ait-multilingual-translate.php:2568 534 msgid "YandexGPT usage depends on your Yandex Cloud Foundation Models billing account and quotas." 535 msgstr "" 536 537 #: ait-multilingual-translate.php:2570 538 msgid "Use the lite model while testing" 539 msgstr "" 540 541 #: ait-multilingual-translate.php:2571 542 msgid "Set provider-side quotas and billing notifications" 543 msgstr "" 544 545 #: ait-multilingual-translate.php:2572 546 msgid "Use Translation Memory and small queue batches" 547 msgstr "" 548 549 #: ait-multilingual-translate.php:2584 550 msgid "Mistral API usage is billed according to the model and account plan selected in La Plateforme." 551 msgstr "" 552 553 #: ait-multilingual-translate.php:2586 554 msgid "Use mistral-small-latest for initial translation tests" 555 msgstr "" 556 557 #: ait-multilingual-translate.php:2587 558 msgid "Configure provider-side usage controls before bulk translation" 559 msgstr "" 560 561 #: ait-multilingual-translate.php:2588 562 msgid "Keep queue batches small until limits are confirmed" 563 msgstr "" 564 565 #: ait-multilingual-translate.php:2600 566 msgid "DeepSeek API usage is charged to your DeepSeek platform balance according to the selected model." 567 msgstr "" 568 569 #: ait-multilingual-translate.php:2602 570 msgid "Use the Flash model for initial tests" 571 msgstr "" 572 573 #: ait-multilingual-translate.php:2603 574 msgid "Add only a small prepaid balance while testing" 575 msgstr "" 576 577 #: ait-multilingual-translate.php:2604 578 msgid "Process a small manual queue batch before Translate All" 579 msgstr "" 580 581 #: ait-multilingual-translate.php:2616 582 msgid "Alibaba Cloud Model Studio usage and availability depend on the selected region, model, and account plan." 583 msgstr "" 584 585 #: ait-multilingual-translate.php:2618 586 msgid "Choose the correct Model Studio region before creating a key" 587 msgstr "" 588 589 #: ait-multilingual-translate.php:2619 590 msgid "Use the Flash model for initial tests where available" 591 msgstr "" 592 593 #: ait-multilingual-translate.php:2620 594 msgid "Configure billing alerts in Alibaba Cloud before bulk translation" 595 msgstr "" 596 597 #: ait-multilingual-translate.php:2632 598 msgid "Baidu AI Cloud Qianfan usage depends on the selected ERNIE model and your cloud account billing configuration." 599 msgstr "" 600 601 #: ait-multilingual-translate.php:2634 602 msgid "Confirm Qianfan access and regional availability first" 603 msgstr "" 604 605 #: ait-multilingual-translate.php:2635 606 msgid "Configure cloud account quotas or alerts before bulk translation" 607 msgstr "" 608 609 #: ait-multilingual-translate.php:2636 610 msgid "Run a single provider test before queue processing" 611 msgstr "" 612 613 #: ait-multilingual-translate.php:2656 614 msgid "Provider pricing depends on your provider account plan." 615 msgstr "" 616 617 #: ait-multilingual-translate.php:2668 618 msgid "No plugin-side daily character limit is configured." 619 msgstr "" 620 621 #: ait-multilingual-translate.php:2672 622 msgid "No plugin-side monthly character limit is configured." 623 msgstr "" 624 625 #: ait-multilingual-translate.php:2676 626 msgid "No plugin-side per-request character limit is configured." 627 msgstr "" 628 629 #: ait-multilingual-translate.php:2680 630 msgid "Batch size is higher than the safest free-tier recommendation of 3-5 strings." 631 msgstr "" 632 633 #: ait-multilingual-translate.php:2684 634 msgid "Background queue processing is enabled. Use it only after confirming provider quotas." 635 msgstr "" 636 637 #: ait-multilingual-translate.php:2688 638 msgid "Page-load translation is enabled. This can create API usage while visitors browse." 639 msgstr "" 640 641 #: ait-multilingual-translate.php:2692 642 msgid "Auto scan on save is enabled. Disable it during imports or large WooCommerce edits." 643 msgstr "" 644 645 #: ait-multilingual-translate.php:2704 646 #: ait-multilingual-translate.php:2716 647 #: ait-multilingual-translate.php:2761 648 msgid "Unknown (depends on your provider plan)" 649 msgstr "" 650 651 #. translators: %s: Provider name. 652 #: ait-multilingual-translate.php:2726 653 #, php-format 654 msgid "How to configure %s safely" 655 msgstr "" 656 657 #. translators: %s: Number of characters. 658 #: ait-multilingual-translate.php:2758 659 #: ait-multilingual-translate.php:2760 660 #, php-format 661 msgid "%s character" 662 msgid_plural "%s characters" 663 msgstr[0] "" 664 msgstr[1] "" 665 666 #. translators: %s: Number of characters. 667 #: ait-multilingual-translate.php:2758 668 #: ait-multilingual-translate.php:2760 669 msgid "Unlimited in plugin settings" 670 msgstr "" 671 672 #: ait-multilingual-translate.php:2764 673 msgid "Safe cost estimator" 674 msgstr "" 675 676 #: ait-multilingual-translate.php:2765 677 msgid "These are plugin-side safety estimates only. Provider billing dashboards and quotas remain authoritative." 678 msgstr "" 679 680 #: ait-multilingual-translate.php:2767 681 #: ait-multilingual-translate.php:10076 682 #: ait-multilingual-translate.php:10888 683 #: ait-multilingual-translate.php:11649 684 #: ait-multilingual-translate.php:11829 685 #: includes/class-aitmt-settings.php:255 686 msgid "Provider" 687 msgstr "" 688 689 #: ait-multilingual-translate.php:2768 690 msgid "Characters per request" 691 msgstr "" 692 693 #: ait-multilingual-translate.php:2769 694 msgid "Estimated tokens" 695 msgstr "" 696 697 #: ait-multilingual-translate.php:2770 698 msgid "Estimated monthly requests" 699 msgstr "" 700 701 #: ait-multilingual-translate.php:2770 702 #: includes/class-aitmt-scanner.php:1130 703 msgid "Unknown" 704 msgstr "" 705 706 #: ait-multilingual-translate.php:2771 707 msgid "Estimated daily usage" 708 msgstr "" 709 710 #: ait-multilingual-translate.php:2772 711 msgid "Estimated monthly usage" 712 msgstr "" 713 714 #: ait-multilingual-translate.php:2773 715 msgid "Estimated monthly cost" 716 msgstr "" 717 718 #: ait-multilingual-translate.php:2774 719 #: ait-multilingual-translate.php:10111 720 #: ait-multilingual-translate.php:12039 721 #: includes/class-aitmt-runtime-maintenance.php:469 722 #: includes/class-aitmt-settings.php:301 723 msgid "Batch size" 724 msgstr "" 725 726 #: ait-multilingual-translate.php:2899 727 msgid "Cheap" 728 msgstr "" 729 730 #: ait-multilingual-translate.php:2900 731 msgid "Balanced" 732 msgstr "" 733 734 #: ait-multilingual-translate.php:2901 735 msgid "Premium" 736 msgstr "" 737 738 #: ait-multilingual-translate.php:2982 739 msgid "Neutral / Accurate" 740 msgstr "" 741 742 #: ait-multilingual-translate.php:2986 743 msgid "SEO Optimized" 744 msgstr "" 745 746 #: ait-multilingual-translate.php:2990 747 msgid "Marketing" 748 msgstr "" 749 750 #: ait-multilingual-translate.php:2994 751 msgid "eCommerce" 752 msgstr "" 753 754 #: ait-multilingual-translate.php:2998 755 msgid "Formal" 756 msgstr "" 757 758 #: ait-multilingual-translate.php:3002 759 msgid "Casual" 760 msgstr "" 761 762 #: ait-multilingual-translate.php:3006 763 #: includes/class-aitmt-scanner.php:1127 764 msgid "Technical" 765 msgstr "" 766 767 #: ait-multilingual-translate.php:3010 768 msgid "Legal" 769 msgstr "" 770 771 #: ait-multilingual-translate.php:3014 772 msgid "Luxury Brand" 773 msgstr "" 774 775 #: ait-multilingual-translate.php:3018 776 msgid "Friendly" 777 msgstr "" 778 779 #: ait-multilingual-translate.php:3022 780 msgid "Custom Prompt" 781 msgstr "" 782 783 #: ait-multilingual-translate.php:3061 784 msgid "The target language is not recognized. Select a supported language before translating." 785 msgstr "" 786 787 #. translators: %s: requested target locale. 788 #: ait-multilingual-translate.php:3105 789 #, php-format 790 msgid "The provider response does not appear to use the requested target language (%s). The translation was not saved." 791 msgstr "" 792 793 #. translators: %s: rejected CSS selectors. 794 #: ait-multilingual-translate.php:3536 795 #, php-format 796 msgid "These exclusion selectors are invalid and were not saved: %s" 797 msgstr "" 798 799 #: ait-multilingual-translate.php:3987 800 #: includes/class-aitmt-switcher.php:132 801 msgid "Language switcher" 802 msgstr "" 803 804 #: ait-multilingual-translate.php:4011 805 #: includes/class-aitmt-switcher.php:103 806 msgid "Select language" 807 msgstr "" 808 809 #: ait-multilingual-translate.php:4052 810 #: includes/class-aitmt-widget.php:13 811 msgid "AIT Multilingual Translate Switcher" 812 msgstr "" 813 814 #: ait-multilingual-translate.php:4053 815 msgid "Display the AIT Multilingual Translate language switcher." 816 msgstr "" 817 818 #: ait-multilingual-translate.php:4079 819 #: ait-multilingual-translate.php:12081 820 #: includes/class-aitmt-settings.php:346 821 msgid "Style" 822 msgstr "" 823 824 #: ait-multilingual-translate.php:4081 825 #: ait-multilingual-translate.php:4153 826 #: ait-multilingual-translate.php:12084 827 #: includes/class-aitmt-settings.php:349 828 msgid "Dropdown" 829 msgstr "" 830 831 #: ait-multilingual-translate.php:4082 832 #: ait-multilingual-translate.php:4152 833 #: ait-multilingual-translate.php:12085 834 #: includes/class-aitmt-settings.php:350 835 msgid "List" 836 msgstr "" 837 838 #: ait-multilingual-translate.php:4120 839 msgid "AI Translate Language Switcher" 840 msgstr "" 841 842 #: ait-multilingual-translate.php:4142 843 #: ait-multilingual-translate.php:4466 844 #: ait-multilingual-translate.php:12074 845 #: includes/class-aitmt-settings.php:342 846 msgid "Language Switcher" 847 msgstr "" 848 849 #: ait-multilingual-translate.php:4148 850 msgid "Layout type" 851 msgstr "" 852 853 #: ait-multilingual-translate.php:4154 854 msgid "Buttons" 855 msgstr "" 856 857 #: ait-multilingual-translate.php:4155 858 msgid "Flags only" 859 msgstr "" 860 861 #: ait-multilingual-translate.php:4156 862 msgid "Flags + language name" 863 msgstr "" 864 865 #: ait-multilingual-translate.php:4157 866 msgid "Language name only" 867 msgstr "" 868 869 #: ait-multilingual-translate.php:4165 870 msgid "Show flags" 871 msgstr "" 872 873 #: ait-multilingual-translate.php:4167 874 #: ait-multilingual-translate.php:4179 875 #: ait-multilingual-translate.php:4191 876 #: ait-multilingual-translate.php:4203 877 #: ait-multilingual-translate.php:4215 878 #: ait-multilingual-translate.php:4227 879 msgid "Yes" 880 msgstr "" 881 882 #: ait-multilingual-translate.php:4168 883 #: ait-multilingual-translate.php:4180 884 #: ait-multilingual-translate.php:4192 885 #: ait-multilingual-translate.php:4204 886 #: ait-multilingual-translate.php:4216 887 #: ait-multilingual-translate.php:4228 888 msgid "No" 889 msgstr "" 890 891 #: ait-multilingual-translate.php:4177 892 #: ait-multilingual-translate.php:4483 893 msgid "Show language name" 894 msgstr "" 895 896 #: ait-multilingual-translate.php:4189 897 #: ait-multilingual-translate.php:4484 898 msgid "Show language code" 899 msgstr "" 900 901 #: ait-multilingual-translate.php:4201 902 msgid "Show current language" 903 msgstr "" 904 905 #: ait-multilingual-translate.php:4213 906 #: ait-multilingual-translate.php:4485 907 msgid "Hide current language" 908 msgstr "" 909 910 #: ait-multilingual-translate.php:4225 911 msgid "Open links in current page" 912 msgstr "" 913 914 #: ait-multilingual-translate.php:4237 915 msgid "Orientation" 916 msgstr "" 917 918 #: ait-multilingual-translate.php:4241 919 msgid "Horizontal" 920 msgstr "" 921 922 #: ait-multilingual-translate.php:4242 923 msgid "Vertical" 924 msgstr "" 925 926 #: ait-multilingual-translate.php:4250 927 msgid "Alignment" 928 msgstr "" 929 930 #: ait-multilingual-translate.php:4254 931 msgid "Left" 932 msgstr "" 933 934 #: ait-multilingual-translate.php:4255 935 msgid "Center" 936 msgstr "" 937 938 #: ait-multilingual-translate.php:4256 939 msgid "Right" 940 msgstr "" 941 942 #: ait-multilingual-translate.php:4269 943 msgid "Switcher Style" 944 msgstr "" 945 946 #: ait-multilingual-translate.php:4287 947 msgid "Text color" 948 msgstr "" 949 950 #: ait-multilingual-translate.php:4298 951 msgid "Hover color" 952 msgstr "" 953 954 #: ait-multilingual-translate.php:4309 955 msgid "Active language color" 956 msgstr "" 957 958 #: ait-multilingual-translate.php:4320 959 msgid "Background color" 960 msgstr "" 961 962 #: ait-multilingual-translate.php:4331 963 msgid "Border color" 964 msgstr "" 965 966 #: ait-multilingual-translate.php:4342 967 msgid "Dropdown background" 968 msgstr "" 969 970 #: ait-multilingual-translate.php:4353 971 msgid "Border radius" 972 msgstr "" 973 974 #: ait-multilingual-translate.php:4369 975 msgid "Padding" 976 msgstr "" 977 978 #: ait-multilingual-translate.php:4381 979 msgid "Gap between items" 980 msgstr "" 981 982 #: ait-multilingual-translate.php:4394 983 msgid "Dropdown width" 984 msgstr "" 985 986 #: ait-multilingual-translate.php:4413 987 msgid "Dropdown shadow" 988 msgstr "" 989 990 #: ait-multilingual-translate.php:4480 991 msgid "Add the language switcher to a WordPress menu." 992 msgstr "" 993 994 #: ait-multilingual-translate.php:4482 995 msgid "Show flag" 996 msgstr "" 997 998 #: ait-multilingual-translate.php:4487 999 msgid "Menu display mode" 1000 msgstr "" 1001 1002 #: ait-multilingual-translate.php:4488 1003 msgid "List mode" 1004 msgstr "" 1005 1006 #: ait-multilingual-translate.php:4489 1007 msgid "Dropdown mode" 1008 msgstr "" 1009 1010 #: ait-multilingual-translate.php:4494 1011 msgid "Current language switcher" 1012 msgstr "" 1013 1014 #: ait-multilingual-translate.php:4495 1015 msgid "All enabled languages" 1016 msgstr "" 1017 1018 #. translators: %s: Language name. 1019 #: ait-multilingual-translate.php:4500 1020 #, php-format 1021 msgid "Specific language: %s" 1022 msgstr "" 1023 1024 #: ait-multilingual-translate.php:4507 1025 msgid "Add to Menu" 1026 msgstr "" 1027 1028 #: ait-multilingual-translate.php:5420 1029 msgid "AI Translate Edit" 1030 msgstr "" 1031 1032 #: ait-multilingual-translate.php:5443 1033 #: includes/class-aitmt-frontend-editor.php:37 14 1034 msgid "AI Translate edit" 15 1035 msgstr "" 16 1036 17 #: includes/class-wpait-frontend-editor.php 1037 #: ait-multilingual-translate.php:5444 1038 msgid "Editing on" 1039 msgstr "" 1040 1041 #: ait-multilingual-translate.php:5445 1042 #: includes/class-aitmt-frontend-editor.php:38 18 1043 msgid "Edit translation" 19 1044 msgstr "" 20 1045 21 #: includes/class-wpait-frontend-editor.php 1046 #: ait-multilingual-translate.php:5446 1047 msgid "Save" 1048 msgstr "" 1049 1050 #: ait-multilingual-translate.php:5448 1051 #: includes/class-aitmt-frontend-editor.php:39 22 1052 msgid "Auto Translate" 23 1053 msgstr "" 24 1054 25 #: includes/class-wpait-frontend-editor.php 1055 #: ait-multilingual-translate.php:5449 1056 #: includes/class-aitmt-frontend-editor.php:40 26 1057 msgid "AI translating..." 27 1058 msgstr "" 28 1059 29 #: includes/class-wpait-frontend-editor.php 1060 #: ait-multilingual-translate.php:5450 1061 #: includes/class-aitmt-frontend-editor.php:41 30 1062 msgid "AI Translation Ready" 31 1063 msgstr "" 32 1064 33 #: includes/class-wpait-frontend-editor.php 1065 #: ait-multilingual-translate.php:5451 1066 #: ait-multilingual-translate.php:8791 1067 #: ait-multilingual-translate.php:8807 1068 #: ait-multilingual-translate.php:8821 1069 #: includes/class-aitmt-frontend-editor.php:42 1070 #: includes/class-aitmt-frontend-editor.php:111 1071 #: includes/class-aitmt-frontend-editor.php:126 1072 #: includes/class-aitmt-frontend-editor.php:140 34 1073 msgid "Translation failed. Please try again." 35 1074 msgstr "" 36 1075 37 #: includes/class-wpait-frontend-editor.php 1076 #: ait-multilingual-translate.php:5452 1077 #: includes/class-aitmt-frontend-editor.php:43 38 1078 msgid "Saving..." 39 1079 msgstr "" 40 1080 41 #: includes/class-wpait-frontend-editor.php 1081 #: ait-multilingual-translate.php:5453 1082 #: includes/class-aitmt-frontend-editor.php:44 42 1083 msgid "Saved" 43 1084 msgstr "" 44 1085 45 #: includes/class-wpait-frontend-editor.php 1086 #: ait-multilingual-translate.php:5454 1087 #: includes/class-aitmt-frontend-editor.php:45 46 1088 msgid "Could not save translation" 47 1089 msgstr "" 48 1090 49 #: includes/class-wpait-frontend-editor.php 50 msgid "Permission denied." 51 msgstr "" 52 53 #: includes/class-wpait-frontend-editor.php 1091 #: ait-multilingual-translate.php:5455 1092 msgid "No editable translation text found yet. Scan/process the queue, or click source text that is highlighted for admins." 1093 msgstr "" 1094 1095 #. translators: %s: Target language code. 1096 #: ait-multilingual-translate.php:5457 1097 #, php-format 1098 msgid "Target: %s" 1099 msgstr "" 1100 1101 #. translators: %s: translation provider name. 1102 #: ait-multilingual-translate.php:6269 1103 #, php-format 1104 msgid "%s returned an unexpected translation payload." 1105 msgstr "" 1106 1107 #. translators: %s: translation provider name. 1108 #: ait-multilingual-translate.php:6298 1109 #, php-format 1110 msgid "%s API key is missing." 1111 msgstr "" 1112 1113 #. translators: %s: translation provider name. 1114 #: ait-multilingual-translate.php:6337 1115 #, php-format 1116 msgid "%s request failed." 1117 msgstr "" 1118 1119 #: ait-multilingual-translate.php:6385 1120 msgid "Claude API key is missing." 1121 msgstr "" 1122 1123 #: ait-multilingual-translate.php:6419 1124 msgid "Claude request failed." 1125 msgstr "" 1126 1127 #: ait-multilingual-translate.php:6437 1128 msgid "Yandex Translate API key and folder ID are required." 1129 msgstr "" 1130 1131 #: ait-multilingual-translate.php:6467 1132 msgid "Yandex Translate request failed." 1133 msgstr "" 1134 1135 #: ait-multilingual-translate.php:6485 1136 msgid "Yandex Translate returned an unexpected translation payload." 1137 msgstr "" 1138 1139 #: ait-multilingual-translate.php:6497 1140 msgid "YandexGPT API key and folder ID are required." 1141 msgstr "" 1142 1143 #: ait-multilingual-translate.php:6533 1144 msgid "YandexGPT request failed." 1145 msgstr "" 1146 1147 #: ait-multilingual-translate.php:7546 1148 msgid "Legacy/missing source" 1149 msgstr "" 1150 1151 #: ait-multilingual-translate.php:7550 1152 msgid "Unknown source" 1153 msgstr "" 1154 1155 #: ait-multilingual-translate.php:8037 1156 msgid "Background translation is blocked until unknown and technical queue rows are reviewed or cleared." 1157 msgstr "" 1158 1159 #: ait-multilingual-translate.php:8052 1160 msgid "Background translation worker started." 1161 msgstr "" 1162 1163 #: ait-multilingual-translate.php:8052 1164 #: ait-multilingual-translate.php:8218 1165 msgid "Queue is empty." 1166 msgstr "" 1167 1168 #: ait-multilingual-translate.php:8072 1169 msgid "Worker paused by an administrator." 1170 msgstr "" 1171 1172 #: ait-multilingual-translate.php:8075 1173 msgid "Worker resumed." 1174 msgstr "" 1175 1176 #: ait-multilingual-translate.php:8081 1177 msgid "Worker stopped by an administrator. Queued rows were preserved." 1178 msgstr "" 1179 1180 #: ait-multilingual-translate.php:8088 1181 msgid "Background schedule was rebuilt." 1182 msgstr "" 1183 1184 #: ait-multilingual-translate.php:8093 1185 msgid "Stale queue lock cleared." 1186 msgstr "" 1187 1188 #: ait-multilingual-translate.php:8095 1189 msgid "The queue lock is active and was not cleared." 1190 msgstr "" 1191 1192 #: ait-multilingual-translate.php:8130 1193 #: ait-multilingual-translate.php:10543 1194 msgid "Every 1 minute" 1195 msgstr "" 1196 1197 #: ait-multilingual-translate.php:8131 1198 #: ait-multilingual-translate.php:10543 1199 msgid "Every 2 minutes" 1200 msgstr "" 1201 1202 #: ait-multilingual-translate.php:8132 1203 #: ait-multilingual-translate.php:10543 1204 msgid "Every 5 minutes" 1205 msgstr "" 1206 1207 #: ait-multilingual-translate.php:8133 1208 #: ait-multilingual-translate.php:10543 1209 msgid "Every 10 minutes" 1210 msgstr "" 1211 1212 #: ait-multilingual-translate.php:8134 1213 #: ait-multilingual-translate.php:10543 1214 msgid "Every 15 minutes" 1215 msgstr "" 1216 1217 #: ait-multilingual-translate.php:8135 1218 #: ait-multilingual-translate.php:10543 1219 msgid "Every 30 minutes" 1220 msgstr "" 1221 1222 #: ait-multilingual-translate.php:8136 1223 #: ait-multilingual-translate.php:10543 1224 msgid "Hourly" 1225 msgstr "" 1226 1227 #: ait-multilingual-translate.php:8725 1228 #: ait-multilingual-translate.php:8750 1229 #: includes/class-aitmt-frontend-editor.php:63 1230 #: includes/class-aitmt-frontend-editor.php:87 54 1231 msgid "Missing translation data." 55 1232 msgstr "" 56 1233 57 #: includes/class-wpait-frontend-editor.php 1234 #: ait-multilingual-translate.php:8731 1235 #: includes/class-aitmt-frontend-editor.php:69 58 1236 msgid "Translation was not saved." 59 1237 msgstr "" 60 1238 61 #: includes/class-wpait-frontend-editor.php 1239 #: ait-multilingual-translate.php:8734 1240 #: includes/class-aitmt-frontend-editor.php:72 62 1241 msgid "Translation saved." 63 1242 msgstr "" 64 1243 65 #: includes/class-wpait-frontend-editor.php 1244 #: ait-multilingual-translate.php:8754 1245 #: includes/class-aitmt-frontend-editor.php:91 66 1246 msgid "Source and target languages are the same." 67 1247 msgstr "" 68 1248 69 #: includes/class-wpait-frontend-editor.php 1249 #: ait-multilingual-translate.php:8758 1250 msgid "This text looks technical or non-translatable and was not sent to the provider." 1251 msgstr "" 1252 1253 #: ait-multilingual-translate.php:8763 1254 #: ait-multilingual-translate.php:8814 1255 #: includes/class-aitmt-frontend-editor.php:95 1256 #: includes/class-aitmt-frontend-editor.php:133 70 1257 msgid "No translation provider configured." 71 1258 msgstr "" 72 1259 73 #: includes/class-wpait-frontend-editor.php 1260 #: ait-multilingual-translate.php:8773 1261 msgid "Translation memory hit" 1262 msgstr "" 1263 1264 #. translators: %s: Active translation provider label. 1265 #: ait-multilingual-translate.php:8798 1266 #, php-format 1267 msgid "Translated via %s" 1268 msgstr "" 1269 1270 #: ait-multilingual-translate.php:8818 1271 #: includes/class-aitmt-frontend-editor.php:137 1272 msgid "Provider rate limit reached." 1273 msgstr "" 1274 1275 #: ait-multilingual-translate.php:9184 1276 #: ait-multilingual-translate.php:9252 1277 msgid "Invalid target language." 1278 msgstr "" 1279 1280 #: ait-multilingual-translate.php:9237 1281 msgid "No valid import file was uploaded." 1282 msgstr "" 1283 1284 #: ait-multilingual-translate.php:9240 1285 msgid "Import file must be smaller than 10 MB." 1286 msgstr "" 1287 1288 #: ait-multilingual-translate.php:9248 1289 msgid "The uploaded file extension must match the selected import format." 1290 msgstr "" 1291 1292 #: ait-multilingual-translate.php:9810 1293 msgid "The message could not be sent. Please check WordPress email delivery on this site." 1294 msgstr "" 1295 1296 #: ait-multilingual-translate.php:9813 1297 msgid "Please confirm consent before sending diagnostic information." 1298 msgstr "" 1299 1300 #: ait-multilingual-translate.php:9826 1301 msgid "AI Translate - Report Bug" 1302 msgstr "" 1303 1304 #: ait-multilingual-translate.php:9827 1305 msgid "Send a safe technical report for Public Beta troubleshooting." 1306 msgstr "" 1307 1308 #: ait-multilingual-translate.php:9829 1309 msgid "Bug report sent. Thank you for helping test AIT Multilingual Translate." 1310 msgstr "" 1311 1312 #: ait-multilingual-translate.php:9835 1313 msgid "The technical report includes WordPress, WooCommerce, PHP, plugin, language, provider, queue, and route status. Sensitive values are redacted before sending." 1314 msgstr "" 1315 1316 #: ait-multilingual-translate.php:9841 1317 #: ait-multilingual-translate.php:9909 1318 msgid "Name" 1319 msgstr "" 1320 1321 #: ait-multilingual-translate.php:9845 1322 #: ait-multilingual-translate.php:9913 1323 msgid "Email" 1324 msgstr "" 1325 1326 #: ait-multilingual-translate.php:9849 1327 msgid "Website" 1328 msgstr "" 1329 1330 #: ait-multilingual-translate.php:9853 1331 msgid "Problem type" 1332 msgstr "" 1333 1334 #: ait-multilingual-translate.php:9855 1335 #: ait-multilingual-translate.php:9921 1336 msgid "Translation issue" 1337 msgstr "" 1338 1339 #: ait-multilingual-translate.php:9856 1340 #: ait-multilingual-translate.php:9922 1341 msgid "WooCommerce issue" 1342 msgstr "" 1343 1344 #: ait-multilingual-translate.php:9857 1345 #: ait-multilingual-translate.php:9923 1346 msgid "Elementor issue" 1347 msgstr "" 1348 1349 #: ait-multilingual-translate.php:9858 1350 msgid "Frontend editor issue" 1351 msgstr "" 1352 1353 #: ait-multilingual-translate.php:9859 1354 msgid "Provider / API issue" 1355 msgstr "" 1356 1357 #: ait-multilingual-translate.php:9860 1358 msgid "Install / update issue" 1359 msgstr "" 1360 1361 #: ait-multilingual-translate.php:9861 1362 #: ait-multilingual-translate.php:9924 1363 msgid "Other" 1364 msgstr "" 1365 1366 #: ait-multilingual-translate.php:9866 1367 msgid "Short description" 1368 msgstr "" 1369 1370 #: ait-multilingual-translate.php:9870 1371 msgid "Steps to reproduce" 1372 msgstr "" 1373 1374 #: ait-multilingual-translate.php:9871 1375 msgid "1. Open page...\\n2. Switch language...\\n3. Expected...\\n4. Actual..." 1376 msgstr "" 1377 1378 #: ait-multilingual-translate.php:9875 1379 msgid "Attach technical log" 1380 msgstr "" 1381 1382 #: ait-multilingual-translate.php:9879 1383 msgid "I agree to send this report and redacted technical diagnostics to info@itdesign.biz." 1384 msgstr "" 1385 1386 #: ait-multilingual-translate.php:9881 1387 msgid "Send bug report" 1388 msgstr "" 1389 1390 #: ait-multilingual-translate.php:9895 1391 msgid "AI Translate - Feedback" 1392 msgstr "" 1393 1394 #: ait-multilingual-translate.php:9896 1395 msgid "Share Public Beta feedback, feature requests, and translation notes." 1396 msgstr "" 1397 1398 #: ait-multilingual-translate.php:9898 1399 msgid "Feedback sent. Thank you for shaping AIT Multilingual Translate." 1400 msgstr "" 1401 1402 #: ait-multilingual-translate.php:9903 1403 msgid "Feedback / Feature Request" 1404 msgstr "" 1405 1406 #: ait-multilingual-translate.php:9917 1407 #: ait-multilingual-translate.php:11054 1408 msgid "Type" 1409 msgstr "" 1410 1411 #: ait-multilingual-translate.php:9919 1412 msgid "Bug" 1413 msgstr "" 1414 1415 #: ait-multilingual-translate.php:9920 1416 msgid "Feature request" 1417 msgstr "" 1418 1419 #: ait-multilingual-translate.php:9929 1420 #: ait-multilingual-translate.php:11055 1421 msgid "Message" 1422 msgstr "" 1423 1424 #: ait-multilingual-translate.php:9934 1425 msgid "I agree to send this feedback to info@itdesign.biz." 1426 msgstr "" 1427 1428 #: ait-multilingual-translate.php:9936 1429 msgid "Send feedback" 1430 msgstr "" 1431 1432 #: ait-multilingual-translate.php:9950 1433 msgid "AI Translate - Support" 1434 msgstr "" 1435 1436 #: ait-multilingual-translate.php:9951 1437 msgid "Support the free Public Beta and help prioritize the roadmap." 1438 msgstr "" 1439 1440 #: ait-multilingual-translate.php:9971 1441 msgid "Need help?" 1442 msgstr "" 1443 1444 #: ait-multilingual-translate.php:9976 1445 msgid "Plugin Website" 1446 msgstr "" 1447 1448 #: ait-multilingual-translate.php:9980 1449 msgid "Public Beta Notice" 1450 msgstr "" 1451 1452 #: ait-multilingual-translate.php:9981 1453 msgid "This Public Beta includes temporary unrestricted access for testing and feedback purposes. Commercial licensing and additional editions may be introduced in future releases." 1454 msgstr "" 1455 1456 #: ait-multilingual-translate.php:9984 1457 msgid "Support contact" 1458 msgstr "" 1459 1460 #: ait-multilingual-translate.php:10012 1461 msgid "AI Translate - Onboarding" 1462 msgstr "" 1463 1464 #: ait-multilingual-translate.php:10013 1465 msgid "Set up languages, provider keys, scanning, queue translation, and frontend editing." 1466 msgstr "" 1467 1468 #: ait-multilingual-translate.php:10017 1469 msgid "Onboarding settings saved." 1470 msgstr "" 1471 1472 #: ait-multilingual-translate.php:10029 1473 msgid "Welcome" 1474 msgstr "" 1475 1476 #: ait-multilingual-translate.php:10030 1477 msgid "AIT Multilingual Translate adds AI translation, WooCommerce support, frontend editing, SEO-friendly language URLs, and saved translation memory. This is a Public Beta, so make a backup before bulk translating production websites." 1478 msgstr "" 1479 1480 #: ait-multilingual-translate.php:10034 1481 #: includes/class-aitmt-settings.php:207 1482 msgid "Languages" 1483 msgstr "" 1484 1485 #: ait-multilingual-translate.php:10036 1486 #: ait-multilingual-translate.php:11749 1487 msgid "Plugin Interface Language" 1488 msgstr "" 1489 1490 #: ait-multilingual-translate.php:10043 1491 msgid "Changes only this plugin interface; website languages and translation targets stay unchanged." 1492 msgstr "" 1493 1494 #: ait-multilingual-translate.php:10045 1495 #: ait-multilingual-translate.php:11774 1496 #: includes/class-aitmt-settings.php:212 1497 msgid "Source language" 1498 msgstr "" 1499 1500 #: ait-multilingual-translate.php:10047 1501 #: ait-multilingual-translate.php:11778 1502 msgid "Auto: WordPress site language" 1503 msgstr "" 1504 1505 #: ait-multilingual-translate.php:10054 1506 #: ait-multilingual-translate.php:11790 1507 msgid "Default frontend language" 1508 msgstr "" 1509 1510 #: ait-multilingual-translate.php:10056 1511 #: ait-multilingual-translate.php:11794 1512 msgid "Same as source language" 1513 msgstr "" 1514 1515 #: ait-multilingual-translate.php:10062 1516 msgid "The root URL displays this language. It can differ from the language of the original content." 1517 msgstr "" 1518 1519 #: ait-multilingual-translate.php:10063 1520 #: ait-multilingual-translate.php:11809 1521 #: includes/class-aitmt-settings.php:229 1522 msgid "Search languages..." 1523 msgstr "" 1524 1525 #: ait-multilingual-translate.php:10082 1526 msgid "Saved translations are reused. The provider is used only for queue items that still need translation." 1527 msgstr "" 1528 1529 #: ait-multilingual-translate.php:10087 1530 msgid "API Keys" 1531 msgstr "" 1532 1533 #: ait-multilingual-translate.php:10092 1534 #: includes/class-aitmt-scanner.php:986 1535 msgid "Scanner Sources" 1536 msgstr "" 1537 1538 #: ait-multilingual-translate.php:10093 1539 msgid "Select only published, visitor-facing sources that should create translation jobs. Technical content is off by default." 1540 msgstr "" 1541 1542 #: ait-multilingual-translate.php:10108 1543 msgid "Cost Safety" 1544 msgstr "" 1545 1546 #: ait-multilingual-translate.php:10109 1547 msgid "Plugin limits are checked before provider calls. Also configure hard budgets and quotas in the provider account." 1548 msgstr "" 1549 1550 #: ait-multilingual-translate.php:10112 1551 #: ait-multilingual-translate.php:11958 1552 msgid "Daily character limit" 1553 msgstr "" 1554 1555 #: ait-multilingual-translate.php:10113 1556 #: ait-multilingual-translate.php:11961 1557 msgid "Monthly character limit" 1558 msgstr "" 1559 1560 #: ait-multilingual-translate.php:10114 1561 #: ait-multilingual-translate.php:11964 1562 msgid "Requests per minute" 1563 msgstr "" 1564 1565 #: ait-multilingual-translate.php:10115 1566 #: ait-multilingual-translate.php:11970 1567 msgid "Requests per day" 1568 msgstr "" 1569 1570 #: ait-multilingual-translate.php:10120 1571 msgid "Enable background queue processing after setup" 1572 msgstr "" 1573 1574 #: ait-multilingual-translate.php:10125 1575 #: ait-multilingual-translate.php:12125 1576 #: includes/class-aitmt-settings.php:391 1577 msgid "Frontend editor" 1578 msgstr "" 1579 1580 #: ait-multilingual-translate.php:10129 1581 #: ait-multilingual-translate.php:12127 1582 msgid "Allow administrators to edit translations from the frontend" 1583 msgstr "" 1584 1585 #: ait-multilingual-translate.php:10133 1586 msgid "Save setup" 1587 msgstr "" 1588 1589 #: ait-multilingual-translate.php:10138 1590 msgid "Scan and Translate" 1591 msgstr "" 1592 1593 #: ait-multilingual-translate.php:10142 1594 msgid "Scan" 1595 msgstr "" 1596 1597 #: ait-multilingual-translate.php:10143 1598 msgid "Collect site strings into the saved translation queue." 1599 msgstr "" 1600 1601 #: ait-multilingual-translate.php:10151 1602 #: ait-multilingual-translate.php:10773 1603 #: ait-multilingual-translate.php:12195 1604 msgid "Scan site strings" 1605 msgstr "" 1606 1607 #: ait-multilingual-translate.php:10156 1608 msgid "Translate" 1609 msgstr "" 1610 1611 #: ait-multilingual-translate.php:10157 1612 msgid "Start one translation queue batch. Repeat or enable background processing later in Advanced mode." 1613 msgstr "" 1614 1615 #: ait-multilingual-translate.php:10165 1616 msgid "Start translation queue" 1617 msgstr "" 1618 1619 #: ait-multilingual-translate.php:10172 1620 msgid "Finish setup" 1621 msgstr "" 1622 1623 #: ait-multilingual-translate.php:10173 1624 msgid "Open dashboard" 1625 msgstr "" 1626 1627 #: ait-multilingual-translate.php:10205 1628 msgid "Save visible translations" 1629 msgstr "" 1630 1631 #: ait-multilingual-translate.php:10208 1632 msgid "First" 1633 msgstr "" 1634 1635 #: ait-multilingual-translate.php:10209 1636 #: includes/class-aitmt-scanner.php:1114 1637 msgid "Previous" 1638 msgstr "" 1639 1640 #: ait-multilingual-translate.php:10230 1641 #: includes/class-aitmt-scanner.php:1115 1642 msgid "Next" 1643 msgstr "" 1644 1645 #: ait-multilingual-translate.php:10231 1646 msgid "Last" 1647 msgstr "" 1648 1649 #: ait-multilingual-translate.php:10266 1650 msgid "AI Translate - Translations" 1651 msgstr "" 1652 1653 #: ait-multilingual-translate.php:10267 1654 msgid "Edit each source string per target language. Empty fields can be filled manually or translated from the queue." 1655 msgstr "" 1656 1657 #: ait-multilingual-translate.php:10271 1658 msgid "Translations Matrix" 1659 msgstr "" 1660 1661 #: ait-multilingual-translate.php:10281 1662 msgid "Scan new strings" 1663 msgstr "" 1664 1665 #: ait-multilingual-translate.php:10291 1666 msgid "Process queue" 1667 msgstr "" 1668 1669 #: ait-multilingual-translate.php:10298 1670 msgid "Per page" 1671 msgstr "" 1672 1673 #: ait-multilingual-translate.php:10311 1674 msgid "Search" 1675 msgstr "" 1676 1677 #: ait-multilingual-translate.php:10312 1678 msgid "Source text or hash..." 1679 msgstr "" 1680 1681 #: ait-multilingual-translate.php:10315 1682 #: ait-multilingual-translate.php:10893 1683 #: includes/class-aitmt-runtime-maintenance.php:444 1684 #: includes/class-aitmt-runtime-maintenance.php:505 1685 msgid "Status" 1686 msgstr "" 1687 1688 #: ait-multilingual-translate.php:10317 1689 msgid "All strings" 1690 msgstr "" 1691 1692 #: ait-multilingual-translate.php:10318 1693 msgid "Needs translation" 1694 msgstr "" 1695 1696 #: ait-multilingual-translate.php:10319 1697 #: includes/class-aitmt-scanner.php:815 1698 msgid "Translated" 1699 msgstr "" 1700 1701 #: ait-multilingual-translate.php:10322 1702 #: includes/class-aitmt-scanner.php:1058 1703 msgid "Filter" 1704 msgstr "" 1705 1706 #: ait-multilingual-translate.php:10323 1707 msgid "Reset" 1708 msgstr "" 1709 1710 #: ait-multilingual-translate.php:10327 1711 msgid "Last bulk save:" 1712 msgstr "" 1713 1714 #: ait-multilingual-translate.php:10333 1715 msgid "Export / Import" 1716 msgstr "" 1717 1718 #: ait-multilingual-translate.php:10338 1719 #: ait-multilingual-translate.php:10355 1720 msgid "Export translations" 1721 msgstr "" 1722 1723 #: ait-multilingual-translate.php:10340 1724 msgid "Export language" 1725 msgstr "" 1726 1727 #: ait-multilingual-translate.php:10348 1728 #: ait-multilingual-translate.php:10370 1729 msgid "Format" 1730 msgstr "" 1731 1732 #: ait-multilingual-translate.php:10360 1733 #: ait-multilingual-translate.php:10378 1734 msgid "Import translations" 1735 msgstr "" 1736 1737 #: ait-multilingual-translate.php:10362 1738 msgid "Import language" 1739 msgstr "" 1740 1741 #: ait-multilingual-translate.php:10379 1742 msgid "CSV columns: source_text and translated_text. PO/MO import uses msgid as the original string and msgstr as the saved translation." 1743 msgstr "" 1744 1745 #: ait-multilingual-translate.php:10385 1746 msgid "Select target languages first." 1747 msgstr "" 1748 1749 #: ait-multilingual-translate.php:10387 1750 msgid "No strings collected yet. Run the scanner or open translated pages once to collect frontend strings." 1751 msgstr "" 1752 1753 #: ait-multilingual-translate.php:10400 1754 #: includes/class-aitmt-scanner.php:1083 1755 msgid "Original" 1756 msgstr "" 1757 1758 #: ait-multilingual-translate.php:10423 1759 #: ait-multilingual-translate.php:10509 1760 msgid "Status:" 1761 msgstr "" 1762 1763 #: ait-multilingual-translate.php:10482 1764 msgid "AI Translate - Scanner" 1765 msgstr "" 1766 1767 #: ait-multilingual-translate.php:10483 1768 msgid "Collect site strings into the queue, then translate queued strings in controlled batches." 1769 msgstr "" 1770 1771 #: ait-multilingual-translate.php:10487 1772 msgid "Scanner and Queue" 1773 msgstr "" 1774 1775 #: ait-multilingual-translate.php:10490 1776 msgid "Auto scan on save:" 1777 msgstr "" 1778 1779 #: ait-multilingual-translate.php:10492 1780 msgid "Background processing:" 1781 msgstr "" 1782 1783 #: ait-multilingual-translate.php:10494 1784 msgid "Page-load collection:" 1785 msgstr "" 1786 1787 #: ait-multilingual-translate.php:10496 1788 msgid "Translation Mode:" 1789 msgstr "" 1790 1791 #: ait-multilingual-translate.php:10498 1792 msgid "Quality:" 1793 msgstr "" 1794 1795 #: ait-multilingual-translate.php:10503 1796 msgid "Page-load collection is enabled. It can add new queue rows while admins or visitors browse pages. Keep it disabled on production unless you intentionally collect missing strings from page views." 1797 msgstr "" 1798 1799 #: ait-multilingual-translate.php:10507 1800 msgid "Background Translate All" 1801 msgstr "" 1802 1803 #: ait-multilingual-translate.php:10510 1804 msgid "Processed:" 1805 msgstr "" 1806 1807 #: ait-multilingual-translate.php:10511 1808 msgid "From cache:" 1809 msgstr "" 1810 1811 #: ait-multilingual-translate.php:10512 1812 msgid "Skipped:" 1813 msgstr "" 1814 1815 #: ait-multilingual-translate.php:10513 1816 msgid "Failed:" 1817 msgstr "" 1818 1819 #: ait-multilingual-translate.php:10514 1820 msgid "Remaining:" 1821 msgstr "" 1822 1823 #: ait-multilingual-translate.php:10515 1824 msgid "Progress:" 1825 msgstr "" 1826 1827 #: ait-multilingual-translate.php:10522 1828 msgid "Next queue event:" 1829 msgstr "" 1830 1831 #: ait-multilingual-translate.php:10523 1832 msgid "not scheduled" 1833 msgstr "" 1834 1835 #: ait-multilingual-translate.php:10528 1836 msgid "Start / Restart" 1837 msgstr "" 1838 1839 #: ait-multilingual-translate.php:10529 1840 msgid "Pause" 1841 msgstr "" 1842 1843 #: ait-multilingual-translate.php:10530 1844 msgid "Resume" 1845 msgstr "" 1846 1847 #: ait-multilingual-translate.php:10531 1848 msgid "Stop worker" 1849 msgstr "" 1850 1851 #: ait-multilingual-translate.php:10532 1852 msgid "Rebuild schedule" 1853 msgstr "" 1854 1855 #: ait-multilingual-translate.php:10534 1856 msgid "Clear stale lock" 1857 msgstr "" 1858 1859 #: ait-multilingual-translate.php:10541 1860 msgid "Recurring background queue" 1861 msgstr "" 1862 1863 #: ait-multilingual-translate.php:10541 1864 msgid "Enable recurring WP-Cron processing" 1865 msgstr "" 1866 1867 #: ait-multilingual-translate.php:10542 1868 msgid "Interval" 1869 msgstr "" 1870 1871 #: ait-multilingual-translate.php:10547 1872 msgid "Batches per event" 1873 msgstr "" 1874 1875 #: ait-multilingual-translate.php:10548 1876 msgid "Runtime per event (seconds)" 1877 msgstr "" 1878 1879 #: ait-multilingual-translate.php:10549 1880 msgid "Rows per provider batch" 1881 msgstr "" 1882 1883 #: ait-multilingual-translate.php:10549 1884 msgid "Use 3-5 for free/test quotas. Increase only after confirming provider limits." 1885 msgstr "" 1886 1887 #: ait-multilingual-translate.php:10551 1888 msgid "Save background settings" 1889 msgstr "" 1890 1891 #: ait-multilingual-translate.php:10555 1892 msgid "Cost optimization:" 1893 msgstr "" 1894 1895 #: ait-multilingual-translate.php:10568 1896 msgid "Queue lock:" 1897 msgstr "" 1898 1899 #: ait-multilingual-translate.php:10569 1900 msgid "inactive" 1901 msgstr "" 1902 1903 #: ait-multilingual-translate.php:10572 1904 msgid "Translate All estimate:" 1905 msgstr "" 1906 1907 #: ait-multilingual-translate.php:10576 1908 msgid "Blocked before provider:" 1909 msgstr "" 1910 1911 #: ait-multilingual-translate.php:10591 1912 msgid "Billable by language:" 1913 msgstr "" 1914 1915 #: ait-multilingual-translate.php:10600 1916 msgid "Queue source report:" 1917 msgstr "" 1918 1919 #: ait-multilingual-translate.php:10614 1920 msgid "Queue debug report" 1921 msgstr "" 1922 1923 #: ait-multilingual-translate.php:10616 1924 msgid "Duplicate hash groups:" 1925 msgstr "" 1926 1927 #: ait-multilingual-translate.php:10618 1928 msgid "duplicate rows:" 1929 msgstr "" 1930 1931 #: ait-multilingual-translate.php:10622 1932 msgid "Top skipped reasons" 1933 msgstr "" 1934 1935 #: ait-multilingual-translate.php:10624 1936 #: ait-multilingual-translate.php:10719 1937 msgid "Reason" 1938 msgstr "" 1939 1940 #: ait-multilingual-translate.php:10624 1941 msgid "Skipped strings" 1942 msgstr "" 1943 1944 #: ait-multilingual-translate.php:10624 1945 #: ait-multilingual-translate.php:10641 1946 #: ait-multilingual-translate.php:10656 1947 #: ait-multilingual-translate.php:10671 1948 #: ait-multilingual-translate.php:10686 1949 #: ait-multilingual-translate.php:10891 1950 #: includes/class-aitmt-scanner.php:1084 1951 msgid "Characters" 1952 msgstr "" 1953 1954 #: ait-multilingual-translate.php:10634 1955 msgid "No rejected technical strings recorded yet." 1956 msgstr "" 1957 1958 #: ait-multilingual-translate.php:10639 1959 msgid "Top source types" 1960 msgstr "" 1961 1962 #: ait-multilingual-translate.php:10641 1963 #: ait-multilingual-translate.php:10701 1964 #: ait-multilingual-translate.php:10719 1965 #: ait-multilingual-translate.php:10867 1966 #: includes/class-aitmt-runtime-maintenance.php:443 1967 #: includes/class-aitmt-runtime-maintenance.php:501 1968 msgid "Source type" 1969 msgstr "" 1970 1971 #: ait-multilingual-translate.php:10641 1972 #: ait-multilingual-translate.php:10656 1973 #: ait-multilingual-translate.php:10671 1974 #: ait-multilingual-translate.php:10686 1975 #: includes/class-aitmt-runtime-maintenance.php:581 1976 msgid "Rows" 1977 msgstr "" 1978 1979 #: ait-multilingual-translate.php:10641 1980 #: ait-multilingual-translate.php:10656 1981 #: ait-multilingual-translate.php:10671 1982 #: ait-multilingual-translate.php:10686 1983 msgid "Unique sources" 1984 msgstr "" 1985 1986 #: ait-multilingual-translate.php:10654 1987 msgid "Rows by target language" 1988 msgstr "" 1989 1990 #: ait-multilingual-translate.php:10656 1991 #: ait-multilingual-translate.php:10701 1992 msgid "Language" 1993 msgstr "" 1994 1995 #: ait-multilingual-translate.php:10669 1996 msgid "Top object IDs" 1997 msgstr "" 1998 1999 #: ait-multilingual-translate.php:10671 2000 msgid "Object ID" 2001 msgstr "" 2002 2003 #: ait-multilingual-translate.php:10684 2004 msgid "Top field keys" 2005 msgstr "" 2006 2007 #: ait-multilingual-translate.php:10686 2008 msgid "Field key" 2009 msgstr "" 2010 2011 #: ait-multilingual-translate.php:10699 2012 msgid "Top 50 longest queued strings" 2013 msgstr "" 2014 2015 #: ait-multilingual-translate.php:10701 2016 #: ait-multilingual-translate.php:10719 2017 msgid "Chars" 2018 msgstr "" 2019 2020 #: ait-multilingual-translate.php:10701 2021 #: ait-multilingual-translate.php:10719 2022 #: includes/class-aitmt-runtime-maintenance.php:502 2023 #: includes/class-aitmt-scanner.php:1032 2024 #: includes/class-aitmt-scanner.php:1081 2025 msgid "Object" 2026 msgstr "" 2027 2028 #: ait-multilingual-translate.php:10701 2029 #: ait-multilingual-translate.php:10719 2030 #: includes/class-aitmt-runtime-maintenance.php:503 2031 msgid "Field" 2032 msgstr "" 2033 2034 #: ait-multilingual-translate.php:10701 2035 #: ait-multilingual-translate.php:10719 2036 msgid "Sample" 2037 msgstr "" 2038 2039 #: ait-multilingual-translate.php:10701 2040 msgid "Hash" 2041 msgstr "" 2042 2043 #: ait-multilingual-translate.php:10717 2044 msgid "Top 50 rejected technical strings" 2045 msgstr "" 2046 2047 #: ait-multilingual-translate.php:10719 2048 #: ait-multilingual-translate.php:10887 2049 #: ait-multilingual-translate.php:11053 2050 msgid "Time" 2051 msgstr "" 2052 2053 #: ait-multilingual-translate.php:10733 2054 msgid "No rejected technical string samples recorded yet." 2055 msgstr "" 2056 2057 #: ait-multilingual-translate.php:10741 2058 #: ait-multilingual-translate.php:12173 2059 msgid "Last scan:" 2060 msgstr "" 2061 2062 #: ait-multilingual-translate.php:10748 2063 #: ait-multilingual-translate.php:12222 2064 msgid "Last automatic scan:" 2065 msgstr "" 2066 2067 #: ait-multilingual-translate.php:10755 2068 #: ait-multilingual-translate.php:12229 2069 msgid "Last queue run:" 2070 msgstr "" 2071 2072 #: ait-multilingual-translate.php:10758 2073 #: ait-multilingual-translate.php:12232 2074 msgid "Routes:" 2075 msgstr "" 2076 2077 #: ait-multilingual-translate.php:10765 2078 #: ait-multilingual-translate.php:12241 2079 msgid "Last background run:" 2080 msgstr "" 2081 2082 #: ait-multilingual-translate.php:10778 2083 #: ait-multilingual-translate.php:12249 2084 msgid "Process translation queue" 2085 msgstr "" 2086 2087 #: ait-multilingual-translate.php:10788 2088 msgid "Translate All" 2089 msgstr "" 2090 2091 #: ait-multilingual-translate.php:10791 2092 msgid "Translate All blocked" 2093 msgstr "" 2094 2095 #: ait-multilingual-translate.php:10801 2096 msgid "Clear unknown queue items" 2097 msgstr "" 2098 2099 #: ait-multilingual-translate.php:10812 2100 msgid "Clear technical queue items" 2101 msgstr "" 2102 2103 #: ait-multilingual-translate.php:10823 2104 msgid "Clear page-load queue items" 2105 msgstr "" 2106 2107 #: ait-multilingual-translate.php:10829 2108 #: ait-multilingual-translate.php:12254 2109 msgid "Clear queued strings" 2110 msgstr "" 2111 2112 #: ait-multilingual-translate.php:10832 2113 msgid "Translate All uses the active provider and can consume API quota quickly. It runs in safe batches and may stop if provider quota, cooldown, or server time limits are reached." 2114 msgstr "" 2115 2116 #: ait-multilingual-translate.php:10850 2117 msgid "AI Translate - Usage" 2118 msgstr "" 2119 2120 #: ait-multilingual-translate.php:10851 2121 msgid "Review provider request usage, character counts, queue sources and blocked requests." 2122 msgstr "" 2123 2124 #: ait-multilingual-translate.php:10855 2125 msgid "Provider Usage" 2126 msgstr "" 2127 2128 #: ait-multilingual-translate.php:10857 2129 msgid "Today" 2130 msgstr "" 2131 2132 #: ait-multilingual-translate.php:10857 2133 #: ait-multilingual-translate.php:10858 2134 msgid "characters" 2135 msgstr "" 2136 2137 #: ait-multilingual-translate.php:10858 2138 msgid "This month" 2139 msgstr "" 2140 2141 #: ait-multilingual-translate.php:10859 2142 msgid "Successful" 2143 msgstr "" 2144 2145 #: ait-multilingual-translate.php:10859 2146 #: ait-multilingual-translate.php:10861 2147 msgid "requests" 2148 msgstr "" 2149 2150 #: ait-multilingual-translate.php:10860 2151 msgid "Blocked" 2152 msgstr "" 2153 2154 #: ait-multilingual-translate.php:10860 2155 msgid "before API or by provider" 2156 msgstr "" 2157 2158 #: ait-multilingual-translate.php:10861 2159 msgid "Errors" 2160 msgstr "" 2161 2162 #: ait-multilingual-translate.php:10865 2163 msgid "Queue Sources" 2164 msgstr "" 2165 2166 #: ait-multilingual-translate.php:10867 2167 #: includes/class-aitmt-scanner.php:855 2168 msgid "Queued" 2169 msgstr "" 2170 2171 #: ait-multilingual-translate.php:10867 2172 #: includes/class-aitmt-scanner.php:856 2173 msgid "Processing" 2174 msgstr "" 2175 2176 #: ait-multilingual-translate.php:10867 2177 #: includes/class-aitmt-scanner.php:857 2178 msgid "Failed" 2179 msgstr "" 2180 2181 #: ait-multilingual-translate.php:10867 2182 msgid "Queued chars" 2183 msgstr "" 2184 2185 #: ait-multilingual-translate.php:10867 2186 msgid "Total chars" 2187 msgstr "" 2188 2189 #: ait-multilingual-translate.php:10883 2190 msgid "Recent Provider Requests" 2191 msgstr "" 2192 2193 #: ait-multilingual-translate.php:10889 2194 msgid "Source" 2195 msgstr "" 2196 2197 #: ait-multilingual-translate.php:10890 2198 msgid "Target" 2199 msgstr "" 2200 2201 #: ait-multilingual-translate.php:10892 2202 msgid "Batch" 2203 msgstr "" 2204 2205 #: ait-multilingual-translate.php:10894 2206 msgid "Error" 2207 msgstr "" 2208 2209 #: ait-multilingual-translate.php:10911 2210 msgid "No provider requests logged yet." 2211 msgstr "" 2212 2213 #: ait-multilingual-translate.php:10939 2214 msgid "AI Translate - Debugger" 2215 msgstr "" 2216 2217 #: ait-multilingual-translate.php:10940 2218 msgid "Check provider access, current route, translation table, and the latest API error." 2219 msgstr "" 2220 2221 #: ait-multilingual-translate.php:11005 2222 msgid "Write extended debug log to a separate file" 2223 msgstr "" 2224 2225 #: ait-multilingual-translate.php:11007 2226 msgid "Save debug settings" 2227 msgstr "" 2228 2229 #: ait-multilingual-translate.php:11008 2230 msgid "Use this only while testing. The file stores route/API events as JSON lines and is cleared by Clear debug log." 2231 msgstr "" 2232 2233 #: ait-multilingual-translate.php:11013 2234 msgid "Last frontend route:" 2235 msgstr "" 2236 2237 #: ait-multilingual-translate.php:11036 2238 #: ait-multilingual-translate.php:12314 2239 msgid "Last provider test:" 2240 msgstr "" 2241 2242 #: ait-multilingual-translate.php:11039 2243 #: ait-multilingual-translate.php:12320 2244 msgid "HTTP status:" 2245 msgstr "" 2246 2247 #: ait-multilingual-translate.php:11042 2248 #: ait-multilingual-translate.php:12234 2249 #: ait-multilingual-translate.php:12326 2250 msgid "Route:" 2251 msgstr "" 2252 2253 #: ait-multilingual-translate.php:11049 2254 msgid "Debug log" 2255 msgstr "" 2256 2257 #: ait-multilingual-translate.php:11049 2258 msgid "Latest 20 events" 2259 msgstr "" 2260 2261 #: ait-multilingual-translate.php:11056 2262 msgid "Context" 2263 msgstr "" 2264 2265 #: ait-multilingual-translate.php:11082 2266 #: ait-multilingual-translate.php:12338 2267 msgid "Test provider translation" 2268 msgstr "" 2269 2270 #: ait-multilingual-translate.php:11087 2271 #: ait-multilingual-translate.php:12343 2272 msgid "Download log file" 2273 msgstr "" 2274 2275 #: ait-multilingual-translate.php:11092 2276 #: ait-multilingual-translate.php:12348 2277 msgid "Clear debug log" 2278 msgstr "" 2279 2280 #: ait-multilingual-translate.php:11097 2281 #: ait-multilingual-translate.php:12353 2282 msgid "Clear provider cooldown" 2283 msgstr "" 2284 2285 #: ait-multilingual-translate.php:11180 2286 msgid "Debug log file was not found." 2287 msgstr "" 2288 2289 #: ait-multilingual-translate.php:11249 2290 msgid "Translate All was not started because the cost confirmation checkbox was not selected." 2291 msgstr "" 2292 2293 #: ait-multilingual-translate.php:11251 2294 msgid "Translate All is blocked until unknown and technical queue rows are reviewed or cleared." 2295 msgstr "" 2296 2297 #: ait-multilingual-translate.php:11331 2298 msgid "Unknown queue items were not cleared because the confirmation checkbox was not selected." 2299 msgstr "" 2300 2301 #: ait-multilingual-translate.php:11367 2302 msgid "Technical queue items were not cleared because the confirmation checkbox was not selected." 2303 msgstr "" 2304 2305 #: ait-multilingual-translate.php:11407 2306 msgid "Page-load queue items were not cleared because the confirmation checkbox was not selected." 2307 msgstr "" 2308 2309 #: ait-multilingual-translate.php:11622 2310 msgid "Scan strings, translate them in batches, then edit saved translations on the frontend." 2311 msgstr "" 2312 2313 #: ait-multilingual-translate.php:11633 2314 msgid "Settings saved." 2315 msgstr "" 2316 2317 #: ait-multilingual-translate.php:11639 2318 msgid "Safe API defaults were applied. Background processing, page-load translation, and auto scan on save are off; small batches and conservative character limits are enabled." 2319 msgstr "" 2320 2321 #: ait-multilingual-translate.php:11646 2322 msgid "Dashboard Overview" 2323 msgstr "" 2324 2325 #: ait-multilingual-translate.php:11651 2326 msgid "API key configured" 2327 msgstr "" 2328 2329 #: ait-multilingual-translate.php:11651 2330 msgid "API key missing" 2331 msgstr "" 2332 2333 #: ait-multilingual-translate.php:11654 2334 msgid "Queue / Worker" 2335 msgstr "" 2336 2337 #: ait-multilingual-translate.php:11659 2338 msgid "Usage Today" 2339 msgstr "" 2340 2341 #. translators: %s: configured daily character limit. 2342 #. translators: %s: configured monthly character limit. 2343 #: ait-multilingual-translate.php:11662 2344 #: ait-multilingual-translate.php:11668 2345 #, php-format 2346 msgid "Limit: %s characters" 2347 msgstr "" 2348 2349 #: ait-multilingual-translate.php:11665 2350 msgid "Usage This Month" 2351 msgstr "" 2352 2353 #: ait-multilingual-translate.php:11671 2354 msgid "Translation Memory" 2355 msgstr "" 2356 2357 #: ait-multilingual-translate.php:11673 2358 msgid "API requests avoided by local reuse" 2359 msgstr "" 2360 2361 #: ait-multilingual-translate.php:11676 2362 msgid "Last Translation" 2363 msgstr "" 2364 2365 #: ait-multilingual-translate.php:11677 2366 msgid "None yet" 2367 msgstr "" 2368 2369 #: ait-multilingual-translate.php:11681 2370 msgid "Next Background Event" 2371 msgstr "" 2372 2373 #: ait-multilingual-translate.php:11682 2374 msgid "Not scheduled" 2375 msgstr "" 2376 2377 #: ait-multilingual-translate.php:11683 2378 msgid "Cron enabled" 2379 msgstr "" 2380 2381 #: ait-multilingual-translate.php:11683 2382 msgid "Cron disabled" 2383 msgstr "" 2384 2385 #: ait-multilingual-translate.php:11690 2386 msgid "API spending protection needs attention." 2387 msgstr "" 2388 2389 #: ait-multilingual-translate.php:11691 2390 msgid "Before translating a large site, configure provider-side budgets/quotas and plugin-side character limits. Translation Memory and duplicate detection reduce usage, but external providers may still charge by characters or tokens." 2391 msgstr "" 2392 2393 #: ait-multilingual-translate.php:11700 2394 msgid "Apply safe API defaults" 2395 msgstr "" 2396 2397 #: ait-multilingual-translate.php:11707 2398 msgid "AIT Multilingual Translate is using the single-file queued translation engine. Pages use saved translations first; new strings are collected into the queue for batch translation." 2399 msgstr "" 2400 2401 #: ait-multilingual-translate.php:11712 2402 msgid "Last translation error:" 2403 msgstr "" 2404 2405 #: ait-multilingual-translate.php:11716 2406 msgid "AIT Multilingual Translate sections" 2407 msgstr "" 2408 2409 #: ait-multilingual-translate.php:11717 2410 #: ait-multilingual-translate.php:11736 2411 msgid "General" 2412 msgstr "" 2413 2414 #: ait-multilingual-translate.php:11718 2415 #: ait-multilingual-translate.php:11770 2416 msgid "Languages & Locales" 2417 msgstr "" 2418 2419 #: ait-multilingual-translate.php:11719 2420 #: ait-multilingual-translate.php:11826 2421 msgid "Translation Providers" 2422 msgstr "" 2423 2424 #: ait-multilingual-translate.php:11720 2425 msgid "Frontend Editor" 2426 msgstr "" 2427 2428 #: ait-multilingual-translate.php:11721 2429 msgid "Exclusions" 2430 msgstr "" 2431 2432 #: ait-multilingual-translate.php:11722 2433 msgid "Cache" 2434 msgstr "" 2435 2436 #: ait-multilingual-translate.php:11723 2437 msgid "SEO & URLs" 2438 msgstr "" 2439 2440 #: ait-multilingual-translate.php:11725 2441 msgid "Queue" 2442 msgstr "" 2443 2444 #: ait-multilingual-translate.php:11726 2445 msgid "Diagnostics" 2446 msgstr "" 2447 2448 #: ait-multilingual-translate.php:11739 2449 msgid "Interface mode" 2450 msgstr "" 2451 2452 #: ait-multilingual-translate.php:11742 2453 msgid "Basic" 2454 msgstr "" 2455 2456 #: ait-multilingual-translate.php:11743 2457 msgid "Advanced" 2458 msgstr "" 2459 2460 #: ait-multilingual-translate.php:11745 2461 msgid "Basic mode shows the setup needed for everyday translation. Advanced mode reveals queue diagnostics, provider internals, scanner details, routes, and logs." 2462 msgstr "" 2463 2464 #: ait-multilingual-translate.php:11756 2465 msgid "This changes only the AIT Multilingual Translate interface. It does not change the WordPress site language, website source language, default frontend language, or translation targets." 2466 msgstr "" 2467 2468 #: ait-multilingual-translate.php:11760 2469 msgid "Build status" 2470 msgstr "" 2471 2472 #: ait-multilingual-translate.php:11762 2473 msgid "Public Beta preparation build." 2474 msgstr "" 2475 2476 #: ait-multilingual-translate.php:11763 2477 msgid "Feature flag helpers are prepared for future package variants. All current beta features remain enabled." 2478 msgstr "" 2479 2480 #: ait-multilingual-translate.php:11785 2481 msgid "This is the language your original site content is written in. Auto uses the WordPress site language." 2482 msgstr "" 2483 2484 #: ait-multilingual-translate.php:11801 2485 msgid "The root URL displays this language without a prefix when default-language hiding is enabled. The original content language remains unchanged." 2486 msgstr "" 2487 2488 #: ait-multilingual-translate.php:11806 2489 #: includes/class-aitmt-settings.php:227 2490 msgid "Target languages" 2491 msgstr "" 2492 2493 #: ait-multilingual-translate.php:11819 2494 msgid "Select only the languages you want visitors to use. Every selected target language creates its own saved translation rows." 2495 msgstr "" 2496 2497 #: ait-multilingual-translate.php:11836 2498 msgid "The provider used for new queue translations. Already saved manual/published translations are reused and are not sent again." 2499 msgstr "" 2500 2501 #: ait-multilingual-translate.php:11838 2502 msgid "Translation Memory first: existing manual, imported, draft, and published translations are reused before any provider call. Duplicate strings are skipped before API requests." 2503 msgstr "" 2504 2505 #: ait-multilingual-translate.php:11840 2506 msgid "Provider capabilities" 2507 msgstr "" 2508 2509 #: ait-multilingual-translate.php:11846 2510 msgid "Active" 2511 msgstr "" 2512 2513 #: ait-multilingual-translate.php:11846 2514 msgid "Planned" 2515 msgstr "" 2516 2517 #: ait-multilingual-translate.php:11853 2518 msgid "Free quota available" 2519 msgstr "" 2520 2521 #: ait-multilingual-translate.php:11867 2522 msgid "Each card represents an active provider adapter. Open the matching settings panel below to configure and test it before saving." 2523 msgstr "" 2524 2525 #: ait-multilingual-translate.php:11871 2526 msgid "Provider settings" 2527 msgstr "" 2528 2529 #: ait-multilingual-translate.php:11876 2530 msgid "Translation Style / Tone of Voice" 2531 msgstr "" 2532 2533 #: ait-multilingual-translate.php:11877 2534 msgid "Set the global translation style for prompt-based AI providers. Per content type modes for pages, posts, WooCommerce products, SEO meta, buttons, and short strings are planned for a future version." 2535 msgstr "" 2536 2537 #: ait-multilingual-translate.php:11882 2538 msgid "Global Translation Mode" 2539 msgstr "" 2540 2541 #: ait-multilingual-translate.php:11890 2542 msgid "Tone of Voice affects AI-based translations only. Use Neutral mode for accurate translation, SEO mode for search-oriented content, and eCommerce mode for product pages." 2543 msgstr "" 2544 2545 #: ait-multilingual-translate.php:11891 2546 msgid "Tone of Voice is applied to prompt-based AI providers: OpenAI, Gemini, Grok/xAI, Claude, YandexGPT, Mistral, DeepSeek, Qwen, and Baidu Qianfan. Google Translate, DeepL, and Yandex Translate ignore this setting." 2547 msgstr "" 2548 2549 #: ait-multilingual-translate.php:11896 2550 msgid "Custom translation instruction" 2551 msgstr "" 2552 2553 #: ait-multilingual-translate.php:11899 2554 msgid "Example: Translate naturally for Georgian customers, keep product names unchanged, use friendly but professional tone." 2555 msgstr "" 2556 2557 #: ait-multilingual-translate.php:11900 2558 msgid "Used only when Translation Mode is Custom Prompt. Maximum 500 characters." 2559 msgstr "" 2560 2561 #: ait-multilingual-translate.php:11905 2562 msgid "AI Cost Optimization" 2563 msgstr "" 2564 2565 #: ait-multilingual-translate.php:11906 2566 msgid "Use cheap models by default, deduplicate strings before provider calls, reuse translation memory, and keep safety limits enabled while testing provider quota." 2567 msgstr "" 2568 2569 #: ait-multilingual-translate.php:11908 2570 msgid "Safety presets" 2571 msgstr "" 2572 2573 #: ait-multilingual-translate.php:11909 2574 msgid "Choose a profile only when you want it to replace the related local limits and worker settings. Provider-side billing limits remain required." 2575 msgstr "" 2576 2577 #: ait-multilingual-translate.php:11910 2578 msgid "Apply Safe Free Mode" 2579 msgstr "" 2580 2581 #: ait-multilingual-translate.php:11911 2582 msgid "Apply Recommended" 2583 msgstr "" 2584 2585 #: ait-multilingual-translate.php:11912 2586 msgid "Fast Bulk can consume substantial paid API quota. Continue only after setting provider billing limits." 2587 msgstr "" 2588 2589 #: ait-multilingual-translate.php:11912 2590 msgid "Apply Fast Bulk Translation" 2591 msgstr "" 2592 2593 #: ait-multilingual-translate.php:11919 2594 msgid "Translation quality mode" 2595 msgstr "" 2596 2597 #: ait-multilingual-translate.php:11927 2598 msgid "Cheap is recommended for Public Beta testing. Balanced and Premium are guidance modes for choosing provider models; custom model fields still remain under your control." 2599 msgstr "" 2600 2601 #: ait-multilingual-translate.php:11931 2602 msgid "Recommended default models" 2603 msgstr "" 2604 2605 #: ait-multilingual-translate.php:11934 2606 msgid "OpenAI Cheap: gpt-4o-mini" 2607 msgstr "" 2608 2609 #: ait-multilingual-translate.php:11935 2610 msgid "OpenAI Premium: gpt-4o" 2611 msgstr "" 2612 2613 #: ait-multilingual-translate.php:11936 2614 msgid "Gemini Cheap: Flash model" 2615 msgstr "" 2616 2617 #: ait-multilingual-translate.php:11937 2618 msgid "Gemini Premium: Pro model" 2619 msgstr "" 2620 2621 #: ait-multilingual-translate.php:11938 2622 msgid "Grok Cheap: lightweight/mini model where available" 2623 msgstr "" 2624 2625 #: ait-multilingual-translate.php:11939 2626 msgid "Claude Cheap: Haiku model where available" 2627 msgstr "" 2628 2629 #: ait-multilingual-translate.php:11940 2630 msgid "Mistral Cheap: small/fast model" 2631 msgstr "" 2632 2633 #: ait-multilingual-translate.php:11941 2634 msgid "DeepSeek Cheap: chat/standard model" 2635 msgstr "" 2636 2637 #: ait-multilingual-translate.php:11943 2638 msgid "The active Public Beta providers keep using the model field you set above. Planned provider model names are shown as release-planning guidance only." 2639 msgstr "" 2640 2641 #: ait-multilingual-translate.php:11948 2642 msgid "Temperature" 2643 msgstr "" 2644 2645 #: ait-multilingual-translate.php:11952 2646 msgid "Default 0.1 keeps translations stable and predictable for AI providers." 2647 msgstr "" 2648 2649 #: ait-multilingual-translate.php:11956 2650 msgid "Quota control" 2651 msgstr "" 2652 2653 #: ait-multilingual-translate.php:11960 2654 msgid "Local safety limit for the active provider. 0 means no plugin-side daily stop." 2655 msgstr "" 2656 2657 #: ait-multilingual-translate.php:11963 2658 msgid "Local safety limit for the active provider. Provider billing limits still need to be configured in OpenAI/Google/DeepL/xAI accounts." 2659 msgstr "" 2660 2661 #: ait-multilingual-translate.php:11966 2662 msgid "Local request-rate ceiling checked before contacting the provider. Recommended: 5 for free/test plans." 2663 msgstr "" 2664 2665 #: ait-multilingual-translate.php:11967 2666 msgid "Estimated tokens per minute" 2667 msgstr "" 2668 2669 #: ait-multilingual-translate.php:11969 2670 msgid "Conservative local estimate based on source characters. Provider token accounting remains authoritative." 2671 msgstr "" 2672 2673 #: ait-multilingual-translate.php:11972 2674 msgid "Optional local daily request ceiling. 0 disables only this local check." 2675 msgstr "" 2676 2677 #: ait-multilingual-translate.php:11973 2678 msgid "Max characters per request" 2679 msgstr "" 2680 2681 #: ait-multilingual-translate.php:11975 2682 msgid "0 means no plugin-side per-request character stop. Set a limit to avoid accidentally sending very large batches." 2683 msgstr "" 2684 2685 #: ait-multilingual-translate.php:11976 2686 msgid "Estimated cost limit per request" 2687 msgstr "" 2688 2689 #: ait-multilingual-translate.php:11978 2690 msgid "0 means no estimated-cost stop. Estimates are approximate and provider billing remains authoritative." 2691 msgstr "" 2692 2693 #: ait-multilingual-translate.php:11979 2694 msgid "Queue lock TTL" 2695 msgstr "" 2696 2697 #: ait-multilingual-translate.php:11981 2698 msgid "Prevents cron, Translate All and manual queue processing from running at the same time. Expired locks are released automatically." 2699 msgstr "" 2700 2701 #: ait-multilingual-translate.php:11982 2702 msgid "Processing TTL" 2703 msgstr "" 2704 2705 #: ait-multilingual-translate.php:11984 2706 msgid "If a processing row is abandoned for longer than this, it can be returned to the queue or marked failed depending on retry count." 2707 msgstr "" 2708 2709 #: ait-multilingual-translate.php:11985 2710 msgid "Retry limit" 2711 msgstr "" 2712 2713 #: ait-multilingual-translate.php:11987 2714 msgid "Failed rows stop retrying after this many attempts and are marked as failed." 2715 msgstr "" 2716 2717 #: ait-multilingual-translate.php:11991 2718 #: includes/class-aitmt-settings.php:281 2719 msgid "Translation behavior" 2720 msgstr "" 2721 2722 #: ait-multilingual-translate.php:11993 2723 msgid "Enable frontend translation output" 2724 msgstr "" 2725 2726 #: ait-multilingual-translate.php:11994 2727 msgid "Collect missing strings into the translation queue" 2728 msgstr "" 2729 2730 #: ait-multilingual-translate.php:11995 2731 msgid "Automatically scan saved/updated content for new strings" 2732 msgstr "" 2733 2734 #: ait-multilingual-translate.php:11996 2735 msgid "Automatically process the translation queue in the background" 2736 msgstr "" 2737 2738 #: ait-multilingual-translate.php:11997 2739 msgid "Translate missing strings during page load (slower, use only for testing)" 2740 msgstr "" 2741 2742 #: ait-multilingual-translate.php:11998 2743 #: includes/class-aitmt-settings.php:290 2744 msgid "Save new AI translations as drafts" 2745 msgstr "" 2746 2747 #: ait-multilingual-translate.php:11999 2748 #: includes/class-aitmt-settings.php:295 2749 msgid "Translate alt, title, placeholder, aria-label, and SEO meta attributes" 2750 msgstr "" 2751 2752 #: ait-multilingual-translate.php:12000 2753 msgid "Recommended production mode: frontend output on, queue missing strings on, scan on save off during imports, page-load translation off. Turn background processing on only when your API quota/billing is ready." 2754 msgstr "" 2755 2756 #: ait-multilingual-translate.php:12002 2757 msgid "Warning: draft mode plus page-load translation can create many draft rows while visitors browse. Use queue processing instead for production sites." 2758 msgstr "" 2759 2760 #: ait-multilingual-translate.php:12007 2761 msgid "Runtime translation cache" 2762 msgstr "" 2763 2764 #: ait-multilingual-translate.php:12009 2765 msgid "Allow direct provider translation during a public page request" 2766 msgstr "" 2767 2768 #: ait-multilingual-translate.php:12010 2769 msgid "Allow page-load strings and translations to be stored in the database" 2770 msgstr "" 2771 2772 #: ait-multilingual-translate.php:12011 2773 msgid "Automatically remove a small daily batch of expired page-load cache rows" 2774 msgstr "" 2775 2776 #: ait-multilingual-translate.php:12012 2777 msgid "Render existing saved translations on cart, checkout, and account pages" 2778 msgstr "" 2779 2780 #: ait-multilingual-translate.php:12013 2781 msgid "This option never collects, stores, or sends private WooCommerce page text to a provider. It only permits rendering translations that already exist." 2782 msgstr "" 2783 2784 #: ait-multilingual-translate.php:12014 2785 msgid "All three options are disabled by default. Normal scanner, queue, translation matrix, menu, widget, and saved translation output continue to work without runtime database storage." 2786 msgstr "" 2787 2788 #: ait-multilingual-translate.php:12016 2789 msgid "Runtime cache TTL (days)" 2790 msgstr "" 2791 2792 #: ait-multilingual-translate.php:12018 2793 msgid "Maximum page-load rows per target language" 2794 msgstr "" 2795 2796 #: ait-multilingual-translate.php:12020 2797 msgid "Minimum runtime text length" 2798 msgstr "" 2799 2800 #: ait-multilingual-translate.php:12022 2801 msgid "Maximum runtime text length" 2802 msgstr "" 2803 2804 #: ait-multilingual-translate.php:12026 2805 msgid "Excluded URL paths (one per line)" 2806 msgstr "" 2807 2808 #: ait-multilingual-translate.php:12028 2809 msgid "Excluded selectors (class, ID, tag, or [attribute])" 2810 msgstr "" 2811 2812 #: ait-multilingual-translate.php:12030 2813 msgid "Excluded source types (comma or line separated)" 2814 msgstr "" 2815 2816 #: ait-multilingual-translate.php:12033 2817 msgid "Runtime requests use per-string locks, a page-content signature, translation memory, and a separate usage table. Cart, checkout, account, AJAX, REST, previews, filters, hidden nodes, scripts, styles, and technical values are excluded." 2818 msgstr "" 2819 2820 #: ait-multilingual-translate.php:12034 2821 msgid "Review or clear runtime cache safely" 2822 msgstr "" 2823 2824 #: ait-multilingual-translate.php:12043 2825 msgid "How many strings are sent in one API request. Use 3-5 for free/test keys, 10-25 for normal paid keys, higher only when quota is confirmed." 2826 msgstr "" 2827 2828 #: ait-multilingual-translate.php:12050 2829 #: includes/class-aitmt-settings.php:312 2830 msgid "URLs and SEO" 2831 msgstr "" 2832 2833 #: ait-multilingual-translate.php:12053 2834 msgid "URL mode" 2835 msgstr "" 2836 2837 #: ait-multilingual-translate.php:12057 2838 msgid "Directory URLs: /ka/about/" 2839 msgstr "" 2840 2841 #: ait-multilingual-translate.php:12062 2842 msgid "Query URLs: /about/?lang=ka" 2843 msgstr "" 2844 2845 #: ait-multilingual-translate.php:12064 2846 msgid "Directory URLs are better for SEO, but require WordPress permalinks and rewrite rules. Query URLs are simpler and useful while testing." 2847 msgstr "" 2848 2849 #: ait-multilingual-translate.php:12066 2850 #: includes/class-aitmt-settings.php:334 2851 msgid "Keep the source language without a language prefix" 2852 msgstr "" 2853 2854 #: ait-multilingual-translate.php:12067 2855 msgid "When enabled, the original language stays on normal URLs while only translated languages use a language code." 2856 msgstr "" 2857 2858 #: ait-multilingual-translate.php:12076 2859 msgid "Shortcode:" 2860 msgstr "" 2861 2862 #: ait-multilingual-translate.php:12087 2863 msgid "Dropdown is compact for headers. List is useful in footers, sidebars, and menu-like layouts." 2864 msgstr "" 2865 2866 #: ait-multilingual-translate.php:12091 2867 #: includes/class-aitmt-settings.php:355 2868 msgid "Display parts" 2869 msgstr "" 2870 2871 #: ait-multilingual-translate.php:12093 2872 #: includes/class-aitmt-settings.php:359 2873 msgid "Flags" 2874 msgstr "" 2875 2876 #: ait-multilingual-translate.php:12094 2877 #: includes/class-aitmt-settings.php:364 2878 msgid "Language names" 2879 msgstr "" 2880 2881 #: ait-multilingual-translate.php:12095 2882 #: includes/class-aitmt-settings.php:369 2883 msgid "Language codes" 2884 msgstr "" 2885 2886 #: ait-multilingual-translate.php:12096 2887 msgid "Choose what visitors see inside the selector, for example Georgian, KA, or both." 2888 msgstr "" 2889 2890 #: ait-multilingual-translate.php:12100 2891 #: includes/class-aitmt-settings.php:374 2892 msgid "Automatic placement" 2893 msgstr "" 2894 2895 #: ait-multilingual-translate.php:12102 2896 msgid "Try to show in header" 2897 msgstr "" 2898 2899 #: ait-multilingual-translate.php:12103 2900 #: includes/class-aitmt-settings.php:383 2901 msgid "Show in footer" 2902 msgstr "" 2903 2904 #: ait-multilingual-translate.php:12104 2905 msgid "Automatic placement is optional. You can also place the selector with a shortcode, widget, or theme/menu area." 2906 msgstr "" 2907 2908 #: ait-multilingual-translate.php:12109 2909 msgid "Selector custom CSS" 2910 msgstr "" 2911 2912 #: ait-multilingual-translate.php:12113 2913 msgid "Optional CSS loaded on the frontend only for styling the language selector." 2914 msgstr "" 2915 2916 #: ait-multilingual-translate.php:12115 2917 msgid "Useful classes:" 2918 msgstr "" 2919 2920 #: ait-multilingual-translate.php:12128 2921 msgid "When enabled, logged-in administrators see the AI Translate button in the admin bar on translated pages and can save manual corrections." 2922 msgstr "" 2923 2924 #: ait-multilingual-translate.php:12153 2925 msgid "Buy Me a Coffee URL" 2926 msgstr "" 2927 2928 #: ait-multilingual-translate.php:12157 2929 msgid "PayPal donation URL" 2930 msgstr "" 2931 2932 #: ait-multilingual-translate.php:12164 2933 #: includes/class-aitmt-settings.php:403 2934 msgid "Save settings" 2935 msgstr "" 2936 2937 #: ait-multilingual-translate.php:12168 2938 msgid "String Scanner" 2939 msgstr "" 2940 2941 #: ait-multilingual-translate.php:12169 2942 msgid "Scan posts, pages, products, menus, widgets, taxonomy terms, SEO meta, and public custom fields. New strings are added to the queue once and are translated from there." 2943 msgstr "" 2944 2945 #: ait-multilingual-translate.php:12176 2946 msgid "Targets:" 2947 msgstr "" 2948 2949 #: ait-multilingual-translate.php:12183 2950 msgid "Queued strings" 2951 msgstr "" 2952 2953 #: ait-multilingual-translate.php:12187 2954 msgid "Saved translations" 2955 msgstr "" 2956 2957 #: ait-multilingual-translate.php:12200 2958 msgid "Translation Queue" 2959 msgstr "" 2960 2961 #: ait-multilingual-translate.php:12201 2962 msgid "Frontend pages now use saved translations only. New strings are collected here and translated in batches, so visitors do not wait for the AI provider." 2963 msgstr "" 2964 2965 #: ait-multilingual-translate.php:12317 2966 msgid "Provider:" 2967 msgstr "" 2968 2969 #: ait-multilingual-translate.php:12323 2970 msgid "Sample:" 2971 msgstr "" 2972 2973 #: ait-multilingual-translate.php:12329 2974 msgid "Duration:" 2975 msgstr "" 2976 2977 #: includes/class-aitmt-frontend-editor.php:117 74 2978 msgid "Translated via OpenAI" 75 2979 msgstr "" 76 2980 77 #: includes/class-wpait-frontend-editor.php 78 msgid "Provider rate limit reached." 79 msgstr "" 80 81 #: includes/class-wpait-openai-provider.php 2981 #: includes/class-aitmt-interface-language.php:25 2982 msgid "Use WordPress language" 2983 msgstr "" 2984 2985 #: includes/class-aitmt-interface-language.php:26 2986 msgid "English" 2987 msgstr "" 2988 2989 #: includes/class-aitmt-interface-language.php:27 2990 msgid "Russian" 2991 msgstr "" 2992 2993 #: includes/class-aitmt-interface-language.php:28 2994 msgid "Georgian" 2995 msgstr "" 2996 2997 #: includes/class-aitmt-openai-provider.php:14 82 2998 msgid "OpenAI API key is missing." 83 2999 msgstr "" 84 3000 85 #: includes/class- wpait-openai-provider.php3001 #: includes/class-aitmt-openai-provider.php:94 86 3002 msgid "OpenAI request failed." 87 3003 msgstr "" 88 3004 89 #: includes/class- wpait-openai-provider.php3005 #: includes/class-aitmt-openai-provider.php:107 90 3006 msgid "OpenAI returned an unexpected translation payload." 91 3007 msgstr "" 92 3008 93 #: includes/class-wpait-plugin.php 94 msgid "Settings" 95 msgstr "" 96 97 #: includes/class-wpait-settings.php 98 msgid "AIT Multilingual Translate" 99 msgstr "" 100 101 #: includes/class-wpait-settings.php 102 msgid "Languages" 103 msgstr "" 104 105 #: includes/class-wpait-settings.php 106 msgid "Source language" 107 msgstr "" 108 109 #: includes/class-wpait-settings.php 3009 #: includes/class-aitmt-provider-registry.php:99 3010 msgid "The selected translation provider is not available." 3011 msgstr "" 3012 3013 #: includes/class-aitmt-runtime-maintenance.php:169 3014 msgid "The settings migration could not be saved. Existing settings were left available in the migration backup." 3015 msgstr "" 3016 3017 #: includes/class-aitmt-runtime-maintenance.php:175 3018 msgid "The settings migration verification failed. Review the migration backup before changing provider settings." 3019 msgstr "" 3020 3021 #: includes/class-aitmt-runtime-maintenance.php:200 3022 msgid "AIT Multilingual Translate preserved all existing translations during the update. A manual scanner maintenance tool is available to preview obsolete aggregated scanner records and rebuild only missing queue items." 3023 msgstr "" 3024 3025 #: includes/class-aitmt-runtime-maintenance.php:201 3026 msgid "Review scanner maintenance" 3027 msgstr "" 3028 3029 #: includes/class-aitmt-runtime-maintenance.php:395 3030 #: includes/class-aitmt-runtime-maintenance.php:396 3031 msgid "Database Maintenance" 3032 msgstr "" 3033 3034 #: includes/class-aitmt-runtime-maintenance.php:420 3035 msgid "AIT Multilingual Translate - Database Maintenance" 3036 msgstr "" 3037 3038 #: includes/class-aitmt-runtime-maintenance.php:421 3039 msgid "Inspect and clean translation data in small, resumable batches. Updates never delete translations automatically." 3040 msgstr "" 3041 3042 #: includes/class-aitmt-runtime-maintenance.php:431 3043 msgid "Translation rows" 3044 msgstr "" 3045 3046 #: includes/class-aitmt-runtime-maintenance.php:432 3047 msgid "Data size" 3048 msgstr "" 3049 3050 #: includes/class-aitmt-runtime-maintenance.php:433 3051 msgid "Index size" 3052 msgstr "" 3053 3054 #: includes/class-aitmt-runtime-maintenance.php:434 3055 msgid "Empty source type" 3056 msgstr "" 3057 3058 #: includes/class-aitmt-runtime-maintenance.php:435 3059 msgid "Page-load rows" 3060 msgstr "" 3061 3062 #: includes/class-aitmt-runtime-maintenance.php:436 3063 msgid "Legacy runtime rows" 3064 msgstr "" 3065 3066 #: includes/class-aitmt-runtime-maintenance.php:440 3067 msgid "Grouped statistics" 3068 msgstr "" 3069 3070 #: includes/class-aitmt-runtime-maintenance.php:445 3071 msgid "Target language" 3072 msgstr "" 3073 3074 #: includes/class-aitmt-runtime-maintenance.php:449 3075 msgid "Dry run and batched maintenance" 3076 msgstr "" 3077 3078 #: includes/class-aitmt-runtime-maintenance.php:455 3079 msgid "Operation" 3080 msgstr "" 3081 3082 #: includes/class-aitmt-runtime-maintenance.php:465 3083 msgid "Minimum age in days" 3084 msgstr "" 3085 3086 #: includes/class-aitmt-runtime-maintenance.php:474 3087 msgid "Dry run" 3088 msgstr "" 3089 3090 #: includes/class-aitmt-runtime-maintenance.php:479 3091 msgid "I understand that Apply changes one confirmed batch. Run Dry run first." 3092 msgstr "" 3093 3094 #: includes/class-aitmt-runtime-maintenance.php:483 3095 msgid "Apply one batch" 3096 msgstr "" 3097 3098 #: includes/class-aitmt-runtime-maintenance.php:487 3099 msgid "Scanner-derived Translation Memory" 3100 msgstr "" 3101 3102 #: includes/class-aitmt-runtime-maintenance.php:489 3103 msgid "Use Obsolete scanner-derived translations in the maintenance selector to preview records that no longer match any current scanner source. Manual/imported translations and every still-valid Translation Memory entry are always preserved. Nothing is removed during a plugin update." 3104 msgstr "" 3105 3106 #. translators: 1: rows, 2: characters. 3107 #: includes/class-aitmt-runtime-maintenance.php:493 3108 #, php-format 3109 msgid "Detected obsolete scanner candidates: %1$s row(s), %2$s character(s)." 3110 msgstr "" 3111 3112 #: includes/class-aitmt-runtime-maintenance.php:504 3113 msgid "Original text preview" 3114 msgstr "" 3115 3116 #: includes/class-aitmt-runtime-maintenance.php:525 3117 msgid "Rebuild scans the current site and queues only missing translations. It does not delete or overwrite published, draft, manual, or imported translations." 3118 msgstr "" 3119 3120 #: includes/class-aitmt-runtime-maintenance.php:526 3121 msgid "Rebuild scanner queue" 3122 msgstr "" 3123 3124 #: includes/class-aitmt-runtime-maintenance.php:529 3125 msgid "Runtime diagnostics (last 14 days)" 3126 msgstr "" 3127 3128 #: includes/class-aitmt-runtime-maintenance.php:547 3129 msgid "Clear all queued jobs (translations are preserved)" 3130 msgstr "" 3131 3132 #: includes/class-aitmt-runtime-maintenance.php:548 3133 msgid "Clear all failed jobs (published/manual/import translations are preserved)" 3134 msgstr "" 3135 3136 #: includes/class-aitmt-runtime-maintenance.php:549 3137 msgid "Delete stale page-load cache rows" 3138 msgstr "" 3139 3140 #: includes/class-aitmt-runtime-maintenance.php:550 3141 msgid "Clear generated runtime translation cache (page-load and legacy rows)" 3142 msgstr "" 3143 3144 #: includes/class-aitmt-runtime-maintenance.php:551 3145 msgid "Delete confirmed legacy runtime rows" 3146 msgstr "" 3147 3148 #: includes/class-aitmt-runtime-maintenance.php:552 3149 msgid "Delete old failed rows" 3150 msgstr "" 3151 3152 #: includes/class-aitmt-runtime-maintenance.php:553 3153 msgid "Delete old unclassified runtime candidates" 3154 msgstr "" 3155 3156 #: includes/class-aitmt-runtime-maintenance.php:554 3157 msgid "Delete rows linked to missing posts" 3158 msgstr "" 3159 3160 #: includes/class-aitmt-runtime-maintenance.php:555 3161 msgid "Delete obsolete scanner-derived translations" 3162 msgstr "" 3163 3164 #: includes/class-aitmt-runtime-maintenance.php:556 3165 msgid "Classify high-confidence legacy rows" 3166 msgstr "" 3167 3168 #: includes/class-aitmt-runtime-maintenance.php:557 3169 msgid "Analyze translation table" 3170 msgstr "" 3171 3172 #: includes/class-aitmt-runtime-maintenance.php:558 3173 msgid "Optimize translation table (manual only)" 3174 msgstr "" 3175 3176 #. translators: %s: scanner source type. 3177 #: includes/class-aitmt-runtime-maintenance.php:563 3178 #, php-format 3179 msgid "Clear queued jobs from source: %s" 3180 msgstr "" 3181 3182 #: includes/class-aitmt-runtime-maintenance.php:627 3183 msgid "The scanner is unavailable." 3184 msgstr "" 3185 3186 #: includes/class-aitmt-runtime-maintenance.php:632 3187 msgid "Scanner rebuild finished." 3188 msgstr "" 3189 3190 #: includes/class-aitmt-runtime-maintenance.php:648 3191 msgid "Unknown maintenance operation." 3192 msgstr "" 3193 3194 #: includes/class-aitmt-runtime-maintenance.php:665 3195 #: includes/class-aitmt-runtime-maintenance.php:685 3196 msgid "Run Dry run first, then confirm Apply." 3197 msgstr "" 3198 3199 #: includes/class-aitmt-runtime-maintenance.php:1325 3200 msgid "Translation table is unavailable." 3201 msgstr "" 3202 3203 #: includes/class-aitmt-scanner.php:60 3204 msgid "Pages" 3205 msgstr "" 3206 3207 #: includes/class-aitmt-scanner.php:60 3208 msgid "Published WordPress pages." 3209 msgstr "" 3210 3211 #: includes/class-aitmt-scanner.php:61 3212 msgid "Posts" 3213 msgstr "" 3214 3215 #: includes/class-aitmt-scanner.php:61 3216 msgid "Published blog and news posts." 3217 msgstr "" 3218 3219 #: includes/class-aitmt-scanner.php:62 3220 msgid "Products" 3221 msgstr "" 3222 3223 #: includes/class-aitmt-scanner.php:62 3224 msgid "Published WooCommerce product titles, descriptions, and selected visible attributes." 3225 msgstr "" 3226 3227 #: includes/class-aitmt-scanner.php:63 3228 msgid "Elementor Page Content" 3229 msgstr "" 3230 3231 #: includes/class-aitmt-scanner.php:63 3232 msgid "Visible widget fields from published Elementor pages." 3233 msgstr "" 3234 3235 #: includes/class-aitmt-scanner.php:64 3236 msgid "Navigation Menus" 3237 msgstr "" 3238 3239 #: includes/class-aitmt-scanner.php:64 3240 msgid "Shared menu labels visible on the frontend." 3241 msgstr "" 3242 3243 #: includes/class-aitmt-scanner.php:65 3244 #: includes/class-aitmt-scanner.php:900 3245 msgid "Widgets" 3246 msgstr "" 3247 3248 #: includes/class-aitmt-scanner.php:65 3249 msgid "Visible text from active widget instances." 3250 msgstr "" 3251 3252 #: includes/class-aitmt-scanner.php:66 3253 msgid "Litho Mega Menu" 3254 msgstr "" 3255 3256 #: includes/class-aitmt-scanner.php:66 3257 msgid "Published reusable mega menu content." 3258 msgstr "" 3259 3260 #: includes/class-aitmt-scanner.php:67 3261 msgid "Elementor Library" 3262 msgstr "" 3263 3264 #: includes/class-aitmt-scanner.php:67 3265 msgid "Saved Elementor templates. Enable only when they are used on the frontend." 3266 msgstr "" 3267 3268 #: includes/class-aitmt-scanner.php:68 3269 msgid "Section Builder" 3270 msgstr "" 3271 3272 #: includes/class-aitmt-scanner.php:68 3273 msgid "Reusable section templates." 3274 msgstr "" 3275 3276 #: includes/class-aitmt-scanner.php:69 3277 msgid "SEO Title" 3278 msgstr "" 3279 3280 #: includes/class-aitmt-scanner.php:69 3281 msgid "SEO titles from supported SEO plugins." 3282 msgstr "" 3283 3284 #: includes/class-aitmt-scanner.php:70 3285 msgid "SEO Meta Description" 3286 msgstr "" 3287 3288 #: includes/class-aitmt-scanner.php:70 3289 msgid "SEO descriptions from supported SEO plugins." 3290 msgstr "" 3291 3292 #: includes/class-aitmt-scanner.php:71 3293 msgid "Taxonomies" 3294 msgstr "" 3295 3296 #: includes/class-aitmt-scanner.php:71 3297 msgid "Public term names and descriptions." 3298 msgstr "" 3299 3300 #: includes/class-aitmt-scanner.php:72 3301 msgid "Image Alt Text" 3302 msgstr "" 3303 3304 #: includes/class-aitmt-scanner.php:72 3305 msgid "Alternative text for published media used by content." 3306 msgstr "" 3307 3308 #: includes/class-aitmt-scanner.php:73 3309 msgid "Image Captions" 3310 msgstr "" 3311 3312 #: includes/class-aitmt-scanner.php:73 3313 msgid "Published image captions." 3314 msgstr "" 3315 3316 #: includes/class-aitmt-scanner.php:74 3317 msgid "Custom Fields" 3318 msgstr "" 3319 3320 #: includes/class-aitmt-scanner.php:74 3321 msgid "Public human-readable custom fields. Keep disabled unless required." 3322 msgstr "" 3323 3324 #: includes/class-aitmt-scanner.php:75 3325 msgid "Other Public Post Types" 3326 msgstr "" 3327 3328 #: includes/class-aitmt-scanner.php:75 3329 msgid "Published content from other public post types." 3330 msgstr "" 3331 3332 #. translators: %s: singular post type label. 3333 #: includes/class-aitmt-scanner.php:85 3334 #, php-format 3335 msgid "Post type: %s" 3336 msgstr "" 3337 3338 #. translators: %s: plural post type label. 3339 #: includes/class-aitmt-scanner.php:88 3340 #, php-format 3341 msgid "Published %s content." 3342 msgstr "" 3343 3344 #: includes/class-aitmt-scanner.php:601 3345 msgid "Scanner preview exclusions saved." 3346 msgstr "" 3347 3348 #: includes/class-aitmt-scanner.php:651 3349 msgid "Translation table unavailable." 3350 msgstr "" 3351 3352 #: includes/class-aitmt-scanner.php:664 3353 #: includes/class-aitmt-scanner.php:669 3354 msgid "Queue removal failed. No saved translations were removed." 3355 msgstr "" 3356 3357 #: includes/class-aitmt-scanner.php:699 3358 msgid "Invalid scanner operation." 3359 msgstr "" 3360 3361 #. translators: %d: number of deleted empty queued jobs. 3362 #: includes/class-aitmt-scanner.php:710 3363 #, php-format 3364 msgid "Removed %d queued jobs. Saved translations and scanner exclusions are unchanged. Maximum 1000 jobs per action." 3365 msgstr "" 3366 3367 #: includes/class-aitmt-scanner.php:713 3368 msgid "Scanner exclusions saved. Existing translations were preserved. Object exclusions override individual field includes." 3369 msgstr "" 3370 3371 #: includes/class-aitmt-scanner.php:784 3372 #: includes/class-aitmt-scanner.php:813 3373 msgid "New" 3374 msgstr "" 3375 3376 #: includes/class-aitmt-scanner.php:817 3377 msgid "Partially translated" 3378 msgstr "" 3379 3380 #: includes/class-aitmt-scanner.php:852 3381 msgid "Missing" 3382 msgstr "" 3383 3384 #: includes/class-aitmt-scanner.php:853 3385 msgid "Partial" 3386 msgstr "" 3387 3388 #: includes/class-aitmt-scanner.php:854 3389 msgid "Complete" 3390 msgstr "" 3391 3392 #: includes/class-aitmt-scanner.php:858 3393 msgid "Published" 3394 msgstr "" 3395 3396 #: includes/class-aitmt-scanner.php:859 3397 msgid "Manual" 3398 msgstr "" 3399 3400 #: includes/class-aitmt-scanner.php:860 3401 msgid "Imported" 3402 msgstr "" 3403 3404 #: includes/class-aitmt-scanner.php:861 3405 msgid "Draft" 3406 msgstr "" 3407 3408 #: includes/class-aitmt-scanner.php:947 3409 msgid "Scanner source selection saved." 3410 msgstr "" 3411 3412 #: includes/class-aitmt-scanner.php:978 3413 msgid "Frontend Content" 3414 msgstr "" 3415 3416 #: includes/class-aitmt-scanner.php:979 3417 msgid "Frontend Shared Content" 3418 msgstr "" 3419 3420 #: includes/class-aitmt-scanner.php:980 3421 msgid "Backend / Templates" 3422 msgstr "" 3423 3424 #: includes/class-aitmt-scanner.php:981 3425 #: includes/class-aitmt-scanner.php:982 3426 msgid "Technical / Metadata" 3427 msgstr "" 3428 3429 #: includes/class-aitmt-scanner.php:987 3430 msgid "Choose only content visitors need. New installations use a conservative frontend-first selection." 3431 msgstr "" 3432 3433 #: includes/class-aitmt-scanner.php:1008 3434 msgid "Save Scanner Sources" 3435 msgstr "" 3436 3437 #: includes/class-aitmt-scanner.php:1012 3438 msgid "Scanner Preview" 3439 msgstr "" 3440 3441 #: includes/class-aitmt-scanner.php:1013 3442 msgid "Preview is generated locally. No provider request is made." 3443 msgstr "" 3444 3445 #: includes/class-aitmt-scanner.php:1018 3446 msgid "All usage types" 3447 msgstr "" 3448 3449 #: includes/class-aitmt-scanner.php:1024 3450 #: includes/class-aitmt-scanner.php:1080 3451 msgid "Source Type" 3452 msgstr "" 3453 3454 #: includes/class-aitmt-scanner.php:1026 3455 msgid "All source types" 3456 msgstr "" 3457 3458 #: includes/class-aitmt-scanner.php:1034 3459 msgid "All objects" 3460 msgstr "" 3461 3462 #: includes/class-aitmt-scanner.php:1040 3463 msgid "Inclusion" 3464 msgstr "" 3465 3466 #: includes/class-aitmt-scanner.php:1042 3467 msgid "All" 3468 msgstr "" 3469 3470 #: includes/class-aitmt-scanner.php:1043 3471 #: includes/class-aitmt-scanner.php:1092 3472 msgid "Included" 3473 msgstr "" 3474 3475 #: includes/class-aitmt-scanner.php:1044 3476 #: includes/class-aitmt-scanner.php:1092 3477 msgid "Excluded" 3478 msgstr "" 3479 3480 #: includes/class-aitmt-scanner.php:1047 3481 #: includes/class-aitmt-scanner.php:1085 3482 msgid "Translation Status" 3483 msgstr "" 3484 3485 #: includes/class-aitmt-scanner.php:1049 3486 msgid "All statuses" 3487 msgstr "" 3488 3489 #: includes/class-aitmt-scanner.php:1055 3490 msgid "Page / field / text" 3491 msgstr "" 3492 3493 #: includes/class-aitmt-scanner.php:1056 3494 msgid "Filter preview..." 3495 msgstr "" 3496 3497 #: includes/class-aitmt-scanner.php:1060 3498 msgid "Remove selected queued jobs? Saved translations will be preserved. A future scan may queue these fields again." 3499 msgstr "" 3500 3501 #: includes/class-aitmt-scanner.php:1064 3502 msgid "Select all visible" 3503 msgstr "" 3504 3505 #: includes/class-aitmt-scanner.php:1065 3506 msgid "Select none" 3507 msgstr "" 3508 3509 #: includes/class-aitmt-scanner.php:1066 3510 msgid "Bulk action" 3511 msgstr "" 3512 3513 #: includes/class-aitmt-scanner.php:1067 3514 msgid "Exclude selected" 3515 msgstr "" 3516 3517 #: includes/class-aitmt-scanner.php:1068 3518 msgid "Remove selected from queue" 3519 msgstr "" 3520 3521 #: includes/class-aitmt-scanner.php:1069 3522 msgid "Include selected again" 3523 msgstr "" 3524 3525 #: includes/class-aitmt-scanner.php:1071 3526 msgid "Apply" 3527 msgstr "" 3528 3529 #. translators: 1: matching unit count, 2: current page, 3: total pages. 3530 #: includes/class-aitmt-scanner.php:1072 3531 #, php-format 3532 msgid "%1$d units; page %2$d of %3$d" 3533 msgstr "" 3534 3535 #: includes/class-aitmt-scanner.php:1077 3536 #: includes/class-aitmt-scanner.php:1091 3537 msgid "Select" 3538 msgstr "" 3539 3540 #: includes/class-aitmt-scanner.php:1078 3541 msgid "Location" 3542 msgstr "" 3543 3544 #: includes/class-aitmt-scanner.php:1082 3545 msgid "Widget / Field" 3546 msgstr "" 3547 3548 #: includes/class-aitmt-scanner.php:1086 3549 msgid "Actions" 3550 msgstr "" 3551 3552 #: includes/class-aitmt-scanner.php:1092 3553 msgid "Open Source Object" 3554 msgstr "" 3555 3556 #: includes/class-aitmt-scanner.php:1101 3557 msgid "Exclude" 3558 msgstr "" 3559 3560 #: includes/class-aitmt-scanner.php:1101 3561 msgid "Include again" 3562 msgstr "" 3563 3564 #: includes/class-aitmt-scanner.php:1102 3565 msgid "Remove from queue" 3566 msgstr "" 3567 3568 #: includes/class-aitmt-scanner.php:1104 3569 msgid "Include object again" 3570 msgstr "" 3571 3572 #: includes/class-aitmt-scanner.php:1104 3573 msgid "Exclude entire object" 3574 msgstr "" 3575 3576 #: includes/class-aitmt-scanner.php:1124 3577 msgid "Frontend" 3578 msgstr "" 3579 3580 #: includes/class-aitmt-scanner.php:1125 3581 msgid "Frontend Shared" 3582 msgstr "" 3583 3584 #: includes/class-aitmt-scanner.php:1126 3585 msgid "Template / Backend" 3586 msgstr "" 3587 3588 #: includes/class-aitmt-scanner.php:1128 3589 msgid "Metadata" 3590 msgstr "" 3591 3592 #. translators: %s: Current WordPress site language code. 3593 #: includes/class-aitmt-settings.php:216 3594 #, php-format 110 3595 msgid "Auto: site language (%s)" 111 3596 msgstr "" 112 3597 113 #: includes/class- wpait-settings.php3598 #: includes/class-aitmt-settings.php:223 114 3599 msgid "Leave on Auto to follow the WordPress site language." 115 3600 msgstr "" 116 3601 117 #: includes/class-wpait-settings.php 118 msgid "Target languages" 119 msgstr "" 120 121 #: includes/class-wpait-settings.php 122 msgid "Search languages..." 123 msgstr "" 124 125 #: includes/class-wpait-settings.php 3602 #: includes/class-aitmt-settings.php:251 126 3603 msgid "AI Provider" 127 3604 msgstr "" 128 3605 129 #: includes/class-wpait-settings.php 130 msgid "Provider" 131 msgstr "" 132 133 #: includes/class-wpait-settings.php 3606 #: includes/class-aitmt-settings.php:258 134 3607 msgid "OpenAI / ChatGPT" 135 3608 msgstr "" 136 3609 137 #: includes/class- wpait-settings.php3610 #: includes/class-aitmt-settings.php:260 138 3611 msgid "Configure only the providers you plan to use. Provider requests are sent only after you save a key and start a translation action." 139 3612 msgstr "" 140 3613 141 #: includes/class- wpait-settings.php3614 #: includes/class-aitmt-settings.php:265 142 3615 msgid "OpenAI API key" 143 3616 msgstr "" 144 3617 145 #: includes/class- wpait-settings.php146 msgid "You can also define WPAIT_OPENAI_API_KEY in wp-config.php."147 msgstr "" 148 149 #: includes/class- wpait-settings.php3618 #: includes/class-aitmt-settings.php:269 3619 msgid "You can also define AITMT_OPENAI_API_KEY in wp-config.php." 3620 msgstr "" 3621 3622 #: includes/class-aitmt-settings.php:274 150 3623 msgid "OpenAI model" 151 3624 msgstr "" 152 3625 153 #: includes/class-wpait-settings.php 154 msgid "Translation behavior" 155 msgstr "" 156 157 #: includes/class-wpait-settings.php 3626 #: includes/class-aitmt-settings.php:285 158 3627 msgid "Automatically translate missing strings on page view" 159 3628 msgstr "" 160 3629 161 #: includes/class-wpait-settings.php 162 msgid "Save new AI translations as drafts" 163 msgstr "" 164 165 #: includes/class-wpait-settings.php 166 msgid "Translate alt, title, placeholder, aria-label, and SEO meta attributes" 167 msgstr "" 168 169 #: includes/class-wpait-settings.php 170 msgid "Batch size" 171 msgstr "" 172 173 #: includes/class-wpait-settings.php 3630 #: includes/class-aitmt-settings.php:305 174 3631 msgid "Limits how many new strings are sent to AI from one page render." 175 3632 msgstr "" 176 3633 177 #: includes/class-wpait-settings.php 178 msgid "URLs and SEO" 179 msgstr "" 180 181 #: includes/class-wpait-settings.php 3634 #: includes/class-aitmt-settings.php:316 182 3635 msgid "Language URL mode" 183 3636 msgstr "" 184 3637 185 #: includes/class- wpait-settings.php3638 #: includes/class-aitmt-settings.php:320 186 3639 msgid "Directory URLs: /en/about/" 187 3640 msgstr "" 188 3641 189 #: includes/class- wpait-settings.php3642 #: includes/class-aitmt-settings.php:325 190 3643 msgid "Query URLs: /about/?lang=en" 191 3644 msgstr "" 192 3645 193 #: includes/class- wpait-settings.php3646 #: includes/class-aitmt-settings.php:330 194 3647 msgid "Default language URL" 195 3648 msgstr "" 196 3649 197 #: includes/class-wpait-settings.php 198 msgid "Keep the source language without a language prefix" 199 msgstr "" 200 201 #: includes/class-wpait-settings.php 202 msgid "Language Switcher" 203 msgstr "" 204 205 #: includes/class-wpait-settings.php 206 msgid "Style" 207 msgstr "" 208 209 #: includes/class-wpait-settings.php 210 msgid "Dropdown" 211 msgstr "" 212 213 #: includes/class-wpait-settings.php 214 msgid "List" 215 msgstr "" 216 217 #: includes/class-wpait-settings.php 218 msgid "Display parts" 219 msgstr "" 220 221 #: includes/class-wpait-settings.php 222 msgid "Flags" 223 msgstr "" 224 225 #: includes/class-wpait-settings.php 226 msgid "Language names" 227 msgstr "" 228 229 #: includes/class-wpait-settings.php 230 msgid "Language codes" 231 msgstr "" 232 233 #: includes/class-wpait-settings.php 234 msgid "Automatic placement" 235 msgstr "" 236 237 #: includes/class-wpait-settings.php 3650 #: includes/class-aitmt-settings.php:378 238 3651 msgid "Try to show in header via wp_body_open" 239 3652 msgstr "" 240 3653 241 #: includes/class-wpait-settings.php 242 msgid "Show in footer" 243 msgstr "" 244 245 #: includes/class-wpait-settings.php 246 msgid "Shortcodes: [wp_ai_translate_switcher] or [ai_language_switcher]." 247 msgstr "" 248 249 #: includes/class-wpait-settings.php 250 msgid "Frontend editor" 251 msgstr "" 252 253 #: includes/class-wpait-settings.php 3654 #: includes/class-aitmt-settings.php:386 3655 msgid "Shortcode: [aitmt_language_switcher]." 3656 msgstr "" 3657 3658 #: includes/class-aitmt-settings.php:395 254 3659 msgid "Allow administrators to edit translated text from the frontend" 255 3660 msgstr "" 256 3661 257 #: includes/class-wpait-settings.php 258 msgid "Save settings" 259 msgstr "" 260 261 #: includes/class-wpait-switcher.php 262 msgid "Select language" 263 msgstr "" 264 265 #: includes/class-wpait-widget.php 266 msgid "AIT Multilingual Translate Switcher" 267 msgstr "" 268 269 #: includes/class-wpait-widget.php 3662 #: includes/class-aitmt-widget.php:14 270 3663 msgid "Language switcher for AIT Multilingual Translate." 271 3664 msgstr "" 272 3665 273 #: includes/class- wpait-widget.php3666 #: includes/class-aitmt-widget.php:35 274 3667 msgid "Title:" 275 3668 msgstr "" 276 277 #: ait-multilingual-translate.php278 msgid "AI Translate"279 msgstr ""280 281 #: ait-multilingual-translate.php282 msgid "Dashboard"283 msgstr ""284 285 #: ait-multilingual-translate.php286 msgid "Translations"287 msgstr ""288 289 #: ait-multilingual-translate.php290 msgid "Scanner"291 msgstr ""292 293 #: ait-multilingual-translate.php294 msgid "Report Bug"295 msgstr ""296 297 #: ait-multilingual-translate.php298 msgid "Feedback"299 msgstr ""300 301 #: ait-multilingual-translate.php302 msgid "Setup Wizard"303 msgstr ""304 305 #: ait-multilingual-translate.php306 msgid "Support"307 msgstr ""308 309 #: ait-multilingual-translate.php310 msgid "Debugger"311 msgstr ""312 313 #: ait-multilingual-translate.php314 msgid "Public Beta Build"315 msgstr ""316 317 #: ait-multilingual-translate.php318 msgid "Support Development"319 msgstr ""320 321 #: ait-multilingual-translate.php322 msgid "Support AIT Multilingual Translate"323 msgstr ""324 325 #: ait-multilingual-translate.php326 msgid "AIT Multilingual Translate is currently in Public Beta and includes temporary full feature access while the platform is actively tested and improved."327 msgstr ""328 329 #: ait-multilingual-translate.php330 msgid "Users who support the project with a donation during the Public Beta period may receive a significant discount or special early-supporter offer for the future commercial release of AIT Multilingual Translate."331 msgstr ""332 333 #: ait-multilingual-translate.php334 msgid "Your support helps improve the plugin, optimize AI providers, expand language support, and accelerate development."335 msgstr ""336 337 #: ait-multilingual-translate.php338 msgid "AIT Multilingual Translate is currently in Public Beta. Please make a backup before bulk translating production websites."339 msgstr ""340 341 #: ait-multilingual-translate.php342 msgid "Send Feedback"343 msgstr ""344 345 #: ait-multilingual-translate.php346 msgid "Cheap"347 msgstr ""348 349 #: ait-multilingual-translate.php350 msgid "Balanced"351 msgstr ""352 353 #: ait-multilingual-translate.php354 msgid "Premium"355 msgstr ""356 357 #: ait-multilingual-translate.php358 msgid "Neutral / Accurate"359 msgstr ""360 361 #: ait-multilingual-translate.php362 msgid "SEO Optimized"363 msgstr ""364 365 #: ait-multilingual-translate.php366 msgid "Marketing"367 msgstr ""368 369 #: ait-multilingual-translate.php370 msgid "eCommerce"371 msgstr ""372 373 #: ait-multilingual-translate.php374 msgid "Formal"375 msgstr ""376 377 #: ait-multilingual-translate.php378 msgid "Casual"379 msgstr ""380 381 #: ait-multilingual-translate.php382 msgid "Technical"383 msgstr ""384 385 #: ait-multilingual-translate.php386 msgid "Legal"387 msgstr ""388 389 #: ait-multilingual-translate.php390 msgid "Luxury Brand"391 msgstr ""392 393 #: ait-multilingual-translate.php394 msgid "Friendly"395 msgstr ""396 397 #: ait-multilingual-translate.php398 msgid "Custom Prompt"399 msgstr ""400 401 #: ait-multilingual-translate.php402 msgid "Display the AIT Multilingual Translate language switcher."403 msgstr ""404 405 #: ait-multilingual-translate.php406 msgid "AI Translate Language Switcher"407 msgstr ""408 409 #: ait-multilingual-translate.php410 msgid "Layout type"411 msgstr ""412 413 #: ait-multilingual-translate.php414 msgid "Buttons"415 msgstr ""416 417 #: ait-multilingual-translate.php418 msgid "Flags only"419 msgstr ""420 421 #: ait-multilingual-translate.php422 msgid "Flags + language name"423 msgstr ""424 425 #: ait-multilingual-translate.php426 msgid "Language name only"427 msgstr ""428 429 #: ait-multilingual-translate.php430 msgid "Show flags"431 msgstr ""432 433 #: ait-multilingual-translate.php434 msgid "Yes"435 msgstr ""436 437 #: ait-multilingual-translate.php438 msgid "No"439 msgstr ""440 441 #: ait-multilingual-translate.php442 msgid "Show language name"443 msgstr ""444 445 #: ait-multilingual-translate.php446 msgid "Show language code"447 msgstr ""448 449 #: ait-multilingual-translate.php450 msgid "Show current language"451 msgstr ""452 453 #: ait-multilingual-translate.php454 msgid "Hide current language"455 msgstr ""456 457 #: ait-multilingual-translate.php458 msgid "Open links in current page"459 msgstr ""460 461 #: ait-multilingual-translate.php462 msgid "Orientation"463 msgstr ""464 465 #: ait-multilingual-translate.php466 msgid "Horizontal"467 msgstr ""468 469 #: ait-multilingual-translate.php470 msgid "Vertical"471 msgstr ""472 473 #: ait-multilingual-translate.php474 msgid "Alignment"475 msgstr ""476 477 #: ait-multilingual-translate.php478 msgid "Left"479 msgstr ""480 481 #: ait-multilingual-translate.php482 msgid "Center"483 msgstr ""484 485 #: ait-multilingual-translate.php486 msgid "Right"487 msgstr ""488 489 #: ait-multilingual-translate.php490 msgid "Switcher Style"491 msgstr ""492 493 #: ait-multilingual-translate.php494 msgid "Text color"495 msgstr ""496 497 #: ait-multilingual-translate.php498 msgid "Hover color"499 msgstr ""500 501 #: ait-multilingual-translate.php502 msgid "Active language color"503 msgstr ""504 505 #: ait-multilingual-translate.php506 msgid "Background color"507 msgstr ""508 509 #: ait-multilingual-translate.php510 msgid "Border color"511 msgstr ""512 513 #: ait-multilingual-translate.php514 msgid "Dropdown background"515 msgstr ""516 517 #: ait-multilingual-translate.php518 msgid "Border radius"519 msgstr ""520 521 #: ait-multilingual-translate.php522 msgid "Padding"523 msgstr ""524 525 #: ait-multilingual-translate.php526 msgid "Gap between items"527 msgstr ""528 529 #: ait-multilingual-translate.php530 msgid "Dropdown width"531 msgstr ""532 533 #: ait-multilingual-translate.php534 msgid "Dropdown shadow"535 msgstr ""536 537 #: ait-multilingual-translate.php538 msgid "Add the language switcher to a WordPress menu."539 msgstr ""540 541 #: ait-multilingual-translate.php542 msgid "Show flag"543 msgstr ""544 545 #: ait-multilingual-translate.php546 msgid "Menu display mode"547 msgstr ""548 549 #: ait-multilingual-translate.php550 msgid "List mode"551 msgstr ""552 553 #: ait-multilingual-translate.php554 msgid "Dropdown mode"555 msgstr ""556 557 #: ait-multilingual-translate.php558 msgid "Current language switcher"559 msgstr ""560 561 #: ait-multilingual-translate.php562 msgid "All enabled languages"563 msgstr ""564 565 #: ait-multilingual-translate.php566 msgid "Specific language: %s"567 msgstr ""568 569 #: ait-multilingual-translate.php570 msgid "Add to Menu"571 msgstr ""572 573 #: ait-multilingual-translate.php574 msgid "Editing on"575 msgstr ""576 577 #: ait-multilingual-translate.php578 msgid "Save"579 msgstr ""580 581 #: ait-multilingual-translate.php582 msgid "Cancel"583 msgstr ""584 585 #: ait-multilingual-translate.php586 msgid "No editable translation text found yet. Scan/process the queue, or click source text that is highlighted for admins."587 msgstr ""588 589 #: ait-multilingual-translate.php590 msgid "Target: %s"591 msgstr ""592 593 #: ait-multilingual-translate.php594 msgid "Every 5 minutes"595 msgstr ""596 597 #: ait-multilingual-translate.php598 msgid "Translation memory hit"599 msgstr ""600 601 #: ait-multilingual-translate.php602 msgid "Translated via %s"603 msgstr ""604 605 #: ait-multilingual-translate.php606 msgid "Invalid target language."607 msgstr ""608 609 #: ait-multilingual-translate.php610 msgid "No import file uploaded."611 msgstr ""612 613 #: ait-multilingual-translate.php614 msgid "The message could not be sent. Please check WordPress email delivery on this site."615 msgstr ""616 617 #: ait-multilingual-translate.php618 msgid "Please confirm consent before sending diagnostic information."619 msgstr ""620 621 #: ait-multilingual-translate.php622 msgid "AI Translate - Report Bug"623 msgstr ""624 625 #: ait-multilingual-translate.php626 msgid "Send a safe technical report for Public Beta troubleshooting."627 msgstr ""628 629 #: ait-multilingual-translate.php630 msgid "Bug report sent. Thank you for helping test AIT Multilingual Translate."631 msgstr ""632 633 #: ait-multilingual-translate.php634 msgid "The technical report includes WordPress, WooCommerce, PHP, plugin, language, provider, queue, and route status. Sensitive values are redacted before sending."635 msgstr ""636 637 #: ait-multilingual-translate.php638 msgid "Name"639 msgstr ""640 641 #: ait-multilingual-translate.php642 msgid "Email"643 msgstr ""644 645 #: ait-multilingual-translate.php646 msgid "Website"647 msgstr ""648 649 #: ait-multilingual-translate.php650 msgid "Problem type"651 msgstr ""652 653 #: ait-multilingual-translate.php654 msgid "Translation issue"655 msgstr ""656 657 #: ait-multilingual-translate.php658 msgid "WooCommerce issue"659 msgstr ""660 661 #: ait-multilingual-translate.php662 msgid "Elementor issue"663 msgstr ""664 665 #: ait-multilingual-translate.php666 msgid "Frontend editor issue"667 msgstr ""668 669 #: ait-multilingual-translate.php670 msgid "Provider / API issue"671 msgstr ""672 673 #: ait-multilingual-translate.php674 msgid "Install / update issue"675 msgstr ""676 677 #: ait-multilingual-translate.php678 msgid "Other"679 msgstr ""680 681 #: ait-multilingual-translate.php682 msgid "Short description"683 msgstr ""684 685 #: ait-multilingual-translate.php686 msgid "Steps to reproduce"687 msgstr ""688 689 #: ait-multilingual-translate.php690 msgid "1. Open page...\\n2. Switch language...\\n3. Expected...\\n4. Actual..."691 msgstr ""692 693 #: ait-multilingual-translate.php694 msgid "Attach technical log"695 msgstr ""696 697 #: ait-multilingual-translate.php698 msgid "I agree to send this report and redacted technical diagnostics to info@itdesign.biz."699 msgstr ""700 701 #: ait-multilingual-translate.php702 msgid "Send bug report"703 msgstr ""704 705 #: ait-multilingual-translate.php706 msgid "AI Translate - Feedback"707 msgstr ""708 709 #: ait-multilingual-translate.php710 msgid "Share Public Beta feedback, feature requests, and translation notes."711 msgstr ""712 713 #: ait-multilingual-translate.php714 msgid "Feedback sent. Thank you for shaping AIT Multilingual Translate."715 msgstr ""716 717 #: ait-multilingual-translate.php718 msgid "Feedback / Feature Request"719 msgstr ""720 721 #: ait-multilingual-translate.php722 msgid "Type"723 msgstr ""724 725 #: ait-multilingual-translate.php726 msgid "Bug"727 msgstr ""728 729 #: ait-multilingual-translate.php730 msgid "Feature request"731 msgstr ""732 733 #: ait-multilingual-translate.php734 msgid "Message"735 msgstr ""736 737 #: ait-multilingual-translate.php738 msgid "I agree to send this feedback to info@itdesign.biz."739 msgstr ""740 741 #: ait-multilingual-translate.php742 msgid "AI Translate - Support"743 msgstr ""744 745 #: ait-multilingual-translate.php746 msgid "Support the free Public Beta and help prioritize the roadmap."747 msgstr ""748 749 #: ait-multilingual-translate.php750 msgid "Need help?"751 msgstr ""752 753 #: ait-multilingual-translate.php754 msgid "Documentation"755 msgstr ""756 757 #: ait-multilingual-translate.php758 msgid "Plugin Website"759 msgstr ""760 761 #: ait-multilingual-translate.php762 msgid "Public Beta Notice"763 msgstr ""764 765 #: ait-multilingual-translate.php766 msgid "This Public Beta includes temporary unrestricted access for testing and feedback purposes. Commercial licensing and additional editions may be introduced in future releases."767 msgstr ""768 769 #: ait-multilingual-translate.php770 msgid "Support contact"771 msgstr ""772 773 #: ait-multilingual-translate.php774 msgid "AI Translate - Onboarding"775 msgstr ""776 777 #: ait-multilingual-translate.php778 msgid "Set up languages, provider keys, scanning, queue translation, and frontend editing."779 msgstr ""780 781 #: ait-multilingual-translate.php782 msgid "Onboarding settings saved."783 msgstr ""784 785 #: ait-multilingual-translate.php786 msgid "Welcome"787 msgstr ""788 789 #: ait-multilingual-translate.php790 msgid "AIT Multilingual Translate adds AI translation, WooCommerce support, frontend editing, SEO-friendly language URLs, and saved translation memory. This is a Public Beta, so make a backup before bulk translating production websites."791 msgstr ""792 793 #: ait-multilingual-translate.php794 msgid "Auto: WordPress site language"795 msgstr ""796 797 #: ait-multilingual-translate.php798 msgid "Google Translate"799 msgstr ""800 801 #: ait-multilingual-translate.php802 msgid "DeepL"803 msgstr ""804 805 #: ait-multilingual-translate.php806 msgid "Google Gemini"807 msgstr ""808 809 #: ait-multilingual-translate.php810 msgid "Grok / xAI"811 msgstr ""812 813 #: ait-multilingual-translate.php814 msgid "Saved translations are reused. The provider is used only for queue items that still need translation."815 msgstr ""816 817 #: ait-multilingual-translate.php818 msgid "API Keys"819 msgstr ""820 821 #: ait-multilingual-translate.php822 msgid "Google Translate API key"823 msgstr ""824 825 #: ait-multilingual-translate.php826 msgid "DeepL API key"827 msgstr ""828 829 #: ait-multilingual-translate.php830 msgid "Gemini API key"831 msgstr ""832 833 #: ait-multilingual-translate.php834 msgid "Grok API key"835 msgstr ""836 837 #: ait-multilingual-translate.php838 msgid "Allow administrators to edit translations from the frontend"839 msgstr ""840 841 #: ait-multilingual-translate.php842 msgid "Save setup"843 msgstr ""844 845 #: ait-multilingual-translate.php846 msgid "Scan and Translate"847 msgstr ""848 849 #: ait-multilingual-translate.php850 msgid "Scan"851 msgstr ""852 853 #: ait-multilingual-translate.php854 msgid "Collect site strings into the saved translation queue."855 msgstr ""856 857 #: ait-multilingual-translate.php858 msgid "Scan site strings"859 msgstr ""860 861 #: ait-multilingual-translate.php862 msgid "Translate"863 msgstr ""864 865 #: ait-multilingual-translate.php866 msgid "Start one translation queue batch. Repeat or enable background processing later in Advanced mode."867 msgstr ""868 869 #: ait-multilingual-translate.php870 msgid "Start translation queue"871 msgstr ""872 873 #: ait-multilingual-translate.php874 msgid "Finish setup"875 msgstr ""876 877 #: ait-multilingual-translate.php878 msgid "Open dashboard"879 msgstr ""880 881 #: ait-multilingual-translate.php882 msgid "Save visible translations"883 msgstr ""884 885 #: ait-multilingual-translate.php886 msgid "First"887 msgstr ""888 889 #: ait-multilingual-translate.php890 msgid "Previous"891 msgstr ""892 893 #: ait-multilingual-translate.php894 msgid "Next"895 msgstr ""896 897 #: ait-multilingual-translate.php898 msgid "Last"899 msgstr ""900 901 #: ait-multilingual-translate.php902 msgid "AI Translate - Translations"903 msgstr ""904 905 #: ait-multilingual-translate.php906 msgid "Edit each source string per target language. Empty fields can be filled manually or translated from the queue."907 msgstr ""908 909 #: ait-multilingual-translate.php910 msgid "Translations Matrix"911 msgstr ""912 913 #: ait-multilingual-translate.php914 msgid "Scan new strings"915 msgstr ""916 917 #: ait-multilingual-translate.php918 msgid "Process queue"919 msgstr ""920 921 #: ait-multilingual-translate.php922 msgid "Per page"923 msgstr ""924 925 #: ait-multilingual-translate.php926 msgid "Search"927 msgstr ""928 929 #: ait-multilingual-translate.php930 msgid "Source text or hash..."931 msgstr ""932 933 #: ait-multilingual-translate.php934 msgid "Status"935 msgstr ""936 937 #: ait-multilingual-translate.php938 msgid "All strings"939 msgstr ""940 941 #: ait-multilingual-translate.php942 msgid "Needs translation"943 msgstr ""944 945 #: ait-multilingual-translate.php946 msgid "Translated"947 msgstr ""948 949 #: ait-multilingual-translate.php950 msgid "Filter"951 msgstr ""952 953 #: ait-multilingual-translate.php954 msgid "Reset"955 msgstr ""956 957 #: ait-multilingual-translate.php958 msgid "Last bulk save:"959 msgstr ""960 961 #: ait-multilingual-translate.php962 msgid "Export / Import"963 msgstr ""964 965 #: ait-multilingual-translate.php966 msgid "Export translations"967 msgstr ""968 969 #: ait-multilingual-translate.php970 msgid "Export language"971 msgstr ""972 973 #: ait-multilingual-translate.php974 msgid "Format"975 msgstr ""976 977 #: ait-multilingual-translate.php978 msgid "Import translations"979 msgstr ""980 981 #: ait-multilingual-translate.php982 msgid "Import language"983 msgstr ""984 985 #: ait-multilingual-translate.php986 msgid "CSV columns: source_text and translated_text. PO/MO import uses msgid as the original string and msgstr as the saved translation."987 msgstr ""988 989 #: ait-multilingual-translate.php990 msgid "Select target languages first."991 msgstr ""992 993 #: ait-multilingual-translate.php994 msgid "No strings collected yet. Run the scanner or open translated pages once to collect frontend strings."995 msgstr ""996 997 #: ait-multilingual-translate.php998 msgid "Original"999 msgstr ""1000 1001 #: ait-multilingual-translate.php1002 msgid "Status:"1003 msgstr ""1004 1005 #: ait-multilingual-translate.php1006 msgid "AI Translate - Scanner"1007 msgstr ""1008 1009 #: ait-multilingual-translate.php1010 msgid "Collect site strings into the queue, then translate queued strings in controlled batches."1011 msgstr ""1012 1013 #: ait-multilingual-translate.php1014 msgid "Scanner and Queue"1015 msgstr ""1016 1017 #: ait-multilingual-translate.php1018 msgid "Auto scan on save:"1019 msgstr ""1020 1021 #: ait-multilingual-translate.php1022 msgid "Background processing:"1023 msgstr ""1024 1025 #: ait-multilingual-translate.php1026 msgid "Translation Mode:"1027 msgstr ""1028 1029 #: ait-multilingual-translate.php1030 msgid "Quality:"1031 msgstr ""1032 1033 #: ait-multilingual-translate.php1034 msgid "Cost optimization:"1035 msgstr ""1036 1037 #: ait-multilingual-translate.php1038 msgid "Last scan:"1039 msgstr ""1040 1041 #: ait-multilingual-translate.php1042 msgid "Last automatic scan:"1043 msgstr ""1044 1045 #: ait-multilingual-translate.php1046 msgid "Last queue run:"1047 msgstr ""1048 1049 #: ait-multilingual-translate.php1050 msgid "Routes:"1051 msgstr ""1052 1053 #: ait-multilingual-translate.php1054 msgid "Last background run:"1055 msgstr ""1056 1057 #: ait-multilingual-translate.php1058 msgid "Process translation queue"1059 msgstr ""1060 1061 #: ait-multilingual-translate.php1062 msgid "Translate All"1063 msgstr ""1064 1065 #: ait-multilingual-translate.php1066 msgid "Clear queued strings"1067 msgstr ""1068 1069 #: ait-multilingual-translate.php1070 msgid "Translate All uses the active provider and can consume API quota quickly. It runs in safe batches and may stop if provider quota, cooldown, or server time limits are reached."1071 msgstr ""1072 1073 #: ait-multilingual-translate.php1074 msgid "AI Translate - Debugger"1075 msgstr ""1076 1077 #: ait-multilingual-translate.php1078 msgid "Check provider access, current route, translation table, and the latest API error."1079 msgstr ""1080 1081 #: ait-multilingual-translate.php1082 msgid "Write extended debug log to a separate file"1083 msgstr ""1084 1085 #: ait-multilingual-translate.php1086 msgid "Save debug settings"1087 msgstr ""1088 1089 #: ait-multilingual-translate.php1090 msgid "Use this only while testing. The file stores route/API events as JSON lines and is cleared by Clear debug log."1091 msgstr ""1092 1093 #: ait-multilingual-translate.php1094 msgid "Last frontend route:"1095 msgstr ""1096 1097 #: ait-multilingual-translate.php1098 msgid "Last provider test:"1099 msgstr ""1100 1101 #: ait-multilingual-translate.php1102 msgid "HTTP status:"1103 msgstr ""1104 1105 #: ait-multilingual-translate.php1106 msgid "Route:"1107 msgstr ""1108 1109 #: ait-multilingual-translate.php1110 msgid "Debug log"1111 msgstr ""1112 1113 #: ait-multilingual-translate.php1114 msgid "Latest 20 events"1115 msgstr ""1116 1117 #: ait-multilingual-translate.php1118 msgid "Time"1119 msgstr ""1120 1121 #: ait-multilingual-translate.php1122 msgid "Context"1123 msgstr ""1124 1125 #: ait-multilingual-translate.php1126 msgid "Test provider translation"1127 msgstr ""1128 1129 #: ait-multilingual-translate.php1130 msgid "Download log file"1131 msgstr ""1132 1133 #: ait-multilingual-translate.php1134 msgid "Clear debug log"1135 msgstr ""1136 1137 #: ait-multilingual-translate.php1138 msgid "Clear provider cooldown"1139 msgstr ""1140 1141 #: ait-multilingual-translate.php1142 msgid "Debug log file was not found."1143 msgstr ""1144 1145 #: ait-multilingual-translate.php1146 msgid "Queue is empty."1147 msgstr ""1148 1149 #: ait-multilingual-translate.php1150 msgid "Translate All stopped after processing %1$d of %2$d queued translation(s): %3$s"1151 msgstr ""1152 1153 #: ait-multilingual-translate.php1154 msgid "Provider error."1155 msgstr ""1156 1157 #: ait-multilingual-translate.php1158 msgid "Translate All processed %1$d of %2$d queued translation(s) in %4$d safe batch(es). %3$d item(s) remain. Run it again or enable background processing; provider quota or server time limits may stop long runs."1159 msgstr ""1160 1161 #: ait-multilingual-translate.php1162 msgid "Translate All finished. Processed %1$d queued translation(s) in %2$d safe batch(es)."1163 msgstr ""1164 1165 #: ait-multilingual-translate.php1166 msgid "Scan strings, translate them in batches, then edit saved translations on the frontend."1167 msgstr ""1168 1169 #: ait-multilingual-translate.php1170 msgid "Upload updates are not safe for this install yet."1171 msgstr ""1172 1173 #: ait-multilingual-translate.php1174 msgid "Repair plugin folder"1175 msgstr ""1176 1177 #: ait-multilingual-translate.php1178 msgid "This copies the current plugin into wp-ai-translate, deactivates this temporary folder, and opens the normal activation link for the repaired copy. It does not delete the old folder automatically."1179 msgstr ""1180 1181 #: ait-multilingual-translate.php1182 msgid "Settings saved."1183 msgstr ""1184 1185 #: ait-multilingual-translate.php1186 msgid "AIT Multilingual Translate is using the single-file queued translation engine. Pages use saved translations first; new strings are collected into the queue for batch translation."1187 msgstr ""1188 1189 #: ait-multilingual-translate.php1190 msgid "Last translation error:"1191 msgstr ""1192 1193 #: ait-multilingual-translate.php1194 msgid "AIT Multilingual Translate sections"1195 msgstr ""1196 1197 #: ait-multilingual-translate.php1198 msgid "General"1199 msgstr ""1200 1201 #: ait-multilingual-translate.php1202 msgid "Providers"1203 msgstr ""1204 1205 #: ait-multilingual-translate.php1206 msgid "SEO & URLs"1207 msgstr ""1208 1209 #: ait-multilingual-translate.php1210 msgid "Queue"1211 msgstr ""1212 1213 #: ait-multilingual-translate.php1214 msgid "Interface mode"1215 msgstr ""1216 1217 #: ait-multilingual-translate.php1218 msgid "Basic"1219 msgstr ""1220 1221 #: ait-multilingual-translate.php1222 msgid "Advanced"1223 msgstr ""1224 1225 #: ait-multilingual-translate.php1226 msgid "Basic mode shows the setup needed for everyday translation. Advanced mode reveals queue diagnostics, provider internals, scanner details, routes, and logs."1227 msgstr ""1228 1229 #: ait-multilingual-translate.php1230 msgid "Build status"1231 msgstr ""1232 1233 #: ait-multilingual-translate.php1234 msgid "Public Beta preparation build."1235 msgstr ""1236 1237 #: ait-multilingual-translate.php1238 msgid "Feature flag helpers are prepared for future package variants. All current beta features remain enabled."1239 msgstr ""1240 1241 #: ait-multilingual-translate.php1242 msgid "This is the language your original site content is written in. Auto uses the WordPress site language."1243 msgstr ""1244 1245 #: ait-multilingual-translate.php1246 msgid "Select only the languages you want visitors to use. Every selected target language creates its own saved translation rows."1247 msgstr ""1248 1249 #: ait-multilingual-translate.php1250 msgid "The provider used for new queue translations. Already saved manual/published translations are reused and are not sent again."1251 msgstr ""1252 1253 #: ait-multilingual-translate.php1254 msgid "Provider capabilities"1255 msgstr ""1256 1257 #: ait-multilingual-translate.php1258 msgid "Active"1259 msgstr ""1260 1261 #: ait-multilingual-translate.php1262 msgid "Planned"1263 msgstr ""1264 1265 #: ait-multilingual-translate.php1266 msgid "Provider cards show active integrations plus planned architecture targets. Planned providers are documented but are not enabled until their API layer is added in a future release."1267 msgstr ""1268 1269 #: ait-multilingual-translate.php1270 msgid "Create a key in the OpenAI dashboard, then paste it here."1271 msgstr ""1272 1273 #: ait-multilingual-translate.php1274 msgid "Open API keys page"1275 msgstr ""1276 1277 #: ait-multilingual-translate.php1278 msgid "Use a model available to your OpenAI account. If quota/billing is missing, the provider returns 429."1279 msgstr ""1280 1281 #: ait-multilingual-translate.php1282 msgid "Create a Gemini key in Google AI Studio, then paste it here."1283 msgstr ""1284 1285 #: ait-multilingual-translate.php1286 msgid "Open Google AI Studio API keys"1287 msgstr ""1288 1289 #: ait-multilingual-translate.php1290 msgid "Gemini model"1291 msgstr ""1292 1293 #: ait-multilingual-translate.php1294 msgid "For testing, use a small batch size because Gemini free tier limits can be very low."1295 msgstr ""1296 1297 #: ait-multilingual-translate.php1298 msgid "Create an xAI API key, then paste it here."1299 msgstr ""1300 1301 #: ait-multilingual-translate.php1302 msgid "Open xAI Console"1303 msgstr ""1304 1305 #: ait-multilingual-translate.php1306 msgid "Grok model"1307 msgstr ""1308 1309 #: ait-multilingual-translate.php1310 msgid "Use a model enabled for your xAI account. A 403 usually means the key cannot access the selected model or billing is not ready."1311 msgstr ""1312 1313 #: ait-multilingual-translate.php1314 msgid "Uses Google Cloud Translation Basic v2. Enable Cloud Translation API in Google Cloud and use an API key with billing/quota configured."1315 msgstr ""1316 1317 #: ait-multilingual-translate.php1318 msgid "Google docs"1319 msgstr ""1320 1321 #: ait-multilingual-translate.php1322 msgid "DeepL has separate api-free.deepl.com and api.deepl.com endpoints. Pick the endpoint that matches your key."1323 msgstr ""1324 1325 #: ait-multilingual-translate.php1326 msgid "DeepL docs"1327 msgstr ""1328 1329 #: ait-multilingual-translate.php1330 msgid "DeepL plan"1331 msgstr ""1332 1333 #: ait-multilingual-translate.php1334 msgid "api-free.deepl.com endpoint"1335 msgstr ""1336 1337 #: ait-multilingual-translate.php1338 msgid "api.deepl.com endpoint"1339 msgstr ""1340 1341 #: ait-multilingual-translate.php1342 msgid "Match this setting to the endpoint assigned to your DeepL API key."1343 msgstr ""1344 1345 #: ait-multilingual-translate.php1346 msgid "Translation Style / Tone of Voice"1347 msgstr ""1348 1349 #: ait-multilingual-translate.php1350 msgid "Set the global translation style for prompt-based AI providers. Per content type modes for pages, posts, WooCommerce products, SEO meta, buttons, and short strings are planned for a future version."1351 msgstr ""1352 1353 #: ait-multilingual-translate.php1354 msgid "Global Translation Mode"1355 msgstr ""1356 1357 #: ait-multilingual-translate.php1358 msgid "Tone of Voice affects AI-based translations only. Use Neutral mode for accurate translation, SEO mode for search-oriented content, and eCommerce mode for product pages."1359 msgstr ""1360 1361 #: ait-multilingual-translate.php1362 msgid "Tone of Voice is applied to prompt-based AI providers: OpenAI, Gemini, Grok/xAI, Claude, YandexGPT, Mistral, DeepSeek, Qwen, and Baidu Qianfan. Google Translate, DeepL, and Yandex Translate ignore this setting."1363 msgstr ""1364 1365 #: ait-multilingual-translate.php1366 msgid "Custom translation instruction"1367 msgstr ""1368 1369 #: ait-multilingual-translate.php1370 msgid "Example: Translate naturally for Georgian customers, keep product names unchanged, use friendly but professional tone."1371 msgstr ""1372 1373 #: ait-multilingual-translate.php1374 msgid "Used only when Translation Mode is Custom Prompt. Maximum 500 characters."1375 msgstr ""1376 1377 #: ait-multilingual-translate.php1378 msgid "AI Cost Optimization"1379 msgstr ""1380 1381 #: ait-multilingual-translate.php1382 msgid "Use cheap models by default, deduplicate strings before provider calls, reuse translation memory, and keep safety limits enabled while testing provider quota."1383 msgstr ""1384 1385 #: ait-multilingual-translate.php1386 msgid "Translation quality mode"1387 msgstr ""1388 1389 #: ait-multilingual-translate.php1390 msgid "Cheap is recommended for Public Beta testing. Balanced and Premium are guidance modes for choosing provider models; custom model fields still remain under your control."1391 msgstr ""1392 1393 #: ait-multilingual-translate.php1394 msgid "Recommended default models"1395 msgstr ""1396 1397 #: ait-multilingual-translate.php1398 msgid "OpenAI Cheap: gpt-4o-mini"1399 msgstr ""1400 1401 #: ait-multilingual-translate.php1402 msgid "OpenAI Premium: gpt-4o"1403 msgstr ""1404 1405 #: ait-multilingual-translate.php1406 msgid "Gemini Cheap: Flash model"1407 msgstr ""1408 1409 #: ait-multilingual-translate.php1410 msgid "Gemini Premium: Pro model"1411 msgstr ""1412 1413 #: ait-multilingual-translate.php1414 msgid "Grok Cheap: lightweight/mini model where available"1415 msgstr ""1416 1417 #: ait-multilingual-translate.php1418 msgid "Claude Cheap: Haiku model where available"1419 msgstr ""1420 1421 #: ait-multilingual-translate.php1422 msgid "Mistral Cheap: small/fast model"1423 msgstr ""1424 1425 #: ait-multilingual-translate.php1426 msgid "DeepSeek Cheap: chat/standard model"1427 msgstr ""1428 1429 #: ait-multilingual-translate.php1430 msgid "The active Public Beta providers keep using the model field you set above. Planned provider model names are shown as release-planning guidance only."1431 msgstr ""1432 1433 #: ait-multilingual-translate.php1434 msgid "Temperature"1435 msgstr ""1436 1437 #: ait-multilingual-translate.php1438 msgid "Default 0.1 keeps translations stable and predictable for AI providers."1439 msgstr ""1440 1441 #: ait-multilingual-translate.php1442 msgid "Quota control"1443 msgstr ""1444 1445 #: ait-multilingual-translate.php1446 msgid "Daily character limit"1447 msgstr ""1448 1449 #: ait-multilingual-translate.php1450 msgid "Local safety limit for the active provider. 0 means no plugin-side daily stop."1451 msgstr ""1452 1453 #: ait-multilingual-translate.php1454 msgid "Monthly character limit"1455 msgstr ""1456 1457 #: ait-multilingual-translate.php1458 msgid "Local safety limit for the active provider. Provider billing limits still need to be configured in OpenAI/Google/DeepL/xAI accounts."1459 msgstr ""1460 1461 #: ait-multilingual-translate.php1462 msgid "Max characters per request"1463 msgstr ""1464 1465 #: ait-multilingual-translate.php1466 msgid "0 means no plugin-side per-request character stop. Set a limit to avoid accidentally sending very large batches."1467 msgstr ""1468 1469 #: ait-multilingual-translate.php1470 msgid "Estimated cost limit per request"1471 msgstr ""1472 1473 #: ait-multilingual-translate.php1474 msgid "0 means no estimated-cost stop. Estimates are approximate and provider billing remains authoritative."1475 msgstr ""1476 1477 #: ait-multilingual-translate.php1478 msgid "Enable frontend translation output"1479 msgstr ""1480 1481 #: ait-multilingual-translate.php1482 msgid "Collect missing strings into the translation queue"1483 msgstr ""1484 1485 #: ait-multilingual-translate.php1486 msgid "Automatically scan saved/updated content for new strings"1487 msgstr ""1488 1489 #: ait-multilingual-translate.php1490 msgid "Automatically process the translation queue in the background"1491 msgstr ""1492 1493 #: ait-multilingual-translate.php1494 msgid "Translate missing strings during page load (slower, use only for testing)"1495 msgstr ""1496 1497 #: ait-multilingual-translate.php1498 msgid "Recommended production mode: frontend output on, queue missing strings on, scan on save on, page-load translation off. Turn background processing on only when your API quota/billing is ready."1499 msgstr ""1500 1501 #: ait-multilingual-translate.php1502 msgid "How many strings are sent in one API request. Use 3-5 for free/test keys, 10-25 for normal paid keys, higher only when quota is confirmed."1503 msgstr ""1504 1505 #: ait-multilingual-translate.php1506 msgid "URL mode"1507 msgstr ""1508 1509 #: ait-multilingual-translate.php1510 msgid "Directory URLs: /ka/about/"1511 msgstr ""1512 1513 #: ait-multilingual-translate.php1514 msgid "Query URLs: /about/?lang=ka"1515 msgstr ""1516 1517 #: ait-multilingual-translate.php1518 msgid "Directory URLs are better for SEO, but require WordPress permalinks and rewrite rules. Query URLs are simpler and useful while testing."1519 msgstr ""1520 1521 #: ait-multilingual-translate.php1522 msgid "When enabled, the original language stays on normal URLs while only translated languages use a language code."1523 msgstr ""1524 1525 #: ait-multilingual-translate.php1526 msgid "Shortcodes:"1527 msgstr ""1528 1529 #: ait-multilingual-translate.php1530 msgid "Dropdown is compact for headers. List is useful in footers, sidebars, and menu-like layouts."1531 msgstr ""1532 1533 #: ait-multilingual-translate.php1534 msgid "Choose what visitors see inside the selector, for example Georgian, KA, or both."1535 msgstr ""1536 1537 #: ait-multilingual-translate.php1538 msgid "Try to show in header"1539 msgstr ""1540 1541 #: ait-multilingual-translate.php1542 msgid "Automatic placement is optional. You can also place the selector with a shortcode, widget, or theme/menu area."1543 msgstr ""1544 1545 #: ait-multilingual-translate.php1546 msgid "Selector custom CSS"1547 msgstr ""1548 1549 #: ait-multilingual-translate.php1550 msgid "Optional CSS loaded on the frontend only for styling the language selector."1551 msgstr ""1552 1553 #: ait-multilingual-translate.php1554 msgid "Useful classes:"1555 msgstr ""1556 1557 #: ait-multilingual-translate.php1558 msgid "When enabled, logged-in administrators see the AI Translate button in the admin bar on translated pages and can save manual corrections."1559 msgstr ""1560 1561 #: ait-multilingual-translate.php1562 msgid "Buy Me a Coffee URL"1563 msgstr ""1564 1565 #: ait-multilingual-translate.php1566 msgid "PayPal donation URL"1567 msgstr ""1568 1569 #: ait-multilingual-translate.php1570 msgid "String Scanner"1571 msgstr ""1572 1573 #: ait-multilingual-translate.php1574 msgid "Scan posts, pages, products, menus, widgets, taxonomy terms, SEO meta, and public custom fields. New strings are added to the queue once and are translated from there."1575 msgstr ""1576 1577 #: ait-multilingual-translate.php1578 msgid "Targets:"1579 msgstr ""1580 1581 #: ait-multilingual-translate.php1582 msgid "Queued strings"1583 msgstr ""1584 1585 #: ait-multilingual-translate.php1586 msgid "Saved translations"1587 msgstr ""1588 1589 #: ait-multilingual-translate.php1590 msgid "Translation Queue"1591 msgstr ""1592 1593 #: ait-multilingual-translate.php1594 msgid "Frontend pages now use saved translations only. New strings are collected here and translated in batches, so visitors do not wait for the AI provider."1595 msgstr ""1596 1597 #: ait-multilingual-translate.php1598 msgid "Provider:"1599 msgstr ""1600 1601 #: ait-multilingual-translate.php1602 msgid "Sample:"1603 msgstr ""1604 1605 #: ait-multilingual-translate.php1606 msgid "Duration:"1607 msgstr "" -
ait-multilingual-translate/trunk/readme.txt
r3665107 r3761123 5 5 Requires at least: 6.0 6 6 Tested up to: 7.1 7 Stable tag: 0.4. 07 Stable tag: 0.4.2 8 8 Requires PHP: 7.4 9 9 License: GPLv2 or later … … 28 28 * Store translations locally and reuse them until source text changes. 29 29 * Scan posts, pages, products, menus, widgets, taxonomy terms, SEO meta, and public custom fields. 30 * Choose Scanner Sources by Frontend, Shared Frontend, Template/Backend, Technical, and Metadata classifications and preview each translation unit before queueing. 31 * Export and import Translation Matrix entries as CSV, PO, or MO. 32 * Run Translate All in a persistent background worker with pause, resume, stop, progress, and configurable Cron scheduling. 33 * Select Russian or Georgian for the plugin interface independently from website and target languages. 30 34 * Process translation work in a queue so visitors do not wait for provider calls. 31 35 * Edit saved translations from the frontend as an administrator. … … 173 177 174 178 == Changelog == 179 180 = 0.4.2 = 181 182 * Prevented duplicate flattened post_content scanning when structured Elementor Page Content is enabled. 183 * Preserved separate widget fields and stable source identities, including reordered Elementor repeater items. 184 * Added persistent field and whole-object exclusions with Include again controls in Scanner Preview. 185 * Added queue-only removal, bulk actions, source/object/status/inclusion/search filters and pagination. 186 * Blocked legacy flattened Elementor queued jobs before processing while preserving saved Translation Memory. 187 188 = 0.4.1 = 189 190 * Added Scanner Sources, classifications, counts, Preview filters, and per-unit include/exclude controls. 191 * Added structured Elementor field scanning and explicit WooCommerce Products scope. 192 * Added background Translate All worker controls and configurable Cron scheduling. 193 * Added CSV, PO, and MO Translation Matrix import/export. 194 * Added independent Russian and Georgian plugin interface language packs. 195 * Added Dashboard overview, safe provider presets, and local RPM/TPM/RPD and character limits. 196 * Added opt-in queue, runtime, orphan, and obsolete-scanner maintenance with dry-run confirmation. 197 * Preserves published, draft, manual, imported, and still-valid Translation Memory entries during upgrades. 198 * Fixed Russian/Georgian locale separation, target-script validation, worker progress, and debug downloads. 199 * Separated provider cooldown, local quota, queue-lock, and fatal worker states for safer background recovery. 200 * Updated Setup Wizard, POT, documentation, tests, and release safety checks. 175 201 176 202 = 0.4.0 = … … 350 376 == Upgrade Notice == 351 377 378 = 0.4.2 = 379 Scanner fixes and persistent exclusion controls. Rebuild the scanner queue to use structured Elementor fields. Existing published, manual and imported translations are preserved; old queued flattened content is blocked, not automatically deleted. 380 381 = 0.4.1 = 382 Adds opt-in scanner maintenance and queue rebuilding without automatically deleting or overwriting existing translations. 383 352 384 = 0.4.0 = 353 385 Adds protected-content restoration, regional locales, a separate frontend default language, and seven provider adapters. Back up settings and test provider credentials on staging before production use.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)