From de58a1c42fa8a55843f5c36c5e1398eedf91c86f Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 27 Jul 2026 20:31:41 +0800 Subject: [PATCH] feat: full i18n support - all pages use Lang.t() for zh/en --- static/js/components/sidebar.js | 4 +- static/js/components/topbar.js | 8 +- static/js/pages/dashboard.js | 86 ++++---- static/js/pages/debug.js | 22 +- static/js/pages/home.js | 38 ++-- static/js/pages/market.js | 173 +++++++-------- static/js/pages/record.js | 20 +- static/js/pages/scene.js | 46 ++-- static/js/pages/settings.js | 86 ++++---- static/js/utils/lang.js | 377 ++++++++++++++++++++++++++++---- 10 files changed, 578 insertions(+), 282 deletions(-) diff --git a/static/js/components/sidebar.js b/static/js/components/sidebar.js index 8f719db..992dcd4 100644 --- a/static/js/components/sidebar.js +++ b/static/js/components/sidebar.js @@ -49,7 +49,7 @@ const Sidebar = { this._gameListEl.innerHTML = ''; if (!Array.isArray(games) || games.length === 0) { - this._gameListEl.innerHTML = '
暂无可用的游戏插件
'; + this._gameListEl.innerHTML = '
' + Lang.t('noGamePlugins') + '
'; return; } @@ -77,7 +77,7 @@ const Sidebar = { return; } } - this._gameLabelEl.textContent = '选择游戏'; + this._gameLabelEl.textContent = Lang.t('selectGame'); }, async _selectGame(gameId) { diff --git a/static/js/components/topbar.js b/static/js/components/topbar.js index ae427e5..09e3326 100644 --- a/static/js/components/topbar.js +++ b/static/js/components/topbar.js @@ -8,15 +8,15 @@ const Topbar = { if (!WS.connected) { WS._reconnectDelay = 500; WS._connect(); - Toast.show('正在重连...', 'info', 1500); + Toast.show(Lang.t('loading'), 'info', 1500); } }); WS.on('status', (status) => { this._statusEl.className = `status-indicator ${status}`; this._statusEl.querySelector('.status-text').textContent = - status === 'connected' ? '已连接' : - status === 'error' ? '连接错误' : '未连接'; + status === 'connected' ? Lang.t('connected') : + status === 'error' ? Lang.t('connectionError') : Lang.t('disconnected'); }); setInterval(() => this._refreshStatus(), 5000); @@ -28,7 +28,7 @@ const Topbar = { const wsStatus = status.ws_clients > 0 ? 'connected' : 'disconnected'; this._statusEl.className = `status-indicator ${wsStatus}`; this._statusEl.querySelector('.status-text').textContent = - wsStatus === 'connected' ? `已连接 (${status.ws_clients})` : '未连接'; + wsStatus === 'connected' ? `${Lang.t('connected')} (${status.ws_clients})` : Lang.t('disconnected'); } catch (e) { console.error(e); } } }; diff --git a/static/js/pages/dashboard.js b/static/js/pages/dashboard.js index 69e74ef..3d9426e 100644 --- a/static/js/pages/dashboard.js +++ b/static/js/pages/dashboard.js @@ -27,7 +27,7 @@ const PageDashboard = { const allItem = document.createElement('div'); allItem.className = 'category-item active'; - allItem.textContent = '全部'; + allItem.textContent = Lang.t('allCategory'); allItem.dataset.cat = 'all'; listEl.appendChild(allItem); @@ -46,7 +46,7 @@ const PageDashboard = { async _loadThemes() { const gridEl = document.getElementById('dash-theme-grid'); if (!gridEl) return; - gridEl.innerHTML = '
加载中...
'; + gridEl.innerHTML = '
' + Lang.t('loading') + '
'; if (this._marketMode) { await this._loadMarket(gridEl); @@ -61,9 +61,9 @@ const PageDashboard = { if (searchQuery) { themes = themes.filter(t => (t.name||'').toLowerCase().includes(searchQuery) || (t.description||'').toLowerCase().includes(searchQuery) || (t.author||'').toLowerCase().includes(searchQuery)); } const sortBy = document.getElementById('dash-sort')?.value || 'name'; themes.sort((a, b) => (a[sortBy]||'').localeCompare(b[sortBy]||'')); - if (themes.length === 0) { gridEl.innerHTML = this._emptyHtml('当前游戏没有兼容的仪表盘'); return; } + if (themes.length === 0) { gridEl.innerHTML = this._emptyHtml(Lang.t('noCompatibleDash')); return; } gridEl.innerHTML = themes.map(t => this._themeCardHtml(t)).join(''); - } catch (e) { gridEl.innerHTML = '
加载失败
'; } + } catch (e) { gridEl.innerHTML = '
' + Lang.t('loadFailed') + '
'; } }, async _loadMarket(gridEl) { @@ -71,7 +71,7 @@ const PageDashboard = { const res = await fetch('/api/market/dashboards'); const data = await res.json(); const items = data.items || []; - if (!items.length) { gridEl.innerHTML = this._emptyHtml('市场暂无仪表盘'); return; } + if (!items.length) { gridEl.innerHTML = this._emptyHtml(Lang.t('marketEmpty')); return; } gridEl.innerHTML = items.map(d => { const icon = d.icon || '📦'; return `
@@ -82,12 +82,12 @@ const PageDashboard = { ${d.category||''}v${d.version||'1.0'}
- ${d.author||''}${d.downloads||0} 次下载 + ${d.author||''}${Lang.t('downloadCountShort').replace('%d', d.downloads||0)}
- + `; }).join(''); - } catch (e) { gridEl.innerHTML = '
市场加载失败
'; } + } catch (e) { gridEl.innerHTML = '
' + Lang.t('marketLoadFailed') + '
'; } }, _isGameSupported(theme) { @@ -101,8 +101,8 @@ const PageDashboard = { return `
-

${msg || '暂无仪表盘主题'}

-

创建或导入你的仪表盘主题

+

${msg || Lang.t('noDashboards')}

+

${Lang.t('createFirstDash')}

`; }, @@ -149,8 +149,8 @@ const PageDashboard = { const fd = new FormData(); fd.append('file', file); const res = await fetch('/api/dashboards/import', { method: 'POST', body: fd }); const data = await res.json(); - if (data.ok) { Toast.show('导入成功', 'success'); this._loadThemes(); } - else Toast.show('导入失败', 'error'); + if (data.ok) { Toast.show(Lang.t('importSuccess'), 'success'); this._loadThemes(); } + else Toast.show(Lang.t('importFailed'), 'error'); }; input.click(); }); @@ -171,14 +171,14 @@ const PageDashboard = { try { const res = await fetch(`/api/market/install?id=${id}&filename=${filename}`); const data = await res.json(); - if (data.ok) { Toast.show('安装成功', 'success'); this._loadThemes(); } - else Toast.show('安装失败', 'error'); - } catch (e) { Toast.show('安装失败', 'error'); } + if (data.ok) { Toast.show(Lang.t('installSuccess'), 'success'); this._loadThemes(); } + else Toast.show(Lang.t('installFailed'), 'error'); + } catch (e) { Toast.show(Lang.t('installFailed'), 'error'); } }, _showMarketUpload() { if (!this._marketToken) { - Toast.show('请先登录市场账户', 'info'); + Toast.show(Lang.t('marketLoginPrompt'), 'info'); this._showMarketLogin(); return; } @@ -193,8 +193,8 @@ const PageDashboard = { try { const res = await fetch('/api/market/upload', { method: 'POST', body: fd }); const data = await res.json(); - Toast.show(data.error ? '上传失败: ' + data.error : '上传成功', data.error ? 'error' : 'success'); - } catch (e) { Toast.show('上传失败', 'error'); } + Toast.show(data.error ? Lang.t('uploadFailedMsg') + data.error : Lang.t('uploadSuccess'), data.error ? 'error' : 'success'); + } catch (e) { Toast.show(Lang.t('uploadFailed'), 'error'); } }; input.click(); }, @@ -202,13 +202,13 @@ const PageDashboard = { const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = ``; document.body.appendChild(overlay); @@ -221,7 +221,7 @@ const PageDashboard = { localStorage.removeItem('turbosu-market-token'); overlay.remove(); this._updateMarketUI(); - Toast.show('已退出', 'info'); + Toast.show(Lang.t('loggedOut'), 'info'); }); } @@ -234,17 +234,17 @@ const PageDashboard = { if (data.token) { this._marketToken = data.token; localStorage.setItem('turbosu-market-token', data.token); - Toast.show('登录成功', 'success'); + Toast.show(Lang.t('loginSuccess'), 'success'); overlay.remove(); this._updateMarketUI(); - } else { Toast.show(data.message || '登录失败', 'error'); } + } else { Toast.show(data.message || Lang.t('loginFailed'), 'error'); } }); overlay.querySelector('#ml-register').addEventListener('click', async () => { const email = document.getElementById('ml-email').value; const pass = document.getElementById('ml-pass').value; const res = await fetch('/api/market/register', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({email, password: pass, passwordConfirm: pass}) }); const data = await res.json(); - Toast.show(data.error ? '注册失败: ' + data.error : '注册成功,请查收验证邮件', data.error ? 'error' : 'success'); + Toast.show(data.error ? Lang.t('registerFailed') + ': ' + data.error : Lang.t('registerSuccess'), data.error ? 'error' : 'success'); }); }, @@ -253,11 +253,11 @@ const PageDashboard = { const uploadBtn = document.getElementById('btn-market-upload'); if (!loginBtn || !uploadBtn) return; if (this._marketToken) { - loginBtn.textContent = '已登录'; + loginBtn.textContent = Lang.t('loggedIn'); loginBtn.className = 'btn btn-sm btn-secondary'; uploadBtn.style.display = ''; } else { - loginBtn.textContent = '登录'; + loginBtn.textContent = Lang.t('login'); loginBtn.className = 'btn btn-sm btn-primary'; uploadBtn.style.display = 'none'; } @@ -268,7 +268,7 @@ const PageDashboard = { const url = `${location.origin}/dashboard/${themeId}${gameParam}`; window.open(url, '_blank'); navigator.clipboard.writeText(url).then(() => { - Toast.show('链接已复制,可在局域网设备访问', 'success'); + Toast.show(Lang.t('linkCopied'), 'success'); }).catch(() => { Toast.show(`打开地址: ${url}`, 'info'); }); @@ -279,7 +279,7 @@ const PageDashboard = { a.href = `/api/dashboards/${themeId}/export`; a.download = `${themeId}.tsd`; a.click(); - Toast.show('正在下载 .tsd 文件...', 'info'); + Toast.show(Lang.t('exportTsdHint'), 'info'); }, _themeCardHtml(theme) { @@ -287,7 +287,7 @@ const PageDashboard = { const aspect = theme.aspect_ratio || 'auto'; const supported = theme.supported_games || 'all'; const isAll = supported === 'all'; - const compatLabel = isAll ? '全' : '限'; + const compatLabel = isAll ? Lang.t('fullCompatible') : Lang.t('limitedCompatible'); const compatClass = isAll ? 'badge-info' : 'badge-warning'; const preview = theme.preview_ext ? `/dashboard/${theme.id}/preview.${theme.preview_ext}` : ''; const bgStyle = preview ? `background-image:url(${preview});background-size:cover;background-position:center` : ''; @@ -309,7 +309,7 @@ const PageDashboard = { }, async _createNew() { - const name = prompt('仪表盘名称:', 'My Dashboard'); + const name = prompt(Lang.t('dashboardNamePrompt'), 'My Dashboard'); if (!name) return; try { const html = 'TurboSu - ' + name + '
0