1jQuery(document).ready(function($) { 2 3 const locale = document.querySelector('html').getAttribute('lang'); 4 5 var langs = new Map([ 6 // ['English (US)', 'en'] // default 7 ]); 8 9 const LangLabel = { 10 'en':'International', 11 'en-gb':'British', 12 'da':'Danish', 13 'de':'German', 14 'es':'Spanish', 15 'fr':'French', 16 'id':'Indonesian', 17 'it':'Italian', 18 'nb':'Norwegian', 19 'no':'Norwegian', 20 'nl':'Dutch', 21 'pl':'Polish', 22 'pt-br':'Portuguese, Brazilian', 23 'pt':'Portuguese, Brazilian', 24 'sv':'Swedish', 25 'tr':'Turkish', 26 'vi':'Vietnamese', 27 'ar':'Arabic', 28 'ja':'Japanese', 29 'ko':'Korean', 30 'th':'Thai', 31 'uk':'Ukrainian', 32 'zh-cn':'Chinese Simplified', 33 'zh-tw':'Chinese Traditional', 34 'ach':'Acoli' 35 }; 36 37 const langOrder = Object.keys(LangLabel); 38 39 // Carries the current page's query string (e.g. plan_new/plan_cur/plan_quantity 40 // on /platform/confirm-upgrade/) onto a language-switch destination, since 41 // hreflang alternates are plain content permalinks with no query string. 42 function carryOverQueryParams(dest) { 43 const carryOver = new URLSearchParams(window.location.search); 44 carryOver.delete('lang'); 45 carryOver.forEach(function(value, key) { dest.searchParams.set(key, value); }); 46 return dest; 47 } 48 49 // Security guard for the language-redirect sink (SEC-262 DOM XSS). 50 // The code must be a known language BEFORE it is used in a selector, and the 51 // resolved alternate must be a same-host http(s) URL before we navigate to it â 52 // this is what stops a `javascript:` (or other-scheme / cross-host) destination 53 // from ever reaching window.location. Do not bypass either check. 54 function safeLangUrl(code) { 55 if (typeof code !== 'string') { return null; } 56 let normalized = code.toLowerCase(); 57 if (normalized === 'pt') { normalized = 'pt-br'; } 58 if (!Object.prototype.hasOwnProperty.call(LangLabel, normalized)) { return null; } 59 60 const alternate = document.querySelector(`[hreflang="${CSS.escape(normalized)}"]`); 61 if (!alternate) { return null; } 62 63 const href = alternate.getAttribute('href'); 64 if (!href) { return null; } 65 66 let dest; 67 try { 68 dest = new URL(href, window.location.href); 69 } catch (e) { 70 return null; 71 } 72 if (dest.protocol !== 'http:' && dest.protocol !== 'https:') { return null; } 73 if (dest.hostname !== window.location.hostname) { return null; } 74 return dest; 75 } 76 77 $('link[rel="alternate"][hreflang]').each(function(){ 78 var code = $(this).attr('hreflang'); 79 if (LangLabel[code.toLowerCase()]) { 80 langs.set( code.toLowerCase(), { code: code, label: $(this).attr('data-lang-label') } ); 81 } 82 }); 83 84 function setLanguageSelectors(){ 85 var selectorWrap = $(".language-selector"); 86 var sel = $('<div class="language-selector-dropdown">'); 87 88 //Use lang attr to know if page is english 89 var langAttr = $('html').attr('lang'); 90 91 if(langAttr != "en") { 92 var current = locale ? locale : window.location.pathname.split('/')[1]; 93 current = current.toUpperCase(); 94 } else { 95 current = "EN"; 96 } 97 selectorWrap.html(""); 98 //Check if language exists in langs map 99 if(langs.size > 1) { 100 selectorWrap.append($("<button class='language-selector-toggle' aria-label='Open language dropdown, current: "+current+"'>").attr('data-action', 'openLanguageMenu').text(current)); 101 langOrder.forEach(function(code) { 102 if (!langs.has(code)) { return; } 103 const lang = langs.get(code); 104 const rawHref = $('[hreflang="'+lang.code+'"]').attr('href'); 105 let langURL = rawHref; 106 if (rawHref) { 107 try { 108 langURL = carryOverQueryParams(new URL(rawHref, window.location.href)).href; 109 } catch (e) { 110 langURL = rawHref; 111 } 112 } 113 const status = $('[hreflang="'+lang.code+'"]').data('status'); 114 const link = $("<a>").attr('data-value', lang.code).attr("href", langURL).html(lang.label + '<span class="lang">' + LangLabel[code]+'</span>').attr('data-status', status).attr("aria-label", "Switch language to " + lang.label).addClass('lang-link'); 115 if (lang.code.toUpperCase() == current) { 116 link.addClass('active-lang'); 117 } 118 sel.append(link); 119 }); 120 selectorWrap.append(sel); 121 if (langs.size > 9) { 122 langScrollIndicator(sel); 123 } 124 } 125 } 126 127 $(document).on('click', '.lang-link', function () { 128 const selectedLang = $(this).data('value'); 129 if (selectedLang) { 130 const expirationDate = new Date(); 131 expirationDate.setDate(expirationDate.getDate() + 30); // 30 days 132 document.cookie = `preferred_lang=${selectedLang}; path=/; expires=${expirationDate.toUTCString()}`; 133 } 134 }); 135 136 function setBrowserLanguage() { 137 var preferredLang = Cookies.get('preferred_lang')?.toLowerCase(); 138 const fullBrowserLang = ['pt-BR', 'zh-CN', 'zh-TW', 'en-gb']; 139 const commonwealthRegions = ['gb', 'ca', 'au', 'nz', 'in', 'za', 'sg', 'my', 'ie', 'ng', 'pk', 'ke', 'gh', 'hk']; 140 let browserLang = navigator.language || navigator.userLanguage; 141 if(browserLang == 'pt' || browserLang == 'pt-PT') {browserLang = 'pt-BR';} 142 if(browserLang == 'zh' || browserLang == 'zh-HK') {browserLang = 'zh-CN';} 143 if(browserLang.toLowerCase().startsWith('en-') && commonwealthRegions.includes(browserLang.split('-')[1]?.toLowerCase())) {browserLang = 'en-gb';} 144 145 let processedBrowserLang = fullBrowserLang.includes(browserLang) ? browserLang.toLowerCase() : browserLang.split('-')[0].toLowerCase(); 146 147 if (preferredLang == null) { 148 const expirationDate = new Date(); 149 expirationDate.setDate(expirationDate.getDate() + 30); 150 document.cookie = `preferred_lang=${processedBrowserLang}; path=/; expires=${expirationDate.toUTCString()}`; 151 preferredLang = processedBrowserLang; 152 } 153 154 const currentLang = $('html').attr('lang').toLowerCase(); 155 if (currentLang === preferredLang) { 156 return; 157 } 158 if (preferredLang != 'en') { 159 const dest = safeLangUrl(preferredLang); 160 if (dest) { 161 window.location.href = carryOverQueryParams(dest).href; 162 return; 163 } 164 } 165 } 166 167 function langScrollIndicator(el) { 168 const arrowUp = $('<div class="lang-scroll lang-scroll-top" />').prependTo(el); 169 const arrowDown = $('<div class="lang-scroll lang-scroll-bottom" />').appendTo(el); 170 var scrollInterval; 171 172 arrowDown.on('mouseenter', () => { 173 scrollInterval = setInterval(() => { 174 if (el.scrollTop() + el.height() < el.prop('scrollHeight')) { 175 el.scrollTop(el.scrollTop() + 8); 176 } 177 }, 16); 178 }).on('mouseleave', () => clearInterval(scrollInterval)); 179 180 arrowUp.on('mouseenter', () => { 181 scrollInterval = setInterval(() => { 182 if (el.scrollTop() > 0) { 183 el.scrollTop(el.scrollTop() - 8); 184 } 185 }, 16); 186 }).on('mouseleave', () => clearInterval(scrollInterval)); 187 188 el.on('scroll', () => { 189 arrowUp.toggle(el.scrollTop() > 0); 190 arrowDown.toggle(el.scrollTop() + el.height() < el.prop('scrollHeight')); 191 }); 192 193 if (el.scrollTop() === 0) { 194 arrowUp.hide(); 195 } 196 } 197 198 function addingLangParam(){ 199 var langAttr = $('html').attr('lang'); 200 var preferredLang = Cookies.get('preferred_lang'); 201 var lang = preferredLang && preferredLang !== 'en' ? preferredLang : langAttr; 202 203 if(lang != 'en') { 204 var hrefs = document.querySelectorAll('a[href*=kahoot], .logo-link');
205 hrefs.forEach(element => { 206 var currentHref = element.getAttribute('href'); 207 if (currentHref != null ){ 208 var connector = /\?/.test(currentHref) ? '&' : '?'; 209 if ( !/lang/.test(currentHref) ) { 210 element.setAttribute('href', currentHref+connector +'lang='+lang); 211 } 212 } 213 }); 214 } 215 } 216 217 setBrowserLanguage(); 218 addingLangParam(); 219 checkForLanguageAttribute(); 220 setLanguageSelectors(); 221 222 223 function checkForLanguageAttribute() { 224 const hostname = window.location.host; 225 const rightMenuEnvironments = ['kahoot.com', 'qa2.kahoot.com', 'kahoot-qa.com', 'experimental.kahoot.com', 'kahoot-experimental.com', 'localhost:9000']; 226 const leftMenuEnvironments = ['trust.kahoot.com', 'staging-trust.kahoost.com', 'localhost:9090', 'host.docker.internal:9090']; 227 228 if ($('#header-menu').attr('data-translated') == true && $('#header-menu').find('.language-selector').length === 0) { 229 if (rightMenuEnvironments.includes(hostname)) { 230 $('.header-menu-right').append("<li id='language-selector' class='language-selector'></li>"); 231 } else if (leftMenuEnvironments.includes(hostname)) { 232 $('.header-menu-left').after("<li id='language-selector' class='language-selector'></li>"); 233 } 234 } 235 } 236 237 238 function hideDropdown(event) { 239 const dropdown = $('.language-selector-dropdown'); 240 const toggle = $('.language-selector-toggle'); 241 if (dropdown.is(':visible') && !toggle.is(event.target)) { 242 dropdown.hide(); 243 $(document).off('click', hideDropdown); 244 } 245 } 246 247 $(document).on('click', '.language-selector-toggle', function() { 248 const dropdown = $(this).parent().find('.language-selector-dropdown'); 249 dropdown.toggle(); 250 $(document).on('click', hideDropdown); 251 }); 252 253 const searchParams = new URLSearchParams(window.location.search); 254 let langParam = searchParams.get('lang'); 255 const langAttr = document.querySelector('html').getAttribute('lang') 256 const isPlatformPage = searchParams.has('platformPage'); 257 if(langParam && langAttr != langParam && !isPlatformPage) { 258 const dest = safeLangUrl(langParam); 259 if (dest) { 260 window.location.href = carryOverQueryParams(dest).href; 261 } 262 } 263 264 265 266 $(document).on('click', '.translation-close-message', function() { 267 document.getElementById('no-translation-message').style.display = 'none'; 268 }); 269 270 });
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.