Plugin Directory

Changeset 3420034


Ignore:
Timestamp:
12/15/2025 11:26:52 AM (2 months ago)
Author:
razvan.mo
Message:

tagging version 3.0.6

Location:
translatepress-multilingual
Files:
40 edited
1 copied

Legend:

Unmodified
Added
Removed
  • translatepress-multilingual/tags/3.0.6/assets/js/trp-frontend-compatibility.js

    r3288239 r3420034  
    33
    44        // clear WooCommerce cart fragments when switching language
    5         var trp_language_switcher_urls = document.querySelectorAll(".trp-language-switcher-container a:not(.trp-ls-disabled-language)");
     5        var trp_language_switcher_urls = document.querySelectorAll(".trp-language-switcher-container a:not(.trp-ls-disabled-language), .trp-language-item:not(.trp-language-item__current)");
    66
    77        for (i = 0; i < trp_language_switcher_urls.length; i++) {
  • translatepress-multilingual/tags/3.0.6/assets/js/trp-frontend-language-switcher.js

    r3389452 r3420034  
    3535    }
    3636
     37    /**
     38     * Returns true if the list has a non-zero transition duration (for any property).
     39     * We use this to decide whether to rely on `transitionend` or fall back to sync behavior.
     40     */
     41    _hasAnimatedTransition() {
     42        if (!this.list) return false;
     43
     44        const cs = getComputedStyle(this.list);
     45        const durationsRaw = cs.transitionDuration || '';
     46
     47        if (!durationsRaw) return false;
     48
     49        const durations = durationsRaw
     50            .split(',')
     51            .map(str => parseFloat(str) || 0);
     52
     53        return durations.some(d => d > 0);
     54    }
     55
    3756    collapse() {
    3857        this.list.hidden = true;
     
    6685    }
    6786
    68     setOpen(open, { source = null } = {}) {
    69         if (!this.root || !this.list || open === this.isOpen) return;
    70 
    71         // Honor reduced motion: skip the transition entirely (still class-driven)
     87    setOpen( open, { source = null } = {} ) {
     88        if ( !this.root || !this.list || open === this.isOpen ) return;
     89
    7290        const prefersReduced = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches;
     91        const hasTransition  = !prefersReduced && this._hasAnimatedTransition();
    7392
    7493        this.isOpen = open;
    7594
    76         if (open) {
    77             // Prepare: must be visible for CSS transition to run
     95        // No transitions (0s duration) OR reduced motion: do everything synchronously,
     96        if ( !hasTransition ) {
     97            if ( open ) {
     98                this.list.hidden = false;
     99                this.list.removeAttribute( 'inert' );
     100                this.setExpanded( true );
     101
     102                this._pendingFocusOnOpen = ( source?.type === 'keydown' );
     103                if ( this._pendingFocusOnOpen ) {
     104                    this._pendingFocusOnOpen = false;
     105                    const first = this.list.querySelector(
     106                        '[role="option"], a, button, [tabindex]:not([tabindex="-1"])'
     107                    );
     108                    first?.focus?.({ preventScroll: true });
     109                }
     110            } else {
     111                this.setExpanded( false );
     112                this.list.hidden = true;
     113                this.list.setAttribute( 'inert', '' );
     114                this._pendingFocusOnOpen = false;
     115            }
     116            return;
     117        }
     118
     119        // Animated path: rely on transitionend to remove .is-transitioning
     120        if ( open ) {
     121            // Must be visible for CSS transition to run
    78122            this.list.hidden = false;
    79             this.list.removeAttribute('inert');
    80 
    81             if (prefersReduced) {
    82                 this.root.classList.remove('is-transitioning');
    83                 this.setExpanded(true);
    84             } else {
    85                 this.root.classList.add('is-transitioning');
    86                 // Next frame so the browser registers the pre-open (max-height:0) state
    87                 requestAnimationFrame(() => this.setExpanded(true));
    88             }
    89 
    90             // keyboard open should move focus after transition completes
    91             this._pendingFocusOnOpen = (source?.type === 'keydown');
    92 
     123            this.list.removeAttribute( 'inert' );
     124
     125            this._pendingFocusOnOpen = ( source?.type === 'keydown' );
     126
     127            this.root.classList.add( 'is-transitioning' );
     128            // Next frame so browser registers pre-open (max-height: 0) state
     129            requestAnimationFrame( () => this.setExpanded( true ) );
    93130        } else {
    94             if (prefersReduced){
    95                 this.root.classList.add( 'is-transitioning' );
    96             }
    97 
    98             this.setExpanded(false);
     131            this.root.classList.add( 'is-transitioning' );
     132            this.setExpanded( false );
    99133        }
    100134    }
  • translatepress-multilingual/tags/3.0.6/assets/js/trp-translate-dom-changes.js

    r3288239 r3420034  
    468468
    469469        // create an observer instance
    470         observer = new MutationObserver( _this.detect_new_strings_callback );
     470        if ( trp_data['showdynamiccontentbeforetranslation'] === true ){
     471            observer = new MutationObserver(mutations => {
     472                setTimeout(() => _this.detect_new_strings_callback(mutations), 0);
     473            });
     474        }
     475
     476        else {
     477            observer = observer = new MutationObserver(_this.detect_new_strings_callback)
     478        }
    471479
    472480        _this.resume_observer();
  • translatepress-multilingual/tags/3.0.6/changelog.txt

    r3403492 r3420034  
     1= 3.0.5 =
     2* Fixed CSS for Black Friday offer from admin notice
     3
     4= 3.0.4 =
     5* Added 39 more languages including Irish, Maltese and Sicilian
     6* Added support for Latin American Spanish automatic translation in TranslatePress AI
     7* Added support for Divi search in secondary language
     8* Added support for exact match search in String Translation by placing the string in quotes: "example"
     9* Allow schema.org data to be translated
     10* Fixed Regular String Translation search so filtered languages also match terms appearing in the middle of translated strings, not only at the start
     11* Prevent automatic translation of strings that are 0 or 1 characters long or strings that are punctuation only
     12* Change background hover color for transparent preset in order to improve contrast of language switcher
     13* Fixed two missing spaces in floating switcher HTML markup
     14* Fix PHP warning with empty domain for gettext with context
     15* Fixed edge case error "call to a member function is_available() on null"
     16* Moved the "Automatically Translate Slug" setting to be first in line
     17* Extended the license message for item_name_mismatch to provide more context
     18* Improved text information next to the default language
     19
    120= 3.0.3 =
    221* Fixed an issue introduced in the previous update that occurred when using Google Translate as the translation engine
  • translatepress-multilingual/tags/3.0.6/class-translate-press.php

    r3403492 r3420034  
    6868     */
    6969    public function __construct() {
     70        // Early bind to break recursion loop caused by calling get_trp_instance during construction
     71        if ( self::$translate_press === null ) {
     72            self::$translate_press = $this;
     73        }
     74
    7075        define( 'TRP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    7176        define( 'TRP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    7277        define( 'TRP_PLUGIN_BASE', plugin_basename( __DIR__ . '/index.php' ) );
    7378        define( 'TRP_PLUGIN_SLUG', 'translatepress-multilingual' );
    74         define( 'TRP_PLUGIN_VERSION', '3.0.5' );
     79        define( 'TRP_PLUGIN_VERSION', '3.0.6' );
    7580
    7681        wp_cache_add_non_persistent_groups(array('trp'));
     
    103108        require_once TRP_PLUGIN_DIR . 'includes/class-editor-api-regular-strings.php';
    104109        require_once TRP_PLUGIN_DIR . 'includes/class-editor-api-gettext-strings.php';
    105         require_once TRP_PLUGIN_DIR . 'includes/class-translation-manager.php';
    106110        require_once TRP_PLUGIN_DIR . 'includes/class-hooks-loader.php';
    107111        require_once TRP_PLUGIN_DIR . 'includes/class-languages.php';
     
    180184        $this->query                      = new TRP_Query( $this->settings->get_settings() );
    181185        $this->machine_translator_logger  = new TRP_Machine_Translator_Logger( $this->settings->get_settings() );
     186        $this->machine_translator         = new TRP_Machine_Translator( $this->settings->get_settings() ); // Will be overwritten in init_machine_translation with the actual machine translator class. Use this as replacement until then.
    182187        $this->translation_manager        = new TRP_Translation_Manager( $this->settings->get_settings() );
    183188        $this->editor_api_regular_strings = new TRP_Editor_Api_Regular_Strings( $this->settings->get_settings() );
  • translatepress-multilingual/tags/3.0.6/includes/advanced-settings/do-not-translate-certain-paths.php

    r3328103 r3420034  
    490490    return $excluded;
    491491}
     492
     493/**
     494 * Check if the current URL is excluded from translation based on the
     495 * "Do not translate certain paths" / "Translate only certain paths" setting.
     496 *
     497 * Takes into account:
     498 *  - site installed in subdirectory
     499 *  - "Use a subdirectory for the default language"
     500 *  - {{home}} mapping
     501 *  - both "exclude" and "include" modes
     502 *
     503 * @return bool True if the current URL should be treated as excluded from translation.
     504 */
     505function trp_dntcp_is_current_url_excluded() {
     506    if ( is_admin() ) {
     507        return false;
     508    }
     509
     510    $settings          = get_option( 'trp_settings', false );
     511    $advanced_settings = get_option( 'trp_advanced_settings', false );
     512
     513    // No configuration -> nothing is excluded.
     514    if ( empty( $advanced_settings )
     515        || ! isset( $advanced_settings['translateable_content'] )
     516        || empty( $advanced_settings['translateable_content']['paths'] )
     517        || empty( $advanced_settings['translateable_content']['option'] ) ) {
     518        return false;
     519    }
     520
     521    $mode  = $advanced_settings['translateable_content']['option']; // 'exclude' or 'include'
     522    $paths = trp_dntcp_get_paths();
     523
     524    // Build current slug in the same way as trp_exclude_include_paths_to_run_on()
     525    $trp           = TRP_Translate_Press::get_trp_instance();
     526    $url_converter = $trp->get_component( 'url_converter' );
     527
     528    $current_lang = $url_converter->get_lang_from_url_string( $url_converter->cur_page_url() );
     529    if ( empty( $current_lang ) && isset( $settings['default-language'] ) ) {
     530        $current_lang = $settings['default-language'];
     531    }
     532
     533    $site_url_components = parse_url( get_home_url() );
     534    $current_slug        = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( $_SERVER['REQUEST_URI'] ) : '';
     535
     536    // Remove site_url path for installs in subdirectories (e.g. http://localhost/wordpress)
     537    if ( isset( $site_url_components['path'] ) && $site_url_components['path'] !== '' ) {
     538        $current_slug = str_replace( trim( $site_url_components['path'] ), '', $current_slug );
     539    }
     540
     541    // Take into account subdirectory for the default language or other languages.
     542    // This mirrors the logic used in trp_exclude_include_paths_to_run_on().
     543    if ( isset( $settings['add-subdirectory-to-default-language'] )
     544        && $settings['add-subdirectory-to-default-language'] === 'yes'
     545        && ! empty( $current_lang )
     546        && isset( $settings['url-slugs'][ $current_lang ] ) ) {
     547
     548        $replace      = '\/' . $settings['url-slugs'][ $current_lang ];
     549        $current_slug = preg_replace( "/$replace/i", '', ltrim( $current_slug, '/' ), 1 );
     550    }
     551
     552    $array_slugs = array();
     553    trp_test_current_slug( $current_slug, $array_slugs );
     554
     555    $matched = trp_return_exclude_include_url( $paths, $current_slug, $array_slugs );
     556
     557    // In "exclude" mode: listed paths are excluded.
     558    if ( $mode === 'exclude' )
     559        return (bool) $matched;
     560
     561    // In "include" mode: ONLY listed paths are translatable; all others are excluded
     562    if ( $mode === 'include' )
     563        return !$matched;
     564
     565    return false;
     566}
  • translatepress-multilingual/tags/3.0.6/includes/class-language-switcher-v2.php

    r3389452 r3420034  
    130130
    131131        $allow = apply_filters('trp_allow_language_redirect', true, $needed_lang, $this->url_converter->cur_page_url());
    132         if (!$allow) return;
     132
     133        if ( !$allow || trp_dntcp_is_current_url_excluded() )
     134            return;
    133135
    134136        $missing_in_url = ($lang_from_url === null);
  • translatepress-multilingual/tags/3.0.6/includes/class-languages.php

    r3398116 r3420034  
    4141     */
    4242    public function change_locale( $locale ){
    43 
    44         if ( $this->is_string_translation_request_for_different_language() ){
    45             $trp_ajax_language = (isset($_POST['trp_ajax_language']) ) ? sanitize_text_field( $_POST['trp_ajax_language'] ) : '';
    46             if ( !$this->settings ){
    47                 $trp = TRP_Translate_Press::get_trp_instance();
    48                 $trp_settings = $trp->get_component( 'settings' );
    49                 $this->settings = $trp_settings->get_settings();
    50             }
    51             if ( $trp_ajax_language && in_array( $trp_ajax_language, $this->settings['translation-languages'] ) ){
    52                 return $trp_ajax_language;
    53             }
    54         }
    55 
    5643        if ( $this->is_admin_request === null ){
    5744            $trp = TRP_Translate_Press::get_trp_instance();
     
    7057        return $locale;
    7158    }
    72 
    73     public function is_string_translation_request_for_different_language(){
    74         if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
    75             $action = 'trp_string_translation_get_missing_gettext_strings';
    76             if ( isset( $_POST['action'] ) && $_POST['action'] === $action ) {
    77                 return true;
    78             }
    79         }
    80         return false;
    81     }
    8259
    8360    /**
  • translatepress-multilingual/tags/3.0.6/includes/class-machine-translator.php

    r3398116 r3420034  
    4848     * @return bool
    4949     */
    50     public function is_available( $languages = array() ){
    51         if( !empty( $this->settings['trp_machine_translation_settings']['machine-translation'] ) &&
    52             $this->settings['trp_machine_translation_settings']['machine-translation'] == 'yes'
    53         ) {
    54             if ( empty( $languages ) ){
    55                 // can be used to simply know if machine translation is available
     50    public function is_available( $languages = array() ) {
     51        /**
     52         * Return false in case it was directly called from parent and not from a derived class (DeepL / Google / TPAI)
     53         * Calling this method on the parent class means it was called too early and machine translation is not available at this point
     54         */
     55        if ( get_class( $this ) === __CLASS__ )
     56            return false;
     57
     58        $settings = $this->settings['trp_machine_translation_settings'] ?? array();
     59        $enabled  = ( $settings['machine-translation'] ?? '' ) === 'yes';
     60        $is_available = false;
     61
     62        if ( $enabled ) {
     63            $engine = $settings['translation-engine'] ?? null;
     64
     65            if ( $engine === 'deepl' && get_option( 'trp_license_status' ) !== 'valid' ) {
     66                $is_available = false;
     67            } elseif ( empty( $languages ) ) {
    5668                $is_available = true;
    57             }
    58 
    59             // If the license is invalid and the translation engine is DeepL,return false
    60             $license_status = get_option( 'trp_license_status' );
    61             if ( $license_status !== 'valid' && isset( $this->settings['trp_machine_translation_settings']['translation-engine'] ) && $this->settings['trp_machine_translation_settings']['translation-engine'] === 'deepl' ) {
    62                 $is_available = false;
    63             }
    64 
    65             $is_available = $this->check_languages_availability($languages);
    66 
    67         }else {
    68             $is_available = false;
    69         }
    70 
    71         return apply_filters('trp_machine_translator_is_available', $is_available);
     69            } else {
     70                $is_available = $this->check_languages_availability( $languages );
     71            }
     72        }
     73
     74        return apply_filters( 'trp_machine_translator_is_available', $is_available, $languages, $settings );
    7275    }
    7376
  • translatepress-multilingual/tags/3.0.6/includes/class-settings.php

    r3377320 r3420034  
    329329
    330330        unset($settings['translation-languages-formality']);
     331
     332        $trp = TRP_Translate_Press::get_trp_instance();
     333        $language_switcher_tab = $trp->get_component('language_switcher_tab');
     334
     335        if ( !$language_switcher_tab->is_legacy_enabled() && count( $settings['publish-languages'] ) > 2 ) {
     336            $ls_settings     = $language_switcher_tab->get_initial_config();
     337            $new_ls_settings = $ls_settings;
     338
     339            $new_ls_settings['floater']['oppositeLanguage']   = false;
     340            $new_ls_settings['shortcode']['oppositeLanguage'] = false;
     341
     342            if ( $new_ls_settings !== $ls_settings ){
     343                update_option( 'trp_language_switcher_settings', $new_ls_settings );
     344            }
     345        }
    331346
    332347        // check for duplicates in url slugs
  • translatepress-multilingual/tags/3.0.6/includes/class-translation-render.php

    r3398116 r3420034  
    10201020            $translated_strings_manual = array();
    10211021            foreach ( $translateable_strings_manual as $i => $string_manual ) {
    1022                 if ( isset( $translated_strings_manual_dictionary[ $string_manual ]->translated ) ) {
     1022                if ( isset( $translated_strings_manual_dictionary[ $string_manual ]->translated ) && !empty( $translated_strings_manual_dictionary[ $string_manual ]->translated )) {
    10231023                    $translated_strings_manual[$i] = $translated_strings_manual_dictionary[ $string_manual ]->translated;
    10241024                }
     
    16861686
    16871687        foreach ( $translateable_strings as $i => $string ) {
    1688 
     1688            if ( isset( $dictionary[ $string ]->translated ) && empty( $dictionary[ $string ]->translated ) ) {
     1689                /* If we have an empty string with a status != NOT TRANSLATED, it's possible we are dealing with
     1690                 * an intentional thing. After Automatic translation, a text can be translated with unallowed html
     1691                 * thus being stored in DB as empty string with status = MACHINE TRANSLATED. By doing continue; we avoid
     1692                 * re-autotranslating over and over again.
     1693                 */
     1694                continue;
     1695            }
    16891696            // prevent accidentally machine translated strings from db such as for src to be displayed
    16901697            $skip_string = in_array( $string, $skip_machine_translating_strings );
     
    17631770
    17641771            if ( !isset($translated_strings[$i]) && isset( $machine_strings[$string] ) ) {
    1765                 $translated_strings[$i] = $machine_strings[$string];
     1772                $sanitized_machine_string = trp_sanitize_string( $machine_strings[$string] );
     1773                if ( !empty( $sanitized_machine_string ) ){
     1774                    // Unallowed HTML can be turned into empty string. Show original text instead
     1775                    $translated_strings[$i] = $sanitized_machine_string;
     1776                }
    17661777            }
    17671778
     
    20802091     * @return array
    20812092     */
    2082     public function wp_mail_filter( $args ){
    2083         if ( !is_array( $args ) ){
     2093    public function wp_mail_filter( $args ) {
     2094        if ( ! is_array( $args ) ) {
    20842095            return $args;
    20852096        }
    20862097
    2087         $whitelisted_shortcodes = apply_filters( 'trp_whitelisted_shortcodes_for_wp_mail', [ 'trp_language', 'language-include', 'language-exclude' ] );
    2088 
    2089         if ( array_key_exists( 'subject', $args ) ){
    2090             $args['subject'] = $this->translate_page( trp_do_these_shortcodes( $args['subject'], $whitelisted_shortcodes ) );
    2091         }
    2092 
    2093         if ( array_key_exists( 'message', $args ) ){
    2094             $args['message'] = $this->translate_page( trp_do_these_shortcodes( $args['message'], $whitelisted_shortcodes ) );
    2095         }
     2098        if ( empty( $args['to'] ) ) {
     2099            return $args;
     2100        }
     2101
     2102        global $TRP_LANGUAGE;
     2103
     2104        $initial_language = $TRP_LANGUAGE;
     2105
     2106        $recipient = $args['to'];
     2107
     2108        // Normalize $recipient to a single email string (first recipient only - that's the main one)
     2109        if ( is_array( $recipient ) ) {
     2110            $first = reset( $recipient );
     2111            $recipient = is_string( $first ) ? $first : '';
     2112        }
     2113
     2114        $recipient = (string) $recipient;
     2115
     2116        // Keep only the first comma-separated entry if multiple are present in the string
     2117        $recipient = trim( strtok( $recipient, ',' ) );
     2118
     2119        if ( $recipient !== '' ) {
     2120            trp_switch_to_preffered_language( $recipient );
     2121        }
     2122
     2123        $whitelisted_shortcodes = apply_filters(
     2124            'trp_whitelisted_shortcodes_for_wp_mail',
     2125            array( 'trp_language', 'language-include', 'language-exclude' )
     2126        );
     2127
     2128        if ( array_key_exists( 'subject', $args ) ) {
     2129            $args['subject'] = $this->translate_page(
     2130                trp_do_these_shortcodes( $args['subject'], $whitelisted_shortcodes )
     2131            );
     2132        }
     2133
     2134        if ( array_key_exists( 'message', $args ) ) {
     2135            $args['message'] = $this->translate_page(
     2136                trp_do_these_shortcodes( $args['message'], $whitelisted_shortcodes )
     2137            );
     2138        }
     2139
     2140        // Switch back to the language used initially
     2141        $TRP_LANGUAGE = $initial_language;
    20962142
    20972143        return $args;
  • translatepress-multilingual/tags/3.0.6/includes/class-woocommerce-emails.php

    r3398116 r3420034  
    187187        }
    188188
     189        $trp_settings = TRP_Translate_Press::get_trp_instance()->get_component( 'settings' );
     190        $settings     = $trp_settings->get_settings();
     191
     192        $default_language = $settings["default-language"];
     193
    189194        /**
    190195         * At this point in the execution, $wc_email->get_recipient() returns null and throws a PHP warning inside WooCommerce /woocommerce/includes/emails/class-wc-email.php
     
    224229                $registered_user = get_user_by( 'email', $recipients[0] );
    225230                if( $registered_user ){
    226                     // If language is set to site default, user object won't have a locale set. Fallback to WPLANG. In case WPLANG is not set either, fallback to trp_language
     231                    // If language is set to site default, user object won't have a locale set. Fallback to WPLANG. In case WPLANG is not set either, fallback to default language
    227232                    if ( !empty( $registered_user->locale ) ){
    228233                        $language = $registered_user->locale;
    229234                    } else {
    230                         $language = get_option( 'WPLANG' ) ?? get_user_meta( $registered_user->ID, 'trp_language', true );
     235                        $wplang = get_option( 'WPLANG' );
     236                        $language = !empty( $wplang ) ? $wplang : $default_language;
    231237                    }
    232238                } else {
     
    243249        trp_switch_language( $language );
    244250
    245         WC()->load_plugin_textdomain();
    246 
    247         $this->bootstrap_trp_gettext_for_email_language( $language );
     251        add_filter( 'trp_allow_gettext_write', '__return_true' );
     252
     253        $this->reload_woocommerce_textdomain();
     254
     255        $this->bootstrap_trp_gettext_for_emails();
    248256
    249257        // calls necessary because the default additional_content field of an email is localized before this point and stored in a variable in the previous locale
     
    264272
    265273        trp_restore_language();
    266         WC()->load_plugin_textdomain();
     274        $this->reload_woocommerce_textdomain();
    267275
    268276        return false;
     
    289297     * gettext translations stored in TranslatePress are applied correctly.
    290298     */
    291     private function bootstrap_trp_gettext_for_email_language( $language ) {
     299    private function bootstrap_trp_gettext_for_emails() {
    292300        $trp             = TRP_Translate_Press::get_trp_instance();
    293301        $gettext_manager = $trp->get_component( 'gettext_manager' );
     
    308316    }
    309317
     318    function reload_woocommerce_textdomain() {
     319        $domain = 'woocommerce';
     320
     321        $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
     322
     323        $custom_translation_path  = WP_LANG_DIR . '/woocommerce/woocommerce-' . $locale . '.mo';
     324        $global_translation_path  = WP_LANG_DIR . '/plugins/woocommerce-' . $locale . '.mo';
     325        $bundled_translation_path = trailingslashit( WC()->plugin_path() ) . 'i18n/languages/woocommerce-' . $locale . '.mo';
     326
     327        unload_textdomain( $domain );
     328
     329        // Custom file present: mimic WC
     330        if ( is_readable( $custom_translation_path ) ) {
     331            load_textdomain( $domain, $custom_translation_path );
     332
     333            if ( is_readable( $global_translation_path ) ) {
     334                load_textdomain( $domain, $global_translation_path );
     335            }
     336
     337            return true;
     338        }
     339
     340        if ( is_readable( $global_translation_path ) ) {
     341            load_textdomain( $domain, $global_translation_path );
     342            return true;
     343        }
     344
     345        if ( is_readable( $bundled_translation_path ) ) {
     346            load_textdomain( $domain, $bundled_translation_path );
     347            return true;
     348        }
     349
     350        return false;
     351    }
     352
    310353}
  • translatepress-multilingual/tags/3.0.6/includes/compatibility-functions.php

    r3398116 r3420034  
    27092709
    27102710/**
    2711  * Override the wrap_with_post_id condition for Divi
    2712  *
    2713  * Divi doesn't use the main loop in the traditional way, so we need to override
    2714  * the condition that checks for in_the_loop and is_main_query.
    2715  *
    2716  * @param bool $return Whether to bypass the wrap condition
    2717  * @return bool
    2718  */
    2719 add_filter('trp_wrap_with_post_id_overrule', 'trp_divi_override_wrap_condition', 10);
    2720 function trp_divi_override_wrap_condition($return) {
    2721     // Check if Divi is the active theme
    2722     if (function_exists('et_setup_theme')) {
    2723         // Return false to bypass the in_the_loop check
    2724         return false;
    2725     }
    2726     return $return;
    2727 }
     2711 * Add trp-post-container wrapper to Divi module outputs
     2712 * TP is not adding any trp-post-container except here.
     2713 *
     2714 * @param string $output The module HTML output
     2715 * @param string $render_slug The module slug (e.g., 'et_pb_text', 'et_pb_post_title')
     2716 * @param object $module The module object
     2717 * @return string Modified output with trp-post-container wrapper
     2718 */
     2719add_filter('et_module_shortcode_output', 'trp_divi_wrap_module_with_post_id', 10, 3);
     2720
     2721function trp_divi_wrap_module_with_post_id($output, $render_slug, $module) {
     2722    global $post, $TRP_LANGUAGE;
     2723
     2724    // Check if we have a valid post ID
     2725    if (empty($post->ID)) {
     2726        return $output;
     2727    }
     2728
     2729    // Get TranslatePress settings
     2730    $trp = TRP_Translate_Press::get_trp_instance();
     2731    $trp_settings = $trp->get_component('settings');
     2732    $settings = $trp_settings->get_settings();
     2733
     2734    // Only wrap on non-default language
     2735    if ($TRP_LANGUAGE !== $settings['default-language']) {
     2736        // Only wrap modules that typically contain translatable text content
     2737        $modules_to_wrap = apply_filters('trp_divi_modules_to_wrap', array(
     2738            'et_pb_text',
     2739            'et_pb_post_title',
     2740            'et_pb_post_content',
     2741            'et_pb_blurb',
     2742            'et_pb_cta',
     2743            'et_pb_accordion',
     2744            'et_pb_toggle',
     2745            'et_pb_tabs',
     2746            'et_pb_testimonial',
     2747            'et_pb_pricing_tables',
     2748            'et_pb_number_counter',
     2749            'et_pb_countdown_timer'
     2750        ));
     2751
     2752        if (in_array($render_slug, $modules_to_wrap)) {
     2753            $output = "<trp-post-container data-trp-post-id='" . $post->ID . "'>" . $output . "</trp-post-container>";
     2754        }
     2755    }
     2756
     2757    return $output;
     2758}
  • translatepress-multilingual/tags/3.0.6/includes/external-functions.php

    r3288239 r3420034  
    6464    }
    6565
    66     if ( strip_tags( $string ) === '' || trim ($string, $filter_string) === '' ){
    67         $string = '';
    68     }
     66    if ( strip_tags( $string ) === '' || trim( $string, $filter_string ) === '' ) {
     67        // Needs decoding otherwise some strings with special characters won't get detected.
     68        // Example in Hebrew: &#1488;&#1493;&#1499;&#1500; &#1493;&#1514;&#1512;&#1489;&#1493;&#1514;
     69        // Placed inside the "if" to avoid calling html_entity_decode so often as this is a very used function
     70        $decoded_string = html_entity_decode( $string );
     71        if ( trim( $decoded_string, $filter_string ) === '' ) {
     72            $string = '';
     73        }
     74    }
    6975    return $string;
    7076}
  • translatepress-multilingual/tags/3.0.6/includes/functions.php

    r3349766 r3420034  
    274274        return $string;
    275275
    276     if (seems_utf8($string)) {
     276    $seems_utf = ( function_exists( 'wp_is_valid_utf8' ) ) ? wp_is_valid_utf8( $string ) : seems_utf8( $string );
     277
     278    if ( $seems_utf ) {
    277279        $chars = array(
    278280            // Decompositions for Latin-1 Supplement
     
    821823
    822824/**
     825 * Switch to a user's preferred language based on the recipient email.
     826 *
     827 * For managerial users (admin-like roles), prefer user->locale with fallback
     828 * to WPLANG, then trp_language.
     829 *
     830 * For non-managerial users, prefer trp_language, with fallback to locale, then WPLANG.
     831 *
     832 * @param string $email
     833 * @return void
     834 */
     835function trp_switch_to_preffered_language( $email ) {
     836    $email = trim( (string) $email );
     837
     838    if ( $email === '' )
     839        return;
     840
     841    $user = get_user_by( 'email', $email );
     842
     843    if ( ! ( $user instanceof WP_User ) )
     844        return;
     845
     846    $user_roles = is_array( $user->roles ) ? $user->roles : array();
     847
     848    $trp_settings = TRP_Translate_Press::get_trp_instance()->get_component( 'settings' );
     849    $settings     = $trp_settings->get_settings();
     850
     851    $default_language = $settings["default-language"];
     852
     853    /**
     854     * Roles considered "managerial" for email language purposes.
     855     *
     856     * @param string[] $roles
     857     */
     858    $managerial_roles = apply_filters(
     859        'trp_managerial_roles_for_email_language',
     860        [ 'administrator', 'editor', 'shop_manager' ]
     861    );
     862
     863    $is_managerial = !empty( array_intersect( $managerial_roles, $user_roles ) );
     864
     865    if ( $is_managerial ) {
     866        // Managerial: prefer locale, then WPLANG, then default_language
     867        if ( !empty( $user->locale ) ) {
     868            $language = $user->locale;
     869        } else {
     870            $wplang = get_option( 'WPLANG' );
     871            $language = !empty( $wplang ) ? $wplang : $default_language;
     872        }
     873    } else {
     874        // Non-managerial: prefer trp_language.
     875        $language = get_user_meta( $user->ID, 'trp_language', true );
     876
     877        if ( empty( $language ) ) {
     878            if ( !empty( $user->locale ) ) {
     879                $language = $user->locale;
     880            } else {
     881                $wplang = get_option( 'WPLANG' );
     882                $language = !empty( $wplang ) ? $wplang : $default_language;
     883            }
     884        }
     885    }
     886
     887    if ( empty( $language ) )
     888        return;
     889
     890    trp_switch_language( $language );
     891}
     892
     893/**
    823894 * Return $TRP_LANGUAGE as plugin locale
    824895 *
  • translatepress-multilingual/tags/3.0.6/includes/gettext/class-gettext-manager.php

    r3374439 r3420034  
    488488     */
    489489    public function add_missing_language_file_translations( $dictionary, $language ) {
    490 
    491         $trp_plural_forms    = $this->get_gettext_component( 'plural_forms' );
     490        // Ensure translation files are loaded with the correct locale
     491        $locale   = determine_locale();
     492        $switched = switch_to_locale( $language );
     493
     494        // This means that the language is not supported by WordPress. Either a custom language or a language that we support but WordPress does not.
     495        if ( !$switched && $language !== $locale )
     496            return;
     497
     498        $trp_plural_forms    = $this->get_gettext_component( 'plural_forms' );
    492499        if ( ! $this->trp_query ) {
    493500            $trp             = TRP_Translate_Press::get_trp_instance();
     
    611618            $gettext_insert_update->insert_gettext_strings($insert_gettext_strings, $language);
    612619            $gettext_insert_update->update_gettext_strings($update_gettext_strings, $language, array('translated', 'id', 'status'));
     620
     621            if ( $switched )
     622                restore_previous_locale();
    613623        }
    614624    }
  • translatepress-multilingual/tags/3.0.6/includes/queries/class-query.php

    r3288239 r3420034  
    9797            $and_block_type = " AND block_type = " . $block_type;
    9898        }
    99         $query = "SELECT original,translated, status FROM `" . sanitize_text_field( $this->get_table_name( $language_code ) ) . "` WHERE status != " . self::NOT_TRANSLATED . $and_block_type . " AND translated <>'' AND original IN ";
     99
     100        /* Do not add a condition for "translated <> '' because this would cause re-autotranslating. */
     101        $query = "SELECT original,translated, status FROM `" . sanitize_text_field( $this->get_table_name( $language_code ) ) . "` WHERE status != " . self::NOT_TRANSLATED . $and_block_type . " AND original IN ";
    100102
    101103        $placeholders = array();
     
    108110        $query .= "( " . implode ( ", ", $placeholders ) . " )";
    109111        $prepared_query = $this->db->prepare( $query, $values );
    110         $dictionary = $this->db->get_results( $prepared_query, OBJECT_K  );
    111 
     112        $results = $this->db->get_results( $prepared_query, OBJECT  );
     113
     114        if ( !empty( $results ) && is_array( $results ) ) {
     115            // There are edge cases where we have 2 results for the same original:
     116            // one with translated as empty string, one with non-empty translation. Keep the one with translation.
     117            $dictionary = [];
     118            foreach ( $results as $row ) {
     119                $key = $row->original;
     120
     121                // If this key has never been added, simply add it
     122                if ( !isset( $dictionary[ $key ] ) ) {
     123                    $dictionary[ $key ] = $row;
     124                    continue;
     125                }
     126
     127                // If current stored entry has empty 'translated'
     128                // and this new row has non-empty 'translated', replace it
     129                if (
     130                    empty( $dictionary[ $key ]->translated ) &&
     131                    !empty( $row->translated )
     132                ) {
     133                    $dictionary[ $key ] = $row;
     134                }
     135
     136                // Otherwise do nothing — we keep the existing "better" record
     137            }
     138        } else {
     139            $dictionary = $results;
     140        }
    112141
    113142
  • translatepress-multilingual/tags/3.0.6/index.php

    r3403492 r3420034  
    44Plugin URI: https://translatepress.com/
    55Description: Experience a better way of translating your WordPress site using a visual front-end translation editor, with full support for WooCommerce and site builders.
    6 Version: 3.0.5
     6Version: 3.0.6
    77Author: Cozmoslabs, Razvan Mocanu, Madalin Ungureanu, Cristophor Hurduban
    88Author URI: https://cozmoslabs.com/
     
    1111License: GPL2
    1212WC requires at least: 2.5.0
    13 WC tested up to: 10.3.5
     13WC tested up to: 10.4.2
    1414
    1515== Copyright ==
  • translatepress-multilingual/tags/3.0.6/languages/translatepress-multilingual.pot

    r3403492 r3420034  
    77"Content-Type: text/plain; charset=UTF-8\n"
    88"Content-Transfer-Encoding: 8bit\n"
    9 "POT-Creation-Date: 2025-11-26 15:19+0000\n"
     9"POT-Creation-Date: 2025-12-15 11:13+0000\n"
    1010"X-Poedit-Basepath: ..\n"
    1111"X-Poedit-KeywordsList: __;_e;_ex:1,2c;_n:1,2;_n_noop:1,2;_nx:1,2,4c;_nx_noop:1,2,3c;_x:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
     
    319319msgstr ""
    320320
    321 #: includes/class-language-switcher-v2.php:718
     321#: includes/class-language-switcher-v2.php:720
    322322msgid "Change language to %s"
    323323msgstr ""
     
    347347msgstr ""
    348348
    349 #: includes/class-machine-translator.php:154, includes/google-translate/class-google-translate-v2-machine-translator.php:200
     349#: includes/class-machine-translator.php:157, includes/google-translate/class-google-translate-v2-machine-translator.php:200
    350350msgid "Please enter your Google Translate key."
    351351msgstr ""
    352352
    353 #: includes/class-machine-translator.php:169, add-ons-pro/deepl/includes/class-deepl-machine-translator.php:365
     353#: includes/class-machine-translator.php:172, add-ons-pro/deepl/includes/class-deepl-machine-translator.php:365
    354354msgid "Please enter your DeepL API key."
    355355msgstr ""
     
    567567msgstr ""
    568568
    569 #: includes/class-settings.php:471
     569#: includes/class-settings.php:486
    570570msgid "Language codes can contain only A-Z a-z 0-9 - _ characters. Check your language codes in TranslatePress General Settings."
    571571msgstr ""
    572572
    573 #: includes/class-settings.php:534
     573#: includes/class-settings.php:549
    574574msgid "Error! Duplicate URL slug values."
    575575msgstr ""
    576576
    577 #: includes/class-settings.php:535
     577#: includes/class-settings.php:550
    578578msgid "You cannot select two languages that have the same <a href=\"https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes\" target=\"_blank\">iso code</a> but different formalities because doing so will lead to duplicate <a href=\"https://developers.google.com/search/docs/specialty/international/localized-versions\" target=\"_blank\">hreflang tags</a>."
    579579msgstr ""
    580580
    581 #: includes/class-settings.php:536
     581#: includes/class-settings.php:551
    582582msgid "Duplicate language detected.<br>Each language can only be added once to ensure accurate translation management.<br> Please change the duplicate language entry and try again. "
    583583msgstr ""
    584584
    585 #: includes/class-settings.php:593
     585#: includes/class-settings.php:608
    586586msgid "Current Language"
    587587msgstr ""
    588588
    589 #: includes/class-settings.php:599
     589#: includes/class-settings.php:614
    590590msgid "Opposite Language"
    591591msgstr ""
    592592
    593 #: includes/class-settings.php:639
     593#: includes/class-settings.php:654
    594594msgid "General"
    595595msgstr ""
    596596
    597 #: includes/class-settings.php:644, includes/class-translation-manager.php:540, add-ons-pro/translator-accounts/includes/class-translator-accounts.php:156
     597#: includes/class-settings.php:659, includes/class-translation-manager.php:540, add-ons-pro/translator-accounts/includes/class-translator-accounts.php:156
    598598msgid "Translate Site"
    599599msgstr ""
    600600
    601 #: includes/class-settings.php:649
     601#: includes/class-settings.php:664
    602602msgid "Addons"
    603603msgstr ""
    604604
    605 #: includes/class-settings.php:657
     605#: includes/class-settings.php:672
    606606msgid "License"
    607607msgstr ""
    608608
    609 #: includes/class-settings.php:701, includes/class-translation-manager.php:572
     609#: includes/class-settings.php:716, includes/class-translation-manager.php:572
    610610msgid "Settings"
    611611msgstr ""
    612612
    613 #: includes/class-settings.php:715, partials/license-settings-page.php:29, includes/onboarding/class-autotranslation.php:169, includes/onboarding/class-license.php:131
     613#: includes/class-settings.php:730, partials/license-settings-page.php:29, includes/onboarding/class-autotranslation.php:169, includes/onboarding/class-license.php:131
    614614msgid "Activate License"
    615615msgstr ""
    616616
    617 #: includes/class-settings.php:707
     617#: includes/class-settings.php:722
    618618msgid "Pro Features"
    619619msgstr ""
  • translatepress-multilingual/tags/3.0.6/readme.txt

    r3403492 r3420034  
    44Tags: translate, translation, multilingual, automatic translation, bilingual, front-end translation, google translate, language
    55Requires at least: 3.1.0
    6 Tested up to: 6.8.3
     6Tested up to: 6.9
    77Requires PHP: 7.4
    8 Stable tag: 3.0.5
     8Stable tag: 3.0.6
    99License: GPLv2 or later
    1010License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    143143
    144144== Changelog ==
    145 = 3.0.5 =
    146 * Fixed CSS for Black Friday offer from admin notice
    147 
    148 = 3.0.4 =
    149 * Added 39 more languages including Irish, Maltese and Sicilian
    150 * Added support for Latin American Spanish automatic translation in TranslatePress AI
    151 * Added support for Divi search in secondary language
    152 * Added support for exact match search in String Translation by placing the string in quotes: "example"
    153 * Allow schema.org data to be translated
    154 * Fixed Regular String Translation search so filtered languages also match terms appearing in the middle of translated strings, not only at the start
    155 * Prevent automatic translation of strings that are 0 or 1 characters long or strings that are punctuation only
    156 * Change background hover color for transparent preset in order to improve contrast of language switcher
    157 * Fixed two missing spaces in floating switcher HTML markup
    158 * Fix PHP warning with empty domain for gettext with context
    159 * Fixed edge case error "call to a member function is_available() on null"
    160 * Moved the "Automatically Translate Slug" setting to be first in line
    161 * Extended the license message for item_name_mismatch to provide more context
    162 * Improved text information next to the default language
    163 
     145= 3.0.6 =
     146* Extended support for Divi search in secondary languages
     147* Fixed redirect loop bug with excluded paths from translation
     148* Fixed incorrect gettext resolution in string translation when the site locale differed from the default TranslatePress language, which caused untranslated gettext entries to be populated using translations from the wrong locale
     149* Fixed edge case of retranslating the same text multiple times
     150* Fixed not detecting texts containing only certain special characters
     151* Fixed bug where mini cart menu widget doesn't respect language change
     152* Fixed edge case error "Call to a member function is_available() on null"
     153* Send emails in recipient's preferred language, in case the recipient is a user
     154* Improved INP by deferring dynamic translation detection when original content can be shown first
     155* Fixed a bug where the floating language switcher was not displaying all languages (if more than 10 were added and animations disabled)
     156* Disable language switcher show opposite language settings in case more than 2 languages are active
     157* Fixed deprecated notice for seems_utf8 function on WP 6.9
    164158
    165159= Older versions =
  • translatepress-multilingual/trunk/assets/js/trp-frontend-compatibility.js

    r3288239 r3420034  
    33
    44        // clear WooCommerce cart fragments when switching language
    5         var trp_language_switcher_urls = document.querySelectorAll(".trp-language-switcher-container a:not(.trp-ls-disabled-language)");
     5        var trp_language_switcher_urls = document.querySelectorAll(".trp-language-switcher-container a:not(.trp-ls-disabled-language), .trp-language-item:not(.trp-language-item__current)");
    66
    77        for (i = 0; i < trp_language_switcher_urls.length; i++) {
  • translatepress-multilingual/trunk/assets/js/trp-frontend-language-switcher.js

    r3389452 r3420034  
    3535    }
    3636
     37    /**
     38     * Returns true if the list has a non-zero transition duration (for any property).
     39     * We use this to decide whether to rely on `transitionend` or fall back to sync behavior.
     40     */
     41    _hasAnimatedTransition() {
     42        if (!this.list) return false;
     43
     44        const cs = getComputedStyle(this.list);
     45        const durationsRaw = cs.transitionDuration || '';
     46
     47        if (!durationsRaw) return false;
     48
     49        const durations = durationsRaw
     50            .split(',')
     51            .map(str => parseFloat(str) || 0);
     52
     53        return durations.some(d => d > 0);
     54    }
     55
    3756    collapse() {
    3857        this.list.hidden = true;
     
    6685    }
    6786
    68     setOpen(open, { source = null } = {}) {
    69         if (!this.root || !this.list || open === this.isOpen) return;
    70 
    71         // Honor reduced motion: skip the transition entirely (still class-driven)
     87    setOpen( open, { source = null } = {} ) {
     88        if ( !this.root || !this.list || open === this.isOpen ) return;
     89
    7290        const prefersReduced = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches;
     91        const hasTransition  = !prefersReduced && this._hasAnimatedTransition();
    7392
    7493        this.isOpen = open;
    7594
    76         if (open) {
    77             // Prepare: must be visible for CSS transition to run
     95        // No transitions (0s duration) OR reduced motion: do everything synchronously,
     96        if ( !hasTransition ) {
     97            if ( open ) {
     98                this.list.hidden = false;
     99                this.list.removeAttribute( 'inert' );
     100                this.setExpanded( true );
     101
     102                this._pendingFocusOnOpen = ( source?.type === 'keydown' );
     103                if ( this._pendingFocusOnOpen ) {
     104                    this._pendingFocusOnOpen = false;
     105                    const first = this.list.querySelector(
     106                        '[role="option"], a, button, [tabindex]:not([tabindex="-1"])'
     107                    );
     108                    first?.focus?.({ preventScroll: true });
     109                }
     110            } else {
     111                this.setExpanded( false );
     112                this.list.hidden = true;
     113                this.list.setAttribute( 'inert', '' );
     114                this._pendingFocusOnOpen = false;
     115            }
     116            return;
     117        }
     118
     119        // Animated path: rely on transitionend to remove .is-transitioning
     120        if ( open ) {
     121            // Must be visible for CSS transition to run
    78122            this.list.hidden = false;
    79             this.list.removeAttribute('inert');
    80 
    81             if (prefersReduced) {
    82                 this.root.classList.remove('is-transitioning');
    83                 this.setExpanded(true);
    84             } else {
    85                 this.root.classList.add('is-transitioning');
    86                 // Next frame so the browser registers the pre-open (max-height:0) state
    87                 requestAnimationFrame(() => this.setExpanded(true));
    88             }
    89 
    90             // keyboard open should move focus after transition completes
    91             this._pendingFocusOnOpen = (source?.type === 'keydown');
    92 
     123            this.list.removeAttribute( 'inert' );
     124
     125            this._pendingFocusOnOpen = ( source?.type === 'keydown' );
     126
     127            this.root.classList.add( 'is-transitioning' );
     128            // Next frame so browser registers pre-open (max-height: 0) state
     129            requestAnimationFrame( () => this.setExpanded( true ) );
    93130        } else {
    94             if (prefersReduced){
    95                 this.root.classList.add( 'is-transitioning' );
    96             }
    97 
    98             this.setExpanded(false);
     131            this.root.classList.add( 'is-transitioning' );
     132            this.setExpanded( false );
    99133        }
    100134    }
  • translatepress-multilingual/trunk/assets/js/trp-translate-dom-changes.js

    r3288239 r3420034  
    468468
    469469        // create an observer instance
    470         observer = new MutationObserver( _this.detect_new_strings_callback );
     470        if ( trp_data['showdynamiccontentbeforetranslation'] === true ){
     471            observer = new MutationObserver(mutations => {
     472                setTimeout(() => _this.detect_new_strings_callback(mutations), 0);
     473            });
     474        }
     475
     476        else {
     477            observer = observer = new MutationObserver(_this.detect_new_strings_callback)
     478        }
    471479
    472480        _this.resume_observer();
  • translatepress-multilingual/trunk/changelog.txt

    r3403492 r3420034  
     1= 3.0.5 =
     2* Fixed CSS for Black Friday offer from admin notice
     3
     4= 3.0.4 =
     5* Added 39 more languages including Irish, Maltese and Sicilian
     6* Added support for Latin American Spanish automatic translation in TranslatePress AI
     7* Added support for Divi search in secondary language
     8* Added support for exact match search in String Translation by placing the string in quotes: "example"
     9* Allow schema.org data to be translated
     10* Fixed Regular String Translation search so filtered languages also match terms appearing in the middle of translated strings, not only at the start
     11* Prevent automatic translation of strings that are 0 or 1 characters long or strings that are punctuation only
     12* Change background hover color for transparent preset in order to improve contrast of language switcher
     13* Fixed two missing spaces in floating switcher HTML markup
     14* Fix PHP warning with empty domain for gettext with context
     15* Fixed edge case error "call to a member function is_available() on null"
     16* Moved the "Automatically Translate Slug" setting to be first in line
     17* Extended the license message for item_name_mismatch to provide more context
     18* Improved text information next to the default language
     19
    120= 3.0.3 =
    221* Fixed an issue introduced in the previous update that occurred when using Google Translate as the translation engine
  • translatepress-multilingual/trunk/class-translate-press.php

    r3403492 r3420034  
    6868     */
    6969    public function __construct() {
     70        // Early bind to break recursion loop caused by calling get_trp_instance during construction
     71        if ( self::$translate_press === null ) {
     72            self::$translate_press = $this;
     73        }
     74
    7075        define( 'TRP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    7176        define( 'TRP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    7277        define( 'TRP_PLUGIN_BASE', plugin_basename( __DIR__ . '/index.php' ) );
    7378        define( 'TRP_PLUGIN_SLUG', 'translatepress-multilingual' );
    74         define( 'TRP_PLUGIN_VERSION', '3.0.5' );
     79        define( 'TRP_PLUGIN_VERSION', '3.0.6' );
    7580
    7681        wp_cache_add_non_persistent_groups(array('trp'));
     
    103108        require_once TRP_PLUGIN_DIR . 'includes/class-editor-api-regular-strings.php';
    104109        require_once TRP_PLUGIN_DIR . 'includes/class-editor-api-gettext-strings.php';
    105         require_once TRP_PLUGIN_DIR . 'includes/class-translation-manager.php';
    106110        require_once TRP_PLUGIN_DIR . 'includes/class-hooks-loader.php';
    107111        require_once TRP_PLUGIN_DIR . 'includes/class-languages.php';
     
    180184        $this->query                      = new TRP_Query( $this->settings->get_settings() );
    181185        $this->machine_translator_logger  = new TRP_Machine_Translator_Logger( $this->settings->get_settings() );
     186        $this->machine_translator         = new TRP_Machine_Translator( $this->settings->get_settings() ); // Will be overwritten in init_machine_translation with the actual machine translator class. Use this as replacement until then.
    182187        $this->translation_manager        = new TRP_Translation_Manager( $this->settings->get_settings() );
    183188        $this->editor_api_regular_strings = new TRP_Editor_Api_Regular_Strings( $this->settings->get_settings() );
  • translatepress-multilingual/trunk/includes/advanced-settings/do-not-translate-certain-paths.php

    r3328103 r3420034  
    490490    return $excluded;
    491491}
     492
     493/**
     494 * Check if the current URL is excluded from translation based on the
     495 * "Do not translate certain paths" / "Translate only certain paths" setting.
     496 *
     497 * Takes into account:
     498 *  - site installed in subdirectory
     499 *  - "Use a subdirectory for the default language"
     500 *  - {{home}} mapping
     501 *  - both "exclude" and "include" modes
     502 *
     503 * @return bool True if the current URL should be treated as excluded from translation.
     504 */
     505function trp_dntcp_is_current_url_excluded() {
     506    if ( is_admin() ) {
     507        return false;
     508    }
     509
     510    $settings          = get_option( 'trp_settings', false );
     511    $advanced_settings = get_option( 'trp_advanced_settings', false );
     512
     513    // No configuration -> nothing is excluded.
     514    if ( empty( $advanced_settings )
     515        || ! isset( $advanced_settings['translateable_content'] )
     516        || empty( $advanced_settings['translateable_content']['paths'] )
     517        || empty( $advanced_settings['translateable_content']['option'] ) ) {
     518        return false;
     519    }
     520
     521    $mode  = $advanced_settings['translateable_content']['option']; // 'exclude' or 'include'
     522    $paths = trp_dntcp_get_paths();
     523
     524    // Build current slug in the same way as trp_exclude_include_paths_to_run_on()
     525    $trp           = TRP_Translate_Press::get_trp_instance();
     526    $url_converter = $trp->get_component( 'url_converter' );
     527
     528    $current_lang = $url_converter->get_lang_from_url_string( $url_converter->cur_page_url() );
     529    if ( empty( $current_lang ) && isset( $settings['default-language'] ) ) {
     530        $current_lang = $settings['default-language'];
     531    }
     532
     533    $site_url_components = parse_url( get_home_url() );
     534    $current_slug        = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( $_SERVER['REQUEST_URI'] ) : '';
     535
     536    // Remove site_url path for installs in subdirectories (e.g. http://localhost/wordpress)
     537    if ( isset( $site_url_components['path'] ) && $site_url_components['path'] !== '' ) {
     538        $current_slug = str_replace( trim( $site_url_components['path'] ), '', $current_slug );
     539    }
     540
     541    // Take into account subdirectory for the default language or other languages.
     542    // This mirrors the logic used in trp_exclude_include_paths_to_run_on().
     543    if ( isset( $settings['add-subdirectory-to-default-language'] )
     544        && $settings['add-subdirectory-to-default-language'] === 'yes'
     545        && ! empty( $current_lang )
     546        && isset( $settings['url-slugs'][ $current_lang ] ) ) {
     547
     548        $replace      = '\/' . $settings['url-slugs'][ $current_lang ];
     549        $current_slug = preg_replace( "/$replace/i", '', ltrim( $current_slug, '/' ), 1 );
     550    }
     551
     552    $array_slugs = array();
     553    trp_test_current_slug( $current_slug, $array_slugs );
     554
     555    $matched = trp_return_exclude_include_url( $paths, $current_slug, $array_slugs );
     556
     557    // In "exclude" mode: listed paths are excluded.
     558    if ( $mode === 'exclude' )
     559        return (bool) $matched;
     560
     561    // In "include" mode: ONLY listed paths are translatable; all others are excluded
     562    if ( $mode === 'include' )
     563        return !$matched;
     564
     565    return false;
     566}
  • translatepress-multilingual/trunk/includes/class-language-switcher-v2.php

    r3389452 r3420034  
    130130
    131131        $allow = apply_filters('trp_allow_language_redirect', true, $needed_lang, $this->url_converter->cur_page_url());
    132         if (!$allow) return;
     132
     133        if ( !$allow || trp_dntcp_is_current_url_excluded() )
     134            return;
    133135
    134136        $missing_in_url = ($lang_from_url === null);
  • translatepress-multilingual/trunk/includes/class-languages.php

    r3398116 r3420034  
    4141     */
    4242    public function change_locale( $locale ){
    43 
    44         if ( $this->is_string_translation_request_for_different_language() ){
    45             $trp_ajax_language = (isset($_POST['trp_ajax_language']) ) ? sanitize_text_field( $_POST['trp_ajax_language'] ) : '';
    46             if ( !$this->settings ){
    47                 $trp = TRP_Translate_Press::get_trp_instance();
    48                 $trp_settings = $trp->get_component( 'settings' );
    49                 $this->settings = $trp_settings->get_settings();
    50             }
    51             if ( $trp_ajax_language && in_array( $trp_ajax_language, $this->settings['translation-languages'] ) ){
    52                 return $trp_ajax_language;
    53             }
    54         }
    55 
    5643        if ( $this->is_admin_request === null ){
    5744            $trp = TRP_Translate_Press::get_trp_instance();
     
    7057        return $locale;
    7158    }
    72 
    73     public function is_string_translation_request_for_different_language(){
    74         if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
    75             $action = 'trp_string_translation_get_missing_gettext_strings';
    76             if ( isset( $_POST['action'] ) && $_POST['action'] === $action ) {
    77                 return true;
    78             }
    79         }
    80         return false;
    81     }
    8259
    8360    /**
  • translatepress-multilingual/trunk/includes/class-machine-translator.php

    r3398116 r3420034  
    4848     * @return bool
    4949     */
    50     public function is_available( $languages = array() ){
    51         if( !empty( $this->settings['trp_machine_translation_settings']['machine-translation'] ) &&
    52             $this->settings['trp_machine_translation_settings']['machine-translation'] == 'yes'
    53         ) {
    54             if ( empty( $languages ) ){
    55                 // can be used to simply know if machine translation is available
     50    public function is_available( $languages = array() ) {
     51        /**
     52         * Return false in case it was directly called from parent and not from a derived class (DeepL / Google / TPAI)
     53         * Calling this method on the parent class means it was called too early and machine translation is not available at this point
     54         */
     55        if ( get_class( $this ) === __CLASS__ )
     56            return false;
     57
     58        $settings = $this->settings['trp_machine_translation_settings'] ?? array();
     59        $enabled  = ( $settings['machine-translation'] ?? '' ) === 'yes';
     60        $is_available = false;
     61
     62        if ( $enabled ) {
     63            $engine = $settings['translation-engine'] ?? null;
     64
     65            if ( $engine === 'deepl' && get_option( 'trp_license_status' ) !== 'valid' ) {
     66                $is_available = false;
     67            } elseif ( empty( $languages ) ) {
    5668                $is_available = true;
    57             }
    58 
    59             // If the license is invalid and the translation engine is DeepL,return false
    60             $license_status = get_option( 'trp_license_status' );
    61             if ( $license_status !== 'valid' && isset( $this->settings['trp_machine_translation_settings']['translation-engine'] ) && $this->settings['trp_machine_translation_settings']['translation-engine'] === 'deepl' ) {
    62                 $is_available = false;
    63             }
    64 
    65             $is_available = $this->check_languages_availability($languages);
    66 
    67         }else {
    68             $is_available = false;
    69         }
    70 
    71         return apply_filters('trp_machine_translator_is_available', $is_available);
     69            } else {
     70                $is_available = $this->check_languages_availability( $languages );
     71            }
     72        }
     73
     74        return apply_filters( 'trp_machine_translator_is_available', $is_available, $languages, $settings );
    7275    }
    7376
  • translatepress-multilingual/trunk/includes/class-settings.php

    r3377320 r3420034  
    329329
    330330        unset($settings['translation-languages-formality']);
     331
     332        $trp = TRP_Translate_Press::get_trp_instance();
     333        $language_switcher_tab = $trp->get_component('language_switcher_tab');
     334
     335        if ( !$language_switcher_tab->is_legacy_enabled() && count( $settings['publish-languages'] ) > 2 ) {
     336            $ls_settings     = $language_switcher_tab->get_initial_config();
     337            $new_ls_settings = $ls_settings;
     338
     339            $new_ls_settings['floater']['oppositeLanguage']   = false;
     340            $new_ls_settings['shortcode']['oppositeLanguage'] = false;
     341
     342            if ( $new_ls_settings !== $ls_settings ){
     343                update_option( 'trp_language_switcher_settings', $new_ls_settings );
     344            }
     345        }
    331346
    332347        // check for duplicates in url slugs
  • translatepress-multilingual/trunk/includes/class-translation-render.php

    r3398116 r3420034  
    10201020            $translated_strings_manual = array();
    10211021            foreach ( $translateable_strings_manual as $i => $string_manual ) {
    1022                 if ( isset( $translated_strings_manual_dictionary[ $string_manual ]->translated ) ) {
     1022                if ( isset( $translated_strings_manual_dictionary[ $string_manual ]->translated ) && !empty( $translated_strings_manual_dictionary[ $string_manual ]->translated )) {
    10231023                    $translated_strings_manual[$i] = $translated_strings_manual_dictionary[ $string_manual ]->translated;
    10241024                }
     
    16861686
    16871687        foreach ( $translateable_strings as $i => $string ) {
    1688 
     1688            if ( isset( $dictionary[ $string ]->translated ) && empty( $dictionary[ $string ]->translated ) ) {
     1689                /* If we have an empty string with a status != NOT TRANSLATED, it's possible we are dealing with
     1690                 * an intentional thing. After Automatic translation, a text can be translated with unallowed html
     1691                 * thus being stored in DB as empty string with status = MACHINE TRANSLATED. By doing continue; we avoid
     1692                 * re-autotranslating over and over again.
     1693                 */
     1694                continue;
     1695            }
    16891696            // prevent accidentally machine translated strings from db such as for src to be displayed
    16901697            $skip_string = in_array( $string, $skip_machine_translating_strings );
     
    17631770
    17641771            if ( !isset($translated_strings[$i]) && isset( $machine_strings[$string] ) ) {
    1765                 $translated_strings[$i] = $machine_strings[$string];
     1772                $sanitized_machine_string = trp_sanitize_string( $machine_strings[$string] );
     1773                if ( !empty( $sanitized_machine_string ) ){
     1774                    // Unallowed HTML can be turned into empty string. Show original text instead
     1775                    $translated_strings[$i] = $sanitized_machine_string;
     1776                }
    17661777            }
    17671778
     
    20802091     * @return array
    20812092     */
    2082     public function wp_mail_filter( $args ){
    2083         if ( !is_array( $args ) ){
     2093    public function wp_mail_filter( $args ) {
     2094        if ( ! is_array( $args ) ) {
    20842095            return $args;
    20852096        }
    20862097
    2087         $whitelisted_shortcodes = apply_filters( 'trp_whitelisted_shortcodes_for_wp_mail', [ 'trp_language', 'language-include', 'language-exclude' ] );
    2088 
    2089         if ( array_key_exists( 'subject', $args ) ){
    2090             $args['subject'] = $this->translate_page( trp_do_these_shortcodes( $args['subject'], $whitelisted_shortcodes ) );
    2091         }
    2092 
    2093         if ( array_key_exists( 'message', $args ) ){
    2094             $args['message'] = $this->translate_page( trp_do_these_shortcodes( $args['message'], $whitelisted_shortcodes ) );
    2095         }
     2098        if ( empty( $args['to'] ) ) {
     2099            return $args;
     2100        }
     2101
     2102        global $TRP_LANGUAGE;
     2103
     2104        $initial_language = $TRP_LANGUAGE;
     2105
     2106        $recipient = $args['to'];
     2107
     2108        // Normalize $recipient to a single email string (first recipient only - that's the main one)
     2109        if ( is_array( $recipient ) ) {
     2110            $first = reset( $recipient );
     2111            $recipient = is_string( $first ) ? $first : '';
     2112        }
     2113
     2114        $recipient = (string) $recipient;
     2115
     2116        // Keep only the first comma-separated entry if multiple are present in the string
     2117        $recipient = trim( strtok( $recipient, ',' ) );
     2118
     2119        if ( $recipient !== '' ) {
     2120            trp_switch_to_preffered_language( $recipient );
     2121        }
     2122
     2123        $whitelisted_shortcodes = apply_filters(
     2124            'trp_whitelisted_shortcodes_for_wp_mail',
     2125            array( 'trp_language', 'language-include', 'language-exclude' )
     2126        );
     2127
     2128        if ( array_key_exists( 'subject', $args ) ) {
     2129            $args['subject'] = $this->translate_page(
     2130                trp_do_these_shortcodes( $args['subject'], $whitelisted_shortcodes )
     2131            );
     2132        }
     2133
     2134        if ( array_key_exists( 'message', $args ) ) {
     2135            $args['message'] = $this->translate_page(
     2136                trp_do_these_shortcodes( $args['message'], $whitelisted_shortcodes )
     2137            );
     2138        }
     2139
     2140        // Switch back to the language used initially
     2141        $TRP_LANGUAGE = $initial_language;
    20962142
    20972143        return $args;
  • translatepress-multilingual/trunk/includes/class-woocommerce-emails.php

    r3398116 r3420034  
    187187        }
    188188
     189        $trp_settings = TRP_Translate_Press::get_trp_instance()->get_component( 'settings' );
     190        $settings     = $trp_settings->get_settings();
     191
     192        $default_language = $settings["default-language"];
     193
    189194        /**
    190195         * At this point in the execution, $wc_email->get_recipient() returns null and throws a PHP warning inside WooCommerce /woocommerce/includes/emails/class-wc-email.php
     
    224229                $registered_user = get_user_by( 'email', $recipients[0] );
    225230                if( $registered_user ){
    226                     // If language is set to site default, user object won't have a locale set. Fallback to WPLANG. In case WPLANG is not set either, fallback to trp_language
     231                    // If language is set to site default, user object won't have a locale set. Fallback to WPLANG. In case WPLANG is not set either, fallback to default language
    227232                    if ( !empty( $registered_user->locale ) ){
    228233                        $language = $registered_user->locale;
    229234                    } else {
    230                         $language = get_option( 'WPLANG' ) ?? get_user_meta( $registered_user->ID, 'trp_language', true );
     235                        $wplang = get_option( 'WPLANG' );
     236                        $language = !empty( $wplang ) ? $wplang : $default_language;
    231237                    }
    232238                } else {
     
    243249        trp_switch_language( $language );
    244250
    245         WC()->load_plugin_textdomain();
    246 
    247         $this->bootstrap_trp_gettext_for_email_language( $language );
     251        add_filter( 'trp_allow_gettext_write', '__return_true' );
     252
     253        $this->reload_woocommerce_textdomain();
     254
     255        $this->bootstrap_trp_gettext_for_emails();
    248256
    249257        // calls necessary because the default additional_content field of an email is localized before this point and stored in a variable in the previous locale
     
    264272
    265273        trp_restore_language();
    266         WC()->load_plugin_textdomain();
     274        $this->reload_woocommerce_textdomain();
    267275
    268276        return false;
     
    289297     * gettext translations stored in TranslatePress are applied correctly.
    290298     */
    291     private function bootstrap_trp_gettext_for_email_language( $language ) {
     299    private function bootstrap_trp_gettext_for_emails() {
    292300        $trp             = TRP_Translate_Press::get_trp_instance();
    293301        $gettext_manager = $trp->get_component( 'gettext_manager' );
     
    308316    }
    309317
     318    function reload_woocommerce_textdomain() {
     319        $domain = 'woocommerce';
     320
     321        $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
     322
     323        $custom_translation_path  = WP_LANG_DIR . '/woocommerce/woocommerce-' . $locale . '.mo';
     324        $global_translation_path  = WP_LANG_DIR . '/plugins/woocommerce-' . $locale . '.mo';
     325        $bundled_translation_path = trailingslashit( WC()->plugin_path() ) . 'i18n/languages/woocommerce-' . $locale . '.mo';
     326
     327        unload_textdomain( $domain );
     328
     329        // Custom file present: mimic WC
     330        if ( is_readable( $custom_translation_path ) ) {
     331            load_textdomain( $domain, $custom_translation_path );
     332
     333            if ( is_readable( $global_translation_path ) ) {
     334                load_textdomain( $domain, $global_translation_path );
     335            }
     336
     337            return true;
     338        }
     339
     340        if ( is_readable( $global_translation_path ) ) {
     341            load_textdomain( $domain, $global_translation_path );
     342            return true;
     343        }
     344
     345        if ( is_readable( $bundled_translation_path ) ) {
     346            load_textdomain( $domain, $bundled_translation_path );
     347            return true;
     348        }
     349
     350        return false;
     351    }
     352
    310353}
  • translatepress-multilingual/trunk/includes/compatibility-functions.php

    r3398116 r3420034  
    27092709
    27102710/**
    2711  * Override the wrap_with_post_id condition for Divi
    2712  *
    2713  * Divi doesn't use the main loop in the traditional way, so we need to override
    2714  * the condition that checks for in_the_loop and is_main_query.
    2715  *
    2716  * @param bool $return Whether to bypass the wrap condition
    2717  * @return bool
    2718  */
    2719 add_filter('trp_wrap_with_post_id_overrule', 'trp_divi_override_wrap_condition', 10);
    2720 function trp_divi_override_wrap_condition($return) {
    2721     // Check if Divi is the active theme
    2722     if (function_exists('et_setup_theme')) {
    2723         // Return false to bypass the in_the_loop check
    2724         return false;
    2725     }
    2726     return $return;
    2727 }
     2711 * Add trp-post-container wrapper to Divi module outputs
     2712 * TP is not adding any trp-post-container except here.
     2713 *
     2714 * @param string $output The module HTML output
     2715 * @param string $render_slug The module slug (e.g., 'et_pb_text', 'et_pb_post_title')
     2716 * @param object $module The module object
     2717 * @return string Modified output with trp-post-container wrapper
     2718 */
     2719add_filter('et_module_shortcode_output', 'trp_divi_wrap_module_with_post_id', 10, 3);
     2720
     2721function trp_divi_wrap_module_with_post_id($output, $render_slug, $module) {
     2722    global $post, $TRP_LANGUAGE;
     2723
     2724    // Check if we have a valid post ID
     2725    if (empty($post->ID)) {
     2726        return $output;
     2727    }
     2728
     2729    // Get TranslatePress settings
     2730    $trp = TRP_Translate_Press::get_trp_instance();
     2731    $trp_settings = $trp->get_component('settings');
     2732    $settings = $trp_settings->get_settings();
     2733
     2734    // Only wrap on non-default language
     2735    if ($TRP_LANGUAGE !== $settings['default-language']) {
     2736        // Only wrap modules that typically contain translatable text content
     2737        $modules_to_wrap = apply_filters('trp_divi_modules_to_wrap', array(
     2738            'et_pb_text',
     2739            'et_pb_post_title',
     2740            'et_pb_post_content',
     2741            'et_pb_blurb',
     2742            'et_pb_cta',
     2743            'et_pb_accordion',
     2744            'et_pb_toggle',
     2745            'et_pb_tabs',
     2746            'et_pb_testimonial',
     2747            'et_pb_pricing_tables',
     2748            'et_pb_number_counter',
     2749            'et_pb_countdown_timer'
     2750        ));
     2751
     2752        if (in_array($render_slug, $modules_to_wrap)) {
     2753            $output = "<trp-post-container data-trp-post-id='" . $post->ID . "'>" . $output . "</trp-post-container>";
     2754        }
     2755    }
     2756
     2757    return $output;
     2758}
  • translatepress-multilingual/trunk/includes/external-functions.php

    r3288239 r3420034  
    6464    }
    6565
    66     if ( strip_tags( $string ) === '' || trim ($string, $filter_string) === '' ){
    67         $string = '';
    68     }
     66    if ( strip_tags( $string ) === '' || trim( $string, $filter_string ) === '' ) {
     67        // Needs decoding otherwise some strings with special characters won't get detected.
     68        // Example in Hebrew: &#1488;&#1493;&#1499;&#1500; &#1493;&#1514;&#1512;&#1489;&#1493;&#1514;
     69        // Placed inside the "if" to avoid calling html_entity_decode so often as this is a very used function
     70        $decoded_string = html_entity_decode( $string );
     71        if ( trim( $decoded_string, $filter_string ) === '' ) {
     72            $string = '';
     73        }
     74    }
    6975    return $string;
    7076}
  • translatepress-multilingual/trunk/includes/functions.php

    r3349766 r3420034  
    274274        return $string;
    275275
    276     if (seems_utf8($string)) {
     276    $seems_utf = ( function_exists( 'wp_is_valid_utf8' ) ) ? wp_is_valid_utf8( $string ) : seems_utf8( $string );
     277
     278    if ( $seems_utf ) {
    277279        $chars = array(
    278280            // Decompositions for Latin-1 Supplement
     
    821823
    822824/**
     825 * Switch to a user's preferred language based on the recipient email.
     826 *
     827 * For managerial users (admin-like roles), prefer user->locale with fallback
     828 * to WPLANG, then trp_language.
     829 *
     830 * For non-managerial users, prefer trp_language, with fallback to locale, then WPLANG.
     831 *
     832 * @param string $email
     833 * @return void
     834 */
     835function trp_switch_to_preffered_language( $email ) {
     836    $email = trim( (string) $email );
     837
     838    if ( $email === '' )
     839        return;
     840
     841    $user = get_user_by( 'email', $email );
     842
     843    if ( ! ( $user instanceof WP_User ) )
     844        return;
     845
     846    $user_roles = is_array( $user->roles ) ? $user->roles : array();
     847
     848    $trp_settings = TRP_Translate_Press::get_trp_instance()->get_component( 'settings' );
     849    $settings     = $trp_settings->get_settings();
     850
     851    $default_language = $settings["default-language"];
     852
     853    /**
     854     * Roles considered "managerial" for email language purposes.
     855     *
     856     * @param string[] $roles
     857     */
     858    $managerial_roles = apply_filters(
     859        'trp_managerial_roles_for_email_language',
     860        [ 'administrator', 'editor', 'shop_manager' ]
     861    );
     862
     863    $is_managerial = !empty( array_intersect( $managerial_roles, $user_roles ) );
     864
     865    if ( $is_managerial ) {
     866        // Managerial: prefer locale, then WPLANG, then default_language
     867        if ( !empty( $user->locale ) ) {
     868            $language = $user->locale;
     869        } else {
     870            $wplang = get_option( 'WPLANG' );
     871            $language = !empty( $wplang ) ? $wplang : $default_language;
     872        }
     873    } else {
     874        // Non-managerial: prefer trp_language.
     875        $language = get_user_meta( $user->ID, 'trp_language', true );
     876
     877        if ( empty( $language ) ) {
     878            if ( !empty( $user->locale ) ) {
     879                $language = $user->locale;
     880            } else {
     881                $wplang = get_option( 'WPLANG' );
     882                $language = !empty( $wplang ) ? $wplang : $default_language;
     883            }
     884        }
     885    }
     886
     887    if ( empty( $language ) )
     888        return;
     889
     890    trp_switch_language( $language );
     891}
     892
     893/**
    823894 * Return $TRP_LANGUAGE as plugin locale
    824895 *
  • translatepress-multilingual/trunk/includes/gettext/class-gettext-manager.php

    r3374439 r3420034  
    488488     */
    489489    public function add_missing_language_file_translations( $dictionary, $language ) {
    490 
    491         $trp_plural_forms    = $this->get_gettext_component( 'plural_forms' );
     490        // Ensure translation files are loaded with the correct locale
     491        $locale   = determine_locale();
     492        $switched = switch_to_locale( $language );
     493
     494        // This means that the language is not supported by WordPress. Either a custom language or a language that we support but WordPress does not.
     495        if ( !$switched && $language !== $locale )
     496            return;
     497
     498        $trp_plural_forms    = $this->get_gettext_component( 'plural_forms' );
    492499        if ( ! $this->trp_query ) {
    493500            $trp             = TRP_Translate_Press::get_trp_instance();
     
    611618            $gettext_insert_update->insert_gettext_strings($insert_gettext_strings, $language);
    612619            $gettext_insert_update->update_gettext_strings($update_gettext_strings, $language, array('translated', 'id', 'status'));
     620
     621            if ( $switched )
     622                restore_previous_locale();
    613623        }
    614624    }
  • translatepress-multilingual/trunk/includes/queries/class-query.php

    r3288239 r3420034  
    9797            $and_block_type = " AND block_type = " . $block_type;
    9898        }
    99         $query = "SELECT original,translated, status FROM `" . sanitize_text_field( $this->get_table_name( $language_code ) ) . "` WHERE status != " . self::NOT_TRANSLATED . $and_block_type . " AND translated <>'' AND original IN ";
     99
     100        /* Do not add a condition for "translated <> '' because this would cause re-autotranslating. */
     101        $query = "SELECT original,translated, status FROM `" . sanitize_text_field( $this->get_table_name( $language_code ) ) . "` WHERE status != " . self::NOT_TRANSLATED . $and_block_type . " AND original IN ";
    100102
    101103        $placeholders = array();
     
    108110        $query .= "( " . implode ( ", ", $placeholders ) . " )";
    109111        $prepared_query = $this->db->prepare( $query, $values );
    110         $dictionary = $this->db->get_results( $prepared_query, OBJECT_K  );
    111 
     112        $results = $this->db->get_results( $prepared_query, OBJECT  );
     113
     114        if ( !empty( $results ) && is_array( $results ) ) {
     115            // There are edge cases where we have 2 results for the same original:
     116            // one with translated as empty string, one with non-empty translation. Keep the one with translation.
     117            $dictionary = [];
     118            foreach ( $results as $row ) {
     119                $key = $row->original;
     120
     121                // If this key has never been added, simply add it
     122                if ( !isset( $dictionary[ $key ] ) ) {
     123                    $dictionary[ $key ] = $row;
     124                    continue;
     125                }
     126
     127                // If current stored entry has empty 'translated'
     128                // and this new row has non-empty 'translated', replace it
     129                if (
     130                    empty( $dictionary[ $key ]->translated ) &&
     131                    !empty( $row->translated )
     132                ) {
     133                    $dictionary[ $key ] = $row;
     134                }
     135
     136                // Otherwise do nothing — we keep the existing "better" record
     137            }
     138        } else {
     139            $dictionary = $results;
     140        }
    112141
    113142
  • translatepress-multilingual/trunk/index.php

    r3403492 r3420034  
    44Plugin URI: https://translatepress.com/
    55Description: Experience a better way of translating your WordPress site using a visual front-end translation editor, with full support for WooCommerce and site builders.
    6 Version: 3.0.5
     6Version: 3.0.6
    77Author: Cozmoslabs, Razvan Mocanu, Madalin Ungureanu, Cristophor Hurduban
    88Author URI: https://cozmoslabs.com/
     
    1111License: GPL2
    1212WC requires at least: 2.5.0
    13 WC tested up to: 10.3.5
     13WC tested up to: 10.4.2
    1414
    1515== Copyright ==
  • translatepress-multilingual/trunk/languages/translatepress-multilingual.pot

    r3403492 r3420034  
    77"Content-Type: text/plain; charset=UTF-8\n"
    88"Content-Transfer-Encoding: 8bit\n"
    9 "POT-Creation-Date: 2025-11-26 15:19+0000\n"
     9"POT-Creation-Date: 2025-12-15 11:13+0000\n"
    1010"X-Poedit-Basepath: ..\n"
    1111"X-Poedit-KeywordsList: __;_e;_ex:1,2c;_n:1,2;_n_noop:1,2;_nx:1,2,4c;_nx_noop:1,2,3c;_x:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
     
    319319msgstr ""
    320320
    321 #: includes/class-language-switcher-v2.php:718
     321#: includes/class-language-switcher-v2.php:720
    322322msgid "Change language to %s"
    323323msgstr ""
     
    347347msgstr ""
    348348
    349 #: includes/class-machine-translator.php:154, includes/google-translate/class-google-translate-v2-machine-translator.php:200
     349#: includes/class-machine-translator.php:157, includes/google-translate/class-google-translate-v2-machine-translator.php:200
    350350msgid "Please enter your Google Translate key."
    351351msgstr ""
    352352
    353 #: includes/class-machine-translator.php:169, add-ons-pro/deepl/includes/class-deepl-machine-translator.php:365
     353#: includes/class-machine-translator.php:172, add-ons-pro/deepl/includes/class-deepl-machine-translator.php:365
    354354msgid "Please enter your DeepL API key."
    355355msgstr ""
     
    567567msgstr ""
    568568
    569 #: includes/class-settings.php:471
     569#: includes/class-settings.php:486
    570570msgid "Language codes can contain only A-Z a-z 0-9 - _ characters. Check your language codes in TranslatePress General Settings."
    571571msgstr ""
    572572
    573 #: includes/class-settings.php:534
     573#: includes/class-settings.php:549
    574574msgid "Error! Duplicate URL slug values."
    575575msgstr ""
    576576
    577 #: includes/class-settings.php:535
     577#: includes/class-settings.php:550
    578578msgid "You cannot select two languages that have the same <a href=\"https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes\" target=\"_blank\">iso code</a> but different formalities because doing so will lead to duplicate <a href=\"https://developers.google.com/search/docs/specialty/international/localized-versions\" target=\"_blank\">hreflang tags</a>."
    579579msgstr ""
    580580
    581 #: includes/class-settings.php:536
     581#: includes/class-settings.php:551
    582582msgid "Duplicate language detected.<br>Each language can only be added once to ensure accurate translation management.<br> Please change the duplicate language entry and try again. "
    583583msgstr ""
    584584
    585 #: includes/class-settings.php:593
     585#: includes/class-settings.php:608
    586586msgid "Current Language"
    587587msgstr ""
    588588
    589 #: includes/class-settings.php:599
     589#: includes/class-settings.php:614
    590590msgid "Opposite Language"
    591591msgstr ""
    592592
    593 #: includes/class-settings.php:639
     593#: includes/class-settings.php:654
    594594msgid "General"
    595595msgstr ""
    596596
    597 #: includes/class-settings.php:644, includes/class-translation-manager.php:540, add-ons-pro/translator-accounts/includes/class-translator-accounts.php:156
     597#: includes/class-settings.php:659, includes/class-translation-manager.php:540, add-ons-pro/translator-accounts/includes/class-translator-accounts.php:156
    598598msgid "Translate Site"
    599599msgstr ""
    600600
    601 #: includes/class-settings.php:649
     601#: includes/class-settings.php:664
    602602msgid "Addons"
    603603msgstr ""
    604604
    605 #: includes/class-settings.php:657
     605#: includes/class-settings.php:672
    606606msgid "License"
    607607msgstr ""
    608608
    609 #: includes/class-settings.php:701, includes/class-translation-manager.php:572
     609#: includes/class-settings.php:716, includes/class-translation-manager.php:572
    610610msgid "Settings"
    611611msgstr ""
    612612
    613 #: includes/class-settings.php:715, partials/license-settings-page.php:29, includes/onboarding/class-autotranslation.php:169, includes/onboarding/class-license.php:131
     613#: includes/class-settings.php:730, partials/license-settings-page.php:29, includes/onboarding/class-autotranslation.php:169, includes/onboarding/class-license.php:131
    614614msgid "Activate License"
    615615msgstr ""
    616616
    617 #: includes/class-settings.php:707
     617#: includes/class-settings.php:722
    618618msgid "Pro Features"
    619619msgstr ""
  • translatepress-multilingual/trunk/readme.txt

    r3403492 r3420034  
    44Tags: translate, translation, multilingual, automatic translation, bilingual, front-end translation, google translate, language
    55Requires at least: 3.1.0
    6 Tested up to: 6.8.3
     6Tested up to: 6.9
    77Requires PHP: 7.4
    8 Stable tag: 3.0.5
     8Stable tag: 3.0.6
    99License: GPLv2 or later
    1010License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    143143
    144144== Changelog ==
    145 = 3.0.5 =
    146 * Fixed CSS for Black Friday offer from admin notice
    147 
    148 = 3.0.4 =
    149 * Added 39 more languages including Irish, Maltese and Sicilian
    150 * Added support for Latin American Spanish automatic translation in TranslatePress AI
    151 * Added support for Divi search in secondary language
    152 * Added support for exact match search in String Translation by placing the string in quotes: "example"
    153 * Allow schema.org data to be translated
    154 * Fixed Regular String Translation search so filtered languages also match terms appearing in the middle of translated strings, not only at the start
    155 * Prevent automatic translation of strings that are 0 or 1 characters long or strings that are punctuation only
    156 * Change background hover color for transparent preset in order to improve contrast of language switcher
    157 * Fixed two missing spaces in floating switcher HTML markup
    158 * Fix PHP warning with empty domain for gettext with context
    159 * Fixed edge case error "call to a member function is_available() on null"
    160 * Moved the "Automatically Translate Slug" setting to be first in line
    161 * Extended the license message for item_name_mismatch to provide more context
    162 * Improved text information next to the default language
    163 
     145= 3.0.6 =
     146* Extended support for Divi search in secondary languages
     147* Fixed redirect loop bug with excluded paths from translation
     148* Fixed incorrect gettext resolution in string translation when the site locale differed from the default TranslatePress language, which caused untranslated gettext entries to be populated using translations from the wrong locale
     149* Fixed edge case of retranslating the same text multiple times
     150* Fixed not detecting texts containing only certain special characters
     151* Fixed bug where mini cart menu widget doesn't respect language change
     152* Fixed edge case error "Call to a member function is_available() on null"
     153* Send emails in recipient's preferred language, in case the recipient is a user
     154* Improved INP by deferring dynamic translation detection when original content can be shown first
     155* Fixed a bug where the floating language switcher was not displaying all languages (if more than 10 were added and animations disabled)
     156* Disable language switcher show opposite language settings in case more than 2 languages are active
     157* Fixed deprecated notice for seems_utf8 function on WP 6.9
    164158
    165159= Older versions =
Note: See TracChangeset for help on using the changeset viewer.