i18n: translate web UI to Chinese
- Login/config/rules/status cards fully localized - Event rule labels, buttons, placeholders and hints in Chinese - Keep English only in code comments and API internals
This commit is contained in:
+55
-55
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bilibili Live → Yukkuri TTS</title>
|
||||
<title>B站直播 → ゆっくり TTS</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #1a1a2e;
|
||||
@@ -227,11 +227,11 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Bilibili Live → Yukkuri TTS</h1>
|
||||
<h1>B站直播 → ゆっくり TTS</h1>
|
||||
|
||||
<!-- Login Card -->
|
||||
<div class="card" id="login-card">
|
||||
<h2>Login</h2>
|
||||
<h2>登录</h2>
|
||||
<div class="login-area">
|
||||
<div class="qr-box" id="qr-box">
|
||||
<div class="qr-placeholder" id="qr-placeholder">
|
||||
@@ -246,18 +246,18 @@
|
||||
<rect x="6" y="16" width="2" height="2"/>
|
||||
<rect x="14" y="16" width="2" height="2"/>
|
||||
</svg>
|
||||
<span>Click "Login"<br/>to get QR code</span>
|
||||
<span>点击"登录"<br/>获取二维码</span>
|
||||
</div>
|
||||
<img id="qr-img" src="" alt="QR Code" style="display:none">
|
||||
<div id="qr-overlay" style="display:none"></div>
|
||||
</div>
|
||||
<div class="qr-info">
|
||||
<div id="login-status" class="status-text">
|
||||
Not logged in — anonymous danmaku may have censored usernames.
|
||||
未登录 — 匿名弹幕的用户名可能被隐藏。
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-login" id="btn-login" onclick="startLogin()">Login with QR</button>
|
||||
<button class="btn btn-login" id="btn-logout" onclick="doLogout()" style="display:none">Logout</button>
|
||||
<button class="btn btn-login" id="btn-login" onclick="startLogin()">扫码登录</button>
|
||||
<button class="btn btn-login" id="btn-logout" onclick="doLogout()" style="display:none">退出登录</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,55 +265,55 @@
|
||||
|
||||
<!-- Config Card -->
|
||||
<div class="card">
|
||||
<h2>Configuration</h2>
|
||||
<h2>配置</h2>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="room-id">Room ID</label>
|
||||
<input type="number" id="room-id" placeholder="e.g. 12235923" value="">
|
||||
<label for="room-id">房间号</label>
|
||||
<input type="number" id="room-id" placeholder="例如 12235923" value="">
|
||||
</div>
|
||||
<div class="form-group" style="max-width:120px">
|
||||
<label for="voice">Voice</label>
|
||||
<label for="voice">音色</label>
|
||||
<select id="voice">
|
||||
<option value="f1">f1 (Reimu)</option>
|
||||
<option value="f2">f2 (Marisa)</option>
|
||||
<option value="m1">m1 (Male 1)</option>
|
||||
<option value="m2">m2 (Male 2)</option>
|
||||
<option value="r1">r1 (Robot)</option>
|
||||
<option value="f1">f1 (灵梦)</option>
|
||||
<option value="f2">f2 (魔理沙)</option>
|
||||
<option value="m1">m1 (男声1)</option>
|
||||
<option value="m2">m2 (男声2)</option>
|
||||
<option value="r1">r1 (机器人)</option>
|
||||
<option value="dvd">dvd</option>
|
||||
<option value="imd1">imd1</option>
|
||||
<option value="jgr">jgr</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="max-width:140px">
|
||||
<label for="speed">Speed (<span id="speed-val">100</span>)</label>
|
||||
<label for="speed">语速 (<span id="speed-val">100</span>)</label>
|
||||
<input type="range" id="speed" min="50" max="300" value="100"
|
||||
oninput="document.getElementById('speed-val').textContent=this.value">
|
||||
</div>
|
||||
<div class="form-group" style="max-width:140px">
|
||||
<label for="volume">Volume (<span id="volume-val">100</span>%)</label>
|
||||
<label for="volume">音量 (<span id="volume-val">100</span>%)</label>
|
||||
<input type="range" id="volume" min="0" max="200" value="100"
|
||||
oninput="document.getElementById('volume-val').textContent=this.value">
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-start" id="btn-start" onclick="startTTS()">Start</button>
|
||||
<button class="btn btn-stop" id="btn-stop" onclick="stopTTS()" disabled>Stop</button>
|
||||
<button class="btn btn-start" id="btn-start" onclick="startTTS()">开始</button>
|
||||
<button class="btn btn-stop" id="btn-stop" onclick="stopTTS()" disabled>停止</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Event Voice Rules Card -->
|
||||
<div class="card" id="rules-card">
|
||||
<h2>Event Voice Rules</h2>
|
||||
<h2>事件语音规则</h2>
|
||||
<div class="rule-list" id="rule-list"></div>
|
||||
<div class="form-row" style="margin-top:10px; align-items:center">
|
||||
<label class="chk-label"><input type="checkbox" id="rule-skip-free"> Skip free gifts</label>
|
||||
<label class="chk-label"><input type="checkbox" id="rule-skip-free"> 跳过免费礼物</label>
|
||||
<span style="flex:1"></span>
|
||||
<button class="btn btn-login" onclick="saveRules()">Save Rules</button>
|
||||
<button class="btn btn-login" onclick="saveRules()">保存规则</button>
|
||||
</div>
|
||||
<div class="rule-hint" style="margin-top:8px; font-size:0.78rem; color:var(--text-dim); line-height:1.6">
|
||||
Variables: {uname} {msg} | {viewer_name} {gift_name} {gift_num} {blind_box_gift} |
|
||||
可用变量:{uname} {msg} | {viewer_name} {gift_name} {gift_num} {blind_box_gift} |
|
||||
{ships_viewer_name} {ships_level} {ships_num} {guard_days} | {super_chat_content} {super_chat_price} |
|
||||
{interact_type} {user_uid} — empty template = event muted.
|
||||
{interact_type} {user_uid} — 模板留空 = 该事件静音。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -321,14 +321,14 @@
|
||||
<div class="card">
|
||||
<div class="status-bar">
|
||||
<span class="status-dot dot-off" id="status-dot"></span>
|
||||
<span id="status-text">Stopped</span>
|
||||
<span id="status-text">已停止</span>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div>Messages: <span id="stat-msgs">0</span></div>
|
||||
<div>Uptime: <span id="stat-uptime">00:00</span></div>
|
||||
<div>消息数: <span id="stat-msgs">0</span></div>
|
||||
<div>运行时长: <span id="stat-uptime">00:00</span></div>
|
||||
</div>
|
||||
<div id="log" style="margin-top:12px">
|
||||
<span class="msg">Ready. Login with QR (optional), set Room ID, then click Start.</span>
|
||||
<span class="msg">就绪。可选扫码登录,填写房间号后点击开始。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -348,13 +348,13 @@ function fmtUptime(sec) {
|
||||
|
||||
async function startLogin() {
|
||||
document.getElementById('btn-login').disabled = true;
|
||||
document.getElementById('login-status').innerHTML = '<div class="spinner"></div> Generating...';
|
||||
document.getElementById('login-status').innerHTML = '<div class="spinner"></div> 正在生成...';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/qr/generate');
|
||||
const data = await resp.json();
|
||||
if (data.error) {
|
||||
document.getElementById('login-status').textContent = 'Error: ' + data.error;
|
||||
document.getElementById('login-status').textContent = '错误: ' + data.error;
|
||||
document.getElementById('btn-login').disabled = false;
|
||||
return;
|
||||
}
|
||||
@@ -365,13 +365,13 @@ async function startLogin() {
|
||||
document.getElementById('qr-overlay').style.display = 'none';
|
||||
document.getElementById('login-status').textContent = '请使用Bilibili客户端扫码';
|
||||
document.getElementById('btn-login').disabled = false;
|
||||
document.getElementById('btn-login').textContent = 'Refresh QR';
|
||||
document.getElementById('btn-login').textContent = '刷新二维码';
|
||||
|
||||
loginPollKey = data.qrcode_key;
|
||||
if (qrPollTimer) clearInterval(qrPollTimer);
|
||||
qrPollTimer = setInterval(pollLoginStatus, 2000);
|
||||
} catch (e) {
|
||||
document.getElementById('login-status').textContent = 'Network error: ' + e.message;
|
||||
document.getElementById('login-status').textContent = '网络错误: ' + e.message;
|
||||
document.getElementById('btn-login').disabled = false;
|
||||
}
|
||||
}
|
||||
@@ -391,11 +391,11 @@ async function pollLoginStatus() {
|
||||
document.getElementById('qr-placeholder').style.display = 'none';
|
||||
showOverlay(false, '', '');
|
||||
document.getElementById('login-status').innerHTML =
|
||||
'<div class="user-info"><span class="status-dot dot-on"></span> Login: ' + escapeHtml(data.username || 'Unknown') + '</div>';
|
||||
'<div class="user-info"><span class="status-dot dot-on"></span> 已登录: ' + escapeHtml(data.username || '未知') + '</div>';
|
||||
document.getElementById('btn-login').style.display = 'none';
|
||||
document.getElementById('btn-logout').style.display = 'inline-block';
|
||||
document.getElementById('btn-login').disabled = false;
|
||||
document.getElementById('btn-login').textContent = 'Login with QR';
|
||||
document.getElementById('btn-login').textContent = '扫码登录';
|
||||
updateLoginStatusCard(true, data.username || '');
|
||||
} else if (st === 86090) {
|
||||
// Scanned, waiting
|
||||
@@ -411,9 +411,9 @@ async function pollLoginStatus() {
|
||||
loginPollKey = null;
|
||||
showOverlay(true, 'expired', '二维码已过期');
|
||||
document.getElementById('login-status').textContent = '二维码已过期,请点击刷新';
|
||||
document.getElementById('btn-login').textContent = 'Refresh QR';
|
||||
document.getElementById('btn-login').textContent = '刷新二维码';
|
||||
} else {
|
||||
document.getElementById('login-status').textContent = data.message || ('Status: ' + st);
|
||||
document.getElementById('login-status').textContent = data.message || ('状态: ' + st);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore network errors during polling
|
||||
@@ -442,10 +442,10 @@ async function doLogout() {
|
||||
document.getElementById('qr-img').src = '';
|
||||
document.getElementById('qr-placeholder').style.display = 'flex';
|
||||
document.getElementById('qr-overlay').style.display = 'none';
|
||||
document.getElementById('login-status').textContent = 'Not logged in — anonymous danmaku may have censored usernames.';
|
||||
document.getElementById('login-status').textContent = '未登录 — 匿名弹幕的用户名可能被隐藏。';
|
||||
document.getElementById('btn-login').style.display = 'inline-block';
|
||||
document.getElementById('btn-logout').style.display = 'none';
|
||||
document.getElementById('btn-login').textContent = 'Login with QR';
|
||||
document.getElementById('btn-login').textContent = '扫码登录';
|
||||
}
|
||||
|
||||
function updateLoginStatusCard(loggedIn, username) {
|
||||
@@ -456,7 +456,7 @@ function updateLoginStatusCard(loggedIn, username) {
|
||||
|
||||
async function startTTS() {
|
||||
const roomId = document.getElementById('room-id').value;
|
||||
if (!roomId) { alert('Please enter a Room ID'); return; }
|
||||
if (!roomId) { alert('请输入房间号'); return; }
|
||||
|
||||
const config = {
|
||||
room_id: parseInt(roomId),
|
||||
@@ -472,13 +472,13 @@ async function startTTS() {
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.error) { alert('Error: ' + data.error); return; }
|
||||
if (data.error) { alert('错误: ' + data.error); return; }
|
||||
|
||||
running = true;
|
||||
updateButtons();
|
||||
startPolling();
|
||||
} catch (e) {
|
||||
alert('Failed to connect: ' + e.message);
|
||||
alert('连接失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ async function stopTTS() {
|
||||
updateButtons();
|
||||
if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
|
||||
document.getElementById('status-dot').className = 'status-dot dot-off';
|
||||
document.getElementById('status-text').textContent = 'Stopped';
|
||||
document.getElementById('status-text').textContent = '已停止';
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
@@ -519,10 +519,10 @@ async function pollStatus() {
|
||||
const txt = document.getElementById('status-text');
|
||||
if (s.running) {
|
||||
dot.className = 'status-dot dot-on';
|
||||
txt.textContent = 'Running';
|
||||
txt.textContent = '运行中';
|
||||
} else {
|
||||
dot.className = 'status-dot dot-off';
|
||||
txt.textContent = 'Stopped';
|
||||
txt.textContent = '已停止';
|
||||
}
|
||||
|
||||
document.getElementById('stat-msgs').textContent = s.messages_processed;
|
||||
@@ -532,7 +532,7 @@ async function pollStatus() {
|
||||
const msgs = s.recent_messages || [];
|
||||
log.innerHTML = msgs.length > 0
|
||||
? msgs.map(m => '<span class="msg">' + escapeHtml(m) + '</span>').join('\n')
|
||||
: '<span class="msg">Waiting for danmaku...</span>';
|
||||
: '<span class="msg">等待弹幕...</span>';
|
||||
log.scrollTop = log.scrollHeight;
|
||||
} catch(e) {
|
||||
// Server may have stopped
|
||||
@@ -559,7 +559,7 @@ async function checkExistingLogin() {
|
||||
const data = await resp.json();
|
||||
if (data.logged_in) {
|
||||
document.getElementById('login-status').innerHTML =
|
||||
'<div class="user-info"><span class="status-dot dot-on"></span> Login: ' + escapeHtml(data.username) + '</div>';
|
||||
'<div class="user-info"><span class="status-dot dot-on"></span> 已登录: ' + escapeHtml(data.username) + '</div>';
|
||||
document.getElementById('btn-login').style.display = 'none';
|
||||
document.getElementById('btn-logout').style.display = 'inline-block';
|
||||
document.getElementById('qr-placeholder').style.display = 'none';
|
||||
@@ -570,10 +570,10 @@ async function checkExistingLogin() {
|
||||
// ── Event voice rules ────────────────────────────────────────────────────
|
||||
|
||||
const RULE_LABELS = {
|
||||
danmaku: 'Danmaku', gift: 'Gift', blind_box: 'Blind Box',
|
||||
enter_room: 'Enter Room', follow: 'Follow', like: 'Like',
|
||||
interact_other: 'Interact', buy_guard: 'Buy Guard',
|
||||
guard_toast: 'Guard Toast', super_chat: 'Super Chat',
|
||||
danmaku: '弹幕', gift: '礼物', blind_box: '盲盒礼物',
|
||||
enter_room: '进入房间', follow: '关注', like: '点赞',
|
||||
interact_other: '其他互动', buy_guard: '上舰',
|
||||
guard_toast: '上舰提示', super_chat: '醒目留言',
|
||||
};
|
||||
|
||||
async function loadRules() {
|
||||
@@ -600,7 +600,7 @@ function renderRules(rules) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.dataset.key = key;
|
||||
input.placeholder = 'Empty = muted';
|
||||
input.placeholder = '留空 = 静音';
|
||||
input.value = (rules.templates && rules.templates[key]) || '';
|
||||
row.appendChild(name);
|
||||
row.appendChild(chk);
|
||||
@@ -632,11 +632,11 @@ async function saveRules() {
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.error) { alert('Save failed: ' + data.error); return; }
|
||||
if (data.error) { alert('保存失败: ' + data.error); return; }
|
||||
renderRules(data.rules);
|
||||
alert('Rules saved. Restart TTS to apply.');
|
||||
alert('规则已保存。重启TTS后生效。');
|
||||
} catch(e) {
|
||||
alert('Save failed: ' + e.message);
|
||||
alert('保存失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user