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.