Plugin Directory

Changeset 3761123


Ignore:
Timestamp:
10/23/2026 07:44:09 AM (45 hours ago)
Author:
itdesignsro
Message:

Prepare AIT Multilingual Translate 0.4.2 release

Location:
ait-multilingual-translate/trunk
Files:
6 added
9 edited

Legend:

Unmodified
Added
Removed
  • ait-multilingual-translate/trunk/CHANGELOG.md

    r3665107 r3761123  
    44
    55The 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.
    663
    764## [0.4.0] - 2026-08-20
  • ait-multilingual-translate/trunk/README.md

    r3665107 r3761123  
    44[![PHP](https://img.shields.io/badge/PHP-7.4%2B-777bb4.svg)](https://www.php.net/)
    55[![License](https://img.shields.io/badge/License-GPLv2%20or%20later-46a546.svg)](LICENSE)
    6 [![Version](https://img.shields.io/badge/Version-0.4.0-blue.svg)](CHANGELOG.md)
     6[![Version](https://img.shields.io/badge/Version-0.4.2-blue.svg)](CHANGELOG.md)
    77[![Downloads](https://img.shields.io/badge/Downloads-coming%20soon-lightgrey.svg)](https://wordpress.org/plugins/ait-multilingual-translate/)
    88
    … …  
    2020- Translation queue for controlled, batch-based translation workflows.
    2121- 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.
    2226- Translation Matrix for searching, reviewing, editing, saving, importing, and exporting translations.
    2327- Frontend Editor for administrator-only visual translation edits on the live site.
  • ait-multilingual-translate/trunk/ait-multilingual-translate.php

    r3665107 r3761123  
    44 * Description: AI-powered multilingual translation plugin compatible with WooCommerce and Elementor, with SEO-friendly URLs and frontend editing support.
    55 * Plugin URI: https://wp-ai.itdesign.biz
    6  * Version: 0.4.0
     6 * Version: 0.4.2
    77 * Author: sotter IT Design
    88 * Author URI: https://wp-ai.itdesign.biz
    … …  
    1919}
    2020
    21 define('AITMT_VERSION', '0.4.0');
     21define('AITMT_VERSION', '0.4.2');
    2222define('AITMT_PLUGIN_FILE', __FILE__);
    2323define('AITMT_PUBLIC_NAME', 'AIT Multilingual Translate');
    … …  
    3535require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-protected-content.php';
    3636require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-locales.php';
     37require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-interface-language.php';
     38require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-scanner.php';
    3739require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-provider-registry.php';
    3840require_once AITMT_PLUGIN_DIR . 'includes/class-aitmt-runtime-maintenance.php';
    3941AITMT_Runtime_Maintenance::bootstrap();
     42AITMT_Interface_Language::bootstrap();
    4043
    4144if (!defined('AITMT_USE_LEGACY_FULL_ENGINE') || !AITMT_USE_LEGACY_FULL_ENGINE) {
    … …  
    9699    add_action('admin_post_aitmt_onboarding_finish', 'aitmt_fallback_onboarding_finish_handler');
    97100    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'));
    98104    add_action('admin_post_aitmt_save_manual_translation', 'aitmt_fallback_save_manual_translation_handler');
    99105    add_action('admin_post_aitmt_save_translation_matrix', 'aitmt_fallback_save_translation_matrix_handler');
    … …  
    102108    add_action('admin_post_aitmt_process_queue', 'aitmt_fallback_process_queue_handler');
    103109    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');
    104112    add_action('admin_post_aitmt_clear_queue', 'aitmt_fallback_clear_queue_handler');
    105113    add_action('admin_post_aitmt_clear_unknown_queue', 'aitmt_fallback_clear_unknown_queue_handler');
    … …  
    134142    add_action('save_post', 'aitmt_fallback_scan_saved_post', 20, 3);
    135143    add_action('aitmt_fallback_process_queue_event', 'aitmt_fallback_cron_process_queue');
     144    add_action('aitmt_fallback_worker_event', 'aitmt_fallback_cron_process_queue');
    136145    add_filter('cron_schedules', 'aitmt_fallback_cron_schedules');
    137146    add_filter('wp_nav_menu_objects', 'aitmt_fallback_nav_menu_objects', 20, 2);
    … …  
    590599    $input = is_array($raw_input) ? $raw_input : array();
    591600    $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    }
    592606    $options = aitmt_fallback_sanitize($input);
    593607
    … …  
    775789{
    776790    wp_clear_scheduled_hook('aitmt_fallback_process_queue_event');
     791    wp_clear_scheduled_hook('aitmt_fallback_worker_event');
    777792    AITMT_Runtime_Maintenance::deactivate();
    778793    flush_rewrite_rules(false);
    … …  
    14901505    $text = (string) $text;
    14911506    $patterns = array(
     1507        '/authorization\s*:\s*bearer\s+[^,\s"\']+/i',
    14921508        '/sk-[A-Za-z0-9_\-]{8,}/i',
    14931509        '/xai-[A-Za-z0-9_\-]{8,}/i',
    … …  
    16381654    $options = aitmt_fallback_sanitize(wp_parse_args($input, $old_options));
    16391655    update_option('aitmt_options', $options, false);
     1656    aitmt_fallback_sync_cron_schedule();
    16401657    aitmt_fallback_register_rewrites();
    16411658    flush_rewrite_rules(false);
    … …  
    17051722    $url_mode = isset($input['url_mode']) && 'query' === $input['url_mode'] ? 'query' : 'directory';
    17061723    $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');
    17071725    $allowed_providers = AITMT_Provider_Registry::ids();
    17081726    $provider = isset($input['provider']) ? sanitize_key((string) $input['provider']) : 'openai';
    … …  
    17171735    $retry_limit = isset($input['retry_limit']) ? absint($input['retry_limit']) : 3;
    17181736    $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));
    17191743    $daily_limit = isset($input['quota_daily_chars']) ? absint($input['quota_daily_chars']) : 5000;
    17201744    $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)));
    17211748    $max_chars_per_request = isset($input['max_chars_per_request']) ? absint($input['max_chars_per_request']) : 2000;
    17221749    $estimated_cost_limit = isset($input['estimated_cost_limit']) ? (float) str_replace(',', '.', sanitize_text_field((string) $input['estimated_cost_limit'])) : 0.02;
    … …  
    17541781        'enabled_languages' => array_values(array_unique($languages)),
    17551782        'admin_mode' => $admin_mode,
     1783        'interface_language' => $interface_language,
    17561784        'provider' => $provider,
    17571785        'openai_api_key' => aitmt_fallback_sanitize_secret($input, 'openai_api_key', $stored_options),
    … …  
    17821810        'quota_daily_chars' => $daily_limit,
    17831811        '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,
    17841815        'max_chars_per_request' => $max_chars_per_request,
    17851816        'estimated_cost_limit' => $estimated_cost_limit,
    … …  
    18041835        'runtime_excluded_source_types' => $runtime_excluded_source_types,
    18051836        '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),
    18061840        'scan_on_save' => empty($input['scan_on_save']) ? '0' : '1',
    18071841        '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,
    18081845        'draft_mode' => empty($input['draft_mode']) ? '0' : '1',
    18091846        'translate_attributes' => empty($input['translate_attributes']) ? '0' : '1',
    … …  
    20022039        'enabled_languages' => array(),
    20032040        'admin_mode' => 'basic',
     2041        'interface_language' => 'auto',
    20042042        'provider' => 'openai',
    20052043        'openai_api_key' => '',
    … …  
    20302068        'quota_daily_chars' => 5000,
    20312069        'quota_monthly_chars' => 50000,
     2070        'quota_requests_per_minute' => 5,
     2071        'quota_tokens_per_minute' => 10000,
     2072        'quota_requests_per_day' => 100,
    20322073        'max_chars_per_request' => 2000,
    20332074        'estimated_cost_limit' => 0.02,
    … …  
    20522093        'runtime_excluded_source_types' => '',
    20532094        'runtime_translate_woocommerce_private' => '0',
     2095        'scanner_sources' => AITMT_Scanner::defaults(),
     2096        'scanner_exclusions' => array(),
     2097        'scanner_object_exclusions' => array(),
    20542098        'scan_on_save' => '0',
    20552099        'cron_enabled' => '0',
     2100        'cron_interval' => 300,
     2101        'cron_max_batches' => 2,
     2102        'cron_max_runtime' => 20,
    20562103        'draft_mode' => '0',
    20572104        'translate_attributes' => '1',
    … …  
    20922139        'draft_mode' => '0',
    20932140        'translate_attributes' => '1',
     2141    );
     2142}
     2143
     2144function 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        ),
    20942201    );
    20952202}
    … …  
    29473054function aitmt_fallback_translate_with_provider($segments, $source_language, $target_language, $context = array())
    29483055{
     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
    29493064    $provider = aitmt_fallback_active_provider();
    29503065    $deduped = aitmt_fallback_dedupe_segments((array) $segments);
    … …  
    29803095        ? $adapter->translate_batch($segments_for_provider, $source_language, $target_language)
    29813096        : 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    }
    29823113
    29833114    if (!is_wp_error($result)) {
    … …  
    31823313    $monthly_limit = isset($options['quota_monthly_chars']) ? absint($options['quota_monthly_chars']) : 0;
    31833314    $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);
    31843318    $cost_limit = isset($options['estimated_cost_limit']) ? (float) $options['estimated_cost_limit'] : 0.0;
    31853319    $day = gmdate('Ymd');
    31863320    $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    }
    31873355
    31883356    if ($request_char_limit > 0 && $chars > $request_char_limit) {
    … …  
    32613429{
    32623430    $entry = array(
    3263         'created_at' => current_time('mysql'),
     3431        'created_at' => current_time('mysql', true),
    32643432        'provider' => sanitize_key((string) $provider),
    32653433        'source_type' => sanitize_key((string) $source_type),
    … …  
    32673435        'target_language' => aitmt_fallback_normalize_language($target_language),
    32683436        'characters' => aitmt_fallback_provider_char_count((array) $segments),
     3437        'tokens' => aitmt_fallback_estimate_tokens_for_segments((array) $segments),
    32693438        'batch_id' => sanitize_key((string) $batch_id),
    32703439        'status' => sanitize_key((string) $status),
    … …  
    55505719    }
    55515720
    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);
    55575727
    55585728    if (empty($missing)) {
    … …  
    55985768
    55995769    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)) {
    56025773            $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);
    56045775        }
    56055776
    … …  
    56805851        'temperature' => aitmt_fallback_translation_temperature(),
    56815852        '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),
    56855855            $translation_instruction
    56865856        ),
    … …  
    58215991
    58225992    $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),
    58265995        $translation_instruction,
    58275996        wp_json_encode($items, JSON_UNESCAPED_UNICODE)
    … …  
    59406109            'body' => wp_json_encode(array(
    59416110                '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'),
    59446113                'format' => 'text',
    59456114            )),
    … …  
    60506219function aitmt_fallback_deepl_language_code($language, $is_source = false)
    60516220{
    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);
    60596222}
    60606223
    60616224function aitmt_fallback_grok_translate_batch($segments, $source_language, $target_language)
    60626225{
    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);
    60666227}
    60676228
    … …  
    60806241}
    60816242
    6082 function aitmt_fallback_provider_prompt($segments, $target_language)
     6243function aitmt_fallback_provider_prompt($segments, $source_language, $target_language)
    60836244{
    60846245    $instruction = aitmt_fallback_translation_instruction();
     6246    $locale_instruction = AITMT_Locales::translation_instruction($source_language, $target_language, aitmt_fallback_languages());
    60856247
    60866248    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,
    60896251        $instruction,
    60906252        wp_json_encode(aitmt_fallback_provider_prompt_items($segments), JSON_UNESCAPED_UNICODE)
    … …  
    61246286}
    61256287
    6126 function aitmt_fallback_openai_compatible_translate_batch($provider, $endpoint, $segments, $target_language)
     6288function aitmt_fallback_openai_compatible_translate_batch($provider, $endpoint, $segments, $source_language, $target_language)
    61276289{
    61286290    $provider = sanitize_key((string) $provider);
    … …  
    61466308            array(
    61476309                'role' => 'user',
    6148                 'content' => aitmt_fallback_provider_prompt($segments, $target_language),
     6310                'content' => aitmt_fallback_provider_prompt($segments, $source_language, $target_language),
    61496311            ),
    61506312        ),
    … …  
    61916353function aitmt_fallback_mistral_translate_batch($segments, $source_language, $target_language)
    61926354{
    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);
    61966356}
    61976357
    61986358function aitmt_fallback_deepseek_translate_batch($segments, $source_language, $target_language)
    61996359{
    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);
    62036361}
    62046362
    62056363function aitmt_fallback_qwen_translate_batch($segments, $source_language, $target_language)
    62066364{
    6207     unset($source_language);
    62086365    $region = aitmt_fallback_provider_setting('qwen', 'region', 'us');
    62096366    $endpoints = array(
    … …  
    62146371    $endpoint = isset($endpoints[$region]) ? $endpoints[$region] : $endpoints['us'];
    62156372
    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);
    62176374}
    62186375
    62196376function aitmt_fallback_baidu_translate_batch($segments, $source_language, $target_language)
    62206377{
    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);
    62246379}
    62256380
    62266381function aitmt_fallback_claude_translate_batch($segments, $source_language, $target_language)
    62276382{
    6228     unset($source_language);
    62296383    $api_key = aitmt_fallback_provider_key('claude');
    62306384    if (empty($api_key)) {
    … …  
    62496403                        array(
    62506404                            'role' => 'user',
    6251                             'content' => aitmt_fallback_provider_prompt($segments, $target_language),
     6405                            'content' => aitmt_fallback_provider_prompt($segments, $source_language, $target_language),
    62526406                        ),
    62536407                    ),
    … …  
    62966450                array(
    62976451                    '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'),
    63006454                    'format' => 'PLAIN_TEXT',
    63016455                    'texts' => array_values($segments),
    … …  
    63386492function aitmt_fallback_yandexgpt_translate_batch($segments, $source_language, $target_language)
    63396493{
    6340     unset($source_language);
    63416494    $api_key = aitmt_fallback_provider_key('yandexgpt');
    63426495    $folder_id = aitmt_fallback_provider_setting('yandexgpt', 'folder_id');
    … …  
    63646517                        array(
    63656518                            'role' => 'user',
    6366                             'text' => aitmt_fallback_provider_prompt($segments, $target_language),
     6519                            'text' => aitmt_fallback_provider_prompt($segments, $source_language, $target_language),
    63676520                        ),
    63686521                    ),
    … …  
    64616614}
    64626615
    6463 function aitmt_fallback_get_existing_translations($segments, $source_language, $target_language, $allow_locale_fallback = true)
     6616function aitmt_fallback_get_existing_translations($segments, $source_language, $target_language, $allow_locale_fallback = true, $include_drafts = true)
    64646617{
    64656618    global $wpdb;
    … …  
    64876640    // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Dynamic IN placeholders and table name are controlled above; all values are prepared.
    64886641    $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')",
    64906643        ...$args
    64916644    );
    … …  
    64956648
    64966649    foreach ((array) $rows as $row) {
     6650        if (!$include_drafts && 'draft' === (string) ($row['status'] ?? '')) {
     6651            continue;
     6652        }
    64976653        $current_hash = aitmt_fallback_translation_hash((string) ($row['source_text'] ?? ''));
    64986654        if (isset($segments[$current_hash])) {
    … …  
    65186674                    $source_language,
    65196675                    $base_language,
    6520                     false
     6676                    false,
     6677                    $include_drafts
    65216678                );
    65226679                $map = array_replace($base_translations, $map);
    … …  
    66846841    $hash = aitmt_fallback_translation_hash($source_text);
    66856842
     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
    66866849    $legacy_hash = aitmt_fallback_legacy_translation_hash($source_text);
    66876850    if ($legacy_hash !== $hash) {
    … …  
    67216884    $result = $wpdb->query($query);
    67226885
     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
    67236898    if (1 === (int) $result && 'page_load' === $source_type) {
    67246899        AITMT_Runtime_Maintenance::increment_runtime_count($target_language);
    … …  
    67896964        'widget',
    67906965        'elementor',
     6966        'elementor_library',
     6967        'sectionbuilder',
     6968        'litho-mega-menu',
     6969        'page_meta',
    67916970        'wpfd_file',
    67926971        'page_load',
    … …  
    67966975        'manual',
    67976976        'import',
     6977        'provider',
     6978        'frontend_editor',
     6979        'shortcode',
     6980        'runtime',
    67986981        'legacy_runtime',
    67996982    );
    … …  
    68497032function aitmt_fallback_normalize_field_key($field_key)
    68507033{
    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);
    68547036}
    68557037
    … …  
    70627244}
    70637245
     7246function 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
    70647275function aitmt_fallback_mark_processing_rows_after_failure($table, $rows, $reason)
    70657276{
    … …  
    71077318        ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name and placeholders are controlled; values are prepared above.
    71087319    }
     7320
     7321    return array(
     7322        'failed' => count($failed_ids),
     7323        'requeued' => count($retry_ids),
     7324    );
    71097325}
    71107326
    … …  
    74467662                'processed' => 0,
    74477663                'locked' => true,
     7664                'error_code' => $lock->get_error_code(),
     7665                'error_status' => absint($lock->get_error_data('status')),
    74487666            );
    74497667        }
    … …  
    75097727        $options = aitmt_fallback_options();
    75107728        $processed = 0;
     7729        $skipped = 0;
     7730        $cached = 0;
     7731        $failed = 0;
    75117732        $routes = array();
    75127733        $now = current_time('mysql');
    … …  
    75397760                }
    75407761
     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
    75417772                if (!aitmt_fallback_is_translatable_text($row['source_text'] ?? '', true, array(
    75427773                    'source_type' => $row['source_type'] ?? '',
    … …  
    75567787
    75577788            if (!empty($invalid_rows)) {
    7558                 aitmt_fallback_mark_processing_rows_failed($table, $invalid_rows, 'Skipped unsafe or unknown queue 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.');
    75597790            }
    75607791
    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)) {
    75657798                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(
    75677805                        $table,
    75687806                        array(
    7569                             'status' => 'published',
     7807                            'translated_text' => (string) $known_existing[$hash],
     7808                            'status' => $cached_status,
     7809                            'provider' => 'translation_memory',
    75707810                            'failed_reason' => '',
    75717811                            'batch_id' => '',
    … …  
    75737813                        ),
    75747814                        array('id' => (int) $row['id']),
    7575                         array('%s', '%s', '%s', '%s'),
     7815                        array('%s', '%s', '%s', '%s', '%s', '%s'),
    75767816                        array('%d')
    75777817                    ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is validated by aitmt_fallback_translation_table_sql().
     7818                    if (false !== $updated) {
     7819                        $cached++;
     7820                    }
    75787821                }
     7822            }
     7823
     7824            if (empty($segments)) {
    75797825                continue;
    75807826            }
    … …  
    75877833            if (is_wp_error($translated)) {
    75887834                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());
    75907836
    75917837                return array(
    … …  
    75977843                    'routes' => implode(', ', $routes),
    75987844                    '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')),
    75997851                );
    76007852            }
    … …  
    76077859                if (!isset($translated[$row['source_hash']])) {
    76087860                    aitmt_fallback_mark_processing_rows_after_failure($table, array($row), 'Provider did not return a translation for this string.');
     7861                    $failed++;
    76097862                    continue;
    76107863                }
    … …  
    76357888            'message' => sprintf('Processed %d queued translation(s) across %d route(s).', $processed, count($groups)),
    76367889            'processed' => $processed,
     7890            'skipped' => $skipped,
     7891            'cached' => $cached,
     7892            'failed' => $failed,
    76377893            'routes' => implode(', ', $routes),
    76387894            'stale_requeued' => absint($stale['requeued'] ?? 0),
    … …  
    76497905    $post_id = absint($post_id);
    76507906    $post = get_post($post_id);
    7651 
    76527907    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);
    76577915    }
    76587916
    76597917    $source_language = aitmt_fallback_source_language();
    76607918    $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();
    76767920    $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;
    76817928            }
     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            }
    76827935        }
    76837936    }
    76847937
    76857938    return array(
    7686         'strings' => count($strings),
     7939        'strings' => count($unique),
    76877940        'queued' => $queued,
    76887941    );
    … …  
    77267979}
    77277980
     7981function 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
     8003function 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
     8029function 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
     8061function 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
     8103function 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
    77288127function aitmt_fallback_cron_schedules($schedules)
    77298128{
    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,
    77348142        );
    77358143    }
    7736 
    77378144    return $schedules;
    77388145}
    … …  
    77438150    $hook = 'aitmt_fallback_process_queue_event';
    77448151    $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;
    77458155
    77468156    if ('1' === $options['cron_enabled']) {
     8157        if ($event && isset($event->schedule) && $schedule !== $event->schedule) {
     8158            wp_clear_scheduled_hook($hook);
     8159            $scheduled = false;
     8160        }
    77478161        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);
    77498163            if (is_wp_error($result)) {
    77508164                aitmt_fallback_set_last_error($result->get_error_message());
    … …  
    77678181{
    77688182    $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)) {
    77718204        return;
    77728205    }
    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    }
    77788272}
    77798273
    … …  
    83548848    }
    83558849
    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(
    83578852        'text' => $text,
    83588853        'object_id' => absint($object_id),
    … …  
    83638858function aitmt_fallback_add_scan_source_html(&$source_buckets, $source_type, $html, $object_id = 0, $field_key = '')
    83648859{
    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
    83898865function aitmt_fallback_scan_site_handler()
    83908866{
    … …  
    84168892    }
    84178893
    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();
    84788895
    84798896    $unique_strings = array();
    … …  
    84888905            }
    84898906
    8490             $unique_strings[$hash] = $text;
     8907            $unique_strings[aitmt_fallback_translation_hash($text)] = $text;
    84918908            foreach ($target_languages as $target_language) {
    84928909                if (aitmt_fallback_enqueue_translation($text, $source_language, $target_language, $source_type, $object_id, $field_key)) {
    … …  
    85068923}
    85078924
    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 */
     8934function 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 */
     8944function 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) {
    85348958                continue;
    85358959            }
    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    );
    85548969}
    85558970
    … …  
    85758990}
    85768991
    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 }
    86238992function aitmt_fallback_collect_scalar_scan_strings(&$strings, $value, $depth = 0, $current_key = '')
    86248993{
    … …  
    88629231
    88639232    $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'));
    88669241    }
    88679242
    … …  
    88699244    $format = isset($_POST['import_format']) ? sanitize_key(wp_unslash((string) $_POST['import_format'])) : 'csv';
    88709245    $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    }
    88719250
    88729251    if (!$language || !in_array($language, aitmt_fallback_enabled_languages(), true)) {
    … …  
    89399318}
    89409319
     9320function 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
    89419340function aitmt_fallback_build_po($entries, $target_language)
    89429341{
    … …  
    89469345        '"Project-Id-Version: AIT Multilingual Translate ' . AITMT_VERSION . '\n"',
    89479346        '"Language: ' . $target_language . '\n"',
     9347        '"MIME-Version: 1.0\n"',
    89489348        '"Content-Type: text/plain; charset=UTF-8\n"',
    89499349        '"Content-Transfer-Encoding: 8bit\n"',
     9350        '"Plural-Forms: ' . aitmt_fallback_plural_forms($target_language) . '\n"',
     9351        '"X-Generator: AIT Multilingual Translate ' . AITMT_VERSION . '\n"',
    89509352        '',
    89519353    );
    … …  
    89719373function aitmt_fallback_build_mo($entries, $target_language)
    89729374{
    8973     $pairs = array('' => "Project-Id-Version: AIT Multilingual Translate " . AITMT_VERSION . "\nLanguage: " . $target_language . "\nContent-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");
    89749376
    89759377    foreach ($entries as $entry) {
    … …  
    960410006    $queue_result = get_option('aitmt_queue_result', array());
    960510007    $queue_result = is_array($queue_result) ? $queue_result : array();
     10008    $scanner_selected = AITMT_Scanner::selection($options);
     10009    $scanner_catalog = AITMT_Scanner::catalog();
    960610010
    960710011    aitmt_fallback_admin_page_start(
    … …  
    962910033                    <span class="aitmt-step-number">2</span>
    963010034                    <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>
    963110044                    <label>
    963210045                        <span><?php esc_html_e('Source language', 'ait-multilingual-translate'); ?></span>
    … …  
    967410087                    <h3><?php esc_html_e('API Keys', 'ait-multilingual-translate'); ?></h3>
    967510088                    <?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>
    967610122                </section>
    967710123                <section class="aitmt-onboarding-step">
    … …  
    1001910465    $page_load_queued = absint($source_stats['page_load']['queued'] ?? 0);
    1002010466    $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);
    1002110480
    1002210481    aitmt_fallback_admin_page_start(
    … …  
    1002710486    <div class="aitmt-wide-card">
    1002810487        <h2><?php esc_html_e('Scanner and Queue', 'ait-multilingual-translate'); ?></h2>
     10488        <?php AITMT_Scanner::render_ui($options); ?>
    1002910489        <p>
    1003010490            <?php esc_html_e('Auto scan on save:', 'ait-multilingual-translate'); ?>
    … …  
    1004410504            </p>
    1004510505        <?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>
    1004610554        <p class="description">
    1004710555            <?php esc_html_e('Cost optimization:', 'ait-multilingual-translate'); ?>
    … …  
    1067911187    nocache_headers();
    1068011188    header('Content-Type: text/plain; charset=utf-8');
     11189    header('X-Content-Type-Options: nosniff');
    1068111190    header('Content-Disposition: attachment; filename="ait-multilingual-translate-debug-' . gmdate('Ymd-His') . '.log"');
    1068211191    $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.
    1068511195    exit;
    1068611196}
    … …  
    1073211242        wp_die(esc_html__('Permission denied.', 'ait-multilingual-translate'));
    1073311243    }
    10734 
    1073511244    check_admin_referer('aitmt_translate_all_queue');
    10736 
    1073711245    $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
    1073811248    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,
    1079111260        '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,
    1085811262        '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);
    1086711264    wp_safe_redirect(aitmt_fallback_admin_redirect_url_from_post('ait-multilingual-translate-scanner'));
    1086811265    exit;
    … …  
    1120811605    $api_key_source = aitmt_fallback_provider_key_source($active_provider);
    1120911606    $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();
    1121011616    ?>
    1121111617    <div class="wrap aitmt-admin-page aitmt-mode-<?php echo esc_attr($admin_mode); ?>">
    … …  
    1123711643        <?php aitmt_fallback_public_beta_notice(); ?>
    1123811644
     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
    1123911688        <?php if (!empty($cost_safety_issues)) : ?>
    1124011689            <div class="notice notice-warning inline aitmt-cost-warning">
    … …  
    1129511744                            </fieldset>
    1129611745                            <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>
    1129711757                        </td>
    1129811758                    </tr>
    … …  
    1144511905                        <h3 class="aitmt-section-heading"><?php esc_html_e('AI Cost Optimization', 'ait-multilingual-translate'); ?></h3>
    1144611906                        <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>
    1144711914                        <?php aitmt_fallback_render_cost_estimator_panel($options); ?>
    1144811915                    </th>
    … …  
    1149511962                        <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']); ?>">
    1149611963                        <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>
    1149711973                        <label for="aitmt-max-chars-per-request"><?php esc_html_e('Max characters per request', 'ait-multilingual-translate'); ?></label><br>
    1149811974                        <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  
    890890    }
    891891}
     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  
    351351    }
    352352
     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
    353374    function initAdmin() {
    354375        initLanguageSearch();
    … …  
    357378        initAdminMode();
    358379        initMenuSwitcherMetabox();
     380        initScannerPreviewFilters();
    359381    }
    360382
  • ait-multilingual-translate/trunk/includes/class-aitmt-locales.php

    r3665107 r3761123  
    1414 */
    1515final 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        }
    16192        /**
    17193         * Normalize a locale without discarding script or region subtags.
  • ait-multilingual-translate/trunk/includes/class-aitmt-runtime-maintenance.php

    r3665107 r3761123  
    1717{
    1818    const USAGE_SCHEMA_VERSION = 1;
    19     const SETTINGS_MIGRATION_VERSION = 3;
     19    const SETTINGS_MIGRATION_VERSION = 4;
    2020    const CRON_HOOK = 'aitmt_runtime_cache_cleanup_event';
    2121
    … …  
    2929        add_action('admin_menu', array(__CLASS__, 'register_admin_page'), 99);
    3030        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'));
    3132        add_action('admin_notices', array(__CLASS__, 'render_migration_notice'));
    3233        add_action(self::CRON_HOOK, array(__CLASS__, 'scheduled_cleanup'));
    … …  
    4344    public static function maybe_install()
    4445    {
     46        self::maybe_mark_scanner_maintenance();
     47
    4548        if ((int) get_option('aitmt_runtime_usage_schema_version', 0) < self::USAGE_SCHEMA_VERSION) {
    4649            self::create_usage_table();
    … …  
    5760    public static function activate()
    5861    {
     62        self::maybe_mark_scanner_maintenance();
    5963        self::create_usage_table();
    6064        self::migrate_safe_settings();
    6165        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);
    6291    }
    6392
    … …  
    163192        $message = get_option('aitmt_migration_error', '');
    164193        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
    165205            return;
    166206        }
    … …  
    370410        }
    371411
     412        delete_option('aitmt_scanner_maintenance_notice');
     413
    372414        $result = get_transient('aitmt_maintenance_result_' . get_current_user_id());
    373415        $summary = self::database_summary();
    374416        $operations = self::operations();
     417        $obsolete_preview = self::obsolete_scanner_candidates(20);
    375418        ?>
    376419        <div class="wrap aitmt-admin-wrap">
    … …  
    442485            </form>
    443486
     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
    444529            <h2><?php esc_html_e('Runtime diagnostics (last 14 days)', 'ait-multilingual-translate'); ?></h2>
    445530            <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>
    … …  
    459544    private static function operations()
    460545    {
    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'),
    462549            'stale-page-load' => __('Delete stale page-load cache rows', 'ait-multilingual-translate'),
    463550            'all-runtime' => __('Clear generated runtime translation cache (page-load and legacy rows)', 'ait-multilingual-translate'),
    … …  
    466553            'empty-source-type' => __('Delete old unclassified runtime candidates', 'ait-multilingual-translate'),
    467554            'orphaned' => __('Delete rows linked to missing posts', 'ait-multilingual-translate'),
     555            'obsolete-scanner' => __('Delete obsolete scanner-derived translations', 'ait-multilingual-translate'),
    468556            'classify' => __('Classify high-confidence legacy rows', 'ait-multilingual-translate'),
    469557            'analyze' => __('Analyze translation table', 'ait-multilingual-translate'),
    470558            'optimize' => __('Optimize translation table (manual only)', 'ait-multilingual-translate'),
    471559        );
     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;
    472569    }
    473570
    … …  
    518615
    519616    /**
     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    /**
    520642     * Execute a controlled maintenance operation.
    521643     */
    … …  
    606728        $cutoff = gmdate('Y-m-d H:i:s', time() - (max(1, absint($age_days)) * DAY_IN_SECONDS));
    607729
     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
    608738        switch ($operation) {
     739            case 'queued':
     740                return array("status = %s", array('queued'));
     741            case 'failed':
     742                return array("status = %s", array('failed'));
    609743            case 'stale-page-load':
    610744                return array("source_type = %s AND updated_at < %s", array('page_load', $cutoff));
    … …  
    645779            return self::orphaned_stats($age_days);
    646780        }
     781        if ('obsolete-scanner' === $operation) {
     782            return self::obsolete_scanner_stats();
     783        }
    647784
    648785        list($where, $args) = self::operation_condition($operation, $age_days);
    … …  
    673810        } elseif ('orphaned' === $operation) {
    674811            $rows = self::orphaned_rows($age_days, $batch_size);
     812        } elseif ('obsolete-scanner' === $operation) {
     813            $rows = self::obsolete_scanner_rows($batch_size);
    675814        } else {
    676815            list($where, $args) = self::operation_condition($operation, $age_days);
    … …  
    697836
    698837        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());
    699994    }
    700995
  • ait-multilingual-translate/trunk/languages/ait-multilingual-translate.pot

    r3665107 r3761123  
    33msgid ""
    44msgstr ""
    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"
    89"MIME-Version: 1.0\n"
    910"Content-Type: text/plain; charset=UTF-8\n"
    1011"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"
    1115"X-Domain: ait-multilingual-translate\n"
    1216
    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
     23msgid "AIT Multilingual Translate"
     24msgstr ""
     25
     26#. Plugin URI of the plugin
     27#. Author URI of the plugin
     28#: ait-multilingual-translate.php
     29msgid "https://wp-ai.itdesign.biz"
     30msgstr ""
     31
     32#. Description of the plugin
     33#: ait-multilingual-translate.php
     34msgid "AI-powered multilingual translation plugin compatible with WooCommerce and Elementor, with SEO-friendly URLs and frontend editing support."
     35msgstr ""
     36
     37#. Author of the plugin
     38#: ait-multilingual-translate.php
     39msgid "sotter IT Design"
     40msgstr ""
     41
     42#: ait-multilingual-translate.php:267
     43#: ait-multilingual-translate.php:268
     44#: ait-multilingual-translate.php:11621
     45msgid "AI Translate"
     46msgstr ""
     47
     48#: ait-multilingual-translate.php:279
     49msgid "Dashboard"
     50msgstr ""
     51
     52#: ait-multilingual-translate.php:287
     53#: ait-multilingual-translate.php:288
     54#: ait-multilingual-translate.php:11728
     55msgid "Translations"
     56msgstr ""
     57
     58#: ait-multilingual-translate.php:296
     59#: ait-multilingual-translate.php:297
     60#: ait-multilingual-translate.php:11724
     61msgid "Scanner"
     62msgstr ""
     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
     68msgid "Usage"
     69msgstr ""
     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
     77msgid "Report Bug"
     78msgstr ""
     79
     80#: ait-multilingual-translate.php:323
     81#: ait-multilingual-translate.php:324
     82msgid "Feedback"
     83msgstr ""
     84
     85#: ait-multilingual-translate.php:332
     86#: ait-multilingual-translate.php:333
     87#: ait-multilingual-translate.php:10022
     88msgid "Setup Wizard"
     89msgstr ""
     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
     95msgid "Support"
     96msgstr ""
     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
     102msgid "Debugger"
     103msgstr ""
     104
     105#: ait-multilingual-translate.php:440
     106msgid "Public Beta Build"
     107msgstr ""
     108
     109#: ait-multilingual-translate.php:541
     110msgid "Provider Pricing Notice"
     111msgstr ""
     112
     113#: ait-multilingual-translate.php:542
     114msgid "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."
     115msgstr ""
     116
     117#: ait-multilingual-translate.php:543
     118msgid "Continue"
     119msgstr ""
     120
     121#: ait-multilingual-translate.php:544
     122#: ait-multilingual-translate.php:5447
     123msgid "Cancel"
     124msgstr ""
     125
     126#: ait-multilingual-translate.php:545
     127#: ait-multilingual-translate.php:2434
     128msgid "Test connection"
     129msgstr ""
     130
     131#: ait-multilingual-translate.php:546
     132msgid "Testing connection..."
     133msgstr ""
     134
     135#: ait-multilingual-translate.php:547
     136msgid "Provider test failed."
     137msgstr ""
     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
     174msgid "Permission denied."
     175msgstr ""
     176
     177#: ait-multilingual-translate.php:656
     178#: ait-multilingual-translate.php:713
     179msgid "Invalid provider."
     180msgstr ""
     181
     182#: ait-multilingual-translate.php:750
     183msgid "The provider returned no translation."
     184msgstr ""
     185
     186#: ait-multilingual-translate.php:755
     187msgid "Connection test succeeded. The settings were tested without being saved."
     188msgstr ""
     189
     190#: ait-multilingual-translate.php:1342
     191#: includes/class-aitmt-plugin.php:44
     192msgid "Settings"
     193msgstr ""
     194
     195#: ait-multilingual-translate.php:1430
     196msgid "Support Development"
     197msgstr ""
     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
     203msgid "Support AIT Multilingual Translate"
     204msgstr ""
     205
     206#: ait-multilingual-translate.php:1474
     207#: ait-multilingual-translate.php:9960
     208#: ait-multilingual-translate.php:12139
     209msgid "AIT Multilingual Translate is currently in Public Beta and includes temporary full feature access while the platform is actively tested and improved."
     210msgstr ""
     211
     212#: ait-multilingual-translate.php:1475
     213#: ait-multilingual-translate.php:9961
     214#: ait-multilingual-translate.php:12140
     215msgid "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."
     216msgstr ""
     217
     218#: ait-multilingual-translate.php:1476
     219#: ait-multilingual-translate.php:9962
     220#: ait-multilingual-translate.php:12141
     221msgid "Your support helps improve the plugin, optimize AI providers, expand language support, and accelerate development."
     222msgstr ""
     223
     224#: ait-multilingual-translate.php:1490
     225msgid "AIT Multilingual Translate is currently in Public Beta. Please make a backup before bulk translating production websites."
     226msgstr ""
     227
     228#: ait-multilingual-translate.php:1493
     229#: ait-multilingual-translate.php:9974
     230#: ait-multilingual-translate.php:12147
     231msgid "Send Feedback"
     232msgstr ""
     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
     246msgid "API key"
     247msgstr ""
     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
     258msgid "Model"
     259msgstr ""
     260
     261#: ait-multilingual-translate.php:2288
     262msgid "Endpoint plan"
     263msgstr ""
     264
     265#: ait-multilingual-translate.php:2290
     266msgid "DeepL API Free"
     267msgstr ""
     268
     269#: ait-multilingual-translate.php:2291
     270msgid "DeepL API Pro"
     271msgstr ""
     272
     273#: ait-multilingual-translate.php:2305
     274#: ait-multilingual-translate.php:2311
     275msgid "Folder ID"
     276msgstr ""
     277
     278#: ait-multilingual-translate.php:2332
     279msgid "API region"
     280msgstr ""
     281
     282#: ait-multilingual-translate.php:2334
     283msgid "United States"
     284msgstr ""
     285
     286#: ait-multilingual-translate.php:2335
     287msgid "Singapore / international"
     288msgstr ""
     289
     290#: ait-multilingual-translate.php:2336
     291msgid "China"
     292msgstr ""
     293
     294#: ait-multilingual-translate.php:2408
     295msgid "Configured - enter a new value to replace it"
     296msgstr ""
     297
     298#: ait-multilingual-translate.php:2408
     299msgid "Not configured"
     300msgstr ""
     301
     302#: ait-multilingual-translate.php:2410
     303msgid "Clear saved key"
     304msgstr ""
     305
     306#: ait-multilingual-translate.php:2427
     307msgid "Test target language"
     308msgstr ""
     309
     310#: ait-multilingual-translate.php:2439
     311msgid "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."
     312msgstr ""
     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
     324msgid "Usage Based"
     325msgstr ""
     326
     327#: ait-multilingual-translate.php:2450
     328msgid "OpenAI API usage is billed by your OpenAI account. Start with a small budget and the cheapest recommended translation model."
     329msgstr ""
     330
     331#: ait-multilingual-translate.php:2452
     332msgid "Monthly budget: $5 while testing"
     333msgstr ""
     334
     335#: ait-multilingual-translate.php:2453
     336msgid "Soft alert: $3, hard limit: $5 where available"
     337msgstr ""
     338
     339#: ait-multilingual-translate.php:2454
     340msgid "Use gpt-4o-mini with temperature 0.1"
     341msgstr ""
     342
     343#: ait-multilingual-translate.php:2455
     344msgid "Keep Translation Memory, duplicate detection, and queue mode enabled"
     345msgstr ""
     346
     347#: ait-multilingual-translate.php:2456
     348msgid "Use small batches of 3-5 strings until quota is confirmed"
     349msgstr ""
     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
     364msgid "Documentation"
     365msgstr ""
     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
     379msgid "Pricing"
     380msgstr ""
     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
     394msgid "Quota settings"
     395msgstr ""
     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
     409msgid "Budget settings"
     410msgstr ""
     411
     412#: ait-multilingual-translate.php:2467
     413#: ait-multilingual-translate.php:2518
     414msgid "Free Tier Available"
     415msgstr ""
     416
     417#: ait-multilingual-translate.php:2468
     418msgid "Gemini may offer a free tier, but rate limits can be low and paid billing may apply depending on your Google AI/GCP configuration."
     419msgstr ""
     420
     421#: ait-multilingual-translate.php:2470
     422msgid "Use the free tier where available"
     423msgstr ""
     424
     425#: ait-multilingual-translate.php:2471
     426msgid "Use a Flash model for testing"
     427msgstr ""
     428
     429#: ait-multilingual-translate.php:2472
     430msgid "Enable quota limits and billing alerts in Google tools"
     431msgstr ""
     432
     433#: ait-multilingual-translate.php:2473
     434msgid "Keep batches small and process the queue manually first"
     435msgstr ""
     436
     437#: ait-multilingual-translate.php:2484
     438msgid "Paid API"
     439msgstr ""
     440
     441#: ait-multilingual-translate.php:2485
     442msgid "xAI API access usually requires an active paid account or credits. Use the lowest-cost model available to your account."
     443msgstr ""
     444
     445#: ait-multilingual-translate.php:2487
     446msgid "Confirm account credits before testing"
     447msgstr ""
     448
     449#: ait-multilingual-translate.php:2488
     450msgid "Use the lowest-cost mini/lightweight model where available"
     451msgstr ""
     452
     453#: ait-multilingual-translate.php:2489
     454msgid "Set usage alerts in xAI Console if available"
     455msgstr ""
     456
     457#: ait-multilingual-translate.php:2490
     458msgid "Run one small manual queue batch before Translate All"
     459msgstr ""
     460
     461#: ait-multilingual-translate.php:2502
     462msgid "Google Cloud Translation API is billed by characters. Set Google Cloud quotas and billing alerts before bulk translation."
     463msgstr ""
     464
     465#: ait-multilingual-translate.php:2504
     466msgid "Enable Cloud Translation API only for the intended project"
     467msgstr ""
     468
     469#: ait-multilingual-translate.php:2505
     470msgid "Set daily quota and request limits"
     471msgstr ""
     472
     473#: ait-multilingual-translate.php:2506
     474msgid "Create billing alerts and budgets in Google Cloud Billing"
     475msgstr ""
     476
     477#: ait-multilingual-translate.php:2507
     478msgid "Keep plugin monthly and per-request character limits enabled"
     479msgstr ""
     480
     481#: ait-multilingual-translate.php:2519
     482msgid "DeepL API can be used with a free endpoint when your key supports it. Monitor monthly character usage in your DeepL account."
     483msgstr ""
     484
     485#: ait-multilingual-translate.php:2521
     486msgid "Use DeepL Free endpoint when possible"
     487msgstr ""
     488
     489#: ait-multilingual-translate.php:2522
     490msgid "Monitor monthly character usage in DeepL account"
     491msgstr ""
     492
     493#: ait-multilingual-translate.php:2523
     494msgid "Use plugin character limits as an extra safety stop"
     495msgstr ""
     496
     497#: ait-multilingual-translate.php:2524
     498msgid "Run small manual batches before translating the full queue"
     499msgstr ""
     500
     501#: ait-multilingual-translate.php:2536
     502msgid "Anthropic API usage is billed by your Anthropic account. Start with a Haiku model and a small workspace spending limit."
     503msgstr ""
     504
     505#: ait-multilingual-translate.php:2538
     506msgid "Use the configured Haiku model for initial tests"
     507msgstr ""
     508
     509#: ait-multilingual-translate.php:2539
     510msgid "Set a workspace spending limit before bulk translation"
     511msgstr ""
     512
     513#: ait-multilingual-translate.php:2540
     514msgid "Keep batches small until provider limits are confirmed"
     515msgstr ""
     516
     517#: ait-multilingual-translate.php:2552
     518msgid "Yandex Cloud Translate usage depends on your Yandex Cloud billing account and quotas."
     519msgstr ""
     520
     521#: ait-multilingual-translate.php:2554
     522msgid "Use a dedicated service account and folder"
     523msgstr ""
     524
     525#: ait-multilingual-translate.php:2555
     526msgid "Set provider-side quotas before processing a large queue"
     527msgstr ""
     528
     529#: ait-multilingual-translate.php:2556
     530msgid "Test with one small manual batch first"
     531msgstr ""
     532
     533#: ait-multilingual-translate.php:2568
     534msgid "YandexGPT usage depends on your Yandex Cloud Foundation Models billing account and quotas."
     535msgstr ""
     536
     537#: ait-multilingual-translate.php:2570
     538msgid "Use the lite model while testing"
     539msgstr ""
     540
     541#: ait-multilingual-translate.php:2571
     542msgid "Set provider-side quotas and billing notifications"
     543msgstr ""
     544
     545#: ait-multilingual-translate.php:2572
     546msgid "Use Translation Memory and small queue batches"
     547msgstr ""
     548
     549#: ait-multilingual-translate.php:2584
     550msgid "Mistral API usage is billed according to the model and account plan selected in La Plateforme."
     551msgstr ""
     552
     553#: ait-multilingual-translate.php:2586
     554msgid "Use mistral-small-latest for initial translation tests"
     555msgstr ""
     556
     557#: ait-multilingual-translate.php:2587
     558msgid "Configure provider-side usage controls before bulk translation"
     559msgstr ""
     560
     561#: ait-multilingual-translate.php:2588
     562msgid "Keep queue batches small until limits are confirmed"
     563msgstr ""
     564
     565#: ait-multilingual-translate.php:2600
     566msgid "DeepSeek API usage is charged to your DeepSeek platform balance according to the selected model."
     567msgstr ""
     568
     569#: ait-multilingual-translate.php:2602
     570msgid "Use the Flash model for initial tests"
     571msgstr ""
     572
     573#: ait-multilingual-translate.php:2603
     574msgid "Add only a small prepaid balance while testing"
     575msgstr ""
     576
     577#: ait-multilingual-translate.php:2604
     578msgid "Process a small manual queue batch before Translate All"
     579msgstr ""
     580
     581#: ait-multilingual-translate.php:2616
     582msgid "Alibaba Cloud Model Studio usage and availability depend on the selected region, model, and account plan."
     583msgstr ""
     584
     585#: ait-multilingual-translate.php:2618
     586msgid "Choose the correct Model Studio region before creating a key"
     587msgstr ""
     588
     589#: ait-multilingual-translate.php:2619
     590msgid "Use the Flash model for initial tests where available"
     591msgstr ""
     592
     593#: ait-multilingual-translate.php:2620
     594msgid "Configure billing alerts in Alibaba Cloud before bulk translation"
     595msgstr ""
     596
     597#: ait-multilingual-translate.php:2632
     598msgid "Baidu AI Cloud Qianfan usage depends on the selected ERNIE model and your cloud account billing configuration."
     599msgstr ""
     600
     601#: ait-multilingual-translate.php:2634
     602msgid "Confirm Qianfan access and regional availability first"
     603msgstr ""
     604
     605#: ait-multilingual-translate.php:2635
     606msgid "Configure cloud account quotas or alerts before bulk translation"
     607msgstr ""
     608
     609#: ait-multilingual-translate.php:2636
     610msgid "Run a single provider test before queue processing"
     611msgstr ""
     612
     613#: ait-multilingual-translate.php:2656
     614msgid "Provider pricing depends on your provider account plan."
     615msgstr ""
     616
     617#: ait-multilingual-translate.php:2668
     618msgid "No plugin-side daily character limit is configured."
     619msgstr ""
     620
     621#: ait-multilingual-translate.php:2672
     622msgid "No plugin-side monthly character limit is configured."
     623msgstr ""
     624
     625#: ait-multilingual-translate.php:2676
     626msgid "No plugin-side per-request character limit is configured."
     627msgstr ""
     628
     629#: ait-multilingual-translate.php:2680
     630msgid "Batch size is higher than the safest free-tier recommendation of 3-5 strings."
     631msgstr ""
     632
     633#: ait-multilingual-translate.php:2684
     634msgid "Background queue processing is enabled. Use it only after confirming provider quotas."
     635msgstr ""
     636
     637#: ait-multilingual-translate.php:2688
     638msgid "Page-load translation is enabled. This can create API usage while visitors browse."
     639msgstr ""
     640
     641#: ait-multilingual-translate.php:2692
     642msgid "Auto scan on save is enabled. Disable it during imports or large WooCommerce edits."
     643msgstr ""
     644
     645#: ait-multilingual-translate.php:2704
     646#: ait-multilingual-translate.php:2716
     647#: ait-multilingual-translate.php:2761
     648msgid "Unknown (depends on your provider plan)"
     649msgstr ""
     650
     651#. translators: %s: Provider name.
     652#: ait-multilingual-translate.php:2726
     653#, php-format
     654msgid "How to configure %s safely"
     655msgstr ""
     656
     657#. translators: %s: Number of characters.
     658#: ait-multilingual-translate.php:2758
     659#: ait-multilingual-translate.php:2760
     660#, php-format
     661msgid "%s character"
     662msgid_plural "%s characters"
     663msgstr[0] ""
     664msgstr[1] ""
     665
     666#. translators: %s: Number of characters.
     667#: ait-multilingual-translate.php:2758
     668#: ait-multilingual-translate.php:2760
     669msgid "Unlimited in plugin settings"
     670msgstr ""
     671
     672#: ait-multilingual-translate.php:2764
     673msgid "Safe cost estimator"
     674msgstr ""
     675
     676#: ait-multilingual-translate.php:2765
     677msgid "These are plugin-side safety estimates only. Provider billing dashboards and quotas remain authoritative."
     678msgstr ""
     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
     686msgid "Provider"
     687msgstr ""
     688
     689#: ait-multilingual-translate.php:2768
     690msgid "Characters per request"
     691msgstr ""
     692
     693#: ait-multilingual-translate.php:2769
     694msgid "Estimated tokens"
     695msgstr ""
     696
     697#: ait-multilingual-translate.php:2770
     698msgid "Estimated monthly requests"
     699msgstr ""
     700
     701#: ait-multilingual-translate.php:2770
     702#: includes/class-aitmt-scanner.php:1130
     703msgid "Unknown"
     704msgstr ""
     705
     706#: ait-multilingual-translate.php:2771
     707msgid "Estimated daily usage"
     708msgstr ""
     709
     710#: ait-multilingual-translate.php:2772
     711msgid "Estimated monthly usage"
     712msgstr ""
     713
     714#: ait-multilingual-translate.php:2773
     715msgid "Estimated monthly cost"
     716msgstr ""
     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
     723msgid "Batch size"
     724msgstr ""
     725
     726#: ait-multilingual-translate.php:2899
     727msgid "Cheap"
     728msgstr ""
     729
     730#: ait-multilingual-translate.php:2900
     731msgid "Balanced"
     732msgstr ""
     733
     734#: ait-multilingual-translate.php:2901
     735msgid "Premium"
     736msgstr ""
     737
     738#: ait-multilingual-translate.php:2982
     739msgid "Neutral / Accurate"
     740msgstr ""
     741
     742#: ait-multilingual-translate.php:2986
     743msgid "SEO Optimized"
     744msgstr ""
     745
     746#: ait-multilingual-translate.php:2990
     747msgid "Marketing"
     748msgstr ""
     749
     750#: ait-multilingual-translate.php:2994
     751msgid "eCommerce"
     752msgstr ""
     753
     754#: ait-multilingual-translate.php:2998
     755msgid "Formal"
     756msgstr ""
     757
     758#: ait-multilingual-translate.php:3002
     759msgid "Casual"
     760msgstr ""
     761
     762#: ait-multilingual-translate.php:3006
     763#: includes/class-aitmt-scanner.php:1127
     764msgid "Technical"
     765msgstr ""
     766
     767#: ait-multilingual-translate.php:3010
     768msgid "Legal"
     769msgstr ""
     770
     771#: ait-multilingual-translate.php:3014
     772msgid "Luxury Brand"
     773msgstr ""
     774
     775#: ait-multilingual-translate.php:3018
     776msgid "Friendly"
     777msgstr ""
     778
     779#: ait-multilingual-translate.php:3022
     780msgid "Custom Prompt"
     781msgstr ""
     782
     783#: ait-multilingual-translate.php:3061
     784msgid "The target language is not recognized. Select a supported language before translating."
     785msgstr ""
     786
     787#. translators: %s: requested target locale.
     788#: ait-multilingual-translate.php:3105
     789#, php-format
     790msgid "The provider response does not appear to use the requested target language (%s). The translation was not saved."
     791msgstr ""
     792
     793#. translators: %s: rejected CSS selectors.
     794#: ait-multilingual-translate.php:3536
     795#, php-format
     796msgid "These exclusion selectors are invalid and were not saved: %s"
     797msgstr ""
     798
     799#: ait-multilingual-translate.php:3987
     800#: includes/class-aitmt-switcher.php:132
     801msgid "Language switcher"
     802msgstr ""
     803
     804#: ait-multilingual-translate.php:4011
     805#: includes/class-aitmt-switcher.php:103
     806msgid "Select language"
     807msgstr ""
     808
     809#: ait-multilingual-translate.php:4052
     810#: includes/class-aitmt-widget.php:13
     811msgid "AIT Multilingual Translate Switcher"
     812msgstr ""
     813
     814#: ait-multilingual-translate.php:4053
     815msgid "Display the AIT Multilingual Translate language switcher."
     816msgstr ""
     817
     818#: ait-multilingual-translate.php:4079
     819#: ait-multilingual-translate.php:12081
     820#: includes/class-aitmt-settings.php:346
     821msgid "Style"
     822msgstr ""
     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
     828msgid "Dropdown"
     829msgstr ""
     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
     835msgid "List"
     836msgstr ""
     837
     838#: ait-multilingual-translate.php:4120
     839msgid "AI Translate Language Switcher"
     840msgstr ""
     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
     846msgid "Language Switcher"
     847msgstr ""
     848
     849#: ait-multilingual-translate.php:4148
     850msgid "Layout type"
     851msgstr ""
     852
     853#: ait-multilingual-translate.php:4154
     854msgid "Buttons"
     855msgstr ""
     856
     857#: ait-multilingual-translate.php:4155
     858msgid "Flags only"
     859msgstr ""
     860
     861#: ait-multilingual-translate.php:4156
     862msgid "Flags + language name"
     863msgstr ""
     864
     865#: ait-multilingual-translate.php:4157
     866msgid "Language name only"
     867msgstr ""
     868
     869#: ait-multilingual-translate.php:4165
     870msgid "Show flags"
     871msgstr ""
     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
     879msgid "Yes"
     880msgstr ""
     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
     888msgid "No"
     889msgstr ""
     890
     891#: ait-multilingual-translate.php:4177
     892#: ait-multilingual-translate.php:4483
     893msgid "Show language name"
     894msgstr ""
     895
     896#: ait-multilingual-translate.php:4189
     897#: ait-multilingual-translate.php:4484
     898msgid "Show language code"
     899msgstr ""
     900
     901#: ait-multilingual-translate.php:4201
     902msgid "Show current language"
     903msgstr ""
     904
     905#: ait-multilingual-translate.php:4213
     906#: ait-multilingual-translate.php:4485
     907msgid "Hide current language"
     908msgstr ""
     909
     910#: ait-multilingual-translate.php:4225
     911msgid "Open links in current page"
     912msgstr ""
     913
     914#: ait-multilingual-translate.php:4237
     915msgid "Orientation"
     916msgstr ""
     917
     918#: ait-multilingual-translate.php:4241
     919msgid "Horizontal"
     920msgstr ""
     921
     922#: ait-multilingual-translate.php:4242
     923msgid "Vertical"
     924msgstr ""
     925
     926#: ait-multilingual-translate.php:4250
     927msgid "Alignment"
     928msgstr ""
     929
     930#: ait-multilingual-translate.php:4254
     931msgid "Left"
     932msgstr ""
     933
     934#: ait-multilingual-translate.php:4255
     935msgid "Center"
     936msgstr ""
     937
     938#: ait-multilingual-translate.php:4256
     939msgid "Right"
     940msgstr ""
     941
     942#: ait-multilingual-translate.php:4269
     943msgid "Switcher Style"
     944msgstr ""
     945
     946#: ait-multilingual-translate.php:4287
     947msgid "Text color"
     948msgstr ""
     949
     950#: ait-multilingual-translate.php:4298
     951msgid "Hover color"
     952msgstr ""
     953
     954#: ait-multilingual-translate.php:4309
     955msgid "Active language color"
     956msgstr ""
     957
     958#: ait-multilingual-translate.php:4320
     959msgid "Background color"
     960msgstr ""
     961
     962#: ait-multilingual-translate.php:4331
     963msgid "Border color"
     964msgstr ""
     965
     966#: ait-multilingual-translate.php:4342
     967msgid "Dropdown background"
     968msgstr ""
     969
     970#: ait-multilingual-translate.php:4353
     971msgid "Border radius"
     972msgstr ""
     973
     974#: ait-multilingual-translate.php:4369
     975msgid "Padding"
     976msgstr ""
     977
     978#: ait-multilingual-translate.php:4381
     979msgid "Gap between items"
     980msgstr ""
     981
     982#: ait-multilingual-translate.php:4394
     983msgid "Dropdown width"
     984msgstr ""
     985
     986#: ait-multilingual-translate.php:4413
     987msgid "Dropdown shadow"
     988msgstr ""
     989
     990#: ait-multilingual-translate.php:4480
     991msgid "Add the language switcher to a WordPress menu."
     992msgstr ""
     993
     994#: ait-multilingual-translate.php:4482
     995msgid "Show flag"
     996msgstr ""
     997
     998#: ait-multilingual-translate.php:4487
     999msgid "Menu display mode"
     1000msgstr ""
     1001
     1002#: ait-multilingual-translate.php:4488
     1003msgid "List mode"
     1004msgstr ""
     1005
     1006#: ait-multilingual-translate.php:4489
     1007msgid "Dropdown mode"
     1008msgstr ""
     1009
     1010#: ait-multilingual-translate.php:4494
     1011msgid "Current language switcher"
     1012msgstr ""
     1013
     1014#: ait-multilingual-translate.php:4495
     1015msgid "All enabled languages"
     1016msgstr ""
     1017
     1018#. translators: %s: Language name.
     1019#: ait-multilingual-translate.php:4500
     1020#, php-format
     1021msgid "Specific language: %s"
     1022msgstr ""
     1023
     1024#: ait-multilingual-translate.php:4507
     1025msgid "Add to Menu"
     1026msgstr ""
     1027
     1028#: ait-multilingual-translate.php:5420
     1029msgid "AI Translate Edit"
     1030msgstr ""
     1031
     1032#: ait-multilingual-translate.php:5443
     1033#: includes/class-aitmt-frontend-editor.php:37
    141034msgid "AI Translate edit"
    151035msgstr ""
    161036
    17 #: includes/class-wpait-frontend-editor.php
     1037#: ait-multilingual-translate.php:5444
     1038msgid "Editing on"
     1039msgstr ""
     1040
     1041#: ait-multilingual-translate.php:5445
     1042#: includes/class-aitmt-frontend-editor.php:38
    181043msgid "Edit translation"
    191044msgstr ""
    201045
    21 #: includes/class-wpait-frontend-editor.php
     1046#: ait-multilingual-translate.php:5446
     1047msgid "Save"
     1048msgstr ""
     1049
     1050#: ait-multilingual-translate.php:5448
     1051#: includes/class-aitmt-frontend-editor.php:39
    221052msgid "Auto Translate"
    231053msgstr ""
    241054
    25 #: includes/class-wpait-frontend-editor.php
     1055#: ait-multilingual-translate.php:5449
     1056#: includes/class-aitmt-frontend-editor.php:40
    261057msgid "AI translating..."
    271058msgstr ""
    281059
    29 #: includes/class-wpait-frontend-editor.php
     1060#: ait-multilingual-translate.php:5450
     1061#: includes/class-aitmt-frontend-editor.php:41
    301062msgid "AI Translation Ready"
    311063msgstr ""
    321064
    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
    341073msgid "Translation failed. Please try again."
    351074msgstr ""
    361075
    37 #: includes/class-wpait-frontend-editor.php
     1076#: ait-multilingual-translate.php:5452
     1077#: includes/class-aitmt-frontend-editor.php:43
    381078msgid "Saving..."
    391079msgstr ""
    401080
    41 #: includes/class-wpait-frontend-editor.php
     1081#: ait-multilingual-translate.php:5453
     1082#: includes/class-aitmt-frontend-editor.php:44
    421083msgid "Saved"
    431084msgstr ""
    441085
    45 #: includes/class-wpait-frontend-editor.php
     1086#: ait-multilingual-translate.php:5454
     1087#: includes/class-aitmt-frontend-editor.php:45
    461088msgid "Could not save translation"
    471089msgstr ""
    481090
    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
     1092msgid "No editable translation text found yet. Scan/process the queue, or click source text that is highlighted for admins."
     1093msgstr ""
     1094
     1095#. translators: %s: Target language code.
     1096#: ait-multilingual-translate.php:5457
     1097#, php-format
     1098msgid "Target: %s"
     1099msgstr ""
     1100
     1101#. translators: %s: translation provider name.
     1102#: ait-multilingual-translate.php:6269
     1103#, php-format
     1104msgid "%s returned an unexpected translation payload."
     1105msgstr ""
     1106
     1107#. translators: %s: translation provider name.
     1108#: ait-multilingual-translate.php:6298
     1109#, php-format
     1110msgid "%s API key is missing."
     1111msgstr ""
     1112
     1113#. translators: %s: translation provider name.
     1114#: ait-multilingual-translate.php:6337
     1115#, php-format
     1116msgid "%s request failed."
     1117msgstr ""
     1118
     1119#: ait-multilingual-translate.php:6385
     1120msgid "Claude API key is missing."
     1121msgstr ""
     1122
     1123#: ait-multilingual-translate.php:6419
     1124msgid "Claude request failed."
     1125msgstr ""
     1126
     1127#: ait-multilingual-translate.php:6437
     1128msgid "Yandex Translate API key and folder ID are required."
     1129msgstr ""
     1130
     1131#: ait-multilingual-translate.php:6467
     1132msgid "Yandex Translate request failed."
     1133msgstr ""
     1134
     1135#: ait-multilingual-translate.php:6485
     1136msgid "Yandex Translate returned an unexpected translation payload."
     1137msgstr ""
     1138
     1139#: ait-multilingual-translate.php:6497
     1140msgid "YandexGPT API key and folder ID are required."
     1141msgstr ""
     1142
     1143#: ait-multilingual-translate.php:6533
     1144msgid "YandexGPT request failed."
     1145msgstr ""
     1146
     1147#: ait-multilingual-translate.php:7546
     1148msgid "Legacy/missing source"
     1149msgstr ""
     1150
     1151#: ait-multilingual-translate.php:7550
     1152msgid "Unknown source"
     1153msgstr ""
     1154
     1155#: ait-multilingual-translate.php:8037
     1156msgid "Background translation is blocked until unknown and technical queue rows are reviewed or cleared."
     1157msgstr ""
     1158
     1159#: ait-multilingual-translate.php:8052
     1160msgid "Background translation worker started."
     1161msgstr ""
     1162
     1163#: ait-multilingual-translate.php:8052
     1164#: ait-multilingual-translate.php:8218
     1165msgid "Queue is empty."
     1166msgstr ""
     1167
     1168#: ait-multilingual-translate.php:8072
     1169msgid "Worker paused by an administrator."
     1170msgstr ""
     1171
     1172#: ait-multilingual-translate.php:8075
     1173msgid "Worker resumed."
     1174msgstr ""
     1175
     1176#: ait-multilingual-translate.php:8081
     1177msgid "Worker stopped by an administrator. Queued rows were preserved."
     1178msgstr ""
     1179
     1180#: ait-multilingual-translate.php:8088
     1181msgid "Background schedule was rebuilt."
     1182msgstr ""
     1183
     1184#: ait-multilingual-translate.php:8093
     1185msgid "Stale queue lock cleared."
     1186msgstr ""
     1187
     1188#: ait-multilingual-translate.php:8095
     1189msgid "The queue lock is active and was not cleared."
     1190msgstr ""
     1191
     1192#: ait-multilingual-translate.php:8130
     1193#: ait-multilingual-translate.php:10543
     1194msgid "Every 1 minute"
     1195msgstr ""
     1196
     1197#: ait-multilingual-translate.php:8131
     1198#: ait-multilingual-translate.php:10543
     1199msgid "Every 2 minutes"
     1200msgstr ""
     1201
     1202#: ait-multilingual-translate.php:8132
     1203#: ait-multilingual-translate.php:10543
     1204msgid "Every 5 minutes"
     1205msgstr ""
     1206
     1207#: ait-multilingual-translate.php:8133
     1208#: ait-multilingual-translate.php:10543
     1209msgid "Every 10 minutes"
     1210msgstr ""
     1211
     1212#: ait-multilingual-translate.php:8134
     1213#: ait-multilingual-translate.php:10543
     1214msgid "Every 15 minutes"
     1215msgstr ""
     1216
     1217#: ait-multilingual-translate.php:8135
     1218#: ait-multilingual-translate.php:10543
     1219msgid "Every 30 minutes"
     1220msgstr ""
     1221
     1222#: ait-multilingual-translate.php:8136
     1223#: ait-multilingual-translate.php:10543
     1224msgid "Hourly"
     1225msgstr ""
     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
    541231msgid "Missing translation data."
    551232msgstr ""
    561233
    57 #: includes/class-wpait-frontend-editor.php
     1234#: ait-multilingual-translate.php:8731
     1235#: includes/class-aitmt-frontend-editor.php:69
    581236msgid "Translation was not saved."
    591237msgstr ""
    601238
    61 #: includes/class-wpait-frontend-editor.php
     1239#: ait-multilingual-translate.php:8734
     1240#: includes/class-aitmt-frontend-editor.php:72
    621241msgid "Translation saved."
    631242msgstr ""
    641243
    65 #: includes/class-wpait-frontend-editor.php
     1244#: ait-multilingual-translate.php:8754
     1245#: includes/class-aitmt-frontend-editor.php:91
    661246msgid "Source and target languages are the same."
    671247msgstr ""
    681248
    69 #: includes/class-wpait-frontend-editor.php
     1249#: ait-multilingual-translate.php:8758
     1250msgid "This text looks technical or non-translatable and was not sent to the provider."
     1251msgstr ""
     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
    701257msgid "No translation provider configured."
    711258msgstr ""
    721259
    73 #: includes/class-wpait-frontend-editor.php
     1260#: ait-multilingual-translate.php:8773
     1261msgid "Translation memory hit"
     1262msgstr ""
     1263
     1264#. translators: %s: Active translation provider label.
     1265#: ait-multilingual-translate.php:8798
     1266#, php-format
     1267msgid "Translated via %s"
     1268msgstr ""
     1269
     1270#: ait-multilingual-translate.php:8818
     1271#: includes/class-aitmt-frontend-editor.php:137
     1272msgid "Provider rate limit reached."
     1273msgstr ""
     1274
     1275#: ait-multilingual-translate.php:9184
     1276#: ait-multilingual-translate.php:9252
     1277msgid "Invalid target language."
     1278msgstr ""
     1279
     1280#: ait-multilingual-translate.php:9237
     1281msgid "No valid import file was uploaded."
     1282msgstr ""
     1283
     1284#: ait-multilingual-translate.php:9240
     1285msgid "Import file must be smaller than 10 MB."
     1286msgstr ""
     1287
     1288#: ait-multilingual-translate.php:9248
     1289msgid "The uploaded file extension must match the selected import format."
     1290msgstr ""
     1291
     1292#: ait-multilingual-translate.php:9810
     1293msgid "The message could not be sent. Please check WordPress email delivery on this site."
     1294msgstr ""
     1295
     1296#: ait-multilingual-translate.php:9813
     1297msgid "Please confirm consent before sending diagnostic information."
     1298msgstr ""
     1299
     1300#: ait-multilingual-translate.php:9826
     1301msgid "AI Translate - Report Bug"
     1302msgstr ""
     1303
     1304#: ait-multilingual-translate.php:9827
     1305msgid "Send a safe technical report for Public Beta troubleshooting."
     1306msgstr ""
     1307
     1308#: ait-multilingual-translate.php:9829
     1309msgid "Bug report sent. Thank you for helping test AIT Multilingual Translate."
     1310msgstr ""
     1311
     1312#: ait-multilingual-translate.php:9835
     1313msgid "The technical report includes WordPress, WooCommerce, PHP, plugin, language, provider, queue, and route status. Sensitive values are redacted before sending."
     1314msgstr ""
     1315
     1316#: ait-multilingual-translate.php:9841
     1317#: ait-multilingual-translate.php:9909
     1318msgid "Name"
     1319msgstr ""
     1320
     1321#: ait-multilingual-translate.php:9845
     1322#: ait-multilingual-translate.php:9913
     1323msgid "Email"
     1324msgstr ""
     1325
     1326#: ait-multilingual-translate.php:9849
     1327msgid "Website"
     1328msgstr ""
     1329
     1330#: ait-multilingual-translate.php:9853
     1331msgid "Problem type"
     1332msgstr ""
     1333
     1334#: ait-multilingual-translate.php:9855
     1335#: ait-multilingual-translate.php:9921
     1336msgid "Translation issue"
     1337msgstr ""
     1338
     1339#: ait-multilingual-translate.php:9856
     1340#: ait-multilingual-translate.php:9922
     1341msgid "WooCommerce issue"
     1342msgstr ""
     1343
     1344#: ait-multilingual-translate.php:9857
     1345#: ait-multilingual-translate.php:9923
     1346msgid "Elementor issue"
     1347msgstr ""
     1348
     1349#: ait-multilingual-translate.php:9858
     1350msgid "Frontend editor issue"
     1351msgstr ""
     1352
     1353#: ait-multilingual-translate.php:9859
     1354msgid "Provider / API issue"
     1355msgstr ""
     1356
     1357#: ait-multilingual-translate.php:9860
     1358msgid "Install / update issue"
     1359msgstr ""
     1360
     1361#: ait-multilingual-translate.php:9861
     1362#: ait-multilingual-translate.php:9924
     1363msgid "Other"
     1364msgstr ""
     1365
     1366#: ait-multilingual-translate.php:9866
     1367msgid "Short description"
     1368msgstr ""
     1369
     1370#: ait-multilingual-translate.php:9870
     1371msgid "Steps to reproduce"
     1372msgstr ""
     1373
     1374#: ait-multilingual-translate.php:9871
     1375msgid "1. Open page...\\n2. Switch language...\\n3. Expected...\\n4. Actual..."
     1376msgstr ""
     1377
     1378#: ait-multilingual-translate.php:9875
     1379msgid "Attach technical log"
     1380msgstr ""
     1381
     1382#: ait-multilingual-translate.php:9879
     1383msgid "I agree to send this report and redacted technical diagnostics to info@itdesign.biz."
     1384msgstr ""
     1385
     1386#: ait-multilingual-translate.php:9881
     1387msgid "Send bug report"
     1388msgstr ""
     1389
     1390#: ait-multilingual-translate.php:9895
     1391msgid "AI Translate - Feedback"
     1392msgstr ""
     1393
     1394#: ait-multilingual-translate.php:9896
     1395msgid "Share Public Beta feedback, feature requests, and translation notes."
     1396msgstr ""
     1397
     1398#: ait-multilingual-translate.php:9898
     1399msgid "Feedback sent. Thank you for shaping AIT Multilingual Translate."
     1400msgstr ""
     1401
     1402#: ait-multilingual-translate.php:9903
     1403msgid "Feedback / Feature Request"
     1404msgstr ""
     1405
     1406#: ait-multilingual-translate.php:9917
     1407#: ait-multilingual-translate.php:11054
     1408msgid "Type"
     1409msgstr ""
     1410
     1411#: ait-multilingual-translate.php:9919
     1412msgid "Bug"
     1413msgstr ""
     1414
     1415#: ait-multilingual-translate.php:9920
     1416msgid "Feature request"
     1417msgstr ""
     1418
     1419#: ait-multilingual-translate.php:9929
     1420#: ait-multilingual-translate.php:11055
     1421msgid "Message"
     1422msgstr ""
     1423
     1424#: ait-multilingual-translate.php:9934
     1425msgid "I agree to send this feedback to info@itdesign.biz."
     1426msgstr ""
     1427
     1428#: ait-multilingual-translate.php:9936
     1429msgid "Send feedback"
     1430msgstr ""
     1431
     1432#: ait-multilingual-translate.php:9950
     1433msgid "AI Translate - Support"
     1434msgstr ""
     1435
     1436#: ait-multilingual-translate.php:9951
     1437msgid "Support the free Public Beta and help prioritize the roadmap."
     1438msgstr ""
     1439
     1440#: ait-multilingual-translate.php:9971
     1441msgid "Need help?"
     1442msgstr ""
     1443
     1444#: ait-multilingual-translate.php:9976
     1445msgid "Plugin Website"
     1446msgstr ""
     1447
     1448#: ait-multilingual-translate.php:9980
     1449msgid "Public Beta Notice"
     1450msgstr ""
     1451
     1452#: ait-multilingual-translate.php:9981
     1453msgid "This Public Beta includes temporary unrestricted access for testing and feedback purposes. Commercial licensing and additional editions may be introduced in future releases."
     1454msgstr ""
     1455
     1456#: ait-multilingual-translate.php:9984
     1457msgid "Support contact"
     1458msgstr ""
     1459
     1460#: ait-multilingual-translate.php:10012
     1461msgid "AI Translate - Onboarding"
     1462msgstr ""
     1463
     1464#: ait-multilingual-translate.php:10013
     1465msgid "Set up languages, provider keys, scanning, queue translation, and frontend editing."
     1466msgstr ""
     1467
     1468#: ait-multilingual-translate.php:10017
     1469msgid "Onboarding settings saved."
     1470msgstr ""
     1471
     1472#: ait-multilingual-translate.php:10029
     1473msgid "Welcome"
     1474msgstr ""
     1475
     1476#: ait-multilingual-translate.php:10030
     1477msgid "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."
     1478msgstr ""
     1479
     1480#: ait-multilingual-translate.php:10034
     1481#: includes/class-aitmt-settings.php:207
     1482msgid "Languages"
     1483msgstr ""
     1484
     1485#: ait-multilingual-translate.php:10036
     1486#: ait-multilingual-translate.php:11749
     1487msgid "Plugin Interface Language"
     1488msgstr ""
     1489
     1490#: ait-multilingual-translate.php:10043
     1491msgid "Changes only this plugin interface; website languages and translation targets stay unchanged."
     1492msgstr ""
     1493
     1494#: ait-multilingual-translate.php:10045
     1495#: ait-multilingual-translate.php:11774
     1496#: includes/class-aitmt-settings.php:212
     1497msgid "Source language"
     1498msgstr ""
     1499
     1500#: ait-multilingual-translate.php:10047
     1501#: ait-multilingual-translate.php:11778
     1502msgid "Auto: WordPress site language"
     1503msgstr ""
     1504
     1505#: ait-multilingual-translate.php:10054
     1506#: ait-multilingual-translate.php:11790
     1507msgid "Default frontend language"
     1508msgstr ""
     1509
     1510#: ait-multilingual-translate.php:10056
     1511#: ait-multilingual-translate.php:11794
     1512msgid "Same as source language"
     1513msgstr ""
     1514
     1515#: ait-multilingual-translate.php:10062
     1516msgid "The root URL displays this language. It can differ from the language of the original content."
     1517msgstr ""
     1518
     1519#: ait-multilingual-translate.php:10063
     1520#: ait-multilingual-translate.php:11809
     1521#: includes/class-aitmt-settings.php:229
     1522msgid "Search languages..."
     1523msgstr ""
     1524
     1525#: ait-multilingual-translate.php:10082
     1526msgid "Saved translations are reused. The provider is used only for queue items that still need translation."
     1527msgstr ""
     1528
     1529#: ait-multilingual-translate.php:10087
     1530msgid "API Keys"
     1531msgstr ""
     1532
     1533#: ait-multilingual-translate.php:10092
     1534#: includes/class-aitmt-scanner.php:986
     1535msgid "Scanner Sources"
     1536msgstr ""
     1537
     1538#: ait-multilingual-translate.php:10093
     1539msgid "Select only published, visitor-facing sources that should create translation jobs. Technical content is off by default."
     1540msgstr ""
     1541
     1542#: ait-multilingual-translate.php:10108
     1543msgid "Cost Safety"
     1544msgstr ""
     1545
     1546#: ait-multilingual-translate.php:10109
     1547msgid "Plugin limits are checked before provider calls. Also configure hard budgets and quotas in the provider account."
     1548msgstr ""
     1549
     1550#: ait-multilingual-translate.php:10112
     1551#: ait-multilingual-translate.php:11958
     1552msgid "Daily character limit"
     1553msgstr ""
     1554
     1555#: ait-multilingual-translate.php:10113
     1556#: ait-multilingual-translate.php:11961
     1557msgid "Monthly character limit"
     1558msgstr ""
     1559
     1560#: ait-multilingual-translate.php:10114
     1561#: ait-multilingual-translate.php:11964
     1562msgid "Requests per minute"
     1563msgstr ""
     1564
     1565#: ait-multilingual-translate.php:10115
     1566#: ait-multilingual-translate.php:11970
     1567msgid "Requests per day"
     1568msgstr ""
     1569
     1570#: ait-multilingual-translate.php:10120
     1571msgid "Enable background queue processing after setup"
     1572msgstr ""
     1573
     1574#: ait-multilingual-translate.php:10125
     1575#: ait-multilingual-translate.php:12125
     1576#: includes/class-aitmt-settings.php:391
     1577msgid "Frontend editor"
     1578msgstr ""
     1579
     1580#: ait-multilingual-translate.php:10129
     1581#: ait-multilingual-translate.php:12127
     1582msgid "Allow administrators to edit translations from the frontend"
     1583msgstr ""
     1584
     1585#: ait-multilingual-translate.php:10133
     1586msgid "Save setup"
     1587msgstr ""
     1588
     1589#: ait-multilingual-translate.php:10138
     1590msgid "Scan and Translate"
     1591msgstr ""
     1592
     1593#: ait-multilingual-translate.php:10142
     1594msgid "Scan"
     1595msgstr ""
     1596
     1597#: ait-multilingual-translate.php:10143
     1598msgid "Collect site strings into the saved translation queue."
     1599msgstr ""
     1600
     1601#: ait-multilingual-translate.php:10151
     1602#: ait-multilingual-translate.php:10773
     1603#: ait-multilingual-translate.php:12195
     1604msgid "Scan site strings"
     1605msgstr ""
     1606
     1607#: ait-multilingual-translate.php:10156
     1608msgid "Translate"
     1609msgstr ""
     1610
     1611#: ait-multilingual-translate.php:10157
     1612msgid "Start one translation queue batch. Repeat or enable background processing later in Advanced mode."
     1613msgstr ""
     1614
     1615#: ait-multilingual-translate.php:10165
     1616msgid "Start translation queue"
     1617msgstr ""
     1618
     1619#: ait-multilingual-translate.php:10172
     1620msgid "Finish setup"
     1621msgstr ""
     1622
     1623#: ait-multilingual-translate.php:10173
     1624msgid "Open dashboard"
     1625msgstr ""
     1626
     1627#: ait-multilingual-translate.php:10205
     1628msgid "Save visible translations"
     1629msgstr ""
     1630
     1631#: ait-multilingual-translate.php:10208
     1632msgid "First"
     1633msgstr ""
     1634
     1635#: ait-multilingual-translate.php:10209
     1636#: includes/class-aitmt-scanner.php:1114
     1637msgid "Previous"
     1638msgstr ""
     1639
     1640#: ait-multilingual-translate.php:10230
     1641#: includes/class-aitmt-scanner.php:1115
     1642msgid "Next"
     1643msgstr ""
     1644
     1645#: ait-multilingual-translate.php:10231
     1646msgid "Last"
     1647msgstr ""
     1648
     1649#: ait-multilingual-translate.php:10266
     1650msgid "AI Translate - Translations"
     1651msgstr ""
     1652
     1653#: ait-multilingual-translate.php:10267
     1654msgid "Edit each source string per target language. Empty fields can be filled manually or translated from the queue."
     1655msgstr ""
     1656
     1657#: ait-multilingual-translate.php:10271
     1658msgid "Translations Matrix"
     1659msgstr ""
     1660
     1661#: ait-multilingual-translate.php:10281
     1662msgid "Scan new strings"
     1663msgstr ""
     1664
     1665#: ait-multilingual-translate.php:10291
     1666msgid "Process queue"
     1667msgstr ""
     1668
     1669#: ait-multilingual-translate.php:10298
     1670msgid "Per page"
     1671msgstr ""
     1672
     1673#: ait-multilingual-translate.php:10311
     1674msgid "Search"
     1675msgstr ""
     1676
     1677#: ait-multilingual-translate.php:10312
     1678msgid "Source text or hash..."
     1679msgstr ""
     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
     1685msgid "Status"
     1686msgstr ""
     1687
     1688#: ait-multilingual-translate.php:10317
     1689msgid "All strings"
     1690msgstr ""
     1691
     1692#: ait-multilingual-translate.php:10318
     1693msgid "Needs translation"
     1694msgstr ""
     1695
     1696#: ait-multilingual-translate.php:10319
     1697#: includes/class-aitmt-scanner.php:815
     1698msgid "Translated"
     1699msgstr ""
     1700
     1701#: ait-multilingual-translate.php:10322
     1702#: includes/class-aitmt-scanner.php:1058
     1703msgid "Filter"
     1704msgstr ""
     1705
     1706#: ait-multilingual-translate.php:10323
     1707msgid "Reset"
     1708msgstr ""
     1709
     1710#: ait-multilingual-translate.php:10327
     1711msgid "Last bulk save:"
     1712msgstr ""
     1713
     1714#: ait-multilingual-translate.php:10333
     1715msgid "Export / Import"
     1716msgstr ""
     1717
     1718#: ait-multilingual-translate.php:10338
     1719#: ait-multilingual-translate.php:10355
     1720msgid "Export translations"
     1721msgstr ""
     1722
     1723#: ait-multilingual-translate.php:10340
     1724msgid "Export language"
     1725msgstr ""
     1726
     1727#: ait-multilingual-translate.php:10348
     1728#: ait-multilingual-translate.php:10370
     1729msgid "Format"
     1730msgstr ""
     1731
     1732#: ait-multilingual-translate.php:10360
     1733#: ait-multilingual-translate.php:10378
     1734msgid "Import translations"
     1735msgstr ""
     1736
     1737#: ait-multilingual-translate.php:10362
     1738msgid "Import language"
     1739msgstr ""
     1740
     1741#: ait-multilingual-translate.php:10379
     1742msgid "CSV columns: source_text and translated_text. PO/MO import uses msgid as the original string and msgstr as the saved translation."
     1743msgstr ""
     1744
     1745#: ait-multilingual-translate.php:10385
     1746msgid "Select target languages first."
     1747msgstr ""
     1748
     1749#: ait-multilingual-translate.php:10387
     1750msgid "No strings collected yet. Run the scanner or open translated pages once to collect frontend strings."
     1751msgstr ""
     1752
     1753#: ait-multilingual-translate.php:10400
     1754#: includes/class-aitmt-scanner.php:1083
     1755msgid "Original"
     1756msgstr ""
     1757
     1758#: ait-multilingual-translate.php:10423
     1759#: ait-multilingual-translate.php:10509
     1760msgid "Status:"
     1761msgstr ""
     1762
     1763#: ait-multilingual-translate.php:10482
     1764msgid "AI Translate - Scanner"
     1765msgstr ""
     1766
     1767#: ait-multilingual-translate.php:10483
     1768msgid "Collect site strings into the queue, then translate queued strings in controlled batches."
     1769msgstr ""
     1770
     1771#: ait-multilingual-translate.php:10487
     1772msgid "Scanner and Queue"
     1773msgstr ""
     1774
     1775#: ait-multilingual-translate.php:10490
     1776msgid "Auto scan on save:"
     1777msgstr ""
     1778
     1779#: ait-multilingual-translate.php:10492
     1780msgid "Background processing:"
     1781msgstr ""
     1782
     1783#: ait-multilingual-translate.php:10494
     1784msgid "Page-load collection:"
     1785msgstr ""
     1786
     1787#: ait-multilingual-translate.php:10496
     1788msgid "Translation Mode:"
     1789msgstr ""
     1790
     1791#: ait-multilingual-translate.php:10498
     1792msgid "Quality:"
     1793msgstr ""
     1794
     1795#: ait-multilingual-translate.php:10503
     1796msgid "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."
     1797msgstr ""
     1798
     1799#: ait-multilingual-translate.php:10507
     1800msgid "Background Translate All"
     1801msgstr ""
     1802
     1803#: ait-multilingual-translate.php:10510
     1804msgid "Processed:"
     1805msgstr ""
     1806
     1807#: ait-multilingual-translate.php:10511
     1808msgid "From cache:"
     1809msgstr ""
     1810
     1811#: ait-multilingual-translate.php:10512
     1812msgid "Skipped:"
     1813msgstr ""
     1814
     1815#: ait-multilingual-translate.php:10513
     1816msgid "Failed:"
     1817msgstr ""
     1818
     1819#: ait-multilingual-translate.php:10514
     1820msgid "Remaining:"
     1821msgstr ""
     1822
     1823#: ait-multilingual-translate.php:10515
     1824msgid "Progress:"
     1825msgstr ""
     1826
     1827#: ait-multilingual-translate.php:10522
     1828msgid "Next queue event:"
     1829msgstr ""
     1830
     1831#: ait-multilingual-translate.php:10523
     1832msgid "not scheduled"
     1833msgstr ""
     1834
     1835#: ait-multilingual-translate.php:10528
     1836msgid "Start / Restart"
     1837msgstr ""
     1838
     1839#: ait-multilingual-translate.php:10529
     1840msgid "Pause"
     1841msgstr ""
     1842
     1843#: ait-multilingual-translate.php:10530
     1844msgid "Resume"
     1845msgstr ""
     1846
     1847#: ait-multilingual-translate.php:10531
     1848msgid "Stop worker"
     1849msgstr ""
     1850
     1851#: ait-multilingual-translate.php:10532
     1852msgid "Rebuild schedule"
     1853msgstr ""
     1854
     1855#: ait-multilingual-translate.php:10534
     1856msgid "Clear stale lock"
     1857msgstr ""
     1858
     1859#: ait-multilingual-translate.php:10541
     1860msgid "Recurring background queue"
     1861msgstr ""
     1862
     1863#: ait-multilingual-translate.php:10541
     1864msgid "Enable recurring WP-Cron processing"
     1865msgstr ""
     1866
     1867#: ait-multilingual-translate.php:10542
     1868msgid "Interval"
     1869msgstr ""
     1870
     1871#: ait-multilingual-translate.php:10547
     1872msgid "Batches per event"
     1873msgstr ""
     1874
     1875#: ait-multilingual-translate.php:10548
     1876msgid "Runtime per event (seconds)"
     1877msgstr ""
     1878
     1879#: ait-multilingual-translate.php:10549
     1880msgid "Rows per provider batch"
     1881msgstr ""
     1882
     1883#: ait-multilingual-translate.php:10549
     1884msgid "Use 3-5 for free/test quotas. Increase only after confirming provider limits."
     1885msgstr ""
     1886
     1887#: ait-multilingual-translate.php:10551
     1888msgid "Save background settings"
     1889msgstr ""
     1890
     1891#: ait-multilingual-translate.php:10555
     1892msgid "Cost optimization:"
     1893msgstr ""
     1894
     1895#: ait-multilingual-translate.php:10568
     1896msgid "Queue lock:"
     1897msgstr ""
     1898
     1899#: ait-multilingual-translate.php:10569
     1900msgid "inactive"
     1901msgstr ""
     1902
     1903#: ait-multilingual-translate.php:10572
     1904msgid "Translate All estimate:"
     1905msgstr ""
     1906
     1907#: ait-multilingual-translate.php:10576
     1908msgid "Blocked before provider:"
     1909msgstr ""
     1910
     1911#: ait-multilingual-translate.php:10591
     1912msgid "Billable by language:"
     1913msgstr ""
     1914
     1915#: ait-multilingual-translate.php:10600
     1916msgid "Queue source report:"
     1917msgstr ""
     1918
     1919#: ait-multilingual-translate.php:10614
     1920msgid "Queue debug report"
     1921msgstr ""
     1922
     1923#: ait-multilingual-translate.php:10616
     1924msgid "Duplicate hash groups:"
     1925msgstr ""
     1926
     1927#: ait-multilingual-translate.php:10618
     1928msgid "duplicate rows:"
     1929msgstr ""
     1930
     1931#: ait-multilingual-translate.php:10622
     1932msgid "Top skipped reasons"
     1933msgstr ""
     1934
     1935#: ait-multilingual-translate.php:10624
     1936#: ait-multilingual-translate.php:10719
     1937msgid "Reason"
     1938msgstr ""
     1939
     1940#: ait-multilingual-translate.php:10624
     1941msgid "Skipped strings"
     1942msgstr ""
     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
     1951msgid "Characters"
     1952msgstr ""
     1953
     1954#: ait-multilingual-translate.php:10634
     1955msgid "No rejected technical strings recorded yet."
     1956msgstr ""
     1957
     1958#: ait-multilingual-translate.php:10639
     1959msgid "Top source types"
     1960msgstr ""
     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
     1968msgid "Source type"
     1969msgstr ""
     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
     1976msgid "Rows"
     1977msgstr ""
     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
     1983msgid "Unique sources"
     1984msgstr ""
     1985
     1986#: ait-multilingual-translate.php:10654
     1987msgid "Rows by target language"
     1988msgstr ""
     1989
     1990#: ait-multilingual-translate.php:10656
     1991#: ait-multilingual-translate.php:10701
     1992msgid "Language"
     1993msgstr ""
     1994
     1995#: ait-multilingual-translate.php:10669
     1996msgid "Top object IDs"
     1997msgstr ""
     1998
     1999#: ait-multilingual-translate.php:10671
     2000msgid "Object ID"
     2001msgstr ""
     2002
     2003#: ait-multilingual-translate.php:10684
     2004msgid "Top field keys"
     2005msgstr ""
     2006
     2007#: ait-multilingual-translate.php:10686
     2008msgid "Field key"
     2009msgstr ""
     2010
     2011#: ait-multilingual-translate.php:10699
     2012msgid "Top 50 longest queued strings"
     2013msgstr ""
     2014
     2015#: ait-multilingual-translate.php:10701
     2016#: ait-multilingual-translate.php:10719
     2017msgid "Chars"
     2018msgstr ""
     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
     2025msgid "Object"
     2026msgstr ""
     2027
     2028#: ait-multilingual-translate.php:10701
     2029#: ait-multilingual-translate.php:10719
     2030#: includes/class-aitmt-runtime-maintenance.php:503
     2031msgid "Field"
     2032msgstr ""
     2033
     2034#: ait-multilingual-translate.php:10701
     2035#: ait-multilingual-translate.php:10719
     2036msgid "Sample"
     2037msgstr ""
     2038
     2039#: ait-multilingual-translate.php:10701
     2040msgid "Hash"
     2041msgstr ""
     2042
     2043#: ait-multilingual-translate.php:10717
     2044msgid "Top 50 rejected technical strings"
     2045msgstr ""
     2046
     2047#: ait-multilingual-translate.php:10719
     2048#: ait-multilingual-translate.php:10887
     2049#: ait-multilingual-translate.php:11053
     2050msgid "Time"
     2051msgstr ""
     2052
     2053#: ait-multilingual-translate.php:10733
     2054msgid "No rejected technical string samples recorded yet."
     2055msgstr ""
     2056
     2057#: ait-multilingual-translate.php:10741
     2058#: ait-multilingual-translate.php:12173
     2059msgid "Last scan:"
     2060msgstr ""
     2061
     2062#: ait-multilingual-translate.php:10748
     2063#: ait-multilingual-translate.php:12222
     2064msgid "Last automatic scan:"
     2065msgstr ""
     2066
     2067#: ait-multilingual-translate.php:10755
     2068#: ait-multilingual-translate.php:12229
     2069msgid "Last queue run:"
     2070msgstr ""
     2071
     2072#: ait-multilingual-translate.php:10758
     2073#: ait-multilingual-translate.php:12232
     2074msgid "Routes:"
     2075msgstr ""
     2076
     2077#: ait-multilingual-translate.php:10765
     2078#: ait-multilingual-translate.php:12241
     2079msgid "Last background run:"
     2080msgstr ""
     2081
     2082#: ait-multilingual-translate.php:10778
     2083#: ait-multilingual-translate.php:12249
     2084msgid "Process translation queue"
     2085msgstr ""
     2086
     2087#: ait-multilingual-translate.php:10788
     2088msgid "Translate All"
     2089msgstr ""
     2090
     2091#: ait-multilingual-translate.php:10791
     2092msgid "Translate All blocked"
     2093msgstr ""
     2094
     2095#: ait-multilingual-translate.php:10801
     2096msgid "Clear unknown queue items"
     2097msgstr ""
     2098
     2099#: ait-multilingual-translate.php:10812
     2100msgid "Clear technical queue items"
     2101msgstr ""
     2102
     2103#: ait-multilingual-translate.php:10823
     2104msgid "Clear page-load queue items"
     2105msgstr ""
     2106
     2107#: ait-multilingual-translate.php:10829
     2108#: ait-multilingual-translate.php:12254
     2109msgid "Clear queued strings"
     2110msgstr ""
     2111
     2112#: ait-multilingual-translate.php:10832
     2113msgid "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."
     2114msgstr ""
     2115
     2116#: ait-multilingual-translate.php:10850
     2117msgid "AI Translate - Usage"
     2118msgstr ""
     2119
     2120#: ait-multilingual-translate.php:10851
     2121msgid "Review provider request usage, character counts, queue sources and blocked requests."
     2122msgstr ""
     2123
     2124#: ait-multilingual-translate.php:10855
     2125msgid "Provider Usage"
     2126msgstr ""
     2127
     2128#: ait-multilingual-translate.php:10857
     2129msgid "Today"
     2130msgstr ""
     2131
     2132#: ait-multilingual-translate.php:10857
     2133#: ait-multilingual-translate.php:10858
     2134msgid "characters"
     2135msgstr ""
     2136
     2137#: ait-multilingual-translate.php:10858
     2138msgid "This month"
     2139msgstr ""
     2140
     2141#: ait-multilingual-translate.php:10859
     2142msgid "Successful"
     2143msgstr ""
     2144
     2145#: ait-multilingual-translate.php:10859
     2146#: ait-multilingual-translate.php:10861
     2147msgid "requests"
     2148msgstr ""
     2149
     2150#: ait-multilingual-translate.php:10860
     2151msgid "Blocked"
     2152msgstr ""
     2153
     2154#: ait-multilingual-translate.php:10860
     2155msgid "before API or by provider"
     2156msgstr ""
     2157
     2158#: ait-multilingual-translate.php:10861
     2159msgid "Errors"
     2160msgstr ""
     2161
     2162#: ait-multilingual-translate.php:10865
     2163msgid "Queue Sources"
     2164msgstr ""
     2165
     2166#: ait-multilingual-translate.php:10867
     2167#: includes/class-aitmt-scanner.php:855
     2168msgid "Queued"
     2169msgstr ""
     2170
     2171#: ait-multilingual-translate.php:10867
     2172#: includes/class-aitmt-scanner.php:856
     2173msgid "Processing"
     2174msgstr ""
     2175
     2176#: ait-multilingual-translate.php:10867
     2177#: includes/class-aitmt-scanner.php:857
     2178msgid "Failed"
     2179msgstr ""
     2180
     2181#: ait-multilingual-translate.php:10867
     2182msgid "Queued chars"
     2183msgstr ""
     2184
     2185#: ait-multilingual-translate.php:10867
     2186msgid "Total chars"
     2187msgstr ""
     2188
     2189#: ait-multilingual-translate.php:10883
     2190msgid "Recent Provider Requests"
     2191msgstr ""
     2192
     2193#: ait-multilingual-translate.php:10889
     2194msgid "Source"
     2195msgstr ""
     2196
     2197#: ait-multilingual-translate.php:10890
     2198msgid "Target"
     2199msgstr ""
     2200
     2201#: ait-multilingual-translate.php:10892
     2202msgid "Batch"
     2203msgstr ""
     2204
     2205#: ait-multilingual-translate.php:10894
     2206msgid "Error"
     2207msgstr ""
     2208
     2209#: ait-multilingual-translate.php:10911
     2210msgid "No provider requests logged yet."
     2211msgstr ""
     2212
     2213#: ait-multilingual-translate.php:10939
     2214msgid "AI Translate - Debugger"
     2215msgstr ""
     2216
     2217#: ait-multilingual-translate.php:10940
     2218msgid "Check provider access, current route, translation table, and the latest API error."
     2219msgstr ""
     2220
     2221#: ait-multilingual-translate.php:11005
     2222msgid "Write extended debug log to a separate file"
     2223msgstr ""
     2224
     2225#: ait-multilingual-translate.php:11007
     2226msgid "Save debug settings"
     2227msgstr ""
     2228
     2229#: ait-multilingual-translate.php:11008
     2230msgid "Use this only while testing. The file stores route/API events as JSON lines and is cleared by Clear debug log."
     2231msgstr ""
     2232
     2233#: ait-multilingual-translate.php:11013
     2234msgid "Last frontend route:"
     2235msgstr ""
     2236
     2237#: ait-multilingual-translate.php:11036
     2238#: ait-multilingual-translate.php:12314
     2239msgid "Last provider test:"
     2240msgstr ""
     2241
     2242#: ait-multilingual-translate.php:11039
     2243#: ait-multilingual-translate.php:12320
     2244msgid "HTTP status:"
     2245msgstr ""
     2246
     2247#: ait-multilingual-translate.php:11042
     2248#: ait-multilingual-translate.php:12234
     2249#: ait-multilingual-translate.php:12326
     2250msgid "Route:"
     2251msgstr ""
     2252
     2253#: ait-multilingual-translate.php:11049
     2254msgid "Debug log"
     2255msgstr ""
     2256
     2257#: ait-multilingual-translate.php:11049
     2258msgid "Latest 20 events"
     2259msgstr ""
     2260
     2261#: ait-multilingual-translate.php:11056
     2262msgid "Context"
     2263msgstr ""
     2264
     2265#: ait-multilingual-translate.php:11082
     2266#: ait-multilingual-translate.php:12338
     2267msgid "Test provider translation"
     2268msgstr ""
     2269
     2270#: ait-multilingual-translate.php:11087
     2271#: ait-multilingual-translate.php:12343
     2272msgid "Download log file"
     2273msgstr ""
     2274
     2275#: ait-multilingual-translate.php:11092
     2276#: ait-multilingual-translate.php:12348
     2277msgid "Clear debug log"
     2278msgstr ""
     2279
     2280#: ait-multilingual-translate.php:11097
     2281#: ait-multilingual-translate.php:12353
     2282msgid "Clear provider cooldown"
     2283msgstr ""
     2284
     2285#: ait-multilingual-translate.php:11180
     2286msgid "Debug log file was not found."
     2287msgstr ""
     2288
     2289#: ait-multilingual-translate.php:11249
     2290msgid "Translate All was not started because the cost confirmation checkbox was not selected."
     2291msgstr ""
     2292
     2293#: ait-multilingual-translate.php:11251
     2294msgid "Translate All is blocked until unknown and technical queue rows are reviewed or cleared."
     2295msgstr ""
     2296
     2297#: ait-multilingual-translate.php:11331
     2298msgid "Unknown queue items were not cleared because the confirmation checkbox was not selected."
     2299msgstr ""
     2300
     2301#: ait-multilingual-translate.php:11367
     2302msgid "Technical queue items were not cleared because the confirmation checkbox was not selected."
     2303msgstr ""
     2304
     2305#: ait-multilingual-translate.php:11407
     2306msgid "Page-load queue items were not cleared because the confirmation checkbox was not selected."
     2307msgstr ""
     2308
     2309#: ait-multilingual-translate.php:11622
     2310msgid "Scan strings, translate them in batches, then edit saved translations on the frontend."
     2311msgstr ""
     2312
     2313#: ait-multilingual-translate.php:11633
     2314msgid "Settings saved."
     2315msgstr ""
     2316
     2317#: ait-multilingual-translate.php:11639
     2318msgid "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."
     2319msgstr ""
     2320
     2321#: ait-multilingual-translate.php:11646
     2322msgid "Dashboard Overview"
     2323msgstr ""
     2324
     2325#: ait-multilingual-translate.php:11651
     2326msgid "API key configured"
     2327msgstr ""
     2328
     2329#: ait-multilingual-translate.php:11651
     2330msgid "API key missing"
     2331msgstr ""
     2332
     2333#: ait-multilingual-translate.php:11654
     2334msgid "Queue / Worker"
     2335msgstr ""
     2336
     2337#: ait-multilingual-translate.php:11659
     2338msgid "Usage Today"
     2339msgstr ""
     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
     2346msgid "Limit: %s characters"
     2347msgstr ""
     2348
     2349#: ait-multilingual-translate.php:11665
     2350msgid "Usage This Month"
     2351msgstr ""
     2352
     2353#: ait-multilingual-translate.php:11671
     2354msgid "Translation Memory"
     2355msgstr ""
     2356
     2357#: ait-multilingual-translate.php:11673
     2358msgid "API requests avoided by local reuse"
     2359msgstr ""
     2360
     2361#: ait-multilingual-translate.php:11676
     2362msgid "Last Translation"
     2363msgstr ""
     2364
     2365#: ait-multilingual-translate.php:11677
     2366msgid "None yet"
     2367msgstr ""
     2368
     2369#: ait-multilingual-translate.php:11681
     2370msgid "Next Background Event"
     2371msgstr ""
     2372
     2373#: ait-multilingual-translate.php:11682
     2374msgid "Not scheduled"
     2375msgstr ""
     2376
     2377#: ait-multilingual-translate.php:11683
     2378msgid "Cron enabled"
     2379msgstr ""
     2380
     2381#: ait-multilingual-translate.php:11683
     2382msgid "Cron disabled"
     2383msgstr ""
     2384
     2385#: ait-multilingual-translate.php:11690
     2386msgid "API spending protection needs attention."
     2387msgstr ""
     2388
     2389#: ait-multilingual-translate.php:11691
     2390msgid "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."
     2391msgstr ""
     2392
     2393#: ait-multilingual-translate.php:11700
     2394msgid "Apply safe API defaults"
     2395msgstr ""
     2396
     2397#: ait-multilingual-translate.php:11707
     2398msgid "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."
     2399msgstr ""
     2400
     2401#: ait-multilingual-translate.php:11712
     2402msgid "Last translation error:"
     2403msgstr ""
     2404
     2405#: ait-multilingual-translate.php:11716
     2406msgid "AIT Multilingual Translate sections"
     2407msgstr ""
     2408
     2409#: ait-multilingual-translate.php:11717
     2410#: ait-multilingual-translate.php:11736
     2411msgid "General"
     2412msgstr ""
     2413
     2414#: ait-multilingual-translate.php:11718
     2415#: ait-multilingual-translate.php:11770
     2416msgid "Languages & Locales"
     2417msgstr ""
     2418
     2419#: ait-multilingual-translate.php:11719
     2420#: ait-multilingual-translate.php:11826
     2421msgid "Translation Providers"
     2422msgstr ""
     2423
     2424#: ait-multilingual-translate.php:11720
     2425msgid "Frontend Editor"
     2426msgstr ""
     2427
     2428#: ait-multilingual-translate.php:11721
     2429msgid "Exclusions"
     2430msgstr ""
     2431
     2432#: ait-multilingual-translate.php:11722
     2433msgid "Cache"
     2434msgstr ""
     2435
     2436#: ait-multilingual-translate.php:11723
     2437msgid "SEO & URLs"
     2438msgstr ""
     2439
     2440#: ait-multilingual-translate.php:11725
     2441msgid "Queue"
     2442msgstr ""
     2443
     2444#: ait-multilingual-translate.php:11726
     2445msgid "Diagnostics"
     2446msgstr ""
     2447
     2448#: ait-multilingual-translate.php:11739
     2449msgid "Interface mode"
     2450msgstr ""
     2451
     2452#: ait-multilingual-translate.php:11742
     2453msgid "Basic"
     2454msgstr ""
     2455
     2456#: ait-multilingual-translate.php:11743
     2457msgid "Advanced"
     2458msgstr ""
     2459
     2460#: ait-multilingual-translate.php:11745
     2461msgid "Basic mode shows the setup needed for everyday translation. Advanced mode reveals queue diagnostics, provider internals, scanner details, routes, and logs."
     2462msgstr ""
     2463
     2464#: ait-multilingual-translate.php:11756
     2465msgid "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."
     2466msgstr ""
     2467
     2468#: ait-multilingual-translate.php:11760
     2469msgid "Build status"
     2470msgstr ""
     2471
     2472#: ait-multilingual-translate.php:11762
     2473msgid "Public Beta preparation build."
     2474msgstr ""
     2475
     2476#: ait-multilingual-translate.php:11763
     2477msgid "Feature flag helpers are prepared for future package variants. All current beta features remain enabled."
     2478msgstr ""
     2479
     2480#: ait-multilingual-translate.php:11785
     2481msgid "This is the language your original site content is written in. Auto uses the WordPress site language."
     2482msgstr ""
     2483
     2484#: ait-multilingual-translate.php:11801
     2485msgid "The root URL displays this language without a prefix when default-language hiding is enabled. The original content language remains unchanged."
     2486msgstr ""
     2487
     2488#: ait-multilingual-translate.php:11806
     2489#: includes/class-aitmt-settings.php:227
     2490msgid "Target languages"
     2491msgstr ""
     2492
     2493#: ait-multilingual-translate.php:11819
     2494msgid "Select only the languages you want visitors to use. Every selected target language creates its own saved translation rows."
     2495msgstr ""
     2496
     2497#: ait-multilingual-translate.php:11836
     2498msgid "The provider used for new queue translations. Already saved manual/published translations are reused and are not sent again."
     2499msgstr ""
     2500
     2501#: ait-multilingual-translate.php:11838
     2502msgid "Translation Memory first: existing manual, imported, draft, and published translations are reused before any provider call. Duplicate strings are skipped before API requests."
     2503msgstr ""
     2504
     2505#: ait-multilingual-translate.php:11840
     2506msgid "Provider capabilities"
     2507msgstr ""
     2508
     2509#: ait-multilingual-translate.php:11846
     2510msgid "Active"
     2511msgstr ""
     2512
     2513#: ait-multilingual-translate.php:11846
     2514msgid "Planned"
     2515msgstr ""
     2516
     2517#: ait-multilingual-translate.php:11853
     2518msgid "Free quota available"
     2519msgstr ""
     2520
     2521#: ait-multilingual-translate.php:11867
     2522msgid "Each card represents an active provider adapter. Open the matching settings panel below to configure and test it before saving."
     2523msgstr ""
     2524
     2525#: ait-multilingual-translate.php:11871
     2526msgid "Provider settings"
     2527msgstr ""
     2528
     2529#: ait-multilingual-translate.php:11876
     2530msgid "Translation Style / Tone of Voice"
     2531msgstr ""
     2532
     2533#: ait-multilingual-translate.php:11877
     2534msgid "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."
     2535msgstr ""
     2536
     2537#: ait-multilingual-translate.php:11882
     2538msgid "Global Translation Mode"
     2539msgstr ""
     2540
     2541#: ait-multilingual-translate.php:11890
     2542msgid "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."
     2543msgstr ""
     2544
     2545#: ait-multilingual-translate.php:11891
     2546msgid "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."
     2547msgstr ""
     2548
     2549#: ait-multilingual-translate.php:11896
     2550msgid "Custom translation instruction"
     2551msgstr ""
     2552
     2553#: ait-multilingual-translate.php:11899
     2554msgid "Example: Translate naturally for Georgian customers, keep product names unchanged, use friendly but professional tone."
     2555msgstr ""
     2556
     2557#: ait-multilingual-translate.php:11900
     2558msgid "Used only when Translation Mode is Custom Prompt. Maximum 500 characters."
     2559msgstr ""
     2560
     2561#: ait-multilingual-translate.php:11905
     2562msgid "AI Cost Optimization"
     2563msgstr ""
     2564
     2565#: ait-multilingual-translate.php:11906
     2566msgid "Use cheap models by default, deduplicate strings before provider calls, reuse translation memory, and keep safety limits enabled while testing provider quota."
     2567msgstr ""
     2568
     2569#: ait-multilingual-translate.php:11908
     2570msgid "Safety presets"
     2571msgstr ""
     2572
     2573#: ait-multilingual-translate.php:11909
     2574msgid "Choose a profile only when you want it to replace the related local limits and worker settings. Provider-side billing limits remain required."
     2575msgstr ""
     2576
     2577#: ait-multilingual-translate.php:11910
     2578msgid "Apply Safe Free Mode"
     2579msgstr ""
     2580
     2581#: ait-multilingual-translate.php:11911
     2582msgid "Apply Recommended"
     2583msgstr ""
     2584
     2585#: ait-multilingual-translate.php:11912
     2586msgid "Fast Bulk can consume substantial paid API quota. Continue only after setting provider billing limits."
     2587msgstr ""
     2588
     2589#: ait-multilingual-translate.php:11912
     2590msgid "Apply Fast Bulk Translation"
     2591msgstr ""
     2592
     2593#: ait-multilingual-translate.php:11919
     2594msgid "Translation quality mode"
     2595msgstr ""
     2596
     2597#: ait-multilingual-translate.php:11927
     2598msgid "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."
     2599msgstr ""
     2600
     2601#: ait-multilingual-translate.php:11931
     2602msgid "Recommended default models"
     2603msgstr ""
     2604
     2605#: ait-multilingual-translate.php:11934
     2606msgid "OpenAI Cheap: gpt-4o-mini"
     2607msgstr ""
     2608
     2609#: ait-multilingual-translate.php:11935
     2610msgid "OpenAI Premium: gpt-4o"
     2611msgstr ""
     2612
     2613#: ait-multilingual-translate.php:11936
     2614msgid "Gemini Cheap: Flash model"
     2615msgstr ""
     2616
     2617#: ait-multilingual-translate.php:11937
     2618msgid "Gemini Premium: Pro model"
     2619msgstr ""
     2620
     2621#: ait-multilingual-translate.php:11938
     2622msgid "Grok Cheap: lightweight/mini model where available"
     2623msgstr ""
     2624
     2625#: ait-multilingual-translate.php:11939
     2626msgid "Claude Cheap: Haiku model where available"
     2627msgstr ""
     2628
     2629#: ait-multilingual-translate.php:11940
     2630msgid "Mistral Cheap: small/fast model"
     2631msgstr ""
     2632
     2633#: ait-multilingual-translate.php:11941
     2634msgid "DeepSeek Cheap: chat/standard model"
     2635msgstr ""
     2636
     2637#: ait-multilingual-translate.php:11943
     2638msgid "The active Public Beta providers keep using the model field you set above. Planned provider model names are shown as release-planning guidance only."
     2639msgstr ""
     2640
     2641#: ait-multilingual-translate.php:11948
     2642msgid "Temperature"
     2643msgstr ""
     2644
     2645#: ait-multilingual-translate.php:11952
     2646msgid "Default 0.1 keeps translations stable and predictable for AI providers."
     2647msgstr ""
     2648
     2649#: ait-multilingual-translate.php:11956
     2650msgid "Quota control"
     2651msgstr ""
     2652
     2653#: ait-multilingual-translate.php:11960
     2654msgid "Local safety limit for the active provider. 0 means no plugin-side daily stop."
     2655msgstr ""
     2656
     2657#: ait-multilingual-translate.php:11963
     2658msgid "Local safety limit for the active provider. Provider billing limits still need to be configured in OpenAI/Google/DeepL/xAI accounts."
     2659msgstr ""
     2660
     2661#: ait-multilingual-translate.php:11966
     2662msgid "Local request-rate ceiling checked before contacting the provider. Recommended: 5 for free/test plans."
     2663msgstr ""
     2664
     2665#: ait-multilingual-translate.php:11967
     2666msgid "Estimated tokens per minute"
     2667msgstr ""
     2668
     2669#: ait-multilingual-translate.php:11969
     2670msgid "Conservative local estimate based on source characters. Provider token accounting remains authoritative."
     2671msgstr ""
     2672
     2673#: ait-multilingual-translate.php:11972
     2674msgid "Optional local daily request ceiling. 0 disables only this local check."
     2675msgstr ""
     2676
     2677#: ait-multilingual-translate.php:11973
     2678msgid "Max characters per request"
     2679msgstr ""
     2680
     2681#: ait-multilingual-translate.php:11975
     2682msgid "0 means no plugin-side per-request character stop. Set a limit to avoid accidentally sending very large batches."
     2683msgstr ""
     2684
     2685#: ait-multilingual-translate.php:11976
     2686msgid "Estimated cost limit per request"
     2687msgstr ""
     2688
     2689#: ait-multilingual-translate.php:11978
     2690msgid "0 means no estimated-cost stop. Estimates are approximate and provider billing remains authoritative."
     2691msgstr ""
     2692
     2693#: ait-multilingual-translate.php:11979
     2694msgid "Queue lock TTL"
     2695msgstr ""
     2696
     2697#: ait-multilingual-translate.php:11981
     2698msgid "Prevents cron, Translate All and manual queue processing from running at the same time. Expired locks are released automatically."
     2699msgstr ""
     2700
     2701#: ait-multilingual-translate.php:11982
     2702msgid "Processing TTL"
     2703msgstr ""
     2704
     2705#: ait-multilingual-translate.php:11984
     2706msgid "If a processing row is abandoned for longer than this, it can be returned to the queue or marked failed depending on retry count."
     2707msgstr ""
     2708
     2709#: ait-multilingual-translate.php:11985
     2710msgid "Retry limit"
     2711msgstr ""
     2712
     2713#: ait-multilingual-translate.php:11987
     2714msgid "Failed rows stop retrying after this many attempts and are marked as failed."
     2715msgstr ""
     2716
     2717#: ait-multilingual-translate.php:11991
     2718#: includes/class-aitmt-settings.php:281
     2719msgid "Translation behavior"
     2720msgstr ""
     2721
     2722#: ait-multilingual-translate.php:11993
     2723msgid "Enable frontend translation output"
     2724msgstr ""
     2725
     2726#: ait-multilingual-translate.php:11994
     2727msgid "Collect missing strings into the translation queue"
     2728msgstr ""
     2729
     2730#: ait-multilingual-translate.php:11995
     2731msgid "Automatically scan saved/updated content for new strings"
     2732msgstr ""
     2733
     2734#: ait-multilingual-translate.php:11996
     2735msgid "Automatically process the translation queue in the background"
     2736msgstr ""
     2737
     2738#: ait-multilingual-translate.php:11997
     2739msgid "Translate missing strings during page load (slower, use only for testing)"
     2740msgstr ""
     2741
     2742#: ait-multilingual-translate.php:11998
     2743#: includes/class-aitmt-settings.php:290
     2744msgid "Save new AI translations as drafts"
     2745msgstr ""
     2746
     2747#: ait-multilingual-translate.php:11999
     2748#: includes/class-aitmt-settings.php:295
     2749msgid "Translate alt, title, placeholder, aria-label, and SEO meta attributes"
     2750msgstr ""
     2751
     2752#: ait-multilingual-translate.php:12000
     2753msgid "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."
     2754msgstr ""
     2755
     2756#: ait-multilingual-translate.php:12002
     2757msgid "Warning: draft mode plus page-load translation can create many draft rows while visitors browse. Use queue processing instead for production sites."
     2758msgstr ""
     2759
     2760#: ait-multilingual-translate.php:12007
     2761msgid "Runtime translation cache"
     2762msgstr ""
     2763
     2764#: ait-multilingual-translate.php:12009
     2765msgid "Allow direct provider translation during a public page request"
     2766msgstr ""
     2767
     2768#: ait-multilingual-translate.php:12010
     2769msgid "Allow page-load strings and translations to be stored in the database"
     2770msgstr ""
     2771
     2772#: ait-multilingual-translate.php:12011
     2773msgid "Automatically remove a small daily batch of expired page-load cache rows"
     2774msgstr ""
     2775
     2776#: ait-multilingual-translate.php:12012
     2777msgid "Render existing saved translations on cart, checkout, and account pages"
     2778msgstr ""
     2779
     2780#: ait-multilingual-translate.php:12013
     2781msgid "This option never collects, stores, or sends private WooCommerce page text to a provider. It only permits rendering translations that already exist."
     2782msgstr ""
     2783
     2784#: ait-multilingual-translate.php:12014
     2785msgid "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."
     2786msgstr ""
     2787
     2788#: ait-multilingual-translate.php:12016
     2789msgid "Runtime cache TTL (days)"
     2790msgstr ""
     2791
     2792#: ait-multilingual-translate.php:12018
     2793msgid "Maximum page-load rows per target language"
     2794msgstr ""
     2795
     2796#: ait-multilingual-translate.php:12020
     2797msgid "Minimum runtime text length"
     2798msgstr ""
     2799
     2800#: ait-multilingual-translate.php:12022
     2801msgid "Maximum runtime text length"
     2802msgstr ""
     2803
     2804#: ait-multilingual-translate.php:12026
     2805msgid "Excluded URL paths (one per line)"
     2806msgstr ""
     2807
     2808#: ait-multilingual-translate.php:12028
     2809msgid "Excluded selectors (class, ID, tag, or [attribute])"
     2810msgstr ""
     2811
     2812#: ait-multilingual-translate.php:12030
     2813msgid "Excluded source types (comma or line separated)"
     2814msgstr ""
     2815
     2816#: ait-multilingual-translate.php:12033
     2817msgid "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."
     2818msgstr ""
     2819
     2820#: ait-multilingual-translate.php:12034
     2821msgid "Review or clear runtime cache safely"
     2822msgstr ""
     2823
     2824#: ait-multilingual-translate.php:12043
     2825msgid "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."
     2826msgstr ""
     2827
     2828#: ait-multilingual-translate.php:12050
     2829#: includes/class-aitmt-settings.php:312
     2830msgid "URLs and SEO"
     2831msgstr ""
     2832
     2833#: ait-multilingual-translate.php:12053
     2834msgid "URL mode"
     2835msgstr ""
     2836
     2837#: ait-multilingual-translate.php:12057
     2838msgid "Directory URLs: /ka/about/"
     2839msgstr ""
     2840
     2841#: ait-multilingual-translate.php:12062
     2842msgid "Query URLs: /about/?lang=ka"
     2843msgstr ""
     2844
     2845#: ait-multilingual-translate.php:12064
     2846msgid "Directory URLs are better for SEO, but require WordPress permalinks and rewrite rules. Query URLs are simpler and useful while testing."
     2847msgstr ""
     2848
     2849#: ait-multilingual-translate.php:12066
     2850#: includes/class-aitmt-settings.php:334
     2851msgid "Keep the source language without a language prefix"
     2852msgstr ""
     2853
     2854#: ait-multilingual-translate.php:12067
     2855msgid "When enabled, the original language stays on normal URLs while only translated languages use a language code."
     2856msgstr ""
     2857
     2858#: ait-multilingual-translate.php:12076
     2859msgid "Shortcode:"
     2860msgstr ""
     2861
     2862#: ait-multilingual-translate.php:12087
     2863msgid "Dropdown is compact for headers. List is useful in footers, sidebars, and menu-like layouts."
     2864msgstr ""
     2865
     2866#: ait-multilingual-translate.php:12091
     2867#: includes/class-aitmt-settings.php:355
     2868msgid "Display parts"
     2869msgstr ""
     2870
     2871#: ait-multilingual-translate.php:12093
     2872#: includes/class-aitmt-settings.php:359
     2873msgid "Flags"
     2874msgstr ""
     2875
     2876#: ait-multilingual-translate.php:12094
     2877#: includes/class-aitmt-settings.php:364
     2878msgid "Language names"
     2879msgstr ""
     2880
     2881#: ait-multilingual-translate.php:12095
     2882#: includes/class-aitmt-settings.php:369
     2883msgid "Language codes"
     2884msgstr ""
     2885
     2886#: ait-multilingual-translate.php:12096
     2887msgid "Choose what visitors see inside the selector, for example Georgian, KA, or both."
     2888msgstr ""
     2889
     2890#: ait-multilingual-translate.php:12100
     2891#: includes/class-aitmt-settings.php:374
     2892msgid "Automatic placement"
     2893msgstr ""
     2894
     2895#: ait-multilingual-translate.php:12102
     2896msgid "Try to show in header"
     2897msgstr ""
     2898
     2899#: ait-multilingual-translate.php:12103
     2900#: includes/class-aitmt-settings.php:383
     2901msgid "Show in footer"
     2902msgstr ""
     2903
     2904#: ait-multilingual-translate.php:12104
     2905msgid "Automatic placement is optional. You can also place the selector with a shortcode, widget, or theme/menu area."
     2906msgstr ""
     2907
     2908#: ait-multilingual-translate.php:12109
     2909msgid "Selector custom CSS"
     2910msgstr ""
     2911
     2912#: ait-multilingual-translate.php:12113
     2913msgid "Optional CSS loaded on the frontend only for styling the language selector."
     2914msgstr ""
     2915
     2916#: ait-multilingual-translate.php:12115
     2917msgid "Useful classes:"
     2918msgstr ""
     2919
     2920#: ait-multilingual-translate.php:12128
     2921msgid "When enabled, logged-in administrators see the AI Translate button in the admin bar on translated pages and can save manual corrections."
     2922msgstr ""
     2923
     2924#: ait-multilingual-translate.php:12153
     2925msgid "Buy Me a Coffee URL"
     2926msgstr ""
     2927
     2928#: ait-multilingual-translate.php:12157
     2929msgid "PayPal donation URL"
     2930msgstr ""
     2931
     2932#: ait-multilingual-translate.php:12164
     2933#: includes/class-aitmt-settings.php:403
     2934msgid "Save settings"
     2935msgstr ""
     2936
     2937#: ait-multilingual-translate.php:12168
     2938msgid "String Scanner"
     2939msgstr ""
     2940
     2941#: ait-multilingual-translate.php:12169
     2942msgid "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."
     2943msgstr ""
     2944
     2945#: ait-multilingual-translate.php:12176
     2946msgid "Targets:"
     2947msgstr ""
     2948
     2949#: ait-multilingual-translate.php:12183
     2950msgid "Queued strings"
     2951msgstr ""
     2952
     2953#: ait-multilingual-translate.php:12187
     2954msgid "Saved translations"
     2955msgstr ""
     2956
     2957#: ait-multilingual-translate.php:12200
     2958msgid "Translation Queue"
     2959msgstr ""
     2960
     2961#: ait-multilingual-translate.php:12201
     2962msgid "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."
     2963msgstr ""
     2964
     2965#: ait-multilingual-translate.php:12317
     2966msgid "Provider:"
     2967msgstr ""
     2968
     2969#: ait-multilingual-translate.php:12323
     2970msgid "Sample:"
     2971msgstr ""
     2972
     2973#: ait-multilingual-translate.php:12329
     2974msgid "Duration:"
     2975msgstr ""
     2976
     2977#: includes/class-aitmt-frontend-editor.php:117
    742978msgid "Translated via OpenAI"
    752979msgstr ""
    762980
    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
     2982msgid "Use WordPress language"
     2983msgstr ""
     2984
     2985#: includes/class-aitmt-interface-language.php:26
     2986msgid "English"
     2987msgstr ""
     2988
     2989#: includes/class-aitmt-interface-language.php:27
     2990msgid "Russian"
     2991msgstr ""
     2992
     2993#: includes/class-aitmt-interface-language.php:28
     2994msgid "Georgian"
     2995msgstr ""
     2996
     2997#: includes/class-aitmt-openai-provider.php:14
    822998msgid "OpenAI API key is missing."
    832999msgstr ""
    843000
    85 #: includes/class-wpait-openai-provider.php
     3001#: includes/class-aitmt-openai-provider.php:94
    863002msgid "OpenAI request failed."
    873003msgstr ""
    883004
    89 #: includes/class-wpait-openai-provider.php
     3005#: includes/class-aitmt-openai-provider.php:107
    903006msgid "OpenAI returned an unexpected translation payload."
    913007msgstr ""
    923008
    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
     3010msgid "The selected translation provider is not available."
     3011msgstr ""
     3012
     3013#: includes/class-aitmt-runtime-maintenance.php:169
     3014msgid "The settings migration could not be saved. Existing settings were left available in the migration backup."
     3015msgstr ""
     3016
     3017#: includes/class-aitmt-runtime-maintenance.php:175
     3018msgid "The settings migration verification failed. Review the migration backup before changing provider settings."
     3019msgstr ""
     3020
     3021#: includes/class-aitmt-runtime-maintenance.php:200
     3022msgid "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."
     3023msgstr ""
     3024
     3025#: includes/class-aitmt-runtime-maintenance.php:201
     3026msgid "Review scanner maintenance"
     3027msgstr ""
     3028
     3029#: includes/class-aitmt-runtime-maintenance.php:395
     3030#: includes/class-aitmt-runtime-maintenance.php:396
     3031msgid "Database Maintenance"
     3032msgstr ""
     3033
     3034#: includes/class-aitmt-runtime-maintenance.php:420
     3035msgid "AIT Multilingual Translate - Database Maintenance"
     3036msgstr ""
     3037
     3038#: includes/class-aitmt-runtime-maintenance.php:421
     3039msgid "Inspect and clean translation data in small, resumable batches. Updates never delete translations automatically."
     3040msgstr ""
     3041
     3042#: includes/class-aitmt-runtime-maintenance.php:431
     3043msgid "Translation rows"
     3044msgstr ""
     3045
     3046#: includes/class-aitmt-runtime-maintenance.php:432
     3047msgid "Data size"
     3048msgstr ""
     3049
     3050#: includes/class-aitmt-runtime-maintenance.php:433
     3051msgid "Index size"
     3052msgstr ""
     3053
     3054#: includes/class-aitmt-runtime-maintenance.php:434
     3055msgid "Empty source type"
     3056msgstr ""
     3057
     3058#: includes/class-aitmt-runtime-maintenance.php:435
     3059msgid "Page-load rows"
     3060msgstr ""
     3061
     3062#: includes/class-aitmt-runtime-maintenance.php:436
     3063msgid "Legacy runtime rows"
     3064msgstr ""
     3065
     3066#: includes/class-aitmt-runtime-maintenance.php:440
     3067msgid "Grouped statistics"
     3068msgstr ""
     3069
     3070#: includes/class-aitmt-runtime-maintenance.php:445
     3071msgid "Target language"
     3072msgstr ""
     3073
     3074#: includes/class-aitmt-runtime-maintenance.php:449
     3075msgid "Dry run and batched maintenance"
     3076msgstr ""
     3077
     3078#: includes/class-aitmt-runtime-maintenance.php:455
     3079msgid "Operation"
     3080msgstr ""
     3081
     3082#: includes/class-aitmt-runtime-maintenance.php:465
     3083msgid "Minimum age in days"
     3084msgstr ""
     3085
     3086#: includes/class-aitmt-runtime-maintenance.php:474
     3087msgid "Dry run"
     3088msgstr ""
     3089
     3090#: includes/class-aitmt-runtime-maintenance.php:479
     3091msgid "I understand that Apply changes one confirmed batch. Run Dry run first."
     3092msgstr ""
     3093
     3094#: includes/class-aitmt-runtime-maintenance.php:483
     3095msgid "Apply one batch"
     3096msgstr ""
     3097
     3098#: includes/class-aitmt-runtime-maintenance.php:487
     3099msgid "Scanner-derived Translation Memory"
     3100msgstr ""
     3101
     3102#: includes/class-aitmt-runtime-maintenance.php:489
     3103msgid "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."
     3104msgstr ""
     3105
     3106#. translators: 1: rows, 2: characters.
     3107#: includes/class-aitmt-runtime-maintenance.php:493
     3108#, php-format
     3109msgid "Detected obsolete scanner candidates: %1$s row(s), %2$s character(s)."
     3110msgstr ""
     3111
     3112#: includes/class-aitmt-runtime-maintenance.php:504
     3113msgid "Original text preview"
     3114msgstr ""
     3115
     3116#: includes/class-aitmt-runtime-maintenance.php:525
     3117msgid "Rebuild scans the current site and queues only missing translations. It does not delete or overwrite published, draft, manual, or imported translations."
     3118msgstr ""
     3119
     3120#: includes/class-aitmt-runtime-maintenance.php:526
     3121msgid "Rebuild scanner queue"
     3122msgstr ""
     3123
     3124#: includes/class-aitmt-runtime-maintenance.php:529
     3125msgid "Runtime diagnostics (last 14 days)"
     3126msgstr ""
     3127
     3128#: includes/class-aitmt-runtime-maintenance.php:547
     3129msgid "Clear all queued jobs (translations are preserved)"
     3130msgstr ""
     3131
     3132#: includes/class-aitmt-runtime-maintenance.php:548
     3133msgid "Clear all failed jobs (published/manual/import translations are preserved)"
     3134msgstr ""
     3135
     3136#: includes/class-aitmt-runtime-maintenance.php:549
     3137msgid "Delete stale page-load cache rows"
     3138msgstr ""
     3139
     3140#: includes/class-aitmt-runtime-maintenance.php:550
     3141msgid "Clear generated runtime translation cache (page-load and legacy rows)"
     3142msgstr ""
     3143
     3144#: includes/class-aitmt-runtime-maintenance.php:551
     3145msgid "Delete confirmed legacy runtime rows"
     3146msgstr ""
     3147
     3148#: includes/class-aitmt-runtime-maintenance.php:552
     3149msgid "Delete old failed rows"
     3150msgstr ""
     3151
     3152#: includes/class-aitmt-runtime-maintenance.php:553
     3153msgid "Delete old unclassified runtime candidates"
     3154msgstr ""
     3155
     3156#: includes/class-aitmt-runtime-maintenance.php:554
     3157msgid "Delete rows linked to missing posts"
     3158msgstr ""
     3159
     3160#: includes/class-aitmt-runtime-maintenance.php:555
     3161msgid "Delete obsolete scanner-derived translations"
     3162msgstr ""
     3163
     3164#: includes/class-aitmt-runtime-maintenance.php:556
     3165msgid "Classify high-confidence legacy rows"
     3166msgstr ""
     3167
     3168#: includes/class-aitmt-runtime-maintenance.php:557
     3169msgid "Analyze translation table"
     3170msgstr ""
     3171
     3172#: includes/class-aitmt-runtime-maintenance.php:558
     3173msgid "Optimize translation table (manual only)"
     3174msgstr ""
     3175
     3176#. translators: %s: scanner source type.
     3177#: includes/class-aitmt-runtime-maintenance.php:563
     3178#, php-format
     3179msgid "Clear queued jobs from source: %s"
     3180msgstr ""
     3181
     3182#: includes/class-aitmt-runtime-maintenance.php:627
     3183msgid "The scanner is unavailable."
     3184msgstr ""
     3185
     3186#: includes/class-aitmt-runtime-maintenance.php:632
     3187msgid "Scanner rebuild finished."
     3188msgstr ""
     3189
     3190#: includes/class-aitmt-runtime-maintenance.php:648
     3191msgid "Unknown maintenance operation."
     3192msgstr ""
     3193
     3194#: includes/class-aitmt-runtime-maintenance.php:665
     3195#: includes/class-aitmt-runtime-maintenance.php:685
     3196msgid "Run Dry run first, then confirm Apply."
     3197msgstr ""
     3198
     3199#: includes/class-aitmt-runtime-maintenance.php:1325
     3200msgid "Translation table is unavailable."
     3201msgstr ""
     3202
     3203#: includes/class-aitmt-scanner.php:60
     3204msgid "Pages"
     3205msgstr ""
     3206
     3207#: includes/class-aitmt-scanner.php:60
     3208msgid "Published WordPress pages."
     3209msgstr ""
     3210
     3211#: includes/class-aitmt-scanner.php:61
     3212msgid "Posts"
     3213msgstr ""
     3214
     3215#: includes/class-aitmt-scanner.php:61
     3216msgid "Published blog and news posts."
     3217msgstr ""
     3218
     3219#: includes/class-aitmt-scanner.php:62
     3220msgid "Products"
     3221msgstr ""
     3222
     3223#: includes/class-aitmt-scanner.php:62
     3224msgid "Published WooCommerce product titles, descriptions, and selected visible attributes."
     3225msgstr ""
     3226
     3227#: includes/class-aitmt-scanner.php:63
     3228msgid "Elementor Page Content"
     3229msgstr ""
     3230
     3231#: includes/class-aitmt-scanner.php:63
     3232msgid "Visible widget fields from published Elementor pages."
     3233msgstr ""
     3234
     3235#: includes/class-aitmt-scanner.php:64
     3236msgid "Navigation Menus"
     3237msgstr ""
     3238
     3239#: includes/class-aitmt-scanner.php:64
     3240msgid "Shared menu labels visible on the frontend."
     3241msgstr ""
     3242
     3243#: includes/class-aitmt-scanner.php:65
     3244#: includes/class-aitmt-scanner.php:900
     3245msgid "Widgets"
     3246msgstr ""
     3247
     3248#: includes/class-aitmt-scanner.php:65
     3249msgid "Visible text from active widget instances."
     3250msgstr ""
     3251
     3252#: includes/class-aitmt-scanner.php:66
     3253msgid "Litho Mega Menu"
     3254msgstr ""
     3255
     3256#: includes/class-aitmt-scanner.php:66
     3257msgid "Published reusable mega menu content."
     3258msgstr ""
     3259
     3260#: includes/class-aitmt-scanner.php:67
     3261msgid "Elementor Library"
     3262msgstr ""
     3263
     3264#: includes/class-aitmt-scanner.php:67
     3265msgid "Saved Elementor templates. Enable only when they are used on the frontend."
     3266msgstr ""
     3267
     3268#: includes/class-aitmt-scanner.php:68
     3269msgid "Section Builder"
     3270msgstr ""
     3271
     3272#: includes/class-aitmt-scanner.php:68
     3273msgid "Reusable section templates."
     3274msgstr ""
     3275
     3276#: includes/class-aitmt-scanner.php:69
     3277msgid "SEO Title"
     3278msgstr ""
     3279
     3280#: includes/class-aitmt-scanner.php:69
     3281msgid "SEO titles from supported SEO plugins."
     3282msgstr ""
     3283
     3284#: includes/class-aitmt-scanner.php:70
     3285msgid "SEO Meta Description"
     3286msgstr ""
     3287
     3288#: includes/class-aitmt-scanner.php:70
     3289msgid "SEO descriptions from supported SEO plugins."
     3290msgstr ""
     3291
     3292#: includes/class-aitmt-scanner.php:71
     3293msgid "Taxonomies"
     3294msgstr ""
     3295
     3296#: includes/class-aitmt-scanner.php:71
     3297msgid "Public term names and descriptions."
     3298msgstr ""
     3299
     3300#: includes/class-aitmt-scanner.php:72
     3301msgid "Image Alt Text"
     3302msgstr ""
     3303
     3304#: includes/class-aitmt-scanner.php:72
     3305msgid "Alternative text for published media used by content."
     3306msgstr ""
     3307
     3308#: includes/class-aitmt-scanner.php:73
     3309msgid "Image Captions"
     3310msgstr ""
     3311
     3312#: includes/class-aitmt-scanner.php:73
     3313msgid "Published image captions."
     3314msgstr ""
     3315
     3316#: includes/class-aitmt-scanner.php:74
     3317msgid "Custom Fields"
     3318msgstr ""
     3319
     3320#: includes/class-aitmt-scanner.php:74
     3321msgid "Public human-readable custom fields. Keep disabled unless required."
     3322msgstr ""
     3323
     3324#: includes/class-aitmt-scanner.php:75
     3325msgid "Other Public Post Types"
     3326msgstr ""
     3327
     3328#: includes/class-aitmt-scanner.php:75
     3329msgid "Published content from other public post types."
     3330msgstr ""
     3331
     3332#. translators: %s: singular post type label.
     3333#: includes/class-aitmt-scanner.php:85
     3334#, php-format
     3335msgid "Post type: %s"
     3336msgstr ""
     3337
     3338#. translators: %s: plural post type label.
     3339#: includes/class-aitmt-scanner.php:88
     3340#, php-format
     3341msgid "Published %s content."
     3342msgstr ""
     3343
     3344#: includes/class-aitmt-scanner.php:601
     3345msgid "Scanner preview exclusions saved."
     3346msgstr ""
     3347
     3348#: includes/class-aitmt-scanner.php:651
     3349msgid "Translation table unavailable."
     3350msgstr ""
     3351
     3352#: includes/class-aitmt-scanner.php:664
     3353#: includes/class-aitmt-scanner.php:669
     3354msgid "Queue removal failed. No saved translations were removed."
     3355msgstr ""
     3356
     3357#: includes/class-aitmt-scanner.php:699
     3358msgid "Invalid scanner operation."
     3359msgstr ""
     3360
     3361#. translators: %d: number of deleted empty queued jobs.
     3362#: includes/class-aitmt-scanner.php:710
     3363#, php-format
     3364msgid "Removed %d queued jobs. Saved translations and scanner exclusions are unchanged. Maximum 1000 jobs per action."
     3365msgstr ""
     3366
     3367#: includes/class-aitmt-scanner.php:713
     3368msgid "Scanner exclusions saved. Existing translations were preserved. Object exclusions override individual field includes."
     3369msgstr ""
     3370
     3371#: includes/class-aitmt-scanner.php:784
     3372#: includes/class-aitmt-scanner.php:813
     3373msgid "New"
     3374msgstr ""
     3375
     3376#: includes/class-aitmt-scanner.php:817
     3377msgid "Partially translated"
     3378msgstr ""
     3379
     3380#: includes/class-aitmt-scanner.php:852
     3381msgid "Missing"
     3382msgstr ""
     3383
     3384#: includes/class-aitmt-scanner.php:853
     3385msgid "Partial"
     3386msgstr ""
     3387
     3388#: includes/class-aitmt-scanner.php:854
     3389msgid "Complete"
     3390msgstr ""
     3391
     3392#: includes/class-aitmt-scanner.php:858
     3393msgid "Published"
     3394msgstr ""
     3395
     3396#: includes/class-aitmt-scanner.php:859
     3397msgid "Manual"
     3398msgstr ""
     3399
     3400#: includes/class-aitmt-scanner.php:860
     3401msgid "Imported"
     3402msgstr ""
     3403
     3404#: includes/class-aitmt-scanner.php:861
     3405msgid "Draft"
     3406msgstr ""
     3407
     3408#: includes/class-aitmt-scanner.php:947
     3409msgid "Scanner source selection saved."
     3410msgstr ""
     3411
     3412#: includes/class-aitmt-scanner.php:978
     3413msgid "Frontend Content"
     3414msgstr ""
     3415
     3416#: includes/class-aitmt-scanner.php:979
     3417msgid "Frontend Shared Content"
     3418msgstr ""
     3419
     3420#: includes/class-aitmt-scanner.php:980
     3421msgid "Backend / Templates"
     3422msgstr ""
     3423
     3424#: includes/class-aitmt-scanner.php:981
     3425#: includes/class-aitmt-scanner.php:982
     3426msgid "Technical / Metadata"
     3427msgstr ""
     3428
     3429#: includes/class-aitmt-scanner.php:987
     3430msgid "Choose only content visitors need. New installations use a conservative frontend-first selection."
     3431msgstr ""
     3432
     3433#: includes/class-aitmt-scanner.php:1008
     3434msgid "Save Scanner Sources"
     3435msgstr ""
     3436
     3437#: includes/class-aitmt-scanner.php:1012
     3438msgid "Scanner Preview"
     3439msgstr ""
     3440
     3441#: includes/class-aitmt-scanner.php:1013
     3442msgid "Preview is generated locally. No provider request is made."
     3443msgstr ""
     3444
     3445#: includes/class-aitmt-scanner.php:1018
     3446msgid "All usage types"
     3447msgstr ""
     3448
     3449#: includes/class-aitmt-scanner.php:1024
     3450#: includes/class-aitmt-scanner.php:1080
     3451msgid "Source Type"
     3452msgstr ""
     3453
     3454#: includes/class-aitmt-scanner.php:1026
     3455msgid "All source types"
     3456msgstr ""
     3457
     3458#: includes/class-aitmt-scanner.php:1034
     3459msgid "All objects"
     3460msgstr ""
     3461
     3462#: includes/class-aitmt-scanner.php:1040
     3463msgid "Inclusion"
     3464msgstr ""
     3465
     3466#: includes/class-aitmt-scanner.php:1042
     3467msgid "All"
     3468msgstr ""
     3469
     3470#: includes/class-aitmt-scanner.php:1043
     3471#: includes/class-aitmt-scanner.php:1092
     3472msgid "Included"
     3473msgstr ""
     3474
     3475#: includes/class-aitmt-scanner.php:1044
     3476#: includes/class-aitmt-scanner.php:1092
     3477msgid "Excluded"
     3478msgstr ""
     3479
     3480#: includes/class-aitmt-scanner.php:1047
     3481#: includes/class-aitmt-scanner.php:1085
     3482msgid "Translation Status"
     3483msgstr ""
     3484
     3485#: includes/class-aitmt-scanner.php:1049
     3486msgid "All statuses"
     3487msgstr ""
     3488
     3489#: includes/class-aitmt-scanner.php:1055
     3490msgid "Page / field / text"
     3491msgstr ""
     3492
     3493#: includes/class-aitmt-scanner.php:1056
     3494msgid "Filter preview..."
     3495msgstr ""
     3496
     3497#: includes/class-aitmt-scanner.php:1060
     3498msgid "Remove selected queued jobs? Saved translations will be preserved. A future scan may queue these fields again."
     3499msgstr ""
     3500
     3501#: includes/class-aitmt-scanner.php:1064
     3502msgid "Select all visible"
     3503msgstr ""
     3504
     3505#: includes/class-aitmt-scanner.php:1065
     3506msgid "Select none"
     3507msgstr ""
     3508
     3509#: includes/class-aitmt-scanner.php:1066
     3510msgid "Bulk action"
     3511msgstr ""
     3512
     3513#: includes/class-aitmt-scanner.php:1067
     3514msgid "Exclude selected"
     3515msgstr ""
     3516
     3517#: includes/class-aitmt-scanner.php:1068
     3518msgid "Remove selected from queue"
     3519msgstr ""
     3520
     3521#: includes/class-aitmt-scanner.php:1069
     3522msgid "Include selected again"
     3523msgstr ""
     3524
     3525#: includes/class-aitmt-scanner.php:1071
     3526msgid "Apply"
     3527msgstr ""
     3528
     3529#. translators: 1: matching unit count, 2: current page, 3: total pages.
     3530#: includes/class-aitmt-scanner.php:1072
     3531#, php-format
     3532msgid "%1$d units; page %2$d of %3$d"
     3533msgstr ""
     3534
     3535#: includes/class-aitmt-scanner.php:1077
     3536#: includes/class-aitmt-scanner.php:1091
     3537msgid "Select"
     3538msgstr ""
     3539
     3540#: includes/class-aitmt-scanner.php:1078
     3541msgid "Location"
     3542msgstr ""
     3543
     3544#: includes/class-aitmt-scanner.php:1082
     3545msgid "Widget / Field"
     3546msgstr ""
     3547
     3548#: includes/class-aitmt-scanner.php:1086
     3549msgid "Actions"
     3550msgstr ""
     3551
     3552#: includes/class-aitmt-scanner.php:1092
     3553msgid "Open Source Object"
     3554msgstr ""
     3555
     3556#: includes/class-aitmt-scanner.php:1101
     3557msgid "Exclude"
     3558msgstr ""
     3559
     3560#: includes/class-aitmt-scanner.php:1101
     3561msgid "Include again"
     3562msgstr ""
     3563
     3564#: includes/class-aitmt-scanner.php:1102
     3565msgid "Remove from queue"
     3566msgstr ""
     3567
     3568#: includes/class-aitmt-scanner.php:1104
     3569msgid "Include object again"
     3570msgstr ""
     3571
     3572#: includes/class-aitmt-scanner.php:1104
     3573msgid "Exclude entire object"
     3574msgstr ""
     3575
     3576#: includes/class-aitmt-scanner.php:1124
     3577msgid "Frontend"
     3578msgstr ""
     3579
     3580#: includes/class-aitmt-scanner.php:1125
     3581msgid "Frontend Shared"
     3582msgstr ""
     3583
     3584#: includes/class-aitmt-scanner.php:1126
     3585msgid "Template / Backend"
     3586msgstr ""
     3587
     3588#: includes/class-aitmt-scanner.php:1128
     3589msgid "Metadata"
     3590msgstr ""
     3591
     3592#. translators: %s: Current WordPress site language code.
     3593#: includes/class-aitmt-settings.php:216
     3594#, php-format
    1103595msgid "Auto: site language (%s)"
    1113596msgstr ""
    1123597
    113 #: includes/class-wpait-settings.php
     3598#: includes/class-aitmt-settings.php:223
    1143599msgid "Leave on Auto to follow the WordPress site language."
    1153600msgstr ""
    1163601
    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
    1263603msgid "AI Provider"
    1273604msgstr ""
    1283605
    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
    1343607msgid "OpenAI / ChatGPT"
    1353608msgstr ""
    1363609
    137 #: includes/class-wpait-settings.php
     3610#: includes/class-aitmt-settings.php:260
    1383611msgid "Configure only the providers you plan to use. Provider requests are sent only after you save a key and start a translation action."
    1393612msgstr ""
    1403613
    141 #: includes/class-wpait-settings.php
     3614#: includes/class-aitmt-settings.php:265
    1423615msgid "OpenAI API key"
    1433616msgstr ""
    1443617
    145 #: includes/class-wpait-settings.php
    146 msgid "You can also define WPAIT_OPENAI_API_KEY in wp-config.php."
    147 msgstr ""
    148 
    149 #: includes/class-wpait-settings.php
     3618#: includes/class-aitmt-settings.php:269
     3619msgid "You can also define AITMT_OPENAI_API_KEY in wp-config.php."
     3620msgstr ""
     3621
     3622#: includes/class-aitmt-settings.php:274
    1503623msgid "OpenAI model"
    1513624msgstr ""
    1523625
    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
    1583627msgid "Automatically translate missing strings on page view"
    1593628msgstr ""
    1603629
    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
    1743631msgid "Limits how many new strings are sent to AI from one page render."
    1753632msgstr ""
    1763633
    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
    1823635msgid "Language URL mode"
    1833636msgstr ""
    1843637
    185 #: includes/class-wpait-settings.php
     3638#: includes/class-aitmt-settings.php:320
    1863639msgid "Directory URLs: /en/about/"
    1873640msgstr ""
    1883641
    189 #: includes/class-wpait-settings.php
     3642#: includes/class-aitmt-settings.php:325
    1903643msgid "Query URLs: /about/?lang=en"
    1913644msgstr ""
    1923645
    193 #: includes/class-wpait-settings.php
     3646#: includes/class-aitmt-settings.php:330
    1943647msgid "Default language URL"
    1953648msgstr ""
    1963649
    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
    2383651msgid "Try to show in header via wp_body_open"
    2393652msgstr ""
    2403653
    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
     3655msgid "Shortcode: [aitmt_language_switcher]."
     3656msgstr ""
     3657
     3658#: includes/class-aitmt-settings.php:395
    2543659msgid "Allow administrators to edit translated text from the frontend"
    2553660msgstr ""
    2563661
    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
    2703663msgid "Language switcher for AIT Multilingual Translate."
    2713664msgstr ""
    2723665
    273 #: includes/class-wpait-widget.php
     3666#: includes/class-aitmt-widget.php:35
    2743667msgid "Title:"
    2753668msgstr ""
    276 
    277 #: ait-multilingual-translate.php
    278 msgid "AI Translate"
    279 msgstr ""
    280 
    281 #: ait-multilingual-translate.php
    282 msgid "Dashboard"
    283 msgstr ""
    284 
    285 #: ait-multilingual-translate.php
    286 msgid "Translations"
    287 msgstr ""
    288 
    289 #: ait-multilingual-translate.php
    290 msgid "Scanner"
    291 msgstr ""
    292 
    293 #: ait-multilingual-translate.php
    294 msgid "Report Bug"
    295 msgstr ""
    296 
    297 #: ait-multilingual-translate.php
    298 msgid "Feedback"
    299 msgstr ""
    300 
    301 #: ait-multilingual-translate.php
    302 msgid "Setup Wizard"
    303 msgstr ""
    304 
    305 #: ait-multilingual-translate.php
    306 msgid "Support"
    307 msgstr ""
    308 
    309 #: ait-multilingual-translate.php
    310 msgid "Debugger"
    311 msgstr ""
    312 
    313 #: ait-multilingual-translate.php
    314 msgid "Public Beta Build"
    315 msgstr ""
    316 
    317 #: ait-multilingual-translate.php
    318 msgid "Support Development"
    319 msgstr ""
    320 
    321 #: ait-multilingual-translate.php
    322 msgid "Support AIT Multilingual Translate"
    323 msgstr ""
    324 
    325 #: ait-multilingual-translate.php
    326 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.php
    330 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.php
    334 msgid "Your support helps improve the plugin, optimize AI providers, expand language support, and accelerate development."
    335 msgstr ""
    336 
    337 #: ait-multilingual-translate.php
    338 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.php
    342 msgid "Send Feedback"
    343 msgstr ""
    344 
    345 #: ait-multilingual-translate.php
    346 msgid "Cheap"
    347 msgstr ""
    348 
    349 #: ait-multilingual-translate.php
    350 msgid "Balanced"
    351 msgstr ""
    352 
    353 #: ait-multilingual-translate.php
    354 msgid "Premium"
    355 msgstr ""
    356 
    357 #: ait-multilingual-translate.php
    358 msgid "Neutral / Accurate"
    359 msgstr ""
    360 
    361 #: ait-multilingual-translate.php
    362 msgid "SEO Optimized"
    363 msgstr ""
    364 
    365 #: ait-multilingual-translate.php
    366 msgid "Marketing"
    367 msgstr ""
    368 
    369 #: ait-multilingual-translate.php
    370 msgid "eCommerce"
    371 msgstr ""
    372 
    373 #: ait-multilingual-translate.php
    374 msgid "Formal"
    375 msgstr ""
    376 
    377 #: ait-multilingual-translate.php
    378 msgid "Casual"
    379 msgstr ""
    380 
    381 #: ait-multilingual-translate.php
    382 msgid "Technical"
    383 msgstr ""
    384 
    385 #: ait-multilingual-translate.php
    386 msgid "Legal"
    387 msgstr ""
    388 
    389 #: ait-multilingual-translate.php
    390 msgid "Luxury Brand"
    391 msgstr ""
    392 
    393 #: ait-multilingual-translate.php
    394 msgid "Friendly"
    395 msgstr ""
    396 
    397 #: ait-multilingual-translate.php
    398 msgid "Custom Prompt"
    399 msgstr ""
    400 
    401 #: ait-multilingual-translate.php
    402 msgid "Display the AIT Multilingual Translate language switcher."
    403 msgstr ""
    404 
    405 #: ait-multilingual-translate.php
    406 msgid "AI Translate Language Switcher"
    407 msgstr ""
    408 
    409 #: ait-multilingual-translate.php
    410 msgid "Layout type"
    411 msgstr ""
    412 
    413 #: ait-multilingual-translate.php
    414 msgid "Buttons"
    415 msgstr ""
    416 
    417 #: ait-multilingual-translate.php
    418 msgid "Flags only"
    419 msgstr ""
    420 
    421 #: ait-multilingual-translate.php
    422 msgid "Flags + language name"
    423 msgstr ""
    424 
    425 #: ait-multilingual-translate.php
    426 msgid "Language name only"
    427 msgstr ""
    428 
    429 #: ait-multilingual-translate.php
    430 msgid "Show flags"
    431 msgstr ""
    432 
    433 #: ait-multilingual-translate.php
    434 msgid "Yes"
    435 msgstr ""
    436 
    437 #: ait-multilingual-translate.php
    438 msgid "No"
    439 msgstr ""
    440 
    441 #: ait-multilingual-translate.php
    442 msgid "Show language name"
    443 msgstr ""
    444 
    445 #: ait-multilingual-translate.php
    446 msgid "Show language code"
    447 msgstr ""
    448 
    449 #: ait-multilingual-translate.php
    450 msgid "Show current language"
    451 msgstr ""
    452 
    453 #: ait-multilingual-translate.php
    454 msgid "Hide current language"
    455 msgstr ""
    456 
    457 #: ait-multilingual-translate.php
    458 msgid "Open links in current page"
    459 msgstr ""
    460 
    461 #: ait-multilingual-translate.php
    462 msgid "Orientation"
    463 msgstr ""
    464 
    465 #: ait-multilingual-translate.php
    466 msgid "Horizontal"
    467 msgstr ""
    468 
    469 #: ait-multilingual-translate.php
    470 msgid "Vertical"
    471 msgstr ""
    472 
    473 #: ait-multilingual-translate.php
    474 msgid "Alignment"
    475 msgstr ""
    476 
    477 #: ait-multilingual-translate.php
    478 msgid "Left"
    479 msgstr ""
    480 
    481 #: ait-multilingual-translate.php
    482 msgid "Center"
    483 msgstr ""
    484 
    485 #: ait-multilingual-translate.php
    486 msgid "Right"
    487 msgstr ""
    488 
    489 #: ait-multilingual-translate.php
    490 msgid "Switcher Style"
    491 msgstr ""
    492 
    493 #: ait-multilingual-translate.php
    494 msgid "Text color"
    495 msgstr ""
    496 
    497 #: ait-multilingual-translate.php
    498 msgid "Hover color"
    499 msgstr ""
    500 
    501 #: ait-multilingual-translate.php
    502 msgid "Active language color"
    503 msgstr ""
    504 
    505 #: ait-multilingual-translate.php
    506 msgid "Background color"
    507 msgstr ""
    508 
    509 #: ait-multilingual-translate.php
    510 msgid "Border color"
    511 msgstr ""
    512 
    513 #: ait-multilingual-translate.php
    514 msgid "Dropdown background"
    515 msgstr ""
    516 
    517 #: ait-multilingual-translate.php
    518 msgid "Border radius"
    519 msgstr ""
    520 
    521 #: ait-multilingual-translate.php
    522 msgid "Padding"
    523 msgstr ""
    524 
    525 #: ait-multilingual-translate.php
    526 msgid "Gap between items"
    527 msgstr ""
    528 
    529 #: ait-multilingual-translate.php
    530 msgid "Dropdown width"
    531 msgstr ""
    532 
    533 #: ait-multilingual-translate.php
    534 msgid "Dropdown shadow"
    535 msgstr ""
    536 
    537 #: ait-multilingual-translate.php
    538 msgid "Add the language switcher to a WordPress menu."
    539 msgstr ""
    540 
    541 #: ait-multilingual-translate.php
    542 msgid "Show flag"
    543 msgstr ""
    544 
    545 #: ait-multilingual-translate.php
    546 msgid "Menu display mode"
    547 msgstr ""
    548 
    549 #: ait-multilingual-translate.php
    550 msgid "List mode"
    551 msgstr ""
    552 
    553 #: ait-multilingual-translate.php
    554 msgid "Dropdown mode"
    555 msgstr ""
    556 
    557 #: ait-multilingual-translate.php
    558 msgid "Current language switcher"
    559 msgstr ""
    560 
    561 #: ait-multilingual-translate.php
    562 msgid "All enabled languages"
    563 msgstr ""
    564 
    565 #: ait-multilingual-translate.php
    566 msgid "Specific language: %s"
    567 msgstr ""
    568 
    569 #: ait-multilingual-translate.php
    570 msgid "Add to Menu"
    571 msgstr ""
    572 
    573 #: ait-multilingual-translate.php
    574 msgid "Editing on"
    575 msgstr ""
    576 
    577 #: ait-multilingual-translate.php
    578 msgid "Save"
    579 msgstr ""
    580 
    581 #: ait-multilingual-translate.php
    582 msgid "Cancel"
    583 msgstr ""
    584 
    585 #: ait-multilingual-translate.php
    586 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.php
    590 msgid "Target: %s"
    591 msgstr ""
    592 
    593 #: ait-multilingual-translate.php
    594 msgid "Every 5 minutes"
    595 msgstr ""
    596 
    597 #: ait-multilingual-translate.php
    598 msgid "Translation memory hit"
    599 msgstr ""
    600 
    601 #: ait-multilingual-translate.php
    602 msgid "Translated via %s"
    603 msgstr ""
    604 
    605 #: ait-multilingual-translate.php
    606 msgid "Invalid target language."
    607 msgstr ""
    608 
    609 #: ait-multilingual-translate.php
    610 msgid "No import file uploaded."
    611 msgstr ""
    612 
    613 #: ait-multilingual-translate.php
    614 msgid "The message could not be sent. Please check WordPress email delivery on this site."
    615 msgstr ""
    616 
    617 #: ait-multilingual-translate.php
    618 msgid "Please confirm consent before sending diagnostic information."
    619 msgstr ""
    620 
    621 #: ait-multilingual-translate.php
    622 msgid "AI Translate - Report Bug"
    623 msgstr ""
    624 
    625 #: ait-multilingual-translate.php
    626 msgid "Send a safe technical report for Public Beta troubleshooting."
    627 msgstr ""
    628 
    629 #: ait-multilingual-translate.php
    630 msgid "Bug report sent. Thank you for helping test AIT Multilingual Translate."
    631 msgstr ""
    632 
    633 #: ait-multilingual-translate.php
    634 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.php
    638 msgid "Name"
    639 msgstr ""
    640 
    641 #: ait-multilingual-translate.php
    642 msgid "Email"
    643 msgstr ""
    644 
    645 #: ait-multilingual-translate.php
    646 msgid "Website"
    647 msgstr ""
    648 
    649 #: ait-multilingual-translate.php
    650 msgid "Problem type"
    651 msgstr ""
    652 
    653 #: ait-multilingual-translate.php
    654 msgid "Translation issue"
    655 msgstr ""
    656 
    657 #: ait-multilingual-translate.php
    658 msgid "WooCommerce issue"
    659 msgstr ""
    660 
    661 #: ait-multilingual-translate.php
    662 msgid "Elementor issue"
    663 msgstr ""
    664 
    665 #: ait-multilingual-translate.php
    666 msgid "Frontend editor issue"
    667 msgstr ""
    668 
    669 #: ait-multilingual-translate.php
    670 msgid "Provider / API issue"
    671 msgstr ""
    672 
    673 #: ait-multilingual-translate.php
    674 msgid "Install / update issue"
    675 msgstr ""
    676 
    677 #: ait-multilingual-translate.php
    678 msgid "Other"
    679 msgstr ""
    680 
    681 #: ait-multilingual-translate.php
    682 msgid "Short description"
    683 msgstr ""
    684 
    685 #: ait-multilingual-translate.php
    686 msgid "Steps to reproduce"
    687 msgstr ""
    688 
    689 #: ait-multilingual-translate.php
    690 msgid "1. Open page...\\n2. Switch language...\\n3. Expected...\\n4. Actual..."
    691 msgstr ""
    692 
    693 #: ait-multilingual-translate.php
    694 msgid "Attach technical log"
    695 msgstr ""
    696 
    697 #: ait-multilingual-translate.php
    698 msgid "I agree to send this report and redacted technical diagnostics to info@itdesign.biz."
    699 msgstr ""
    700 
    701 #: ait-multilingual-translate.php
    702 msgid "Send bug report"
    703 msgstr ""
    704 
    705 #: ait-multilingual-translate.php
    706 msgid "AI Translate - Feedback"
    707 msgstr ""
    708 
    709 #: ait-multilingual-translate.php
    710 msgid "Share Public Beta feedback, feature requests, and translation notes."
    711 msgstr ""
    712 
    713 #: ait-multilingual-translate.php
    714 msgid "Feedback sent. Thank you for shaping AIT Multilingual Translate."
    715 msgstr ""
    716 
    717 #: ait-multilingual-translate.php
    718 msgid "Feedback / Feature Request"
    719 msgstr ""
    720 
    721 #: ait-multilingual-translate.php
    722 msgid "Type"
    723 msgstr ""
    724 
    725 #: ait-multilingual-translate.php
    726 msgid "Bug"
    727 msgstr ""
    728 
    729 #: ait-multilingual-translate.php
    730 msgid "Feature request"
    731 msgstr ""
    732 
    733 #: ait-multilingual-translate.php
    734 msgid "Message"
    735 msgstr ""
    736 
    737 #: ait-multilingual-translate.php
    738 msgid "I agree to send this feedback to info@itdesign.biz."
    739 msgstr ""
    740 
    741 #: ait-multilingual-translate.php
    742 msgid "AI Translate - Support"
    743 msgstr ""
    744 
    745 #: ait-multilingual-translate.php
    746 msgid "Support the free Public Beta and help prioritize the roadmap."
    747 msgstr ""
    748 
    749 #: ait-multilingual-translate.php
    750 msgid "Need help?"
    751 msgstr ""
    752 
    753 #: ait-multilingual-translate.php
    754 msgid "Documentation"
    755 msgstr ""
    756 
    757 #: ait-multilingual-translate.php
    758 msgid "Plugin Website"
    759 msgstr ""
    760 
    761 #: ait-multilingual-translate.php
    762 msgid "Public Beta Notice"
    763 msgstr ""
    764 
    765 #: ait-multilingual-translate.php
    766 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.php
    770 msgid "Support contact"
    771 msgstr ""
    772 
    773 #: ait-multilingual-translate.php
    774 msgid "AI Translate - Onboarding"
    775 msgstr ""
    776 
    777 #: ait-multilingual-translate.php
    778 msgid "Set up languages, provider keys, scanning, queue translation, and frontend editing."
    779 msgstr ""
    780 
    781 #: ait-multilingual-translate.php
    782 msgid "Onboarding settings saved."
    783 msgstr ""
    784 
    785 #: ait-multilingual-translate.php
    786 msgid "Welcome"
    787 msgstr ""
    788 
    789 #: ait-multilingual-translate.php
    790 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.php
    794 msgid "Auto: WordPress site language"
    795 msgstr ""
    796 
    797 #: ait-multilingual-translate.php
    798 msgid "Google Translate"
    799 msgstr ""
    800 
    801 #: ait-multilingual-translate.php
    802 msgid "DeepL"
    803 msgstr ""
    804 
    805 #: ait-multilingual-translate.php
    806 msgid "Google Gemini"
    807 msgstr ""
    808 
    809 #: ait-multilingual-translate.php
    810 msgid "Grok / xAI"
    811 msgstr ""
    812 
    813 #: ait-multilingual-translate.php
    814 msgid "Saved translations are reused. The provider is used only for queue items that still need translation."
    815 msgstr ""
    816 
    817 #: ait-multilingual-translate.php
    818 msgid "API Keys"
    819 msgstr ""
    820 
    821 #: ait-multilingual-translate.php
    822 msgid "Google Translate API key"
    823 msgstr ""
    824 
    825 #: ait-multilingual-translate.php
    826 msgid "DeepL API key"
    827 msgstr ""
    828 
    829 #: ait-multilingual-translate.php
    830 msgid "Gemini API key"
    831 msgstr ""
    832 
    833 #: ait-multilingual-translate.php
    834 msgid "Grok API key"
    835 msgstr ""
    836 
    837 #: ait-multilingual-translate.php
    838 msgid "Allow administrators to edit translations from the frontend"
    839 msgstr ""
    840 
    841 #: ait-multilingual-translate.php
    842 msgid "Save setup"
    843 msgstr ""
    844 
    845 #: ait-multilingual-translate.php
    846 msgid "Scan and Translate"
    847 msgstr ""
    848 
    849 #: ait-multilingual-translate.php
    850 msgid "Scan"
    851 msgstr ""
    852 
    853 #: ait-multilingual-translate.php
    854 msgid "Collect site strings into the saved translation queue."
    855 msgstr ""
    856 
    857 #: ait-multilingual-translate.php
    858 msgid "Scan site strings"
    859 msgstr ""
    860 
    861 #: ait-multilingual-translate.php
    862 msgid "Translate"
    863 msgstr ""
    864 
    865 #: ait-multilingual-translate.php
    866 msgid "Start one translation queue batch. Repeat or enable background processing later in Advanced mode."
    867 msgstr ""
    868 
    869 #: ait-multilingual-translate.php
    870 msgid "Start translation queue"
    871 msgstr ""
    872 
    873 #: ait-multilingual-translate.php
    874 msgid "Finish setup"
    875 msgstr ""
    876 
    877 #: ait-multilingual-translate.php
    878 msgid "Open dashboard"
    879 msgstr ""
    880 
    881 #: ait-multilingual-translate.php
    882 msgid "Save visible translations"
    883 msgstr ""
    884 
    885 #: ait-multilingual-translate.php
    886 msgid "First"
    887 msgstr ""
    888 
    889 #: ait-multilingual-translate.php
    890 msgid "Previous"
    891 msgstr ""
    892 
    893 #: ait-multilingual-translate.php
    894 msgid "Next"
    895 msgstr ""
    896 
    897 #: ait-multilingual-translate.php
    898 msgid "Last"
    899 msgstr ""
    900 
    901 #: ait-multilingual-translate.php
    902 msgid "AI Translate - Translations"
    903 msgstr ""
    904 
    905 #: ait-multilingual-translate.php
    906 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.php
    910 msgid "Translations Matrix"
    911 msgstr ""
    912 
    913 #: ait-multilingual-translate.php
    914 msgid "Scan new strings"
    915 msgstr ""
    916 
    917 #: ait-multilingual-translate.php
    918 msgid "Process queue"
    919 msgstr ""
    920 
    921 #: ait-multilingual-translate.php
    922 msgid "Per page"
    923 msgstr ""
    924 
    925 #: ait-multilingual-translate.php
    926 msgid "Search"
    927 msgstr ""
    928 
    929 #: ait-multilingual-translate.php
    930 msgid "Source text or hash..."
    931 msgstr ""
    932 
    933 #: ait-multilingual-translate.php
    934 msgid "Status"
    935 msgstr ""
    936 
    937 #: ait-multilingual-translate.php
    938 msgid "All strings"
    939 msgstr ""
    940 
    941 #: ait-multilingual-translate.php
    942 msgid "Needs translation"
    943 msgstr ""
    944 
    945 #: ait-multilingual-translate.php
    946 msgid "Translated"
    947 msgstr ""
    948 
    949 #: ait-multilingual-translate.php
    950 msgid "Filter"
    951 msgstr ""
    952 
    953 #: ait-multilingual-translate.php
    954 msgid "Reset"
    955 msgstr ""
    956 
    957 #: ait-multilingual-translate.php
    958 msgid "Last bulk save:"
    959 msgstr ""
    960 
    961 #: ait-multilingual-translate.php
    962 msgid "Export / Import"
    963 msgstr ""
    964 
    965 #: ait-multilingual-translate.php
    966 msgid "Export translations"
    967 msgstr ""
    968 
    969 #: ait-multilingual-translate.php
    970 msgid "Export language"
    971 msgstr ""
    972 
    973 #: ait-multilingual-translate.php
    974 msgid "Format"
    975 msgstr ""
    976 
    977 #: ait-multilingual-translate.php
    978 msgid "Import translations"
    979 msgstr ""
    980 
    981 #: ait-multilingual-translate.php
    982 msgid "Import language"
    983 msgstr ""
    984 
    985 #: ait-multilingual-translate.php
    986 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.php
    990 msgid "Select target languages first."
    991 msgstr ""
    992 
    993 #: ait-multilingual-translate.php
    994 msgid "No strings collected yet. Run the scanner or open translated pages once to collect frontend strings."
    995 msgstr ""
    996 
    997 #: ait-multilingual-translate.php
    998 msgid "Original"
    999 msgstr ""
    1000 
    1001 #: ait-multilingual-translate.php
    1002 msgid "Status:"
    1003 msgstr ""
    1004 
    1005 #: ait-multilingual-translate.php
    1006 msgid "AI Translate - Scanner"
    1007 msgstr ""
    1008 
    1009 #: ait-multilingual-translate.php
    1010 msgid "Collect site strings into the queue, then translate queued strings in controlled batches."
    1011 msgstr ""
    1012 
    1013 #: ait-multilingual-translate.php
    1014 msgid "Scanner and Queue"
    1015 msgstr ""
    1016 
    1017 #: ait-multilingual-translate.php
    1018 msgid "Auto scan on save:"
    1019 msgstr ""
    1020 
    1021 #: ait-multilingual-translate.php
    1022 msgid "Background processing:"
    1023 msgstr ""
    1024 
    1025 #: ait-multilingual-translate.php
    1026 msgid "Translation Mode:"
    1027 msgstr ""
    1028 
    1029 #: ait-multilingual-translate.php
    1030 msgid "Quality:"
    1031 msgstr ""
    1032 
    1033 #: ait-multilingual-translate.php
    1034 msgid "Cost optimization:"
    1035 msgstr ""
    1036 
    1037 #: ait-multilingual-translate.php
    1038 msgid "Last scan:"
    1039 msgstr ""
    1040 
    1041 #: ait-multilingual-translate.php
    1042 msgid "Last automatic scan:"
    1043 msgstr ""
    1044 
    1045 #: ait-multilingual-translate.php
    1046 msgid "Last queue run:"
    1047 msgstr ""
    1048 
    1049 #: ait-multilingual-translate.php
    1050 msgid "Routes:"
    1051 msgstr ""
    1052 
    1053 #: ait-multilingual-translate.php
    1054 msgid "Last background run:"
    1055 msgstr ""
    1056 
    1057 #: ait-multilingual-translate.php
    1058 msgid "Process translation queue"
    1059 msgstr ""
    1060 
    1061 #: ait-multilingual-translate.php
    1062 msgid "Translate All"
    1063 msgstr ""
    1064 
    1065 #: ait-multilingual-translate.php
    1066 msgid "Clear queued strings"
    1067 msgstr ""
    1068 
    1069 #: ait-multilingual-translate.php
    1070 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.php
    1074 msgid "AI Translate - Debugger"
    1075 msgstr ""
    1076 
    1077 #: ait-multilingual-translate.php
    1078 msgid "Check provider access, current route, translation table, and the latest API error."
    1079 msgstr ""
    1080 
    1081 #: ait-multilingual-translate.php
    1082 msgid "Write extended debug log to a separate file"
    1083 msgstr ""
    1084 
    1085 #: ait-multilingual-translate.php
    1086 msgid "Save debug settings"
    1087 msgstr ""
    1088 
    1089 #: ait-multilingual-translate.php
    1090 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.php
    1094 msgid "Last frontend route:"
    1095 msgstr ""
    1096 
    1097 #: ait-multilingual-translate.php
    1098 msgid "Last provider test:"
    1099 msgstr ""
    1100 
    1101 #: ait-multilingual-translate.php
    1102 msgid "HTTP status:"
    1103 msgstr ""
    1104 
    1105 #: ait-multilingual-translate.php
    1106 msgid "Route:"
    1107 msgstr ""
    1108 
    1109 #: ait-multilingual-translate.php
    1110 msgid "Debug log"
    1111 msgstr ""
    1112 
    1113 #: ait-multilingual-translate.php
    1114 msgid "Latest 20 events"
    1115 msgstr ""
    1116 
    1117 #: ait-multilingual-translate.php
    1118 msgid "Time"
    1119 msgstr ""
    1120 
    1121 #: ait-multilingual-translate.php
    1122 msgid "Context"
    1123 msgstr ""
    1124 
    1125 #: ait-multilingual-translate.php
    1126 msgid "Test provider translation"
    1127 msgstr ""
    1128 
    1129 #: ait-multilingual-translate.php
    1130 msgid "Download log file"
    1131 msgstr ""
    1132 
    1133 #: ait-multilingual-translate.php
    1134 msgid "Clear debug log"
    1135 msgstr ""
    1136 
    1137 #: ait-multilingual-translate.php
    1138 msgid "Clear provider cooldown"
    1139 msgstr ""
    1140 
    1141 #: ait-multilingual-translate.php
    1142 msgid "Debug log file was not found."
    1143 msgstr ""
    1144 
    1145 #: ait-multilingual-translate.php
    1146 msgid "Queue is empty."
    1147 msgstr ""
    1148 
    1149 #: ait-multilingual-translate.php
    1150 msgid "Translate All stopped after processing %1$d of %2$d queued translation(s): %3$s"
    1151 msgstr ""
    1152 
    1153 #: ait-multilingual-translate.php
    1154 msgid "Provider error."
    1155 msgstr ""
    1156 
    1157 #: ait-multilingual-translate.php
    1158 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.php
    1162 msgid "Translate All finished. Processed %1$d queued translation(s) in %2$d safe batch(es)."
    1163 msgstr ""
    1164 
    1165 #: ait-multilingual-translate.php
    1166 msgid "Scan strings, translate them in batches, then edit saved translations on the frontend."
    1167 msgstr ""
    1168 
    1169 #: ait-multilingual-translate.php
    1170 msgid "Upload updates are not safe for this install yet."
    1171 msgstr ""
    1172 
    1173 #: ait-multilingual-translate.php
    1174 msgid "Repair plugin folder"
    1175 msgstr ""
    1176 
    1177 #: ait-multilingual-translate.php
    1178 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.php
    1182 msgid "Settings saved."
    1183 msgstr ""
    1184 
    1185 #: ait-multilingual-translate.php
    1186 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.php
    1190 msgid "Last translation error:"
    1191 msgstr ""
    1192 
    1193 #: ait-multilingual-translate.php
    1194 msgid "AIT Multilingual Translate sections"
    1195 msgstr ""
    1196 
    1197 #: ait-multilingual-translate.php
    1198 msgid "General"
    1199 msgstr ""
    1200 
    1201 #: ait-multilingual-translate.php
    1202 msgid "Providers"
    1203 msgstr ""
    1204 
    1205 #: ait-multilingual-translate.php
    1206 msgid "SEO & URLs"
    1207 msgstr ""
    1208 
    1209 #: ait-multilingual-translate.php
    1210 msgid "Queue"
    1211 msgstr ""
    1212 
    1213 #: ait-multilingual-translate.php
    1214 msgid "Interface mode"
    1215 msgstr ""
    1216 
    1217 #: ait-multilingual-translate.php
    1218 msgid "Basic"
    1219 msgstr ""
    1220 
    1221 #: ait-multilingual-translate.php
    1222 msgid "Advanced"
    1223 msgstr ""
    1224 
    1225 #: ait-multilingual-translate.php
    1226 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.php
    1230 msgid "Build status"
    1231 msgstr ""
    1232 
    1233 #: ait-multilingual-translate.php
    1234 msgid "Public Beta preparation build."
    1235 msgstr ""
    1236 
    1237 #: ait-multilingual-translate.php
    1238 msgid "Feature flag helpers are prepared for future package variants. All current beta features remain enabled."
    1239 msgstr ""
    1240 
    1241 #: ait-multilingual-translate.php
    1242 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.php
    1246 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.php
    1250 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.php
    1254 msgid "Provider capabilities"
    1255 msgstr ""
    1256 
    1257 #: ait-multilingual-translate.php
    1258 msgid "Active"
    1259 msgstr ""
    1260 
    1261 #: ait-multilingual-translate.php
    1262 msgid "Planned"
    1263 msgstr ""
    1264 
    1265 #: ait-multilingual-translate.php
    1266 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.php
    1270 msgid "Create a key in the OpenAI dashboard, then paste it here."
    1271 msgstr ""
    1272 
    1273 #: ait-multilingual-translate.php
    1274 msgid "Open API keys page"
    1275 msgstr ""
    1276 
    1277 #: ait-multilingual-translate.php
    1278 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.php
    1282 msgid "Create a Gemini key in Google AI Studio, then paste it here."
    1283 msgstr ""
    1284 
    1285 #: ait-multilingual-translate.php
    1286 msgid "Open Google AI Studio API keys"
    1287 msgstr ""
    1288 
    1289 #: ait-multilingual-translate.php
    1290 msgid "Gemini model"
    1291 msgstr ""
    1292 
    1293 #: ait-multilingual-translate.php
    1294 msgid "For testing, use a small batch size because Gemini free tier limits can be very low."
    1295 msgstr ""
    1296 
    1297 #: ait-multilingual-translate.php
    1298 msgid "Create an xAI API key, then paste it here."
    1299 msgstr ""
    1300 
    1301 #: ait-multilingual-translate.php
    1302 msgid "Open xAI Console"
    1303 msgstr ""
    1304 
    1305 #: ait-multilingual-translate.php
    1306 msgid "Grok model"
    1307 msgstr ""
    1308 
    1309 #: ait-multilingual-translate.php
    1310 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.php
    1314 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.php
    1318 msgid "Google docs"
    1319 msgstr ""
    1320 
    1321 #: ait-multilingual-translate.php
    1322 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.php
    1326 msgid "DeepL docs"
    1327 msgstr ""
    1328 
    1329 #: ait-multilingual-translate.php
    1330 msgid "DeepL plan"
    1331 msgstr ""
    1332 
    1333 #: ait-multilingual-translate.php
    1334 msgid "api-free.deepl.com endpoint"
    1335 msgstr ""
    1336 
    1337 #: ait-multilingual-translate.php
    1338 msgid "api.deepl.com endpoint"
    1339 msgstr ""
    1340 
    1341 #: ait-multilingual-translate.php
    1342 msgid "Match this setting to the endpoint assigned to your DeepL API key."
    1343 msgstr ""
    1344 
    1345 #: ait-multilingual-translate.php
    1346 msgid "Translation Style / Tone of Voice"
    1347 msgstr ""
    1348 
    1349 #: ait-multilingual-translate.php
    1350 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.php
    1354 msgid "Global Translation Mode"
    1355 msgstr ""
    1356 
    1357 #: ait-multilingual-translate.php
    1358 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.php
    1362 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.php
    1366 msgid "Custom translation instruction"
    1367 msgstr ""
    1368 
    1369 #: ait-multilingual-translate.php
    1370 msgid "Example: Translate naturally for Georgian customers, keep product names unchanged, use friendly but professional tone."
    1371 msgstr ""
    1372 
    1373 #: ait-multilingual-translate.php
    1374 msgid "Used only when Translation Mode is Custom Prompt. Maximum 500 characters."
    1375 msgstr ""
    1376 
    1377 #: ait-multilingual-translate.php
    1378 msgid "AI Cost Optimization"
    1379 msgstr ""
    1380 
    1381 #: ait-multilingual-translate.php
    1382 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.php
    1386 msgid "Translation quality mode"
    1387 msgstr ""
    1388 
    1389 #: ait-multilingual-translate.php
    1390 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.php
    1394 msgid "Recommended default models"
    1395 msgstr ""
    1396 
    1397 #: ait-multilingual-translate.php
    1398 msgid "OpenAI Cheap: gpt-4o-mini"
    1399 msgstr ""
    1400 
    1401 #: ait-multilingual-translate.php
    1402 msgid "OpenAI Premium: gpt-4o"
    1403 msgstr ""
    1404 
    1405 #: ait-multilingual-translate.php
    1406 msgid "Gemini Cheap: Flash model"
    1407 msgstr ""
    1408 
    1409 #: ait-multilingual-translate.php
    1410 msgid "Gemini Premium: Pro model"
    1411 msgstr ""
    1412 
    1413 #: ait-multilingual-translate.php
    1414 msgid "Grok Cheap: lightweight/mini model where available"
    1415 msgstr ""
    1416 
    1417 #: ait-multilingual-translate.php
    1418 msgid "Claude Cheap: Haiku model where available"
    1419 msgstr ""
    1420 
    1421 #: ait-multilingual-translate.php
    1422 msgid "Mistral Cheap: small/fast model"
    1423 msgstr ""
    1424 
    1425 #: ait-multilingual-translate.php
    1426 msgid "DeepSeek Cheap: chat/standard model"
    1427 msgstr ""
    1428 
    1429 #: ait-multilingual-translate.php
    1430 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.php
    1434 msgid "Temperature"
    1435 msgstr ""
    1436 
    1437 #: ait-multilingual-translate.php
    1438 msgid "Default 0.1 keeps translations stable and predictable for AI providers."
    1439 msgstr ""
    1440 
    1441 #: ait-multilingual-translate.php
    1442 msgid "Quota control"
    1443 msgstr ""
    1444 
    1445 #: ait-multilingual-translate.php
    1446 msgid "Daily character limit"
    1447 msgstr ""
    1448 
    1449 #: ait-multilingual-translate.php
    1450 msgid "Local safety limit for the active provider. 0 means no plugin-side daily stop."
    1451 msgstr ""
    1452 
    1453 #: ait-multilingual-translate.php
    1454 msgid "Monthly character limit"
    1455 msgstr ""
    1456 
    1457 #: ait-multilingual-translate.php
    1458 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.php
    1462 msgid "Max characters per request"
    1463 msgstr ""
    1464 
    1465 #: ait-multilingual-translate.php
    1466 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.php
    1470 msgid "Estimated cost limit per request"
    1471 msgstr ""
    1472 
    1473 #: ait-multilingual-translate.php
    1474 msgid "0 means no estimated-cost stop. Estimates are approximate and provider billing remains authoritative."
    1475 msgstr ""
    1476 
    1477 #: ait-multilingual-translate.php
    1478 msgid "Enable frontend translation output"
    1479 msgstr ""
    1480 
    1481 #: ait-multilingual-translate.php
    1482 msgid "Collect missing strings into the translation queue"
    1483 msgstr ""
    1484 
    1485 #: ait-multilingual-translate.php
    1486 msgid "Automatically scan saved/updated content for new strings"
    1487 msgstr ""
    1488 
    1489 #: ait-multilingual-translate.php
    1490 msgid "Automatically process the translation queue in the background"
    1491 msgstr ""
    1492 
    1493 #: ait-multilingual-translate.php
    1494 msgid "Translate missing strings during page load (slower, use only for testing)"
    1495 msgstr ""
    1496 
    1497 #: ait-multilingual-translate.php
    1498 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.php
    1502 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.php
    1506 msgid "URL mode"
    1507 msgstr ""
    1508 
    1509 #: ait-multilingual-translate.php
    1510 msgid "Directory URLs: /ka/about/"
    1511 msgstr ""
    1512 
    1513 #: ait-multilingual-translate.php
    1514 msgid "Query URLs: /about/?lang=ka"
    1515 msgstr ""
    1516 
    1517 #: ait-multilingual-translate.php
    1518 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.php
    1522 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.php
    1526 msgid "Shortcodes:"
    1527 msgstr ""
    1528 
    1529 #: ait-multilingual-translate.php
    1530 msgid "Dropdown is compact for headers. List is useful in footers, sidebars, and menu-like layouts."
    1531 msgstr ""
    1532 
    1533 #: ait-multilingual-translate.php
    1534 msgid "Choose what visitors see inside the selector, for example Georgian, KA, or both."
    1535 msgstr ""
    1536 
    1537 #: ait-multilingual-translate.php
    1538 msgid "Try to show in header"
    1539 msgstr ""
    1540 
    1541 #: ait-multilingual-translate.php
    1542 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.php
    1546 msgid "Selector custom CSS"
    1547 msgstr ""
    1548 
    1549 #: ait-multilingual-translate.php
    1550 msgid "Optional CSS loaded on the frontend only for styling the language selector."
    1551 msgstr ""
    1552 
    1553 #: ait-multilingual-translate.php
    1554 msgid "Useful classes:"
    1555 msgstr ""
    1556 
    1557 #: ait-multilingual-translate.php
    1558 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.php
    1562 msgid "Buy Me a Coffee URL"
    1563 msgstr ""
    1564 
    1565 #: ait-multilingual-translate.php
    1566 msgid "PayPal donation URL"
    1567 msgstr ""
    1568 
    1569 #: ait-multilingual-translate.php
    1570 msgid "String Scanner"
    1571 msgstr ""
    1572 
    1573 #: ait-multilingual-translate.php
    1574 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.php
    1578 msgid "Targets:"
    1579 msgstr ""
    1580 
    1581 #: ait-multilingual-translate.php
    1582 msgid "Queued strings"
    1583 msgstr ""
    1584 
    1585 #: ait-multilingual-translate.php
    1586 msgid "Saved translations"
    1587 msgstr ""
    1588 
    1589 #: ait-multilingual-translate.php
    1590 msgid "Translation Queue"
    1591 msgstr ""
    1592 
    1593 #: ait-multilingual-translate.php
    1594 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.php
    1598 msgid "Provider:"
    1599 msgstr ""
    1600 
    1601 #: ait-multilingual-translate.php
    1602 msgid "Sample:"
    1603 msgstr ""
    1604 
    1605 #: ait-multilingual-translate.php
    1606 msgid "Duration:"
    1607 msgstr ""
  • ait-multilingual-translate/trunk/readme.txt

    r3665107 r3761123  
    55Requires at least: 6.0
    66Tested up to: 7.1
    7 Stable tag: 0.4.0
     7Stable tag: 0.4.2
    88Requires PHP: 7.4
    99License: GPLv2 or later
    … …  
    2828* Store translations locally and reuse them until source text changes.
    2929* 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.
    3034* Process translation work in a queue so visitors do not wait for provider calls.
    3135* Edit saved translations from the frontend as an administrator.
    … …  
    173177
    174178== 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.
    175201
    176202= 0.4.0 =
    … …  
    350376== Upgrade Notice ==
    351377
     378= 0.4.2 =
     379Scanner 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 =
     382Adds opt-in scanner maintenance and queue rebuilding without automatically deleting or overwriting existing translations.
     383
    352384= 0.4.0 =
    353385Adds 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.