PageSourceSearch

https://uinssc.ac.id/assets/js/translator.js

js uinssc.ac.id collected 2026-09-24 10:14:25 UTC 11,196 bytes, 283 lines download raw bytes

1function googleTranslateElementInit() {
2    new google.translate.TranslateElement({
3        pageLanguage: 'id',
4        includedLanguages: 'id,en,ar',
5        autoDisplay: false
6    }, 'google_translate_element');
7}
8
9/**
10 * Custom Translator Script
11 * Handles Protected Terms (notranslate) and RTL Support
12 */
13const Translator = {
14    protectedTerms: window.PROTECTED_TERMS || [],
15
16    init: function() {
17        this.applyProtection();
18        
19        // Handle RTL and UI sync immediately on load based on saved language
20        const savedLang = localStorage.getItem('user_lang') || 'id';
21        this.handleRTL(savedLang);
22        this.updateSwitcherUI(savedLang);
23
24        // Watch for DOM changes to apply protection to dynamically loaded content
25        const observer = new MutationObserver((mutations) => {
26            if (this.isProcessing) return;
27            this.applyProtection();
28        });
29        
30        observer.observe(document.body, {
31            childList: true,
32            subtree: true
33        });
34
35        // Reinforce hiding the toolbar (Google Translate sometimes forces it back)
36        setInterval(() => {
37            // Reset body/html positioning
38            document.body.style.top = '0px';
39            document.documentElement.style.marginTop = '0px';
40
41            // Hide the toolbar iframe and skiptranslate divs
42            document.querySelectorAll('iframe.skiptranslate, body >
42 .skiptranslate, .VIpgJd-ZVi9od-ORHb-OEVmcd').forEach(el => {
43                el.style.display = 'none';
44                el.style.height = '0';
45                el.style.visibility = 'hidden';
46            });
47        }, 500);
48    },
49
50    updateSwitcherUI: function(lang) {
51        // Find the link in the submenu that matches the language
52        const subMenuLink = document.querySelector(`.switcher-language .sub-menu a[onclick*="'${lang}'"]`);
53        const mainLink = document.querySelector('.switcher-language > li > a');
54        
55        if (subMenuLink && mainLink) {
56            const subImg = subMenuLink.querySelector('img');
57            const subSpan = subMenuLink.querySelector('span');
58            const mainImg = mainLink.querySelector('img');
59            const mainSpan = mainLink.querySelector('span');
60            
61            if (subImg && mainImg) mainImg.src = subImg.src;
62            
63            if (subSpan && mainSpan) {
64                // Get text safely, handling potential font tags from Google Translate
65                let text = subSpan.innerText || subSpan.textContent;
66                
67                // If the language is ID, the user might prefer "Bahasa" as the label
68                // but usually syncing it is clearer.
69                mainSpan.innerText = text.trim();
70            }
71        }
72    },
73
74    isProcessing: false,
75    
76    applyProtection: function() {
77        if (this.protectedTerms.length === 0 || this.isProcessing) return;
78        
79        this.isProcessing = true;
80        
81        // Sort terms by length descending to catch longer phrases first
82        const sortedTerms = [...this.protectedTerms].sort((a, b) => b.length - a.length);
83        
84        const walker = document.createTreeWalker(
85            document.body,
86            NodeFilter.SHOW_TEXT,
87            {
88                acceptNode: function(node) {
89                    const parent = node.parentElement;
90                    if (!parent) return NodeFilter.FILTER_REJECT;
91                    
92                    const tagName = parent.tagName.toLowerCase();
93                    if (['script', 'style', 'textarea', 'input', 'title', 'meta'].includes(tagName)) 
94                        return NodeFilter.FILTER_REJECT;
95                    
96                    if (parent.closest('.notranslate')) return NodeFilter.FILTER_REJECT;
97                    
98                    return NodeFilter.FILTER_ACCEPT;
99                }
100            }
101        );
102
103        let node;
104        const nodesToProcess = [];
105        while (node = walker.nextNode()) {
106            nodesToProcess.push(node);
107        }
108
109        nodesToProcess.forEach(textNode => {
110            if (!textNode.parentNode) return;
111
112            let text = textNode.nodeValue;
113            let fragment = null;
114            let lastIdx = 0;
115
116            // Prepare a combined regex for efficiency, or loop through sorted terms
117            // For precision with word boundaries and case preservation, looping is safer
118            let currentText = text;
119            let parts = [{ type: 'text', val: currentText }];
120
121            for (const term of sortedTerms) {
122                let newParts = [];
123                // Escape regex characters and replace spaces with \s+ to handle line breaks/multiple spaces
124                const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s+');
125                // Regex to capture optional leading/trailing space around whole words, case-insensitive
126                const regex = new RegExp(`(\\s?)\\b${escapedTerm}\\b(\\s?)`, 'gi');
127
128                parts.forEach(part => {
129                    if (part.type === 'protected') {
130                        newParts.push(part);
131                        return;
132                    }
133
134                    let lastMatchIdx = 0;
135                    let match;
136                    regex.lastIndex = 0;
137                    
138                    while ((match = regex.exec(part.val)) !== null) {
139                        // Text before the entire match (including potential leading space)
140                        if (match.index > lastMatchIdx) {
141                            newParts.push({ type: 'text', val: part.val.substring(lastMatchIdx, match.index) });
142                        }
143                        
144                        // The entire match (preceding space + term + following space)
145                        newParts.push({ type: 'protected', val: match[0] });
146                        lastMatchIdx = regex.lastIndex;
147                    }
148
149                    // Remaining text
150                    if (lastMatchIdx < part.val.length) {
151                        newParts.push({ type: 'text', val: part.val.substring(lastMatchIdx) });
152                    }
153                });
154                parts = newParts;
155            }
156
157            // Only replace if we actually found matches
158            if (parts.length > 1 || (parts.length === 1 && parts[0].type === 'protected')) {
159                const fragment = document.createDocumentFragment();
160                parts.forEach(part => {
161                    if (part.type === 'protected') {
162                        const span = document.createElement('span');
163                        span.className = 'notranslate';
164                        span.setAttribute('translate', 'no');
165                        span.textContent = part.val;
166                        fragment.appendChild(span);
167                    } else {
168                        fragment.appendChild(document.createTextNode(part.val));
169                    }
170                });
171                textNode.parentNode.replaceChild(fragment, textNode);
172            }
173        });
174        
175        this.isProcessing = false;
176    },
177
178    changeLanguage: function(lang) {
179        // Save the choice for persistence
180        if (lang === 'id') {
181            localStorage.removeItem('user_lang');
182        } else {
183            localStorage.setItem('user_lang', lang);
184        }
185
186        // Always set cookies for Google Translate (needed for reload-based fallback)
187        if (lang === 'id') {
188            document.cookie = 'googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
189            document.cookie = 'googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.' + location.hostname + ';';
190            document.cookie = 'googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=' + location.hostname + ';';
191        } else {
192            var cookieValue = '/id/' + lang;
193            document.cookie = 'googtrans=' + cookieValue + '; path=/;';
194            document.cookie = 'googtrans=' + cookieValue + '; path=/; domain=.' + location.hostname + ';';
195            document.cookie = 'googtrans=' + cookieValue + '; path=/; domain=' + location.hostname + ';';
196        }
197
198        // Try to trigger Google Translate's select element directly
199        if (!this._triggerGoogleTranslate(lang)) {
200            // If Google Translate widget isn't ready, apply RTL and reload
201            this.handleRTL(lang);
202            location.reload();
203        }
204    },
205
206    /**
207     * Programmatically trigger the Google Translate select element.
208     * Returns true if successful, false if the widget is not yet available.
209     */
210    _triggerGoogleTranslate: function(lang) {
211        var select = document.querySelector('.goog-te-combo');
212        if (select) {
213            select.value = lang;
214            select.dispatchEvent(new Event('change'));
215            this.handleRTL(lang);
216            this.updateSwitcherUI(lang);
217            return true;
218        }
219
220        // Also try the iframe-based approach
221        var frame = document.querySelector('.goog-te-menu-frame');
222        if (frame) {
223            try {
224                var innerDoc = frame.contentDocument || frame.contentWindow.document;
225                var items = innerDoc.querySelectorAll('.goog-te-menu2-item span.text');
226                var langMap = { 'en': 'English', 'ar': 'العربية', 'id': 'Indonesia' };
227                var targetText = langMap[lang];
228                for (var i = 0; i < items.length; i++) {
229                    if (items[i].textContent === targetText) {
230                        items[i].click();
231                        this.handleRTL(lang);
232                        this.updateSwitcherUI(lang);
233                        return true;
234                    }
235                }
236            } catch(e) {
237                // Cross-origin iframe, fall through to reload
238            }
239        }
240
241        return false;
242    },
243
244    /**
245     * Try to apply saved language on page load.
246     * Retries a few times since Google Translate loads asynchronously.
247     */
248    _applySavedLanguage: function() {
249        var savedLang = localStorage.getItem('user_lang');
250        if (!savedLang || savedLang === 'id') return;
251
252        var self = this;
253        var attempts = 0;
254        var maxAttempts = 20;
255
256        var interval = setInterval(function() {
257            attempts++;
258            if (self._triggerGoogleTranslate(savedLang)) {
259                clearInterval(interval);
260            } else if (attempts >= maxAttempts) {
261                clearInterval(interval);
262            }
263        }, 500);
264    },
265
266    handleRTL: function(lang) {
267        if (lang === 'ar') {
268            document.documentElement.setAttribute('dir', 'rtl');
269            document.documentElement.setAttribute('lang', 'ar');
270            document.body.classList.add('rtl-mode');
271        } else {
272            document.documentElement.setAttribute('dir', 'ltr');
273            document.documentElement.setAttribute('lang', lang === 'id' ? 'id' : lang);
274            document.body.classList.remove('rtl-mode');
275        }
276    }
277};
278
279document.addEventListener('DOMContentLoaded', function() {
280    Translator.init();
281    Translator._applySavedLanguage();
282});
283

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.