1 2/** 3 * Script to replace dynamically added strings with their translation. 4 */ 5function TRP_Translator(){ 6 7 this.is_editor = false; 8 var _this = this; 9 var observer = null; 10 var observerConfig = { 11 attributes: true, 12 childList: true, 13 characterData: false,//this could be CDATA so I set it to false in v 1.4.5 14 subtree: true 15 }; 16 var translate_numerals_opt = trp_data.trp_translate_numerals_opt; 17 var custom_ajax_url = trp_data.trp_custom_ajax_url; 18 var wp_ajax_url = trp_data.trp_wp_ajax_url; 19 var language_to_query; 20 this.except_characters = " \t\n\r � �.,/`~!@#$â¬Â£%^&*():;-_=+[]{}\\|?/<>1234567890'"; 21 var trim_characters = " \t\n\r � �\x0A\x0B" + "\302" + "\240"; 22 var already_detected = []; 23 var duplicate_detections_allowed = parseInt( trp_data.duplicate_detections_allowed ) 24 25 /** 26 * Ajax request to get translations for strings 27 */ 28 this.ajax_get_translation = function( nodesInfo, string_originals, url, skip_machine_translation ) { 29 jQuery.ajax({ 30 url: url, 31 type: 'post', 32 dataType: 'json', 33 data: { 34 action : ( _this.is_editor ) ? 'trp_get_translations_regular' : 'trp_get_translations_domchanges', 35 all_languages : 'false', 36 security : trp_data['gettranslationsnonceregular'], 37 language : language_to_query, 38 original_language : original_language, // used for trp custom ajax 39 originals : JSON.stringify( string_originals ), 40 skip_machine_translation : JSON.stringify( skip_machine_translation ), 41 dynamic_strings : 'true', 42 translate_numerals_opt : translate_numerals_opt 43 }, 44 success: function( response ) { 45 if ( response === 'error' ) { 46 _this.ajax_get_translation( nodesInfo, string_originals, wp_ajax_url, skip_machine_translation ); 47 console.log( 'Notice: TranslatePress trp-ajax request uses fall back to admin ajax.' ); 48 }else{ 49 _this.update_strings( response, nodesInfo ); 50 } 51 }, 52 error: function( errorThrown ){ 53 if ( url == custom_ajax_url && custom_ajax_url != wp_ajax_url ){ 54 _this.ajax_get_translation( nodesInfo, string_originals, wp_ajax_url, skip_machine_translation ); 55 console.log( 'Notice: TranslatePress trp-ajax request uses fall back to admin ajax.' ); 56 }else{ 57 _this.update_strings( null, nodesInfo ); 58 console.log( 'TranslatePress AJAX Request Error' ); 59 } 60 } 61 }); 62 }; 63 64 /** 65 * Return given text converted to html. 66 * 67 * Useful for decoding special characters into displayable form. 68 * 69 * @param html 70 * @returns {*} 71 */ 72 this.decode_html = function( html ) { 73 var txt = document.createElement( "textarea" ); 74 txt.innerHTML = html; 75 return txt.value; 76 }; 77 78 /** 79 * Replace original strings with translations if found. 80 */ 81 this.update_strings = function( response, nodesInfo ) { 82 _this.pause_observer(); 83 if ( response != null && response.length > 0 ){ 84 var newEntries = []; 85 for (var j = 0 ; j < nodesInfo.length; j++){ 86 var nodeInfo = nodesInfo[j]; 87 var translation_found = false; 88 var initial_value = nodeInfo.original; 89 for( var i = 0; i < response.length; i++ ) { 90 var response_string = response[i].translationsArray[language_to_query]; 91 if (response[i].original.trim() == nodeInfo.original.trim()) { 92 // The nodeInfo can contain duplicates and response cannot. We need duplicates to refer to different jQuery objects where the same string appears in different places on the page. 93 var entry = response[i] 94 entry.selector = 'data-trp-translate-id' 95 entry.attribute = '' 96 newEntries.push( entry ) 97 if ( _this.is_editor ) { 98 var jquery_object; 99 var trp_translate_id = 'data-trp-translate-id' 100 var trp_node_group = 'data-trp-node-group' 101 if ( nodeInfo.attribute ) { 102 jquery_object = jQuery(nodeInfo.node) 103 trp_translate_id = trp_translate_id + '-' + nodeInfo.attribute 104 trp_node_group = trp_node_group + '-' + nodeInfo.attribute 105 }else{ 106 jquery_object = jQuery(nodeInfo.node).parent('translate-press'); 107 } 108 jquery_object.attr( trp_translate_id, response[i].dbID ); 109 jquery_object.attr( trp_node_group, response[i].group ); 110 } 111 112 if (response_string.translated != '' && language_to_query == current_language ) { 113 var text_to_set = _this.decode_html( initial_value.replace(initial_value.trim(), response_string.translated)); 114 if ( nodeInfo.attribute ){ 115 nodeInfo.node.setAttribute( nodeInfo.attribute, text_to_set ) 116 if ( nodeInfo.attribute == 'src' ){ 117 nodeInfo.node.setAttribute( 'srcset', '' ) 118 nodeInfo.node.setAttribute( 'data-src', text_to_set ) 119 } 120 }else { 121 nodeInfo.node.textContent = text_to_set; 122 } 123 translation_found = true; 124 } 125 break; 126 } 127 } 128 129 already_detected[ initial_value ] = (initial_value in already_detected ) ? already_detected[ initial_value ] + 1 : 0 130 if ( ! translation_found ){ 131 if ( nodeInfo.attribute ){ 132 if ( nodeInfo.attribute != 'src' ) { 133 nodeInfo.node.setAttribute(nodeInfo.attribute, initial_value) 134 } 135 }else { 136 nodeInfo.node.textContent = initial_value; 137 } 138 } 139 140 } 141 // this should always be outside the for loop 142 if ( _this.is_editor ) { 143 window.parent.dispatchEvent( new Event( 'trp_iframe_page_updated' ) ); 144 window.dispatchEvent( new Event( 'trp_iframe_page_updated' ) ); 145 } 146 147 }else{ 148 for (var j = 0 ; j < nodesInfo.length; j++){ 149 if ( nodesInfo[j].attribute ){ 150 if ( nodesInfo[j].attribute != 'src' ) { 151 nodesInfo[j].node.setAttribute(nodesInfo[j].attribute, nodesInfo[j].original) 152 } 153 }else { 154 nodesInfo[j].node.textContent = nodesInfo[j].original; 155 } 156 already_detected[ nodesInfo[j].original ] = (nodesInfo[j].original in already_detected ) ? already_detected[ nodesInfo[j].original ] + 1 : 0 157 } 158 } 159 _this.resume_observer(); 160 }; 161 162 /* 163 * Separated from the detect_new_strings function to allow disconnecting observer. 164 */ 165 this.detect_new_strings_callback = function( mutations ){ 166 // calling disconnect directly instead of calling pause_observer because we don't need to takeRecords() (called from pause_observer) because we would duplicate mutations 167 observer.disconnect() 168 _this.detect_new_strings( mutations ); 169 _this.resume_observer(); 170 } 171 172 /** 173 * Detect and remember added strings. 174 */ 175 this.detect_new_strings = function( mutations ){ 176 var string_originals = []; 177 var nodesInfo = []; 178 var skip_machine_translation = []; 179 var translateable; 180 mutations.forEach( function (mutation) { 181 for (var i = 0; i < mutation.addedNodes.length; i++) { 182 var node = mutation.addedNodes[i] 183 /* if it is an anchor add the trp-edit-translation=preview parameter to it */ 184 if ( _this.is_editor ){ 185 var anchor_tags = jQuery(node).find('a') 186 if ( typeof anchor_tags.context !== 'undefined' ) 187 anchor_tags.context.href = _this.update_query_string('trp-edit-translation', 'preview', anch
187or_tags.context.href); 188 } 189 190 if ( _this.skip_string(node) ){ 191 continue; 192 } 193 194 // Search for innertext modifications or newly added nodes with innertext 195 translateable = _this.get_translateable_textcontent( node ) 196 string_originals = string_originals.concat( translateable.string_originals ); 197 nodesInfo = nodesInfo.concat( translateable.nodesInfo ); 198 skip_machine_translation = skip_machine_translation.concat( translateable.skip_machine_translation ); 199 200 // Search for text inside attributes of newly added nodes 201 translateable = _this.get_translateable_attributes( node ) 202 string_originals = string_originals.concat( translateable.string_originals ); 203 nodesInfo = nodesInfo.concat( translateable.nodesInfo ); 204 skip_machine_translation = skip_machine_translation.concat( translateable.skip_machine_translation ); 205 } 206 207 if ( mutation.attributeName ){ 208 if ( ! _this.in_array( mutation.attributeName, trp_data.trp_attributes_accessors ) ){ 209 return 210 } 211 if ( _this.skip_string_attribute( mutation.target, mutation.attributeName ) || _this.skip_string(mutation.target) ){ 212 return 213 } 214 215 // Search for modified text inside attributes of existing nodes 216 translateable = _this.get_translateable_attributes( mutation.target ) 217 string_originals = string_originals.concat( translateable.string_originals ); 218 nodesInfo = nodesInfo.concat( translateable.nodesInfo ); 219 skip_machine_translation = skip_machine_translation.concat( translateable.skip_machine_translation ); 220 } 221 }); 222 if ( nodesInfo.length > 0 ) { 223 var ajax_url_to_call = (_this.is_editor) ? wp_ajax_url : custom_ajax_url; 224 _this.ajax_get_translation( nodesInfo, string_originals, ajax_url_to_call, skip_machine_translation ); 225 } 226 }; 227 228 this.skip_string = function(node){ 229 // skip nodes containing these attributes 230 var selectors = trp_data.trp_skip_selectors; 231 for (var i = 0; i < selectors.length ; i++ ){ 232 if ( jQuery(node).closest( selectors[ i ] ).length > 0 ){ 233 return true; 234 } 235 } 236 return false; 237 }; 238 239 this.skip_string_from_auto_translation = function(node){ 240 // nodes containing these attributes will not be automatically translated 241 var selectors = trp_data.trp_no_auto_translation_selectors; 242 for (var i = 0; i < selectors.length ; i++ ){ 243 if ( jQuery(node).closest( selectors[ i ] ).length > 0 ){ 244 return true; 245 } 246 } 247 return false; 248 }; 249 250 this.contains_substring_that_needs_skipped = function ( string, attribute ){ 251 for (var attribute_to_skip in trp_data.skip_strings_from_dynamic_translation_for_substrings ){ 252 if (trp_data.skip_strings_from_dynamic_translation_for_substrings.hasOwnProperty(attribute_to_skip) && attribute === attribute_to_skip) { 253 for ( var i = 0 ; i < trp_data.skip_strings_from_dynamic_translation_for_substrings[attribute_to_skip].length; i++ ){ 254 if ( string.indexOf(trp_data.skip_strings_from_dynamic_translation_for_substrings[attribute_to_skip][i]) !== -1 ){ 255 return true 256 } 257 } 258 } 259 } 260 return false 261 }; 262 263 /* 264 * Skip string based on original string text 265 */ 266 this.skip_string_original = function ( string, attribute ){ 267 return ( 268 ( already_detected[string] > duplicate_detections_allowed ) || 269 _this.in_array( string, trp_data.skip_strings_from_dynamic_translation ) || 270 _this.contains_substring_that_needs_skipped( string, attribute) 271 ) 272 } 273 274 this.skip_string_attribute = function(node, attribute){ 275 // skip nodes containing these attributes 276 var selectors = trp_data.trp_base_selectors; 277 for (var i = 0; i < selectors.length ; i++ ){ 278 if ( typeof jQuery(node).attr( selectors[ i ] + '-' + attribute ) !== 'undefined' ){ 279 return true; 280 } 281 } 282 return false;
283 }; 284 285 this.in_array = function (needle, array ) { 286 var i 287 var length = array.length 288 for( i = length - 1; i >= 0; i-- ){ 289 if ( array[i] === needle ){ 290 return true 291 } 292 } 293 return false 294 } 295 296 this.get_translateable_textcontent = function( node ){ 297 var string_originals = []; 298 var nodesInfo = []; 299 var skip_machine_translation = [] 300 if ( node.textContent && _this.trim( node.textContent.trim(), _this.except_characters ) != '' ) { 301 302 var direct_string = get_string_from_node( node ); 303 if ( direct_string ) { 304 // a text without HTML was added 305 if ( _this.trim( direct_string.textContent, _this.except_characters ) != '' ) { 306 var extracted_original = _this.trim(direct_string.textContent, trim_characters); 307 if ( ! _this.skip_string_original( extracted_original, false )) { 308 nodesInfo.push({node: node, original: extracted_original, attribute: ''}); 309 string_originals.push(extracted_original) 310 if ( _this.skip_string_from_auto_translation(node)){ 311 skip_machine_translation.push( extracted_original ) 312 } 313 314 direct_string.textContent = ''; 315 if (_this.is_editor) { 316 jQuery(node).wrap('<translate-press></translate-press>'); 317 } 318 } 319 } 320 }else{ 321 // node contains HTML 322 var all_nodes = jQuery( node ).find( '*').addBack(); 323 var all_strings = all_nodes.contents().filter(function(){ 324 if( this.nodeType === 3 && /\S/.test(this.nodeValue) ){ 325 if ( ! _this.skip_string(this) ){ 326 return this; 327 } 328 }}); 329 if ( _this.is_editor ) { 330 all_strings.wrap('<translate-press></translate-press>'); 331 } 332 var all_strings_length = all_strings.length; 333 for (var j = 0; j < all_strings_length; j++ ) { 334 if ( _this.trim( all_strings[j].textContent, _this.except_characters ) != '' ) { 335 if ( ! _this.skip_string_original( all_strings[j].textContent, false )) { 336 nodesInfo.push({node: all_strings[j], original: all_strings[j].textContent, attribute: ''}); 337 string_originals.push(all_strings[j].textContent) 338 if ( _this.skip_string_from_auto_translation(all_strings[j])){ 339 skip_machine_translation.push( all_strings[j].textContent ) 340 } 341 if (trp_data ['showdynamiccontentbeforetranslation'] == false) { 342 all_strings[j].textContent = ''; 343 } 344 } 345 } 346 } 347 } 348 } 349 return { 'string_originals': string_originals, 'nodesInfo': nodesInfo, 'skip_machine_translation': skip_machine_translation }; 350 } 351 352 this.get_translateable_attributes = function ( node ) { 353 var nodesInfo = [] 354 var string_originals = [] 355 var skip_attr_machine_translation = [ 'href', 'src' ] 356 var skip_machine_translation = [] 357 for (var trp_attribute_key in trp_data.trp_attributes_selectors) { 358 if (trp_data.trp_attributes_selectors.hasOwnProperty(trp_attribute_key)) { 359 var attribute_selector_item = trp_data.trp_attributes_selectors[trp_attribute_key] 360 if ( typeof attribute_selector_item['selector'] !== 'undefined' ){ 361 var all_nodes = jQuery( node ).find( attribute_selector_item.selector ).addBack( attribute_selector_item.selector ) 362 363 var all_nodes_length = all_nodes.length 364 for (var j = 0; j < all_nodes_length; j++ ) { 365 if ( _this.skip_string( all_nodes[j] ) || _this.skip_string_attribute( all_nodes[j], attribute_selector_item.accessor ) ){ 366 continue; 367 } 368 369 var attribute_content = all_nodes[j].getAttribute( attribute_selector_item.accessor ) 370 if ( _this.skip_string_original( attribute_content, attribute_selector_item.accessor )){ 371 continue; 372 } 373 if ( attribute_content && _this.trim( attribute_content.trim(), _this.except_characters ) != '' ) { 374 nodesInfo.push({node: all_nodes[j], original: attribute_content, attribute: attribute_selector_item.accessor });
375 string_originals.push( attribute_content ) 376 if ( trp_data ['showdynamiccontentbeforetranslation'] == false && ( attribute_selector_item.accessor != 'src' ) && ( attribute_selector_item.accessor != 'href' ) ) { 377 all_nodes[j].setAttribute( attribute_selector_item.accessor, '' ); 378 } 379 380 if ( _this.skip_string_from_auto_translation(all_nodes[j])){ 381 skip_machine_translation.push( attribute_content ) 382 }else{ 383 for ( var s = 0; s < skip_attr_machine_translation.length; s++ ) { 384 if ( attribute_selector_item.accessor === skip_attr_machine_translation[ s ] ){ 385 skip_machine_translation.push( attribute_content ) 386 break 387 } 388 } 389 } 390 } 391 } 392 } 393 } 394 } 395 return { 'string_originals': string_originals, 'nodesInfo': nodesInfo, 'skip_machine_translation': skip_machine_translation }; 396 } 397 398 function get_string_from_node( node ){ 399 if( node.nodeType === 3 && /\S/.test(node.nodeValue) ){ 400 if ( ! _this.skip_string(node) ){ 401 return node; 402 } 403 } 404 } 405 406 // cleans the gettext wrappers 407 this.cleanup_gettext_wrapper = function(){ 408 jQuery('trp-gettext').contents().unwrap(); 409 }; 410 411 /** 412 * Update url with query string. 413 * 414 */ 415 this.update_query_string = function(key, value, url) { 416 if (!url) return url; 417 if ( url.startsWith('#') ){ 418 return url; 419 } 420 var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"), 421 hash; 422 423 if (re.test(url)) { 424 if (typeof value !== 'undefined' && value !== null) 425 return url.replace(re, '$1' + key + "=" + value + '$2$3'); 426 else { 427 hash = url.split('#'); 428 url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 429 if (typeof hash[1] !== 'undefined' && hash[1] !== null) 430 url += '#' + hash[1]; 431 return url; 432 } 433 } 434 else { 435 if (typeof value !== 'undefined' && value !== null ) { 436 var separator = url.indexOf('?') !== -1 ? '&' : '?'; 437 hash = url.split('#'); 438 url = hash[0] + separator + key + '=' + value; 439 if (typeof hash[1] !== 'undefined' && hash[1] !== null) 440 url += '#' + hash[1]; 441 return url; 442 } 443 else 444 return url; 445 } 446 }; 447 448 /** 449 * Initialize and configure observer. 450 */ 451 this.initialize = function() { 452 this.is_editor = (typeof window.parent.tpEditorApp !== 'undefined' ) 453 if ( this.is_editor ) { 454 trp_data['gettranslationsnonceregular'] = window.parent.trp_dynamic_nonce; 455 } 456 457 current_language = trp_data.trp_current_language; 458 original_language = trp_data.trp_original_language; 459 language_to_query = trp_data.trp_language_to_query; 460 translate_numerals_opt = trp_data.trp_translate_numerals_opt; 461 462 if ( typeof translate_numerals_opt !== "undefined" && translate_numerals_opt !== '' && translate_numerals_opt === "yes") { 463 _this.except_characters = " \t\n\r � �.,/`~!@#$â¬Â£%^&*():;-_=+[]{}\\|?/<>'"; 464 } 465 466 // create an observer instance 467 if ( trp_data['showdynamiccontentbeforetranslation'] === true ){ 468 observer = new MutationObserver(mutations => { 469 setTimeout(() => _this.detect_new_strings_callback(mutations), 0); 470 }); 471 } 472 473 else { 474 observer = observer = new MutationObserver(_this.detect_new_strings_callback) 475 } 476 477 _this.resume_observer(); 478 479 jQuery( document ).ajaxComplete(function( event, request, settings ) { 480 if( typeof window.parent.jQuery !== "undefined" && window.parent.jQuery('#trp-preview-iframe').length != 0 ) { 481 var settingsdata = "" + settings.data; 482 if( typeof settings.data == 'undefined' || jQuery.isEmptyObject( settings.data ) || settingsdata.indexOf('action=trp_') === -1 ) { 483 window.parent.dispatchEvent( new Event( 'trp_iframe_page_updated' ) ); 484 } 485 } 486 }); 487 488 //try a final attempt at cleaning the gettext wrappers 489 _this.cleanup_gettext_wrapper(); 490 }; 491 492 /** 493 * Resume observing new strings added.
494 */ 495 this.resume_observer = function(){ 496 if ( language_to_query === '' ) { 497 // No translation languages are set other than default language 498 return; 499 } 500 observer.observe(document.body, observerConfig); 501 }; 502 503 /** 504 * Pause observing new strings added. 505 */ 506 this.pause_observer = function(){ 507 /* 508 Disconnect will delete the existing mutations detected prior to being passed to callback function. 509 So we are calling takeRecords to save them first, disconnect, then detect the strings 510 Disconnect happens before detect_new_strings to avoid detecting mutations from within detect new strings functions 511 */ 512 if ( language_to_query === '' ) { 513 // No translation languages are set other than default language 514 return; 515 } 516 var mutations = observer.takeRecords() 517 observer.disconnect() 518 if ( mutations.length > 0 ) { 519 _this.detect_new_strings(mutations) 520 } 521 522 }; 523 524 this.trim = function (str, charlist) { 525 // discuss at: http://locutus.io/php/trim/ 526 // original by: Kevin van Zonneveld (http://kvz.io) 527 // improved by: mdsjack (http://www.mdsjack.bo.it) 528 // improved by: Alexander Ermolaev (http://snippets.dzone.com/user/AlexanderErmolaev) 529 // improved by: Kevin van Zonneveld (http://kvz.io) 530 // improved by: Steven Levithan (http://blog.stevenlevithan.com) 531 // improved by: Jack 532 // input by: Erkekjetter 533 // input by: DxGx 534 // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman) 535 // example 1: trim(' Kevin van Zonneveld ') 536 // returns 1: 'Kevin van Zonneveld' 537 // example 2: trim('Hello World', 'Hdle') 538 // returns 2: 'o Wor' 539 // example 3: trim(16, 1) 540 // returns 3: '6' 541 var whitespace = [ 542 ' ', 543 '\n', 544 '\r', 545 '\t', 546 '\f', 547 '\x0b', 548 '\xa0', 549 '\u2000', 550 '\u2001', 551 '\u2002', 552 '\u2003', 553 '\u2004', 554 '\u2005', 555 '\u2006', 556 '\u2007', 557 '\u2008', 558 '\u2009', 559 '\u200a', 560 '\u200b', 561 '\u2028', 562 '\u2029', 563 '\u3000' 564 ].join(''); 565 var l = 0; 566 var i = 0; 567 str += ''; 568 if (charlist) { 569 whitespace += (charlist + '').replace(/([[\]().?/*{}+$^:])/g, '$1'); 570 } 571 l = str.length; 572 for (i = 0; i < l; i++) { 573 if (whitespace.indexOf(str.charAt(i)) === -1) { 574 str = str.substring(i); 575 break; 576 } 577 } 578 l = str.length; 579 for (i = l - 1; i >= 0; i--) { 580 if (whitespace.indexOf(str.charAt(i)) === -1) { 581 str = str.substring(0, i + 1); 582 break; 583 } 584 } 585 return whitespace.indexOf(str.charAt(0)) === -1 ? str : ''; 586 }; 587 588 _this.initialize(); 589} 590 591var trpTranslator; 592var current_language; 593var original_language; 594 595function trp_get_IE_version() { 596 var sAgent = window.navigator.userAgent; 597 var Idx = sAgent.indexOf("MSIE"); 598 599 // If IE, return version number. 600 if (Idx > 0) 601 return parseInt(sAgent.substring(Idx+ 5, sAgent.indexOf(".", Idx))); 602 603 // If IE 11 then look for Updated user agent string. 604 else if (!!navigator.userAgent.match(/Trident\/7\./)) 605 return 11; 606 else 607 return 0; //It is not IE 608} 609 610function trp_allow_detect_dom_changes_to_run(){ 611 var IE_version = trp_get_IE_version(); 612 if ( IE_version != 0 && IE_version <= 11 ){ 613 return false; 614 } 615 return true; 616} 617 618 619// Initialize the Translate Press Editor when the script loads 620if ( trp_allow_detect_dom_changes_to_run() ) { 621 trpTranslator = new TRP_Translator(); 622}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.