PageSourceSearch

https://kahoot.com/wp-content/plugins/translations-plugin/assets/js/language-dropdown.js?ver=1788770026

js kahoot.com collected 2026-10-01 07:06:11 UTC 11,459 bytes, 270 lines download raw bytes

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.