class LanguageManager { constructor() { const urlParams = new URLSearchParams(window.location.search); const urlLang = urlParams.get('lang'); const localLang = sessionStorage.getItem('language'); if (urlLang && (urlLang === 'zh' || urlLang === 'en')) { this.currentLang = urlLang; sessionStorage.setItem('language', urlLang); } else if (localLang && (localLang === 'zh' || localLang === 'en')) { this.currentLang = localLang; } else { this.currentLang = 'zh'; } this.translations = {}; this.isLoaded = false; document.documentElement.lang = this.currentLang; this.translateDebounceTimer = null; this.version = localStorage.getItem('translationsVersion') || '0'; this.cacheExpiration = 10 * 60 * 1000; this.init(); } async init() { this.cleanUrl(); await this.loadTranslations(); this.updateLanguageButtons(); this.translatePage(); this.isLoaded = true; this.initPeriodicSync(); } cleanUrl() { const url = new URL(window.location.href); let changed = false; if (url.searchParams.has('lang')) { url.searchParams.delete('lang'); changed = true; } if (url.searchParams.has('_lang_t')) { url.searchParams.delete('_lang_t'); changed = true; } if (changed) { window.history.replaceState({}, '', url.toString()); } } async loadTranslations() { try { const cachedTranslations = localStorage.getItem('cachedTranslations'); let isCacheValid = false; let cachedData = null; if (cachedTranslations) { try { cachedData = JSON.parse(cachedTranslations); if (cachedData.data && cachedData.timestamp) { const now = Date.now(); const cacheAge = now - cachedData.timestamp; if (cacheAge < this.cacheExpiration) { isCacheValid = true; this.translations = cachedData.data; if (cachedData.version) { this.version = cachedData.version; } } } } catch (cacheError) { localStorage.removeItem('cachedTranslations'); } } await this.fetchLatestTranslations(); if (Object.keys(this.translations).length === 0) { if (!isCacheValid) { await this.loadFallbackTranslations(); } } } catch (error) { await this.loadFallbackTranslations(); } } async fetchLatestTranslations() { try { const timestamp = new Date().getTime(); const configResponse = await fetch(`/api/config?lang=${this.currentLang}&t=${timestamp}`, { headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' } }); if (configResponse.ok) { const configData = await configResponse.json(); if (configData.success && configData.config) { const apiTextData = configData.config.texts || {}; const fullTranslations = {}; if (apiTextData.zh && typeof apiTextData.zh === 'object') { fullTranslations.zh = apiTextData.zh; } if (apiTextData.en && typeof apiTextData.en === 'object') { fullTranslations.en = apiTextData.en; } if (Object.keys(fullTranslations).length === 0) { fullTranslations[this.currentLang] = apiTextData; } this.updateTranslations(fullTranslations); } } } catch (error) { } } updateTranslations(newTranslations, version) { if (newTranslations.zh || newTranslations.en) { if (!this.translations) this.translations = {}; if (newTranslations.zh) { this.translations.zh = { ...(this.translations.zh || {}), ...newTranslations.zh }; } if (newTranslations.en) { this.translations.en = { ...(this.translations.en || {}), ...newTranslations.en }; } } else { this.translations = newTranslations; } const cacheData = { data: this.translations, version: version || Date.now(), timestamp: Date.now() }; localStorage.setItem('cachedTranslations', JSON.stringify(cacheData)); if (version) { this.version = version; localStorage.setItem('translationsVersion', version); } this.updateLanguageButtons(); this.translatePage(); } filterXSS(data) { if (!data || typeof data !== 'object') { return data; } if (Array.isArray(data)) { return data.map(item => this.filterXSS(item)); } const filteredData = {}; for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { let value = data[key]; if (typeof value === 'string') { value = value.replace(/<[^>]*>/g, ''); value = value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } else if (typeof value === 'object') { value = this.filterXSS(value); } filteredData[key] = value; } } return filteredData; } async loadFallbackTranslations() { try { const timestamp = new Date().getTime(); const configResponse = await fetch(`/api/config?lang=${this.currentLang}&t=${timestamp}`, { headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' } }); if (configResponse.ok) { const configData = await configResponse.json(); if (configData.success && configData.config) { const texts = configData.config.texts || {}; const fullTranslations = {}; if (texts.zh && typeof texts.zh === 'object') { fullTranslations.zh = texts.zh; } if (texts.en && typeof texts.en === 'object') { fullTranslations.en = texts.en; } if (Object.keys(fullTranslations).length === 0) { fullTranslations[this.currentLang] = texts; } this.updateTranslations(fullTranslations); } else { this.updateTranslations({ [this.currentLang]: {} }); } } else { this.updateTranslations({ [this.currentLang]: {} }); } } catch (error) { this.updateTranslations({ [this.currentLang]: {} }); } } extractChineseTexts() { const zhTexts = {}; try { const langElements = document.querySelectorAll('[data-lang-key]'); langElements.forEach(element => { const key = element.getAttribute('data-lang-key'); if (!key) return; let text; if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') { text = element.placeholder || ''; } else { text = element.textContent.trim(); } if (!text || /^\s*$/.test(text)) { return; } if (text && /[\u4e00-\u9fa5]/.test(text)) { zhTexts[key] = text; } }); } catch (error) { } return zhTexts; } async syncTranslations() { try { const zhTexts = this.extractChineseTexts(); if (Object.keys(zhTexts).length === 0) { return; } const response = await fetch('/api/sync-translations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ zhTexts }) }); if (!response.ok) { } } catch (error) { } } initPeriodicSync() { const syncInterval = 3600000; this.syncTranslations(); setInterval(() => { this.syncTranslations(); }, syncInterval); } changeLanguage(lang) { if (lang !== 'zh' && lang !== 'en') { return; } this.currentLang = lang; document.documentElement.lang = lang; sessionStorage.setItem('language', lang); this.updateLanguageButtons(); this.translatePage(); this.retranslateDynamicContent(); this.fetchLatestTranslations(); this.syncLanguageToBackend(); const mobileMenu = document.getElementById('mobile-menu'); if (mobileMenu && !mobileMenu.classList.contains('hidden')) { mobileMenu.classList.add('hidden'); mobileMenu.style.opacity = '0'; mobileMenu.style.transform = 'translateY(-10px)'; } const mobileProductsDropdown = document.getElementById('mobile-products-dropdown'); if (mobileProductsDropdown && !mobileProductsDropdown.classList.contains('hidden')) { mobileProductsDropdown.classList.add('hidden'); mobileProductsDropdown.style.opacity = '0'; mobileProductsDropdown.style.transform = 'translateY(-10px)'; } if (typeof window.initMobileMenu === 'function') { window.initMobileMenu(); } window.dispatchEvent(new Event('languageChanged')); } showLanguageSwitchAlert(targetLang) { let alertEl = document.getElementById('language-switch-alert'); if (!alertEl) { alertEl = document.createElement('div'); alertEl.id = 'language-switch-alert'; alertEl.className = 'fixed top-4 right-4 bg-secondary text-white px-6 py-3 rounded-md shadow-lg z-50 opacity-0 transform translate-y-[-20px] transition-all duration-300'; document.body.appendChild(alertEl); } const langText = targetLang === 'en' ? 'English' : '中文'; alertEl.textContent = `已切换到${langText}`; alertEl.classList.remove('opacity-0', 'translate-y-[-20px]'); alertEl.classList.add('opacity-100', 'translate-y-0'); setTimeout(() => { alertEl.classList.remove('opacity-100', 'translate-y-0'); alertEl.classList.add('opacity-0', 'translate-y-[-20px]'); }, 3000); } translatePage() { clearTimeout(this.translateDebounceTimer); this.translateDebounceTimer = setTimeout(() => { try { const langElements = document.querySelectorAll('[data-lang-key]'); const translationData = this.translations[this.currentLang] || this.translations; langElements.forEach(element => { try { const key = element.getAttribute('data-lang-key'); if (!key) return; if (this.currentLang === 'en') { if (!element.hasAttribute('data-lang-original')) { let currentText; if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') { currentText = element.placeholder || ''; } else { currentText = element.innerHTML || ''; } if (/[\u4e00-\u9fa5]/.test(currentText)) { element.setAttribute('data-lang-original', currentText); } } if (translationData && translationData[key]) { if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') { element.placeholder = translationData[key]; } else { element.innerHTML = translationData[key]; } } } else { if (element.hasAttribute('data-lang-original')) { if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') { element.placeholder = element.getAttribute('data-lang-original'); } else { element.innerHTML = element.getAttribute('data-lang-original'); } } } } catch (error) { } }); this.updateMetaTags(); } catch (error) { } }, 50); } updateMetaTags() { const translationData = this.translations[this.currentLang] || this.translations; const titleElement = document.querySelector('title'); if (titleElement && translationData && translationData.site_title) { titleElement.textContent = translationData.site_title; } const descElement = document.querySelector('meta[name="description"]'); if (descElement && translationData && translationData.site_description) { descElement.setAttribute('content', translationData.site_description); } const keywordsElement = document.querySelector('meta[name="keywords"]'); if (keywordsElement && translationData && translationData.site_keywords) { keywordsElement.setAttribute('content', translationData.site_keywords); } const ogTitleElement = document.querySelector('meta[property="og:title"]'); if (ogTitleElement && translationData && translationData.site_title) { ogTitleElement.setAttribute('content', translationData.site_title); } const ogDescElement = document.querySelector('meta[property="og:description"]'); if (ogDescElement && translationData && translationData.site_description) { ogDescElement.setAttribute('content', translationData.site_description); } } updateLanguageButtons() { const desktopBtn = document.getElementById('desktop-lang-btn'); const mobileBtn = document.getElementById('mobile-lang-btn'); const targetText = this.currentLang === 'zh' ? 'English' : '中文'; if (desktopBtn) { desktopBtn.textContent = targetText; } if (mobileBtn) { mobileBtn.textContent = targetText; } if (!desktopBtn || !mobileBtn) { setTimeout(() => { this.updateLanguageButtons(); }, 500); } } reinit() { this.updateLanguageButtons(); this.translatePage(); } syncLanguageToBackend() { try { fetch('/api/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'language', data: { current: this.currentLang, saved: this.currentLang } }) }); } catch (error) { } } getTranslation(key) { let translationData = this.translations[this.currentLang] || this.translations; if (translationData && translationData[this.currentLang]) { translationData = translationData[this.currentLang]; } if (translationData && translationData[key]) { return translationData[key]; } return ''; } translateDynamicContent(content, type) { if (!content || typeof content !== 'object') { return content; } const translatedContent = Array.isArray(content) ? [...content] : { ...content }; if (Array.isArray(translatedContent)) { return translatedContent.map(item => this.translateDynamicContent(item, type)); } else if (type === 'product') { return { ...translatedContent, name: this.translateContent(translatedContent.name, `product_${translatedContent.id || 'default'}_name`), desc: this.translateContent(translatedContent.desc, `product_${translatedContent.id || 'default'}_desc`), }; } else if (type === 'news') { const result = { ...translatedContent }; if (this.currentLang !== 'zh') { if (translatedContent.titleEn) { result.title = translatedContent.titleEn; } else { result.title = this.translateContent(translatedContent.title, `news_${translatedContent.newsId || 'default'}_title`); } if (translatedContent.contentEn) { result.content = translatedContent.contentEn; } else { result.content = this.translateContent(translatedContent.content, `news_${translatedContent.newsId || 'default'}_content`); } } return result; } else if (type === 'service') { return { ...translatedContent, title: this.translateContent(translatedContent.title, `service_${translatedContent.id || 'default'}_title`), desc: this.translateContent(translatedContent.desc, `service_${translatedContent.id || 'default'}_desc`), }; } return translatedContent; } translateContent(content, key) { const translationData = this.translations[this.currentLang] || this.translations; if (this.currentLang !== 'zh' && translationData && translationData[key]) { return translationData[key]; } if (content && content.trim() !== '') { return content; } if (translationData && translationData[key]) { return translationData[key]; } return content; } retranslateDynamicContent() { this.retranslateProducts(); this.retranslateNews(); this.retranslateServices(); this.retranslateAboutSection(); this.retranslateChatWelcome(); } retranslateAboutSection() { this.retranslateContactInfo(); } retranslateContactInfo() { const translationData = this.translations[this.currentLang] || this.translations; const contactElements = document.querySelectorAll('[data-lang-key^="contact_"]'); contactElements.forEach(element => { const key = element.getAttribute('data-lang-key'); if (this.currentLang === 'en') { if (!element.hasAttribute('data-lang-original')) { const currentText = element.innerHTML || ''; if (/[\u4e00-\u9fa5]/.test(currentText)) { element.setAttribute('data-lang-original', currentText); } } if (translationData && translationData[key]) { element.innerHTML = translationData[key]; } } else { if (element.hasAttribute('data-lang-original')) { element.innerHTML = element.getAttribute('data-lang-original'); } } }); } retranslateChatWelcome() { if (window.chatService) { window.chatService.messages = window.chatService.messages.filter(msg => !msg.id.startsWith('welcome_')); window.chatService.welcomeMessageShown = false; window.chatService.showWelcomeMessage(); } } retranslateProducts() { const productContainers = document.querySelectorAll('.product-item, [data-product-id]'); productContainers.forEach(container => { const translationData = this.translations[this.currentLang] || this.translations; const productName = container.querySelector('[data-lang-key^="product_"]'); if (productName) { const key = productName.getAttribute('data-lang-key'); if (this.currentLang === 'en') { if (!productName.hasAttribute('data-lang-original')) { const currentText = productName.innerHTML || ''; if (/[\u4e00-\u9fa5]/.test(currentText)) { productName.setAttribute('data-lang-original', currentText); } } if (translationData && translationData[key]) { productName.innerHTML = translationData[key]; } } else { if (productName.hasAttribute('data-lang-original')) { productName.innerHTML = productName.getAttribute('data-lang-original'); } } } const productDesc = container.querySelector('.product-desc, [data-lang-key^="product_"] + p'); if (productDesc && !productDesc.hasAttribute('data-lang-key')) { const productNameKey = container.querySelector('[data-lang-key^="product_"]')?.getAttribute('data-lang-key'); if (productNameKey) { const descKey = productNameKey.replace('_name', '_desc'); if (this.currentLang === 'en') { if (translationData && translationData[descKey]) { productDesc.innerHTML = translationData[descKey]; } } else { if (productDesc.hasAttribute('data-lang-original')) { productDesc.innerHTML = productDesc.getAttribute('data-lang-original'); } } } } }); } retranslateNews() { const newsContainers = document.querySelectorAll('.news-item, [data-news-id]'); newsContainers.forEach(container => { const translationData = this.translations[this.currentLang] || this.translations; const newsTitle = container.querySelector('h3, [data-lang-key^="news_"]'); if (newsTitle) { const key = newsTitle.getAttribute('data-lang-key'); if (this.currentLang === 'en') { if (!newsTitle.hasAttribute('data-lang-original')) { const currentText = newsTitle.innerHTML || ''; if (/[\u4e00-\u9fa5]/.test(currentText)) { newsTitle.setAttribute('data-lang-original', currentText); } } if (translationData && translationData[key]) { newsTitle.innerHTML = translationData[key]; } } else { if (newsTitle.hasAttribute('data-lang-original')) { newsTitle.innerHTML = newsTitle.getAttribute('data-lang-original'); } } } const newsExcerpt = container.querySelector('.news-excerpt, [data-lang-key^="news_"] + p'); if (newsExcerpt && !newsExcerpt.hasAttribute('data-lang-key')) { const newsTitleKey = container.querySelector('[data-lang-key^="news_"]')?.getAttribute('data-lang-key'); if (newsTitleKey) { const excerptKey = newsTitleKey.replace('_title', '_excerpt'); if (this.currentLang === 'en') { if (translationData && translationData[excerptKey]) { newsExcerpt.innerHTML = translationData[excerptKey]; } } else { if (newsExcerpt.hasAttribute('data-lang-original')) { newsExcerpt.innerHTML = newsExcerpt.getAttribute('data-lang-original'); } } } } }); } retranslateServices() { const serviceContainers = document.querySelectorAll('.service-item, [data-service-id]'); serviceContainers.forEach(container => { const translationData = this.translations[this.currentLang] || this.translations; const serviceTitle = container.querySelector('[data-lang-key^="service_"]'); if (serviceTitle) { const key = serviceTitle.getAttribute('data-lang-key'); if (this.currentLang === 'en') { if (!serviceTitle.hasAttribute('data-lang-original')) { const currentText = serviceTitle.innerHTML || ''; if (/[\u4e00-\u9fa5]/.test(currentText)) { serviceTitle.setAttribute('data-lang-original', currentText); } } if (translationData && translationData[key]) { serviceTitle.innerHTML = translationData[key]; } } else { if (serviceTitle.hasAttribute('data-lang-original')) { serviceTitle.innerHTML = serviceTitle.getAttribute('data-lang-original'); } } } const serviceDesc = container.querySelector('.service-desc, [data-lang-key^="service_"] + p'); if (serviceDesc && !serviceDesc.hasAttribute('data-lang-key')) { const serviceTitleKey = container.querySelector('[data-lang-key^="service_"]')?.getAttribute('data-lang-key'); if (serviceTitleKey) { const descKey = serviceTitleKey.replace('_title', '_desc'); if (this.currentLang === 'en') { if (translationData && translationData[descKey]) { serviceDesc.innerHTML = translationData[descKey]; } } else { if (serviceDesc.hasAttribute('data-lang-original')) { serviceDesc.innerHTML = serviceDesc.getAttribute('data-lang-original'); } } } } }); } } let languageManager; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { languageManager = new LanguageManager(); window.languageManager = languageManager; initPageVisibilityListener(); }); } else { languageManager = new LanguageManager(); window.languageManager = languageManager; initPageVisibilityListener(); } function initPageVisibilityListener() { document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && window.languageManager) { setTimeout(() => { window.languageManager.loadTranslations(); }, 500); } }); window.addEventListener('focus', () => { if (window.languageManager) { setTimeout(() => { window.languageManager.loadTranslations(); }, 500); } }); } window.changeLanguage = function(lang) { if (window.languageManager) { window.languageManager.changeLanguage(lang); } }; window.toggleLanguage = function() { if (window.languageManager) { const newLang = window.languageManager.currentLang === 'zh' ? 'en' : 'zh'; window.languageManager.changeLanguage(newLang); } }; window.handleContactClick = function(event) { event.preventDefault(); const currentUrl = window.location.href; const isHomePage = currentUrl.includes('wqzn.html') || currentUrl.endsWith('/'); if (isHomePage) { window.location.href = 'wqzn.html#contact'; setTimeout(() => { window.location.href = 'wqzn.html#contact'; }, 1000); } else { window.location.href = 'wqzn.html'; localStorage.setItem('needContactRedirect', 'true'); } }; window.addEventListener('load', function() { const needRedirect = localStorage.getItem('needContactRedirect'); if (needRedirect === 'true') { localStorage.removeItem('needContactRedirect'); setTimeout(() => { window.location.href = 'wqzn.html#contact'; }, 1000); } });