PageSourceSearch

https://hoothin.com/wp-content/themes/simple/js/language-switcher.js?ver=2.0.8

js hoothin.com collected 2026-09-24 08:54:08 UTC 13,749 bytes, 381 lines download raw bytes

1// 多語言切換系統
2class LanguageSwitcher {
3    constructor() {
4        this.currentLang = 'en';
5        this.translations = {
6            'zh-CN': {
7                // 導航和基本UI
8                'home': '首页',
9                'about': '关于',
10                'contact': '联系',
11                'search': '搜索',
12                'menu': '菜单',
13                
14                // 文章相關
15                'published_on': '发布于',
16                'views': '热度',
17                'comments': '评论',
18                'category': '分类',
19                'read_more': '阅读更多',
20                'continue_reading': '继续阅读',
21                'all_posts': '全部文章',
22                
23                // TOC相關
24                'table_of_contents': '目录',
25                'toggle_toc': '切换目录显示',
26                
27                // 時間和數字
28                'comment_count_0': '0 评论',
29                'comment_count_1': '1 评论',
30                'comment_count_n': '% 评论',
31                
32                // 分頁
33                'previous_page': '上一页',
34                'next_page': '下一页',
35                'page': '第 % 页',
36                
37                // 搜索
38                'search_placeholder': '搜索文章...',
39                'search_results': '搜索结果',
40                'no_results': '没有找到相关文章',
41                
42                // 404和錯誤
43                'page_not_found': '页面未找到',
44                'error_404': '404 错误',
45                
46                // Footer
47                'powered_by': '由 WordPress 驱动',
48                'theme_by': '主题制作',
49                
50                // 其他
51                'loading': '加载中...',
52                'edit': '编辑',
53                'says': '说道:',
54                'one_thought_on': '"%s"的一条评论',
55                'thoughts_on': '"%s"的%d条评论'
56            },
57            
58            'zh-TW': {
59                // 導航和基本UI
60                'home': '首頁',
61                'about': '關於',
62                'contact': '聯絡',
63                'search': '搜尋',
64                'menu': '選單',
65                
66                // 文章相關
67                'published_on': '發佈於',
68                'views': '熱度',
69                'comments': '留言',
70                'category': '分類',
71                'read_more': '閱讀更多',
72                'continue_reading': '繼續閱讀',
73                'all_posts': '全部文章',
74                
75                // TOC相關
76                'table_of_contents': '目錄',
77                'toggle_toc': '切換目錄顯示',
78                
79                // 時間和數字
80                'comment_count_0': '0 則留言',
81                'comment_count_1': '1 則留言',
82                'comment_count_n': '% 則留言',
83                
84                // 分頁
85                'previous_page': '上一頁',
86                'next_page': '下一頁',
87                'page': '第 % 頁',
88                
89                // 搜索
90                'search_placeholder': '搜尋文章...',
91                'search_results': '搜尋結果',
92                'no_results': '沒有找到相關文章',
93                
94                // 404和錯誤
95                'page_not_found': '頁面未找到',
96                'error_404': '404 錯誤',
97                
98                // Footer
99                'powered_by': '由 WordPress 驅動',
100                'theme_by': '主題製作',
101                
102                // 其他
103                'loading': '載入中...',
104                'edit': '編輯',
105                'says': '說道:',
106                'one_thought_on': '「%s」的一則評論',
107                'thoughts_on': '「%s」的%d則評論'
108            },
109            
110            'en': {
111                // 導航和基本UI
112                'home': 'Home',
113                'about': 'About',
114                'contact': 'Contact',
115                'search': 'Search',
116                'menu': 'Menu',
117                
118                // 文章相關
119                'published_on': 'Published on',
120                'views': 'Views',
121                'comments': 'Comments',
122                'category': 'Category',
123                'read_more': 'Read More',
124                'continue_reading': 'Continue Reading',
125                'all_posts': 'All Posts',
126                
127                // TOC相關
128                'table_of_contents': 'Table of Contents',
129                'toggle_toc': 'Toggle table of contents',
130                
131                // 時間和數字
132                'comment_count_0': '0 Comments',
133                'comment_count_1': '1 Comment',
134                'comment_count_n': '% Comments',
135                
136                // 分頁
137                'previous_page': 'Previous',
138                'next_page': 'Next',
139                'page': 'Page %',
140                
141                // 搜索
142                'search_placeholder': 'Search articles...',
143                'search_results': 'Search Results',
144                'no_results': 'No articles found',
145                
146                // 404和錯誤
147                'page_not_found': 'Page Not Found',
148                'error_404': '404 Error',
149                
150                // Footer
151                'powered_by': 'Powered by WordPress',
152                'theme_by': 'Theme by',
153                
154                // 其他
155                'loading': 'Loading...',
156                'edit': 'Edit',
157                'says': 'says:',
158                'one_thought_on': 'One thought on “%s”',
159                'thoughts_on': '%d thoughts on “%s”'
160            }
161        };
162        
163        this.init();
164    }
165    
166    init() {
167        this.detectBrowserLanguage();
168        this.createLanguageSwitcher();
169        this.translatePage();
170        this.bindEvents();
171    }
172    
173    detectBrowserLanguage() {
174        const browserLang = navigator.language || navigator.userLanguage;
175        
176        if (browserLang.startsWith('zh')) {
177            // 檢測中文變體
178            if (browserLang.includes('TW') || browserLang.includes('HK') || browserLang.includes('MO')) {
179                this.currentLang = 'zh-TW';
180            } else {
181                this.currentLang = 'zh-CN';
182            }
183        } else if (browserLang.startsWith('en')) {
184            this.currentLang = 'en';
185        }
186        
187        // 檢查localStorage中保存的語言偏好
188        const savedLang = localStorage.getItem('preferred-language');
189        if (savedLang && this.translations[savedLang]) {
190            this.currentLang = savedLang;
191        }
192    }
193    
194    createLanguageSwitcher() {
195        const header = document.querySelector('.site-header .container');
196        if (!header) return;
197        
198        const switcher = document.createElement('div');
199        switcher.className = 'language-switcher';
200        switcher.innerHTML = `
201            <button class="lang-toggle" aria-label="切換語言">
202                <span class="current-lang">${this.getLangDisplay(this.currentLang)}</span>
203                <svg class="lang-icon" width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
204                    <path d="M6 9l3-3H3l3 3z"/>
205                </svg>
206            </button>
207            <div class="lang-dropdown">
208                <button class="lang-option" data-lang="zh-CN">简体中文</button>
209                <button class="lang-option" data-lang="zh-TW">正體中文</button>
210                <button class="lang-option" data-lang="en">English</button>
211            </div>
212        `;
213        
214        // 插入到header的右側
215        const rightWrapper = header.querySelector('.header-right-wrapper');
216        if (rightWrapper) {
217            rightWrapper.insertBefore(switcher, rightWrapper.lastChild);
218        } else {
219            header.appendChild(switcher);
220        }
221    }
222    
223    getLangDisplay(lang) {
224        const displays = {
225            'zh-CN': '简',
226            'zh-TW': '繁',
227            'en': 'EN'
228        };
229        return displays[lang] || '简';
230    }
231    
232    bindEvents() {
233        const toggle = document.querySelector('.lang-toggle');
234        const dropdown = document.querySelector('.lang-dropdown');
235        const options = document.querySelectorAll('.lang-option');
236        
237        if (toggle && dropdown) {
238            toggle.addEventListener('click', (e) => {
239                e.stopPropagation();
240                dropdown.classList.toggle('show');
241            });
242            
243            document.addEventListener('click', () => {
244                dropdown.classList.remove('show');
245            });
246        }
247        
248        options.forEach(option => {
249            option.addEventListener('click', (e) => {
250                e.stopPropagation();
251                const newLang = option.dataset.lang;
252                this.switchLanguage(newLang);
253                dropdown.classList.remove('show');
254            });
255        });
256    }
257    
258    switchLanguage(newLang) {
259        if (newLang === this.currentLang) return;
260        
261        this.currentLang = newLang;
262        localStorage.setItem('preferred-language', newLang);
263        
264        // 更新顯示
265        const currentLangSpan = document.querySelector('.current-lang');
266        if (currentLangSpan) {
267            currentLangSpan.textContent = this.getLangDisplay(newLang);
268        }
269        
270        // 重新翻譯頁面
271        this.translatePage();
272        
273        // 觸發自定義事件
274        window.dispatchEvent(new CustomEvent('languageChanged', {
275            detail: { language: newLang }
276        }));
277    }
278    
279    translatePage() {
280        const elements = document.querySelectorAll('[data-i18n]');
281        elements.forEach(element => {
282            const key = element.dataset.i18n;
283            const translation = this.translate(key);
284            
285            if (element.tagName === 'INPUT' && element.type === 'text') {
286                element.placeholder = translation;
287            } else {
288                element.textContent = translation;
289            }
290        });
291        
292        // 更新TOC標題
293        const tocTitle = document.querySelector('.toc-title');
294        if (tocTitle) {
295            tocTitle.innerHTML = `${this.translate('table_of_contents')}`;
296        }
297        
298        // 更新搜索框placeholder
299        const searchFields = document.querySelectorAll('.search-field, [data-i18n-placeholder]');
300        searchFields.forEach(field => {
301            const key = field.dataset.i18nPlaceholder || 'search_placeholder';
302            field.placeholder = this.translate(key);
303        });
304        
305        // 更新評論數量
306        this.updateCommentCounts();
307
308        // 更新評論標題
309        this.updateCommentsTitle();
310        
311        // 更新TOC切換按鈕
312        const tocToggle = document.querySelector('.toc-toggle');
313        if (tocToggle) {
314            tocToggle.setAttribute('aria-label', this.translate('toggle_toc'));
315        }
316    }
317    
318    translate(key, params = {}) {
319        let translation = this.translations[this.currentLang][key] || key;
320        
321        // 處理參數替換
322        Object.keys(params).forEach(param => {
323            translation = translation.replace('%' + param, params[param]);
324        });
325        
326        return translation;
327    }
328    
329    // 公共方法供其他腳本使用
330    getCurrentLanguage() {
331        return this.currentLang;
332    }
333    
334    t(key, params = {}) {
335        return this.translate(key, params);
336    }
337    
338    updateCommentCounts() {
339        // 更新評論數量顯示
340        const commentLinks = document.querySelectorAll('.meta-comments a, .comments-link');
341        commentLinks.forEach(link => {
342            const text = link.textContent.trim();
343            const match = text.match(/(\d+)/);
344            if (match) {
345                const count = parseInt(match[1]);
346                if (count === 0) {
347                    link.textContent = this.translate('comment_count_0');
348                } else if (count === 1) {
349                    link.textContent = this.translate('comment_count_1');
350                } else {
351                    link.textContent = this.translate('comment_count_n').replace('%', count);
352                }
353            }
354        });
355    }
356
357    updateCommentsTitle() {
358        // 更新評論標題
359        const commentsTitle = document.querySelector('[data-i18n-comments]');
360        if (commentsTitle) {
361            const commentCount = parseInt(commentsTitle.dataset.i18nComments) || 0;
362            const title = commentsTitle.dataset.i18nTitle || '';
363
364            if (commentCount === 1) {
365                const translation = this.translate('one_thought_on');
366                commentsTitle.innerHTML = translation.replace('%s', `<span>${title}</span>`);
367            } else {
368                const translation = this.translate('thoughts_on');
369                commentsTitle.innerHTML = translation.replace('%d', commentCount).replace('%s', `<span>${title}</span>`);
370            }
371        }
372    }
373}
374
375// 初始化語言切換器
376document.addEventListener('DOMContentLoaded', () => {
377    window.languageSwitcher = new LanguageSwitcher();
378});
379
380// 導出到全局
381window.LanguageSwitcher = LanguageSwitcher;

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.