feat: full i18n support - all pages use Lang.t() for zh/en
This commit is contained in:
@@ -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 = '<div style="text-align:center;padding:40px;color:var(--text-tertiary);">加载中...</div>';
|
||||
gridEl.innerHTML = '<div style="text-align:center;padding:40px;color:var(--text-tertiary);">' + Lang.t('loading') + '</div>';
|
||||
|
||||
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 = '<div style="text-align:center;padding:40px;color:var(--danger);">加载失败</div>'; }
|
||||
} catch (e) { gridEl.innerHTML = '<div style="text-align:center;padding:40px;color:var(--danger);">' + Lang.t('loadFailed') + '</div>'; }
|
||||
},
|
||||
|
||||
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 `<div class="glass-card theme-card" data-market-id="${d.id}">
|
||||
@@ -82,12 +82,12 @@ const PageDashboard = {
|
||||
<span>${d.category||''}</span><span>v${d.version||'1.0'}</span>
|
||||
</div>
|
||||
<div class="theme-card-meta" style="margin-top:4px">
|
||||
<span>${d.author||''}</span><span style="color:var(--text-tertiary)">${d.downloads||0} 次下载</span>
|
||||
<span>${d.author||''}</span><span style="color:var(--text-tertiary)">${Lang.t('downloadCountShort').replace('%d', d.downloads||0)}</span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary btn-market-install" data-id="${d.id}" data-file="${d.file||''}" style="margin-top:8px;width:100%">安装</button>
|
||||
<button class="btn btn-sm btn-primary btn-market-install" data-id="${d.id}" data-file="${d.file||''}" style="margin-top:8px;width:100%">${Lang.t('install')}</button>
|
||||
</div></div>`;
|
||||
}).join('');
|
||||
} catch (e) { gridEl.innerHTML = '<div style="text-align:center;padding:40px;color:var(--danger);">市场加载失败</div>'; }
|
||||
} catch (e) { gridEl.innerHTML = '<div style="text-align:center;padding:40px;color:var(--danger);">' + Lang.t('marketLoadFailed') + '</div>'; }
|
||||
},
|
||||
|
||||
_isGameSupported(theme) {
|
||||
@@ -101,8 +101,8 @@ const PageDashboard = {
|
||||
return `
|
||||
<div class="empty-state" style="grid-column:1/-1;">
|
||||
<svg viewBox="0 0 24 24" width="64" height="64"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z" fill="currentColor"/></svg>
|
||||
<h4>${msg || '暂无仪表盘主题'}</h4>
|
||||
<p>创建或导入你的仪表盘主题</p>
|
||||
<h4>${msg || Lang.t('noDashboards')}</h4>
|
||||
<p>${Lang.t('createFirstDash')}</p>
|
||||
</div>`;
|
||||
},
|
||||
|
||||
@@ -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 = `<div class="modal" style="min-width:360px;max-width:400px">
|
||||
<h3>市场</h3>
|
||||
<div class="form-group"><label>邮箱</label><input id="ml-email" type="email"></div>
|
||||
<div class="form-group"><label>密码</label><input id="ml-pass" type="password"></div>
|
||||
<h3>${Lang.t('marketLogin')}</h3>
|
||||
<div class="form-group"><label>${Lang.t('email')}</label><input id="ml-email" type="email"></div>
|
||||
<div class="form-group"><label>${Lang.t('password')}</label><input id="ml-pass" type="password"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary btn-sm" id="ml-logout" style="display:none;margin-right:auto">退出</button>
|
||||
<button class="btn btn-secondary btn-sm" id="ml-register">注册</button>
|
||||
<button class="btn btn-primary btn-sm" id="ml-login">登录</button>
|
||||
<button class="btn btn-secondary btn-sm" id="ml-logout" style="display:none;margin-right:auto">${Lang.t('logout')}</button>
|
||||
<button class="btn btn-secondary btn-sm" id="ml-register">${Lang.t('register')}</button>
|
||||
<button class="btn btn-primary btn-sm" id="ml-login">${Lang.t('login')}</button>
|
||||
</div></div>`;
|
||||
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 = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>TurboSu - ' + name + '</title><style>*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}html,body{width:100%;height:100%;overflow:hidden;background:#0a0a0f;color:#fff;font-family:sans-serif}#root{width:100%;height:100%;display:flex;align-items:center;justify-content:center}#box{position:relative;overflow:hidden}#box.contain{max-width:100vw;max-height:100vh}#box.cover{min-width:100vw;min-height:100vh}#box.fill{width:100vw;height:100vh}</style></head><body><div id="root"><div id="box" class="contain"><div data-bind="speed_kmh" style="font-size:80px;font-weight:200">0</div></div></div><script>const M={aspect_ratio:"auto",render_mode:"contain"};(function(){const b=document.getElementById("box"),p=location.protocol==="https:"?"wss:":"ws:";let w,r;function a(){const rt=M.aspect_ratio==="auto"?null:(c=>{const p=c.split(":");return p.length===2?+p[0]/+p[1]:null})(M.aspect_ratio),md=M.render_mode||"contain";if(!rt){b.style.width="100vw";b.style.height="100vh";b.className="fill";return}const vw=window.innerWidth,vh=window.innerHeight,vr=vw/vh;let cw,ch;if(md==="cover"){cw=vw;ch=vw/rt;b.className="cover"}else{b.className="contain";cw=vr>rt?vh*rt:vw;ch=vr>rt?vh:vw/rt}b.style.width=cw+"px";b.style.height=ch+"px"}function c(){w=new WebSocket(p+"//"+location.host+"/ws");w.onopen=()=>{if(r){clearTimeout(r);r=null}};w.onmessage=(e)=>{try{const m=JSON.parse(e.data);if(m.type==="telemetry")document.querySelectorAll("[data-bind]").forEach(el=>{const v=m.data[el.getAttribute("data-bind")];if(v!==undefined)el.textContent=typeof v==="number"?v.toFixed(1):v})}catch(e){}};w.onclose=()=>{r=setTimeout(c,2000)}}a();window.addEventListener("resize",a);c()})();<\/script></body></html>';
|
||||
@@ -318,9 +318,9 @@ const PageDashboard = {
|
||||
body: JSON.stringify({name, category: 'custom', author: 'Local', config: {icon: '📊', aspect_ratio: 'auto', render_mode: 'contain'}, index_html: html})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.id) { Toast.show('创建成功', 'success'); this._loadThemes(); }
|
||||
else Toast.show('创建失败', 'error');
|
||||
} catch (e) { Toast.show('创建失败', 'error'); }
|
||||
if (data.id) { Toast.show(Lang.t('createSuccess'), 'success'); this._loadThemes(); }
|
||||
else Toast.show(Lang.t('createFailed'), 'error');
|
||||
} catch (e) { Toast.show(Lang.t('createFailed'), 'error'); }
|
||||
},
|
||||
|
||||
_template() {
|
||||
@@ -329,17 +329,17 @@ const PageDashboard = {
|
||||
<div class="dashboard-sub-sidebar">
|
||||
<div id="dash-category-list"></div>
|
||||
<div style="padding:8px 12px;margin-top:auto;display:flex;flex-direction:column;gap:4px">
|
||||
<button id="btn-new-dashboard" class="btn btn-sm btn-primary" style="width:100%;">+ 新建</button>
|
||||
<button id="btn-import-dashboard" class="btn btn-sm btn-secondary" style="width:100%;">导入 .tsd</button>
|
||||
<button id="btn-new-dashboard" class="btn btn-sm btn-primary" style="width:100%;">${Lang.t('newDashboard')}</button>
|
||||
<button id="btn-import-dashboard" class="btn btn-sm btn-secondary" style="width:100%;">${Lang.t('importTsd')}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-main">
|
||||
<div id="dash-search-box" style="display:flex;gap:8px;margin-bottom:12px;align-items:center">
|
||||
<input id="dash-search" type="text" placeholder="搜索仪表盘..." style="flex:1;max-width:300px">
|
||||
<input id="dash-search" type="text" placeholder="${Lang.t('searchDashboards')}" style="flex:1;max-width:300px">
|
||||
<select id="dash-sort" style="width:120px">
|
||||
<option value="name">名称</option>
|
||||
<option value="category">分类</option>
|
||||
<option value="author">作者</option>
|
||||
<option value="name">${Lang.t('sortByName')}</option>
|
||||
<option value="category">${Lang.t('sortByCategory')}</option>
|
||||
<option value="author">${Lang.t('sortByAuthor')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="dash-theme-grid" class="theme-grid"></div>
|
||||
|
||||
Reference in New Issue
Block a user