1/** 2 * Sets proper language cookie 3 * 4 * Sends ajax request to get browser and IP language. 5 * Sets cookie with current language. 6 * Changes cookie when clicking a link pointed to another language. 7 * 8 */ 9function TRP_IN_Determine_Language(){ 10 var _this = this; 11 var trpCookie = null; 12 var trpHelper = null; 13 14 this.get_lang_from_url = function ( url ) { 15 // Check domain mappings first if Multiple Domains is active 16 if ( trp_language_cookie_data['domain_language_map'] ) { 17 var domainLang = _this.get_lang_from_domain( url ); 18 if ( domainLang ) { 19 return domainLang; 20 } 21 } 22 23 // Fall back to path-based detection 24 // we remove http or https 25 // if the user links to a http link but the abs_home_url is https, we're serving the https one so we don't brake cookies if he doesn't have proper redirects 26 var lang = url.replace( /^(http|https):\/\//g, ''); 27 var abs_home = trp_language_cookie_data[ 'abs_home' ].replace( /^(http|https):\/\//g, ''); 28 29 // we have removed the home path from our URL. We're adding a / in case it's the homepage of one of the languages 30 // removing / from the front so it's easier for understanding explode() 31 lang = trpHelper.ltrim( trpHelper.trailingslashit( lang.replace(abs_home, '') ),'/' ); 32 33 // We now have to see if the first part of the string is actually a language slug 34 var lang_array = lang.split("/"); 35 if( lang_array.length < 2 ){ 36 return trp_language_cookie_data['default_language']; 37 } 38 // The language code is the first non-empty item in this array depending on abs_home slashes 39 for ( var i = 0; i < lang_array.length; i++ ){ 40 if ( lang_array[i] != undefined && lang_array[i] != '' ) { 41 lang = lang_array[i]; 42 break; 43 } 44 } 45 // the lang slug != actual lang. So we need to do array_search so we don't end up with en instead of en_US 46 if( trpHelper.in_array( lang, trp_language_cookie_data['url_slugs']) ){ 47 return trpHelper.array_search(lang, trp_language_cookie_data['url_slugs'] ); 48 } else { 49 return trp_language_cookie_data['default_language']; 50 } 51 52 }; 53 54 /** 55 * Extract language from URL's domain using domain_language_map 56 * Used when Multiple Domains addon is active 57 * 58 * @param {string} url - URL to extract domain from 59 * @return {string|null} Language code or null if not found 60 */ 61 this.get_lang_from_domain = function( url ) { 62 var domainMap = trp_language_cookie_data['domain_language_map']; 63 if ( !domainMap ) { 64 return null; 65 } 66 67 // Extract hostname from URL 68 var hostname; 69 try { 70 var urlObj = new URL( url ); 71 hostname = urlObj.hostname; 72 } catch ( e ) { 73 // Fallback for older browsers or malformed URLs 74 var match = url.match( /^(?:https?:\/\/)?([^\/]+)/i ); 75 hostname = match ? match[1] : null; 76 } 77 78 if ( !hostname ) { 79 return null; 80 } 81 82 // Check if hostname matches any configured domain 83 if ( domainMap[hostname] ) { 84 return domainMap[hostname]; 85 } 86 87 return null; 88 }; 89 90 this.get_current_dom_language = function(){ 91 var html_language_document = document.getElementById("tp-language"); 92 var html_language; 93 if ( html_language_document ){ 94 if ( jQuery('#tp-language').attr('data-tp-language') ){ 95 html_language = jQuery( '#tp-language' ).attr( 'data-tp-language' ); 96 }else if (jQuery('#tp-language').attr('lang') ){ 97 html_language = jQuery( '#tp-language' ).attr( 'lang' ); 98 } 99 } else { 100 html_language = jQuery('html').attr('lang'); 101 html_language = html_language.split("-").join("_"); 102 } 103 104 if ( !trpHelper.in_array( html_language, trp_language_cookie_data['publish_languages'] ) ){ 105 html_language = "error_determining_language"; 106 } 107 108 return html_language; 109 }; 110 111 112 this.ajax_get_needed_language = function() { 113 jQuery.ajax({ 114 url: trp_language_cookie_data['trp_ald_ajax_url'], 115 type: 'post', 116 dataType: 'json', 117 data: { 118 action: 'trp_ald_get_needed_language', 119 detection_method: trp_language_cookie_data['detection_method'], 120 popup_option: trp_language_cookie_data['popup_option'], 121 popup_textarea: trp_language_cookie_data['popup_textarea'], 122 popup_type: trp_language_cookie_data['popup_type'], 123 popup_textarea_change_button: trp_language_cookie_data['popup_textarea_change_button'], 124 popup_textarea_close_button: trp_language_cookie_data['popup_textarea_close_button'], 125 publish_languages: trp_language_cookie_data['publish_languages'], 126 iso_codes: trp_language_cookie_data['iso_codes'], 127 english_name: trp_language_cookie_data['english_name'], 128 default_language: trp_language_cookie_data['default_language'], 129 is_iphone_user_check: trp_language_cookie_data['is_iphone_user_check'] 130 }, 131 success: function( response ) { 132 if ( response ) { 133 _this.redirect_if_needed( response ); 134 }else{ 135 _this.redirect_if_needed( _this.get_current_dom_language() ); 136 } 137 138 }, 139 error: function( errorThrown ){ 140 // make current language the needed language 141 _this.redirect_if_needed( _this.get_current_dom_language() ); 142 } 143 }); 144 }; 145 146 this.activate_popup = function( response, url_to_redirect ){ 147 148 _this.make_ls_clickable(); 149 _this.ls_make_default_language_preselected( response ); 150 151 var popup = document.getElementById("trp_ald_modal_container"); 152 var close = document.getElementById("trp_ald_x_button"); 153 var popup_change_button = document.getElementById("trp_ald_popup_change_language"); 154 var close_popup = document.getElementById("trp_ald_x_button_and_textarea"); 155 var no_text_popup_select_current_language = document.querySelector('[special-selector = "trp_ald_popup_current_language"]'); 156 157 popup.style.display = 'block'; 158 159 close_popup.onclick = function(e){ 160 popup.style.display = 'none';
161 trpCookie.setCookie( trp_language_cookie_data['cookie_name'], _this.get_current_dom_language(), trp_language_cookie_data['cookie_age'], trp_language_cookie_data['cookie_path'] ); 162 163 // Anchor looks like this <a href="#">. This prevents navigating to # 164 e.preventDefault(); 165 } 166 167 _this.ls_select_language(); 168 169 popup_change_button.onclick = function(){ 170 var selected_language = no_text_popup_select_current_language.getAttribute('data-trp-ald-selected-language'); 171 trpCookie.setCookie( trp_language_cookie_data['cookie_name'], selected_language, trp_language_cookie_data['cookie_age'], trp_language_cookie_data['cookie_path'] ); 172 } 173 } 174 175 176 this.activate_hello_bar = function (response, url_to_redirect){ 177 178 var no_text_popup = document.getElementById("trp_ald_no_text_popup_template"); 179 var clone = no_text_popup.content.cloneNode(true); 180 181 var close_button = clone.getElementById('trp_close'); 182 var button_change_language_no_text_popup = clone.getElementById('trp_ald_no_text_popup_change_language'); 183 var no_text_popup_select_current_language = clone.querySelector('[special-selector = "trp_ald_popup_current_language"]'); 184 185 var div = document.createElement('div'); 186 div.appendChild(clone); 187 document.body.insertAdjacentElement('afterbegin', div); 188 189 button_change_language_no_text_popup.onclick = function() { 190 var selected_language = no_text_popup_select_current_language.getAttribute( 'data-trp-ald-selected-language' ); 191 trpCookie.setCookie( trp_language_cookie_data[ 'cookie_name' ], selected_language, trp_language_cookie_data[ 'cookie_age' ], trp_language_cookie_data[ 'cookie_path' ] ); 192 } 193 194 var remove_bar = function () { 195 trpCookie.setCookie( trp_language_cookie_data['cookie_name'], _this.get_current_dom_language(), trp_language_cookie_data['cookie_age'], trp_language_cookie_data['cookie_path'] ); 196 document.body.removeChild(div); 197 }; 198 199 close_button.onclick = remove_bar; 200 201 _this.make_ls_clickable(); 202 _this.ls_select_language(); 203 _this.ls_make_default_language_preselected( response ); 204 } 205 206 207 this.make_ls_clickable = function (){ 208 jQuery('.trp_ald_ls_container .trp-ls-shortcode-current-language').click(function () { 209 jQuery( '.trp_ald_ls_container .trp-ls-shortcode-current-language' ).addClass('trp-ls-clicked'); 210 jQuery( '.trp_ald_ls_container .trp-ls-shortcode-language' ).addClass('trp-ls-clicked'); 211 }); 212 213 jQuery('.trp_ald_ls_container .trp-ls-shortcode-language').click(function () { 214 jQuery( '.trp_ald_ls_container .trp-ls-shortcode-current-language' ).removeClass('trp-ls-clicked'); 215 jQuery( '.trp_ald_ls_container .trp-ls-shortcode-language' ).removeClass('trp-ls-clicked'); 216 }); 217 218 219 jQuery(document).keyup(function(e) { 220 if (e.key === "Escape") { 221 jQuery( '.trp_ald_ls_container .trp-ls-shortcode-current-language' ).removeClass('trp-ls-clicked'); 222 jQuery( '.trp_ald_ls_container .trp-ls-shortcode-language' ).removeClass('trp-ls-clicked'); 223 } 224 }); 225 226 jQuery(document).on("click", function(event){ 227 if(!jQuery(event.target).closest(".trp_ald_ls_container .trp-ls-shortcode-current-language").length){ 228 jQuery( '.trp_ald_ls_container .trp-ls-shortcode-current-language' ).removeClass('trp-ls-clicked'); 229 jQuery( '.trp_ald_ls_container .trp-ls-shortcode-language' ).removeClass('trp-ls-clicked'); 230 } 231 }); 232 } 233 234 this.ls_select_language = function(){ 235 236 var no_text_current_language = document.querySelector('.trp_ald_ls_container .trp-ls-shortcode-current-language'); 237 var no_text_popup_select_current_language = document.querySelector('.trp_ald_ls_container .trp-ls-shortcode-current-language'); 238 var popup_change_button = document.getElementById("trp_ald_popup_change_language"); 239 var button_change_language_no_text_popup = document.getElementById('trp_ald_no_text_popup_change_language'); 240 jQuery('.trp-ald-popup-select').click(function ( item ) { 241 no_text_current_language.innerHTML = item.target.innerHTML; 242 no_text_current_language.setAttribute('data-trp-ald-selected-language', item.target.getAttribute('data-trp-ald-selected-language')); 243 var selected_language = no_text_popup_select_current_language.getAttribute('data-trp-ald-selected-language'); 244 _this.set_language_href(selected_language,popup_change_button,button_change_language_no_text_popup); 245 }); 246 } 247 248 this.ls_make_default_language_preselected = function( response ){ 249 250 var no_text_current_language = document.querySelector('[special-selector = "trp_ald_popup_current_language"]'); 251 var all_languages = document.querySelectorAll('.trp_ald_ls_container .trp-ald-popup-select'); 252 var popup_change_button = document.getElementById("trp_ald_popup_change_language"); 253 var button_change_language_no_text_popup = document.getElementById('trp_ald_no_text_popup_change_language'); 254 for ( var i = 0; i< all_languages.length; i++){ 255 if (all_languages[i].id === response){ 256 no_text_current_language.innerHTML = all_languages[i].innerHTML; 257 no_text_current_language.setAttribute('data-trp-ald-selected-language', response); 258 no_text_current_language.setAttribute('id', response); 259 var selected_language = no_text_current_language.getAttribute('data-trp-ald-selected-language'); 260 _this.set_language_href(selected_language, popup_change_button, button_change_language_no_text_popup); 261 break; 262 } 263 } 264 } 265 266 this.set_language_href = function ( selected_language, popup_change_button, button_change_language_no_text_popup ){ 267 if( popup_change_button !== null ){ 268 popup_change_button.href = _this.get_url_for_lang(selected_language); 269 } 270 if( button_change_language_no_text_popup !== null ){ 271 button_change_language_no_text_popup.href = _this.get_url_for_lang(selected_language); 272 } 273 } 274 275 this.decode_HTML_entities = function(str) { 276 let txt = new DOMParser().parseFromString(str, "text/html"); 277 return txt.documentElement.textContent; 278 } 279 280 this.get_url_for_lang = function( language ){ 281 282 for( i in trp_language_cookie_data['language_urls'] ){ 283 if( i === language ) { 284 let url_for_language = _this.decode_HTML_entities(trp_language_cookie_data['language_urls'][i]); 285 286 return url_for_language; 287 } 288 } 289 return false;
290 }; 291 292 this.is_valid_url = function ( url ) { 293 if ( typeof url === 'undefined' || url === '' ){ 294 return false; 295 } 296 var starting_characters = ['#','?', 'javascript']; 297 for (var i = 0; i < starting_characters.length; i++ ){ 298 if ( url.substring(0, starting_characters[i].length) === starting_characters[i]){ 299 return false; 300 } 301 } 302 return true; 303 }; 304 305 this.replace_underscore_with_dash = function (var_replace){ 306 var_replace = var_replace.toLowerCase(); 307 var_replace = var_replace.split("_").join("-"); 308 309 return var_replace; 310 } 311 312 this.is_same_language_code = function (var1, var2){ 313 var1 = _this.replace_underscore_with_dash(var1); 314 var2 = _this.replace_underscore_with_dash(var2); 315 316 if( var1 == var2 || var1 == "error_determining_language" || var2 == "error_determining_language" ){ 317 return true; 318 } 319 return false; 320 } 321 322 this.is_login_url = function( url ){ 323 if( url.includes( "wp-login.php" )){ 324 return true; 325 } 326 return false; 327 } 328 329 this.add_event_handlers = function(){ 330 jQuery('body').on('click', 'a', function(e) { 331 332 var clicked_url = jQuery(this).attr("href"); 333 if ( _this.is_valid_url( clicked_url ) && !_this.is_login_url( clicked_url )) { 334 var clicked_language = _this.get_lang_from_url(clicked_url); 335 var trp_current_language = trpCookie.getCookie(trp_language_cookie_data['cookie_name']); 336 337 if (!(_this.is_same_language_code(trp_current_language, clicked_language))) { 338 // When Multiple Domains is active, only handle clicks to our configured domains 339 if ( trp_language_cookie_data['domain_language_map'] && !_this.get_lang_from_domain( clicked_url ) ) { 340 return; // External URL â don't change cookie or add trp_lang_switch 341 } 342 343 // Skip cross-domain sync here â the landing page will handle it after detecting trp_lang_switch=1 344 trpCookie.setCookie(trp_language_cookie_data['cookie_name'], clicked_language, trp_language_cookie_data['cookie_age'], trp_language_cookie_data['cookie_path'], true); 345 346 // Add query param to signal deliberate language switch (for cross-domain) 347 if ( trp_language_cookie_data['domain_language_map'] ) { 348 e.preventDefault(); 349 var separator = clicked_url.indexOf('?') !== -1 ? '&' : '?'; 350 window.location.href = clicked_url + separator + 'trp_lang_switch=1'; 351 return false; 352 } 353 } 354 } 355 }); 356 }; 357 358 this.check_if_iphone_user = function (){ 359 360 if( trp_language_cookie_data['is_iphone_user_check'] == false ){ 361 return false; 362 }else{ 363 if( /iPhone/g.test(navigator.userAgent) ){ 364 return true; 365 }else{ 366 return false; 367 } 368 } 369 }; 370 371 this.redirect_if_needed = function( needed_language ){ 372 // Skip cross-domain sync here â if redirect happens the landing page handles it, if not the cookie is already correct 373 trpCookie.setCookie( trp_language_cookie_data['cookie_name'], needed_language, trp_language_cookie_data['cookie_age'], trp_language_cookie_data['cookie_path'], true ); 374 _this.add_event_handlers(); 375 376 if (!(_this.is_same_language_code(_this.get_current_dom_language(), needed_language)) && _this.check_if_iphone_user() == false ){ 377 url_to_redirect = _this.get_url_for_lang( needed_language ); 378 if(url_to_redirect != 'undefined' && url_to_redirect!= false ) { 379 // redirect to needed language 380 if(trp_language_cookie_data['popup_type'] == 'normal_popup' && trp_language_cookie_data['popup_option'] == 'popup'){ 381 _this.activate_popup( needed_language, url_to_redirect ); 382 }else{ 383 if(trp_language_cookie_data['popup_type'] == 'hello_bar' && trp_language_cookie_data['popup_option'] == 'popup'){ 384 _this.activate_hello_bar(needed_language, url_to_redirect); 385 }else { 386 window.location.replace( url_to_redirect ); 387 } 388 } 389 } 390 } 391 }; 392 393 this.initialize = function (){
394 trpCookie = new TRP_IN_Cookie(); 395 trpHelper = new TRP_IN_Helper(); 396 if ( ! trpCookie.areCookiesEnabled() ){ 397 _this.add_event_handlers(); 398 return; 399 } 400 401 // Check if this is a deliberate language switch (query param from add_event_handlers) 402 var urlParams = new URLSearchParams( window.location.search ); 403 if ( urlParams.has( 'trp_lang_switch' ) ) { 404 // Remove the query param from URL first 405 urlParams.delete( 'trp_lang_switch' ); 406 var newUrl = window.location.pathname + ( urlParams.toString() ? '?' + urlParams.toString() : '' ) + window.location.hash; 407 history.replaceState( null, '', newUrl ); 408 409 // Deliberate switch - set cookie to current page language, no popup 410 var currentLanguage = _this.get_lang_from_url( window.location.href ); 411 if ( currentLanguage ) { 412 trpCookie.setCookie( trp_language_cookie_data['cookie_name'], currentLanguage, trp_language_cookie_data['cookie_age'], trp_language_cookie_data['cookie_path'] ); 413 _this.add_event_handlers(); 414 return; 415 } 416 // If no domain language found (Multiple Domains not active), continue with normal detection 417 } 418 419 var language_from_cookie = trpCookie.getCookie( trp_language_cookie_data['cookie_name'] ); 420 if ( language_from_cookie && trpHelper.in_array( language_from_cookie, trp_language_cookie_data['publish_languages'] ) ) { 421 // if cookie is set, redirect if needed 422 _this.redirect_if_needed( language_from_cookie ); 423 }else{ 424 // if cookie is not set, send request to find out language and then redirect if needed 425 _this.ajax_get_needed_language(); 426 } 427 }; 428 429 _this.initialize(); 430} 431 432/** 433 * String manipulation functions 434 */ 435function TRP_IN_Helper(){ 436 437 this.trailingslashit = function ( string ){ 438 string = string.replace(/\/+$/,''); 439 string = string + '/'; 440 return string; 441 }; 442 443 this.ltrim = function ( string ) { 444 var trimmed = string.replace(/^\s+/g, ''); 445 return trimmed; 446 }; 447 448 this.in_array = function (needle, haystack) { 449 for(var i in haystack ) { 450 if(haystack[i] == needle) { 451 return true; 452 } 453 } 454 return false; 455 }; 456 457 this.array_search = function(val, array) { 458 if(typeof(array) === 'array' || typeof(array) === 'object') { 459 var rekey; 460 for(var i in array) { 461 if(array[i] == val) { 462 rekey = i; 463 break; 464 } 465 } 466 return rekey; 467 } 468 }; 469 470 /** 471 * Update url with query string. 472 * 473 */ 474 this.update_query_string = function(key, value, url) { 475 if (!url) url = window.location.href; 476 var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"), 477 hash; 478 479 if (re.test(url)) { 480 if (typeof value !== 'undefined' && value !== null) 481 return url.replace(re, '$1' + key + "=" + value + '$2$3'); 482 else { 483 hash = url.split('#'); 484 url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 485 if (typeof hash[1] !== 'undefined' && hash[1] !== null) 486 url += '#' + hash[1]; 487 return url; 488 } 489 } 490 else { 491 if (typeof value !== 'undefined' && value !== null ) { 492 var separator = url.indexOf('?') !== -1 ? '&' : '?'; 493 hash = url.split('#'); 494 url = hash[0] + separator + key + '=' + value; 495 if (typeof hash[1] !== 'undefined' && hash[1] !== null) 496 url += '#' + hash[1]; 497 return url; 498 } 499 else 500 return url; 501 } 502 }; 503} 504 505/** 506 * Manipulate cookie: set/get/erase 507 * Includes cross-domain sync for Multiple Domains addon 508 */ 509function TRP_IN_Cookie() { 510 var _this = this; 511 512 // Track if sync is in progress to prevent duplicate syncs 513 this.syncInProgress = false;
514 515 /** 516 * Set a cookie and trigger cross-domain sync if enabled 517 * 518 * @param {boolean} skipSync - Skip cross-domain sync (e.g. when navigating away immediately) 519 */ 520 this.setCookie = function(cname, cvalue, exdays, cpath, skipSync) { 521 var d = new Date(); 522 d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); 523 var expires = "expires=" + d.toUTCString(); 524 document.cookie = cname + "=" + cvalue + ";" + expires + ";path=" + cpath + ";SameSite=Lax"; 525 526 // Trigger cross-domain sync if this is the language cookie 527 if ( cname === trp_language_cookie_data['cookie_name'] && !skipSync ) { 528 _this.syncToOtherDomains( cvalue ); 529 } 530 }; 531 532 this.getCookie = function(cname) { 533 var name = cname + "="; 534 var ca = document.cookie.split(';'); 535 for(var i = 0; i < ca.length; i++) { 536 var c = ca[i]; 537 while (c.charAt(0) == ' ') { 538 c = c.substring(1); 539 } 540 if (c.indexOf(name) == 0) { 541 return c.substring(name.length, c.length); 542 } 543 } 544 return ""; 545 }; 546 547 this.eraseCookie = function(name) { 548 document.cookie = name+'=; Max-Age=-99999999;'; 549 }; 550 551 this.areCookiesEnabled = function(){ 552 if (navigator.cookieEnabled) return true; 553 554 // set and read cookie 555 document.cookie = "cookietest=1"; 556 var ret = document.cookie.indexOf("cookietest=") != -1; 557 558 // delete cookie 559 document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT"; 560 561 return ret; 562 }; 563 564 /** 565 * Sync cookie to all other configured domains (Multiple Domains support) 566 * 567 * @param {string} cookieValue - The language code to sync 568 */ 569 this.syncToOtherDomains = function( cookieValue ) { 570 // Check if sync is enabled and ajax URLs exist 571 if ( !trp_language_cookie_data['sync_enabled'] || 572 !trp_language_cookie_data['sync_ajax_urls'] || 573 _this.syncInProgress ) { 574 return; 575 } 576 577 _this.syncInProgress = true; 578 579 var currentDomain = trp_language_cookie_data['current_domain']; 580 var ajaxUrls = trp_language_cookie_data['sync_ajax_urls']; 581 582 for ( var language in ajaxUrls ) { 583 if ( !ajaxUrls.hasOwnProperty( language ) ) { 584 continue; 585 } 586 587 var ajaxUrl = ajaxUrls[ language ]; 588 589 // Skip if this ajax URL is on the current domain 590 if ( ajaxUrl.indexOf( currentDomain ) === 0 ) { 591 continue; 592 } 593 594 _this.injectSyncIframe( ajaxUrl, cookieValue ); 595 } 596 597 // Reset sync flag after a delay 598 setTimeout( function() { 599 _this.syncInProgress = false; 600 }, 5000 ); 601 }; 602 603 /** 604 * Inject hidden iframe to set cookie on another domain via REST API 605 * 606 * @param {string} restUrl - Target REST API URL 607 * @param {string} cookieValue - Cookie value to set 608 */ 609 this.injectSyncIframe = function( restUrl, cookieValue ) { 610 var iframe = document.createElement( 'iframe' ); 611 iframe.style.display = 'none'; 612 iframe.className = 'trp-ald-sync-iframe'; 613 iframe.src = restUrl + '?trp_ald_cv=' + encodeURIComponent( cookieValue ); 614 615 // Remove iframe after load or timeout 616 iframe.onload = function() { 617 setTimeout( function() { 618 if ( iframe.parentNode ) { 619 iframe.parentNode.removeChild( iframe ); 620 } 621 }, 1000 ); 622 }; 623 624 // Fallback: remove after timeout even if onload doesn't fire 625 setTimeout( function() { 626 if ( iframe.parentNode ) { 627 iframe.parentNode.removeChild( iframe ); 628 } 629 }, 10000 ); 630 631 document.body.appendChild( iframe ); 632 }; 633} 634 635 636jQuery( function() { 637 trpDetermineLanguage = new TRP_IN_Determine_Language(); 638});
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.