/* i18n — 中英文切换 */ // 词典**不在这里** —— 见同目录的 i18n-data.js。 // 它同时被服务端读取(app/i18n.py),而服务端是 Python;两边读同一份才不会分叉。 // 那个文件在 base.html 里**先于本文件**同步加载,所以这里读得到。 // 真读不到(比如有人单独引这个脚本)时退化成空字典 —— 页面照常起得来, // t() 会返回 key 本身,一眼看得出是加载顺序出了问题。 const I18N = (typeof window !== 'undefined' && window.__I18N__) || { zh: {}, en: {} }; /** * 客户端语言**以服务端渲染时用的那个为准** —— 它就写在 `` 上。 * * ★ 不能只读 localStorage。服务端是从 **cookie** 读语言的,两者可能不一致 * (比如清了 localStorage 但 cookie 还在)。一旦不一致,下面的 * applyTranslations() 会把服务端刚渲染好的中文**又翻回英文** —— 页面反而 * 变成了"先中文后英文",比原来的闪烁更难看。 * * 以 为准还有个好处:它和 DOM 里已有的文案**必然一致**(服务端就是 * 按它渲染的),所以首次 applyTranslations() 是个空操作。 * *
里的脚本执行时 已经解析完了,所以这里读得到。 */ function _initialLang() { const fromHtml = String(document.documentElement.lang || '').toLowerCase(); if (I18N[fromHtml]) return fromHtml; try { const saved = localStorage.getItem('lang'); if (saved && I18N[saved]) return saved; } catch (e) { /* 隐私模式禁 storage */ } return 'en'; } let currentLang = _initialLang(); function t(key) { return (I18N[currentLang] && I18N[currentLang][key]) || I18N['zh'][key] || key; } /** 把静态标注(data-i18n / data-i18n-placeholder)应用到 DOM。 */ function applyTranslations() { document.querySelectorAll('[data-i18n]').forEach(el => { el.textContent = t(el.dataset.i18n); }); document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { el.placeholder = t(el.dataset.i18nPlaceholder); }); // title 属性此前没人处理 —— 只有 data-i18n(写 textContent)和 placeholder 两种, // 于是带提示的按钮/图标没法靠标记翻译,只能写死中文。 document.querySelectorAll('[data-i18n-title]').forEach(el => { el.title = t(el.dataset.i18nTitle); }); } function setLang(lang) { if (!I18N[lang]) return; // 只认字典里有的语言 currentLang = lang; try { localStorage.setItem('lang', lang); } catch (e) { /* 隐私模式禁 storage */ } // ★ 同时写 cookie —— **服务端渲染时要读它**,否则每次整页导航都会先渲染 // 默认语言、再由 JS 纠正,表现为"闪一下另一种语言"。 // localStorage 在这里已经不够用了:它是浏览器本地的,服务端看不到。 // max-age 一年;SameSite=Lax 免得外站发起的请求也带上这个 cookie。 try { document.cookie = `lang=${encodeURIComponent(lang)}; path=/; max-age=31536000; SameSite=Lax`; } catch (e) { /* 禁 cookie:退回旧行为,靠上面的 applyTranslations() 事后翻 */ } // 通知**同页面里的其他应用**:工作流画布是独立的 React 应用,不加载本文件, // 它靠这个事件跟着切。不能用 storage 事件 —— 那个只在**别的标签页**触发, // 而切换按钮就在本页侧边栏上。 try { window.dispatchEvent(new CustomEvent('egodata:lang', { detail: { lang } })); } catch (e) { /* 老浏览器不支持 CustomEvent 构造:画布不跟随,其余照常 */ } applyTranslations(); // Update header nav const navReview = document.getElementById('nav-review'); const navTrash = document.getElementById('nav-trash'); if (navReview) navReview.textContent = t('video_review'); if (navTrash) navTrash.innerHTML = '