-
服务器版本较旧
+
${typeof t === 'function' ? t('otaPage.serverVersionOlder') : '服务器版本较旧'}
${localVersion} → ${serverVersion}
@@ -15805,7 +15872,7 @@ async function checkForUpdates() {
statusDiv.className = 'ota-update-status no-update';
statusDiv.innerHTML = `
- 已是最新版本
+ ${typeof t === 'function' ? t('otaPage.alreadyLatest') : '已是最新版本'}
${localVersion}
`;
@@ -15816,7 +15883,7 @@ async function checkForUpdates() {
statusDiv.className = 'ota-update-status error';
statusDiv.innerHTML = `
-
检查更新失败
+
${typeof t === 'function' ? t('otaPage.checkUpdateFailed') : '检查更新失败'}
${error.message}
`;
@@ -15826,7 +15893,7 @@ async function checkForUpdates() {
async function upgradeFromServer() {
const serverUrl = document.getElementById('ota-server-input').value.trim();
if (!serverUrl) {
- showToast('OTA 服务器地址未设置', 'error');
+ showToast(typeof t === 'function' ? t('toast.otaServerNotSet') : 'OTA 服务器地址未设置', 'error');
return;
}
@@ -15836,7 +15903,7 @@ async function upgradeFromServer() {
document.getElementById('ota-state-text').textContent = typeof t === 'function' ? t('otaPage.preparingUpgrade') : 'Preparing upgrade...';
document.getElementById('ota-progress-bar').style.width = '0%';
document.getElementById('ota-progress-percent').textContent = '';
- document.getElementById('ota-progress-size').textContent = '正在初始化...';
+ document.getElementById('ota-progress-size').textContent = typeof t === 'function' ? t('otaPage.initializing') : '正在初始化...';
document.getElementById('ota-message').textContent = serverUrl;
document.getElementById('ota-abort-btn').style.display = 'none';
@@ -15883,10 +15950,10 @@ async function upgradeViaProxy(serverUrl) {
// ===== 第一步:浏览器下载固件 =====
updateStep(1, '下载固件中...');
const firmwareUrl = serverUrl.replace(/\/$/, '') + '/firmware';
- messageEl.textContent = '从 OTA 服务器下载';
+ messageEl.textContent = typeof t === 'function' ? t('otaPage.downloadingFromServer') : '从 OTA 服务器下载';
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
- progressSize.textContent = '正在连接服务器...';
+ progressSize.textContent = typeof t === 'function' ? t('otaPage.connectingServer') : '正在连接服务器...';
abortBtn.style.display = 'none'; // 浏览器下载阶段暂不支持中止
console.log('Proxy OTA: Downloading firmware from', firmwareUrl);
@@ -15900,14 +15967,14 @@ async function upgradeViaProxy(serverUrl) {
});
console.log('Proxy OTA: Firmware downloaded,', firmwareData.byteLength, 'bytes');
- showToast(`固件下载完成 (${formatSize(firmwareData.byteLength)})`, 'success');
+ showToast(typeof t === 'function' ? t('ota.firmwareDownloadComplete', { size: formatSize(firmwareData.byteLength) }) : `固件下载完成 (${formatSize(firmwareData.byteLength)})`, 'success');
// ===== 第二步:上传固件到 ESP32 =====
updateStep(2, '上传固件到设备...');
- messageEl.textContent = `固件大小: ${formatSize(firmwareData.byteLength)}`;
+ messageEl.textContent = typeof t === 'function' ? t('ui.firmwareSize') + ': ' + formatSize(firmwareData.byteLength) : `固件大小: ${formatSize(firmwareData.byteLength)}`;
progressBar.style.width = '0%';
progressPercent.textContent = '';
- progressSize.textContent = '正在写入 Flash(这可能需要1-2分钟)...';
+ progressSize.textContent = typeof t === 'function' ? t('otaPage.writingFlash') : '正在写入 Flash(这可能需要1-2分钟)...';
// 调用 ESP32 上传接口(复用现有的 /api/v1/ota/firmware)
// 注意:不自动重启,等 www 也完成后再重启
@@ -15918,7 +15985,7 @@ async function upgradeViaProxy(serverUrl) {
}
console.log('Proxy OTA: Firmware uploaded to device');
- showToast('固件写入完成!', 'success');
+ showToast(typeof t === 'function' ? t('toast.firmwareWriteComplete') : '固件写入完成!', 'success');
progressBar.style.width = '100%';
progressPercent.textContent = '';
@@ -15926,10 +15993,10 @@ async function upgradeViaProxy(serverUrl) {
if (includeWww) {
updateStep(3, '下载 WebUI...');
const wwwUrl = serverUrl.replace(/\/$/, '') + '/www';
- messageEl.textContent = '从 OTA 服务器下载';
+ messageEl.textContent = typeof t === 'function' ? t('otaPage.downloadingFromServer') : '从 OTA 服务器下载';
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
- progressSize.textContent = '正在连接...';
+ progressSize.textContent = typeof t === 'function' ? t('otaPage.connecting') : '正在连接...';
try {
// 下载 www.bin
@@ -15941,34 +16008,34 @@ async function upgradeViaProxy(serverUrl) {
});
console.log('Proxy OTA: WWW downloaded,', wwwData.byteLength, 'bytes');
- showToast(`WebUI 下载完成 (${formatSize(wwwData.byteLength)})`, 'success');
+ showToast(typeof t === 'function' ? t('ota.webuiDownloadComplete', { size: formatSize(wwwData.byteLength) }) : `WebUI 下载完成 (${formatSize(wwwData.byteLength)})`, 'success');
// 上传 www.bin
updateStep(4, '上传 WebUI 到设备...');
- messageEl.textContent = `WebUI 大小: ${formatSize(wwwData.byteLength)}`;
+ messageEl.textContent = typeof t === 'function' ? t('ui.webuiSize') + ': ' + formatSize(wwwData.byteLength) : `WebUI 大小: ${formatSize(wwwData.byteLength)}`;
progressBar.style.width = '0%';
progressPercent.textContent = '';
- progressSize.textContent = '正在写入 SPIFFS...';
+ progressSize.textContent = typeof t === 'function' ? t('otaPage.writingSpiffs') : '正在写入 SPIFFS...';
const wwwResult = await uploadWwwToDevice(wwwData);
if (!wwwResult.success) {
console.warn('WWW upload failed:', wwwResult.error);
- showToast('WebUI 升级跳过: ' + wwwResult.error, 'warning');
+ showToast(typeof t === 'function' ? t('toast.webuiUpgradeSkipped', { msg: wwwResult.error }) : 'WebUI 升级跳过: ' + wwwResult.error, 'warning');
} else {
console.log('Proxy OTA: WWW uploaded to device');
- showToast('WebUI 写入完成!', 'success');
+ showToast(typeof t === 'function' ? t('toast.webuiWriteComplete') : 'WebUI 写入完成!', 'success');
progressBar.style.width = '100%';
progressPercent.textContent = '';
}
} catch (wwwError) {
console.warn('WWW download/upload failed:', wwwError);
- showToast('WebUI 升级跳过: ' + wwwError.message, 'warning');
+ showToast(typeof t === 'function' ? t('toast.webuiUpgradeSkipped', { msg: wwwError.message }) : 'WebUI 升级跳过: ' + wwwError.message, 'warning');
}
}
// ===== 最终步骤:升级完成,触发重启 =====
- stateEl.textContent = '全部升级完成';
+ stateEl.textContent = typeof t === 'function' ? t('otaPage.allUpgradeComplete') : '全部升级完成';
progressBar.style.width = '100%';
progressBar.style.background = 'linear-gradient(90deg, #059669, #10b981)';
progressPercent.textContent = '';
@@ -15993,12 +16060,12 @@ async function upgradeViaProxy(serverUrl) {
} catch (error) {
console.error('Proxy OTA failed:', error);
- stateEl.textContent = '升级失败';
+ stateEl.textContent = typeof t === 'function' ? t('otaPage.upgradeFailed') : '升级失败';
messageEl.textContent = error.message;
progressBar.style.width = '0%';
progressPercent.textContent = '';
otaStep = 'idle';
- showToast('升级失败: ' + error.message, 'error');
+ showToast(typeof t === 'function' ? t('toast.upgradeFailedMsg', { msg: error.message }) : '升级失败: ' + error.message, 'error');
}
}
@@ -16822,12 +16889,12 @@ function formatUptimeSec(seconds) {
async function automationControl(action) {
try {
const result = await api.call(`automation.${action}`);
- showToast(`${action}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
+ showToast(typeof t === 'function' ? t('toast.actionResult', { action, msg: result.message || 'OK' }) : `${action}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
if (result.code === 0) {
await refreshAutomationStatus();
}
} catch (error) {
- showToast(`${action} 失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.actionFailedMsg', { action, msg: error.message }) : `${action} 失败: ${error.message}`, 'error');
}
}
@@ -16886,11 +16953,11 @@ async function refreshRules() {
${r.actions_count || 0} |
${r.trigger_count || 0} |
-
-
-
-
-
+
+
+
+
+
|
`}).join('')}
@@ -16913,12 +16980,12 @@ async function toggleRule(id, enable) {
try {
const action = enable ? 'automation.rules.enable' : 'automation.rules.disable';
const result = await api.call(action, { id });
- showToast(`规则 ${id} ${enable ? '启用' : '禁用'}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
+ showToast(typeof t === 'function' ? t('toast.ruleToggled', { id, state: enable ? t('status.enabled') : t('status.disabled') }) + ': ' + (result.message || 'OK') : `规则 ${id} ${enable ? '启用' : '禁用'}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
if (result.code === 0) {
await refreshRules();
}
} catch (error) {
- showToast(`切换规则状态失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.toggleRuleFailed') + ': ' + error.message : `切换规则状态失败: ${error.message}`, 'error');
}
}
@@ -16928,9 +16995,9 @@ async function toggleRule(id, enable) {
async function triggerRule(id) {
try {
const result = await api.call('automation.rules.trigger', { id });
- showToast(`触发规则 ${id}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
+ showToast(typeof t === 'function' ? t('toast.ruleTriggered', { id }) + ': ' + (result.message || 'OK') : `触发规则 ${id}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
} catch (error) {
- showToast(`触发规则失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.triggerRuleFailed') + ': ' + error.message : `触发规则失败: ${error.message}`, 'error');
}
}
@@ -16974,10 +17041,10 @@ async function refreshSources() {
${s.enabled ? enStr : disStr} |
${s.poll_interval_ms ? (s.poll_interval_ms / 1000) + secStr : '-'} |
-
-
-
-
+
+
+
+
|
`).join('')}
@@ -17013,7 +17080,7 @@ async function refreshVariables() {
if (countBadge) countBadge.textContent = allVariables.length;
renderVariables(allVariables);
} else {
- container.innerHTML = `
${result.message || '获取变量失败'}
`;
+ container.innerHTML = `
${result.message || (typeof t === 'function' ? t('sshPage.getVarFailed') : '获取变量失败')}
`;
}
} catch (error) {
container.innerHTML = `
${error.message}
`;
@@ -17044,7 +17111,7 @@ function renderVariables(variables) {
if (!container) return;
if (variables.length === 0) {
- container.innerHTML = '
暂无变量数据
';
+ container.innerHTML = '
' + (typeof t === 'function' ? t('automationPage.noVariables') : '暂无变量数据') + '
';
return;
}
@@ -17068,10 +17135,10 @@ function renderVariables(variables) {
- | 变量名 |
- 类型 |
- 当前值 |
- 更新时间 |
+ ${typeof t === 'function' ? t('sshPage.varTableName') : '变量名'} |
+ ${typeof t === 'function' ? t('sshPage.varTableType') : '类型'} |
+ ${typeof t === 'function' ? t('sshPage.varTableValue') : '当前值'} |
+ ${typeof t === 'function' ? t('sshPage.varTableUpdated') : '更新时间'} |
@@ -17184,10 +17251,10 @@ async function refreshActions() {
${a.async ? '' + asyncStr + '' : '' + syncStr + ''} |
${a.description || '-'} |
-
-
-
-
+
+
+
+
|
`).join('')}
@@ -17457,11 +17524,11 @@ function updateActionTypeFields() {
-
-
-
-
-
+
+
+
+
+
@@ -17473,11 +17540,11 @@ function updateActionTypeFields() {
`,
@@ -17497,15 +17564,15 @@ function updateActionTypeFields() {
@@ -17522,7 +17589,7 @@ function updateActionTypeFields() {
-
+
JSON 格式,支持变量
@@ -17573,7 +17640,7 @@ async function submitAction() {
const async = document.getElementById('action-async')?.checked || false;
if (!id) {
- showToast('请填写动作 ID', 'error');
+ showToast(typeof t === 'function' ? t('toast.fillActionId') : '请填写动作 ID', 'error');
return;
}
if (!type) {
@@ -17588,7 +17655,7 @@ async function submitAction() {
case 'cli':
const cliCmd = document.getElementById('action-cli-command')?.value?.trim();
if (!cliCmd) {
- showToast('请填写命令行', 'error');
+ showToast(typeof t === 'function' ? t('toast.fillCommand') : '请填写命令行', 'error');
return;
}
data.cli = {
@@ -17600,7 +17667,7 @@ async function submitAction() {
case 'ssh_cmd_ref':
const cmdId = document.getElementById('action-ssh-cmd-id')?.value;
if (!cmdId) {
- showToast('请选择 SSH 命令', 'error');
+ showToast(typeof t === 'function' ? t('toast.selectSshCommand') : '请选择 SSH 命令', 'error');
return;
}
data.ssh_ref = { cmd_id: cmdId };
@@ -17608,7 +17675,7 @@ async function submitAction() {
case 'led':
const ledDevice = document.getElementById('action-led-device')?.value;
if (!ledDevice) {
- showToast('请选择 LED 设备', 'error');
+ showToast(typeof t === 'function' ? t('toast.selectLedDevice') : '请选择 LED 设备', 'error');
return;
}
const isMatrix = ledDevice === 'matrix';
@@ -17634,7 +17701,7 @@ async function submitAction() {
data.led.speed = parseInt(document.getElementById('action-led-speed')?.value) || 50;
data.led.color = document.getElementById('action-led-color')?.value || '#FF0000';
if (!data.led.effect) {
- showToast('请选择动画', 'error');
+ showToast(typeof t === 'function' ? t('toast.selectAnimation') : '请选择动画', 'error');
return;
}
break;
@@ -17647,7 +17714,7 @@ async function submitAction() {
case 'text':
data.led.text = document.getElementById('action-led-text')?.value?.trim();
if (!data.led.text) {
- showToast('请输入文本内容', 'error');
+ showToast(typeof t === 'function' ? t('toast.enterText') : '请输入文本内容', 'error');
return;
}
data.led.font = document.getElementById('action-led-font')?.value || '';
@@ -17663,7 +17730,7 @@ async function submitAction() {
case 'image':
data.led.image_path = document.getElementById('action-led-image-path')?.value?.trim();
if (!data.led.image_path) {
- showToast('请输入图像路径', 'error');
+ showToast((typeof t === 'function' ? t('toast.enterImagePath') : '请输入图像路径'), 'error');
return;
}
data.led.center = document.getElementById('action-led-center')?.checked || false;
@@ -17671,7 +17738,7 @@ async function submitAction() {
case 'qrcode':
data.led.qr_text = document.getElementById('action-led-qr-text')?.value?.trim();
if (!data.led.qr_text) {
- showToast('请输入QR码内容', 'error');
+ showToast(typeof t === 'function' ? t('toast.enterQrContent') : '请输入QR码内容', 'error');
return;
}
data.led.qr_ecc = document.getElementById('action-led-qr-ecc')?.value || 'M';
@@ -17681,7 +17748,7 @@ async function submitAction() {
case 'filter':
data.led.filter = document.getElementById('action-led-filter')?.value;
if (!data.led.filter) {
- showToast('请选择滤镜', 'error');
+ showToast(typeof t === 'function' ? t('toast.selectFilter') : '请选择滤镜', 'error');
return;
}
// 根据滤镜类型收集对应参数
@@ -17701,7 +17768,7 @@ async function submitAction() {
case 'log':
const logMsg = document.getElementById('action-log-message')?.value?.trim();
if (!logMsg) {
- showToast('请填写日志消息', 'error');
+ showToast(typeof t === 'function' ? t('toast.fillLogMessage') : '请填写日志消息', 'error');
return;
}
data.log = {
@@ -17713,7 +17780,7 @@ async function submitAction() {
const varName = document.getElementById('action-var-name')?.value?.trim();
const varValue = document.getElementById('action-var-value')?.value?.trim();
if (!varName || !varValue) {
- showToast('请填写变量名和值', 'error');
+ showToast(typeof t === 'function' ? t('toast.fillVarNameValue') : '请填写变量名和值', 'error');
return;
}
data.set_var = {
@@ -17724,7 +17791,7 @@ async function submitAction() {
case 'webhook':
const webhookUrl = document.getElementById('action-webhook-url')?.value?.trim();
if (!webhookUrl) {
- showToast('请填写 Webhook URL', 'error');
+ showToast(typeof t === 'function' ? t('toast.fillWebhookUrl') : '请填写 Webhook URL', 'error');
return;
}
data.webhook = {
@@ -17738,14 +17805,14 @@ async function submitAction() {
try {
const result = await api.call('automation.actions.add', data);
if (result.code === 0) {
- showToast(`动作模板 ${id} 创建成功`, 'success');
+ showToast(typeof t === 'function' ? t('toast.actionCreated', { id }) : `动作模板 ${id} 创建成功`, 'success');
closeModal('action-modal');
await refreshActions();
} else {
- showToast(`创建失败: ${result.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.actionCreateFailed') + ': ' + result.message : `创建失败: ${result.message}`, 'error');
}
} catch (error) {
- showToast(`创建失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.actionCreateFailed') + ': ' + error.message : `创建失败: ${error.message}`, 'error');
}
}
@@ -17788,7 +17855,7 @@ async function loadSshHostsForAction() {
console.error('加载 SSH 主机列表失败:', e);
const select = document.getElementById('action-ssh-host');
if (select) {
- select.innerHTML = '';
+ select.innerHTML = '';
}
}
}
@@ -17941,7 +18008,7 @@ function updateActionLedTypeFields() {
case 'fill':
html = `
-
+
@@ -17957,15 +18024,15 @@ function updateActionLedTypeFields() {
`;
@@ -17975,21 +18042,21 @@ function updateActionLedTypeFields() {
const effectOptions = effects.map(e => `
`).join('');
html = `
-
+
@@ -17999,7 +18066,7 @@ function updateActionLedTypeFields() {
case 'brightness':
html = `
@@ -18555,7 +18622,7 @@ function selectVariable(varName) {
saveDataWidgets();
renderDataWidgets();
refreshDataWidgets();
- showToast(`已绑定 ${widget.label} → ${varName}`, 'success');
+ showToast(typeof t === 'function' ? t('toast.widgetBound', { widget: widget.label, var: varName }) : `已绑定 ${widget.label} → ${varName}`, 'success');
}
}
closeModal('variable-select-modal');
@@ -18650,7 +18717,7 @@ function updateActionFilterParams() {
*/
async function testAction(id) {
try {
- showToast(`正在执行动作: ${id}...`, 'info');
+ showToast(typeof t === 'function' ? t('toast.actionExecuting', { id }) : `正在执行动作: ${id}...`, 'info');
const result = await api.call('automation.actions.execute', { id });
console.log('Action execute result:', result);
@@ -18661,11 +18728,11 @@ async function testAction(id) {
}
showToast(msg, 'success');
} else {
- showToast(`动作 ${id} 失败: ${result.message || '未知错误'}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.actionFailed', { id }) + ': ' + (result.message || t('common.unknown')) : `动作 ${id} 失败: ${result.message || '未知错误'}`, 'error');
}
} catch (error) {
console.error('Action execute error:', error);
- showToast(`动作执行失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.actionExecuteFailed') + ': ' + error.message : `动作执行失败: ${error.message}`, 'error');
}
}
@@ -18676,7 +18743,7 @@ async function editAction(id) {
try {
const result = await api.call('automation.actions.get', { id });
if (result.code !== 0) {
- showToast(`获取动作详情失败: ${result.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.getActionFailed') + ': ' + result.message : `获取动作详情失败: ${result.message}`, 'error');
return;
}
@@ -18883,16 +18950,16 @@ async function editAction(id) {
// 更改模态框标题和按钮
const modalTitle = document.querySelector('#action-modal .modal-header h3');
- if (modalTitle) modalTitle.textContent = '编辑动作模板';
+ if (modalTitle) modalTitle.textContent = typeof t === 'function' ? t('otaPage.editActionTemplate') : '编辑动作模板';
const submitBtn = document.querySelector('#action-modal button[onclick="submitAction()"]');
if (submitBtn) {
- submitBtn.textContent = '更新';
+ submitBtn.textContent = typeof t === 'function' ? t('otaPage.updateAction') : '更新';
submitBtn.setAttribute('onclick', `updateAction('${tpl.id}')`);
}
} catch (error) {
- showToast(`编辑动作失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.editActionFailed') + ': ' + error.message : `编辑动作失败: ${error.message}`, 'error');
}
}
@@ -18908,7 +18975,7 @@ async function updateAction(originalId) {
// 删除旧模板
const deleteResult = await api.call('automation.actions.delete', { id: originalId });
if (deleteResult.code !== 0) {
- showToast(`更新失败: 无法删除旧模板`, 'error');
+ showToast(typeof t === 'function' ? t('toast.deleteOldTemplateFailed') : '更新失败: 无法删除旧模板', 'error');
return;
}
@@ -18919,7 +18986,7 @@ async function updateAction(originalId) {
await submitAction();
} catch (error) {
- showToast(`更新失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.updateFailed') + ': ' + error.message : `更新失败: ${error.message}`, 'error');
}
}
@@ -18927,16 +18994,16 @@ async function updateAction(originalId) {
* 删除动作
*/
async function deleteAction(id) {
- if (!confirm(`确定要删除动作模板 "${id}" 吗?`)) return;
+ if (!confirm(typeof t === 'function' ? t('ui.confirmDeleteAction', { id }) : `确定要删除动作模板 "${id}" 吗?`)) return;
try {
const result = await api.call('automation.actions.delete', { id });
- showToast(`删除动作 ${id}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
+ showToast(typeof t === 'function' ? t('toast.deleteActionResult', { id }) + ': ' + (result.message || 'OK') : `删除动作 ${id}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
if (result.code === 0) {
await refreshActions();
}
} catch (error) {
- showToast(`删除失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.deleteFailedMsg', { msg: error.message }) : `删除失败: ${error.message}`, 'error');
}
}
@@ -18947,12 +19014,12 @@ async function toggleSource(id, enable) {
try {
const action = enable ? 'automation.sources.enable' : 'automation.sources.disable';
const result = await api.call(action, { id });
- showToast(`数据源 ${id} ${enable ? '启用' : '禁用'}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
+ showToast(typeof t === 'function' ? t('toast.sourceToggled', { id, state: enable ? t('status.enabled') : t('status.disabled') }) + ': ' + (result.message || 'OK') : `数据源 ${id} ${enable ? '启用' : '禁用'}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
if (result.code === 0) {
await refreshSources();
}
} catch (error) {
- showToast(`切换数据源状态失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.toggleSourceFailed') + ': ' + error.message : `切换数据源状态失败: ${error.message}`, 'error');
}
}
@@ -18960,18 +19027,18 @@ async function toggleSource(id, enable) {
* 删除数据源
*/
async function deleteSource(id) {
- if (!confirm(`确定要删除数据源 "${id}" 吗?此操作不可撤销。`)) {
+ if (!confirm(typeof t === 'function' ? t('ui.confirmDeleteSource', { id }) : `确定要删除数据源 "${id}" 吗?此操作不可撤销。`)) {
return;
}
try {
const result = await api.call('automation.sources.delete', { id });
- showToast(`删除数据源 ${id}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
+ showToast(typeof t === 'function' ? t('toast.deleteSourceResult', { id }) + ': ' + (result.message || 'OK') : `删除数据源 ${id}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
if (result.code === 0) {
await Promise.all([refreshSources(), refreshAutomationStatus()]);
}
} catch (error) {
- showToast(`删除数据源失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.deleteSourceFailed') + ': ' + error.message : `删除数据源失败: ${error.message}`, 'error');
}
}
@@ -18985,7 +19052,7 @@ async function showSourceVariables(sourceId) {
// 更新标题
const header = modal.querySelector('.modal-header h2');
- if (header) header.textContent = `${sourceId} 变量`;
+ if (header) header.textContent = typeof t === 'function' ? t('ui.sourceVariables', { source: sourceId }) : `${sourceId} 变量`;
body.innerHTML = '
' + t('common.loading') + '
';
modal.classList.remove('hidden');
@@ -18997,7 +19064,7 @@ async function showSourceVariables(sourceId) {
const vars = result.data.variables.filter(v => v.source_id === sourceId);
if (vars.length === 0) {
- body.innerHTML = '
该数据源暂无变量数据
';
+ body.innerHTML = '
' + (typeof t === 'function' ? t('automationPage.sourceNoData') : '该数据源暂无变量数据') + '
';
return;
}
@@ -19005,10 +19072,10 @@ async function showSourceVariables(sourceId) {
- | 变量名 |
- 类型 |
- 当前值 |
- 更新时间 |
+ ${typeof t === 'function' ? t('sshPage.varTableName') : '变量名'} |
+ ${typeof t === 'function' ? t('sshPage.varTableType') : '类型'} |
+ ${typeof t === 'function' ? t('sshPage.varTableValue') : '当前值'} |
+ ${typeof t === 'function' ? t('sshPage.varTableUpdated') : '更新时间'} |
@@ -19024,7 +19091,7 @@ async function showSourceVariables(sourceId) {
`;
} else {
- body.innerHTML = `
${result.message || '获取变量失败'}
`;
+ body.innerHTML = `
${result.message || (typeof t === 'function' ? t('sshPage.getVarFailed') : '获取变量失败')}
`;
}
} catch (error) {
body.innerHTML = `
${error.message}
`;
@@ -19104,13 +19171,13 @@ async function saveShutdownSettings() {
// 验证
if (config.low_threshold >= config.recovery_threshold) {
- errorDiv.textContent = '低电压阈值必须小于恢复电压阈值';
+ errorDiv.textContent = typeof t === 'function' ? t('otaPage.lowVoltageError') : '低电压阈值必须小于恢复电压阈值';
errorDiv.classList.remove('hidden');
return;
}
if (config.shutdown_delay < 10 || config.shutdown_delay > 600) {
- errorDiv.textContent = '关机倒计时必须在 10-600 秒之间';
+ errorDiv.textContent = typeof t === 'function' ? t('otaPage.shutdownDelayError') : '关机倒计时必须在 10-600 秒之间';
errorDiv.classList.remove('hidden');
return;
}
@@ -19118,14 +19185,14 @@ async function saveShutdownSettings() {
try {
const result = await api.powerProtectionSet(config);
if (result.code === 0) {
- showToast('关机设置已保存', 'success');
+ showToast(typeof t === 'function' ? t('toast.shutdownSettingsSaved') : '关机设置已保存', 'success');
closeShutdownSettingsModal();
} else {
errorDiv.textContent = result.message || '保存失败';
errorDiv.classList.remove('hidden');
}
} catch (e) {
- errorDiv.textContent = '保存失败: ' + e.message;
+ errorDiv.textContent = typeof t === 'function' ? t('toast.saveFailedMsg', { msg: e.message }) : '保存失败: ' + e.message;
errorDiv.classList.remove('hidden');
}
}
@@ -19134,7 +19201,7 @@ async function saveShutdownSettings() {
* 恢复默认关机设置
*/
async function resetShutdownSettings() {
- if (!confirm('确认恢复默认设置?')) return;
+ if (!confirm(typeof t === 'function' ? t('automation.confirmRestoreDefaults') : '确认恢复默认设置?')) return;
const config = {
low_threshold: 12.6,
@@ -19154,12 +19221,12 @@ async function resetShutdownSettings() {
document.getElementById('ss-shutdown-delay').value = 60;
document.getElementById('ss-recovery-hold').value = 5;
document.getElementById('ss-fan-stop-delay').value = 60;
- showToast('已恢复默认设置', 'success');
+ showToast(typeof t === 'function' ? t('toast.defaultsRestored') : '已恢复默认设置', 'success');
} else {
- showToast('恢复失败: ' + (result.message || 'Unknown error'), 'error');
+ showToast(typeof t === 'function' ? t('toast.restoreFailedMsg', { msg: result.message || t('common.unknown') }) : '恢复失败: ' + (result.message || '未知错误'), 'error');
}
} catch (e) {
- showToast('恢复失败: ' + e.message, 'error');
+ showToast(typeof t === 'function' ? t('toast.restoreFailedMsg', { msg: e.message }) : '恢复失败: ' + e.message, 'error');
}
}
@@ -19167,18 +19234,18 @@ async function resetShutdownSettings() {
* 删除规则
*/
async function deleteRule(id) {
- if (!confirm(`确定要删除规则 "${id}" 吗?此操作不可撤销。`)) {
+ if (!confirm(typeof t === 'function' ? t('ui.confirmDeleteRule', { id }) : `确定要删除规则 "${id}" 吗?此操作不可撤销。`)) {
return;
}
try {
const result = await api.call('automation.rules.delete', { id });
- showToast(`删除规则 ${id}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
+ showToast(typeof t === 'function' ? t('toast.deleteRuleResult', { id }) + ': ' + (result.message || 'OK') : `删除规则 ${id}: ${result.message || 'OK'}`, result.code === 0 ? 'success' : 'error');
if (result.code === 0) {
await Promise.all([refreshRules(), refreshAutomationStatus()]);
}
} catch (error) {
- showToast(`删除规则失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.deleteRuleFailed') + ': ' + error.message : `删除规则失败: ${error.message}`, 'error');
}
}
@@ -19190,14 +19257,14 @@ async function editRule(id) {
// 获取规则详情
const result = await api.call('automation.rules.get', { id });
if (result.code !== 0 || !result.data) {
- showToast(`获取规则详情失败: ${result.message || '未知错误'}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.getRuleDetailFailed') + ': ' + (result.message || t('common.unknown')) : `获取规则详情失败: ${result.message || '未知错误'}`, 'error');
return;
}
// 打开编辑模态框
showAddRuleModal(result.data);
} catch (error) {
- showToast(`获取规则详情失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.getRuleDetailFailed') + ': ' + error.message : `获取规则详情失败: ${error.message}`, 'error');
}
}
@@ -19352,7 +19419,7 @@ function showAddSourceModal() {
${typeof t === 'function' ? t('automation.autoDiscoverFields') : '自动发现所有 JSON 字段为变量'}
-
${typeof t === 'function' ? t('automation.autoDiscoverHint') : '关闭后仅使用上方选中的字段作为变量'}
+
${typeof t === 'function' ? t('automation.autoDiscoverHint') : '关闭后仅使用上方选中的字段作为变量'}
@@ -19423,7 +19490,7 @@ async function testRestConnection() {
const auth = document.getElementById('source-rest-auth').value.trim();
if (!url) {
- alert('请输入 API 地址');
+ alert(typeof t === 'function' ? t('automation.alertEnterApiAddress') : '请输入 API 地址');
return;
}
@@ -19479,7 +19546,7 @@ async function testWsConnection() {
const uri = document.getElementById('source-ws-uri').value.trim();
if (!uri) {
- alert('请输入 WebSocket 地址');
+ alert(typeof t === 'function' ? t('automation.alertEnterWsAddress') : '请输入 WebSocket 地址');
return;
}
@@ -19537,7 +19604,7 @@ async function testSioConnection() {
const timeout = parseInt(document.getElementById('source-sio-timeout').value) || 15000;
if (!url) {
- alert('请输入 Socket.IO 服务器地址');
+ alert(typeof t === 'function' ? t('automation.alertEnterSioAddress') : '请输入 Socket.IO 服务器地址');
return;
}
@@ -19952,7 +20019,7 @@ async function submitAddSource() {
const enabled = document.getElementById('source-enabled').checked;
if (!id) {
- alert('请输入数据源 ID');
+ alert(typeof t === 'function' ? t('automation.alertEnterSourceId') : '请输入数据源 ID');
return;
}
@@ -19965,7 +20032,7 @@ async function submitAddSource() {
params.reconnect_ms = parseInt(document.getElementById('source-ws-reconnect').value) || 5000;
if (!params.uri) {
- alert('请输入 WebSocket URI');
+ alert(typeof t === 'function' ? t('automation.alertEnterWsUri') : '请输入 WebSocket URI');
return;
}
} else if (type === 'rest') {
@@ -19975,7 +20042,7 @@ async function submitAddSource() {
params.auth_header = document.getElementById('source-rest-auth').value.trim();
if (!params.url) {
- alert('请输入 REST URL');
+ alert(typeof t === 'function' ? t('automation.alertEnterRestUrl') : '请输入 REST URL');
return;
}
} else if (type === 'socketio') {
@@ -19990,11 +20057,11 @@ async function submitAddSource() {
params.auto_discover = autoDiscoverEl ? autoDiscoverEl.checked : true;
if (!params.url) {
- alert('请输入 Socket.IO 服务器地址');
+ alert(typeof t === 'function' ? t('automation.alertEnterSioAddress') : '请输入 Socket.IO 服务器地址');
return;
}
if (!params.event) {
- alert('请输入要监听的事件名称(可先通过测试按钮自动发现)');
+ alert(typeof t === 'function' ? t('automation.alertEnterSioEvent') : '请输入要监听的事件名称(可先通过测试按钮自动发现)');
return;
}
} else if (type === 'variable') {
@@ -20003,18 +20070,18 @@ async function submitAddSource() {
const cmdIdx = document.getElementById('source-ssh-cmd').value;
if (!hostId) {
- alert('请选择 SSH 主机');
+ alert(typeof t === 'function' ? t('automation.alertSelectSshHost') : '请选择 SSH 主机');
return;
}
if (cmdIdx === '') {
- alert('请选择 SSH 指令');
+ alert(typeof t === 'function' ? t('automation.alertSelectSshCmd') : '请选择 SSH 指令');
return;
}
// 获取选中的命令配置
const cmd = sshCommands[hostId]?.[parseInt(cmdIdx)];
if (!cmd) {
- alert('指令不存在,请重新选择');
+ alert(typeof t === 'function' ? t('automation.alertCmdNotExist') : '指令不存在,请重新选择');
return;
}
@@ -20040,14 +20107,14 @@ async function submitAddSource() {
try {
const result = await api.call('automation.sources.add', params);
if (result.code === 0) {
- showToast(`数据源 ${id} 创建成功`, 'success');
+ showToast(typeof t === 'function' ? t('toast.sourceCreated', { id }) : `数据源 ${id} 创建成功`, 'success');
closeModal('add-source-modal');
await Promise.all([refreshSources(), refreshAutomationStatus()]);
} else {
- showToast(`创建数据源失败: ${result.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.sourceCreateFailed') + ': ' + result.message : `创建数据源失败: ${result.message}`, 'error');
}
} catch (error) {
- showToast(`创建数据源失败: ${error.message}`, 'error');
+ showToast(typeof t === 'function' ? t('toast.sourceCreateFailed') + ': ' + error.message : `创建数据源失败: ${error.message}`, 'error');
}
}
@@ -20119,8 +20186,8 @@ function showAddRuleModal(ruleData = null) {
-
`;
if (data.exists) {
- html += `
该配置已存在,导入将覆盖现有文件
`;
+ html += `
${typeof t === 'function' ? t('securityPage.configExistsWarning') : '该配置已存在,导入将覆盖现有文件'}
`;
}
previewDiv.innerHTML = html;
step2.style.display = 'block';
resultBox.className = 'result-box success';
- resultBox.textContent = '签名验证通过';
+ resultBox.textContent = typeof t === 'function' ? t('ssh.signatureVerified') : '签名验证通过';
importBtn.disabled = false;
} else {
resultBox.className = 'result-box error';
@@ -21406,12 +21477,12 @@ async function confirmSourceImport() {
const importBtn = document.getElementById('import-source-btn');
if (!window._importSourceTscfg) {
- showToast('请先选择文件', 'error');
+ showToast((typeof t === 'function' ? t('toast.selectFileFirst') : '请先选择文件'), 'error');
return;
}
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在保存配置...';
+ resultBox.textContent = typeof t === 'function' ? t('ssh.savingConfig') : '正在保存配置...';
importBtn.disabled = true;
try {
@@ -21427,18 +21498,18 @@ async function confirmSourceImport() {
const data = result.data;
if (data?.exists && !data?.imported) {
resultBox.className = 'result-box warning';
- resultBox.textContent = `配置 ${data.id} 已存在,请勾选「覆盖」选项`;
+ resultBox.textContent = typeof t === 'function' ? t('securityPage.configExistsCheckOverwrite', { id: data.id }) : `配置 ${data.id} 已存在,请勾选「覆盖」选项`;
importBtn.disabled = false;
} else {
resultBox.className = 'result-box success';
resultBox.innerHTML = `${typeof t === 'function' ? t('securityPage.savedConfig') : 'Saved config'}:
${escapeHtml(data?.id)}${typeof t === 'function' ? t('securityPage.restartToApply') : 'Restart to apply'}`;
- showToast(`已导入配置,重启后生效`, 'success');
+ showToast(typeof t === 'function' ? t('toast.configImported') : '已导入配置,重启后生效', 'success');
setTimeout(() => hideImportSourceModal(), 2000);
}
} else {
resultBox.className = 'result-box error';
- resultBox.textContent = (result.message || '导入失败');
- importBtn.disabled = false;
+resultBox.textContent = (result.message || (typeof t === 'function' ? t('toast.importFailed') : '导入失败'));
+ importBtn.disabled = false;
}
} catch (e) {
resultBox.className = 'result-box error';
@@ -21462,20 +21533,20 @@ function showExportRuleModal(ruleId) {
modal.innerHTML = `
-
导出规则 ${escapeHtml(ruleId)} 的配置为加密配置包
+
${typeof t === 'function' ? t('ruleConfig.exportDesc', { id: escapeHtml(ruleId) }) : `导出规则 ${escapeHtml(ruleId)} 的配置为加密配置包`}
@@ -21495,7 +21566,7 @@ async function doExportRule(ruleId) {
const exportBtn = document.getElementById('export-rule-btn');
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在生成配置包...';
+ resultBox.textContent = typeof t === 'function' ? t('securityPage.generatingPack') : '正在生成配置包...';
exportBtn.disabled = true;
try {
@@ -21520,8 +21591,8 @@ async function doExportRule(ruleId) {
URL.revokeObjectURL(url);
resultBox.className = 'result-box success';
- resultBox.textContent = '导出成功';
- showToast(`已导出规则配置: ${data.filename}`, 'success');
+ resultBox.textContent = typeof t === 'function' ? t('toast.exportSuccess') : '导出成功';
+ showToast(typeof t === 'function' ? t('toast.ruleConfigExported', { filename: data.filename }) : `已导出规则配置: ${data.filename}`, 'success');
setTimeout(() => hideExportRuleModal(), 1000);
} catch (e) {
resultBox.className = 'result-box error';
@@ -21625,12 +21696,12 @@ async function previewRuleImport() {
`;
if (data.exists) {
- html += `
该配置已存在,导入将覆盖现有文件
`;
+ html += `
${typeof t === 'function' ? t('securityPage.configExistsWarning') : '该配置已存在,导入将覆盖现有文件'}
`;
}
previewDiv.innerHTML = html;
step2.style.display = 'block';
resultBox.className = 'result-box success';
- resultBox.textContent = '签名验证通过';
+ resultBox.textContent = typeof t === 'function' ? t('ssh.signatureVerified') : '签名验证通过';
importBtn.disabled = false;
} else {
resultBox.className = 'result-box error';
@@ -21648,12 +21719,12 @@ async function confirmRuleImport() {
const importBtn = document.getElementById('import-rule-btn');
if (!window._importRuleTscfg) {
- showToast('请先选择文件', 'error');
+ showToast((typeof t === 'function' ? t('toast.selectFileFirst') : '请先选择文件'), 'error');
return;
}
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在保存配置...';
+ resultBox.textContent = typeof t === 'function' ? t('ssh.savingConfig') : '正在保存配置...';
importBtn.disabled = true;
try {
@@ -21669,17 +21740,17 @@ async function confirmRuleImport() {
const data = result.data;
if (data?.exists && !data?.imported) {
resultBox.className = 'result-box warning';
- resultBox.textContent = `配置 ${data.id} 已存在,请勾选「覆盖」选项`;
+ resultBox.textContent = typeof t === 'function' ? t('securityPage.configExistsCheckOverwrite', { id: data.id }) : `配置 ${data.id} 已存在,请勾选「覆盖」选项`;
importBtn.disabled = false;
} else {
resultBox.className = 'result-box success';
resultBox.innerHTML = `${typeof t === 'function' ? t('securityPage.savedConfig') : 'Saved config'}:
${escapeHtml(data?.id)}${typeof t === 'function' ? t('securityPage.restartToApply') : 'Restart to apply'}`;
- showToast(`已导入配置,重启后生效`, 'success');
+ showToast(typeof t === 'function' ? t('toast.configImported') : '已导入配置,重启后生效', 'success');
setTimeout(() => hideImportRuleModal(), 2000);
}
} else {
resultBox.className = 'result-box error';
- resultBox.textContent = (result.message || '导入失败');
+ resultBox.textContent = (result.message || (typeof t === 'function' ? t('toast.importFailed') : '导入失败'));
importBtn.disabled = false;
}
} catch (e) {
@@ -21867,12 +21938,12 @@ async function previewActionImport() {
`;
if (data.exists) {
- html += `
该配置已存在,导入将覆盖现有文件
`;
+ html += `
${typeof t === 'function' ? t('securityPage.configExistsWarning') : '该配置已存在,导入将覆盖现有文件'}
`;
}
previewDiv.innerHTML = html;
step2.style.display = 'block';
resultBox.className = 'result-box success';
- resultBox.textContent = '签名验证通过';
+ resultBox.textContent = typeof t === 'function' ? t('ssh.signatureVerified') : '签名验证通过';
importBtn.disabled = false;
} else {
resultBox.className = 'result-box error';
@@ -21890,12 +21961,12 @@ async function confirmActionImport() {
const importBtn = document.getElementById('import-action-btn');
if (!window._importActionTscfg) {
- showToast('请先选择文件', 'error');
+ showToast((typeof t === 'function' ? t('toast.selectFileFirst') : '请先选择文件'), 'error');
return;
}
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在保存配置...';
+ resultBox.textContent = typeof t === 'function' ? t('ssh.savingConfig') : '正在保存配置...';
importBtn.disabled = true;
try {
@@ -21911,17 +21982,17 @@ async function confirmActionImport() {
const data = result.data;
if (data?.exists && !data?.imported) {
resultBox.className = 'result-box warning';
- resultBox.textContent = `配置 ${data.id} 已存在,请勾选「覆盖」选项`;
+ resultBox.textContent = typeof t === 'function' ? t('securityPage.configExistsCheckOverwrite', { id: data.id }) : `配置 ${data.id} 已存在,请勾选「覆盖」选项`;
importBtn.disabled = false;
} else {
resultBox.className = 'result-box success';
resultBox.innerHTML = `${typeof t === 'function' ? t('securityPage.savedConfig') : 'Saved config'}:
${escapeHtml(data?.id)}${typeof t === 'function' ? t('securityPage.restartToApply') : 'Restart to apply'}`;
- showToast(`已导入配置,重启后生效`, 'success');
+ showToast(typeof t === 'function' ? t('toast.configImported') : '已导入配置,重启后生效', 'success');
setTimeout(() => hideImportActionModal(), 2000);
}
} else {
resultBox.className = 'result-box error';
- resultBox.textContent = (result.message || '导入失败');
+ resultBox.textContent = (result.message || (typeof t === 'function' ? t('toast.importFailed') : '导入失败'));
importBtn.disabled = false;
}
} catch (e) {
diff --git a/components/ts_webui/web/js/lang/en-US.js b/components/ts_webui/web/js/lang/en-US.js
index 28f1a76..7c15f23 100644
--- a/components/ts_webui/web/js/lang/en-US.js
+++ b/components/ts_webui/web/js/lang/en-US.js
@@ -141,7 +141,11 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
low: 'Low',
host: 'Host',
device: 'Device',
- logs: 'Logs'
+ logs: 'Logs',
+ loadFailed: 'Load Failed',
+ loadFailedMsg: 'Load failed: {msg}',
+ refreshOnce: 'Refresh once',
+ uploadFailedMsg: 'Failed: {msg}'
},
// Status labels
@@ -340,7 +344,16 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
maxCurvePoints: 'Maximum 10 curve points supported',
statusUnavailable: 'Fan status unavailable',
importConfig: 'Import Config',
- exportConfig: 'Export Config'
+ exportConfig: 'Export Config',
+ enterValidTemp: 'Please enter a valid temperature (0-100°C)',
+ testTempCleared: 'Test temperature cleared, resumed normal mode',
+ testTempSet: 'Test temperature set to {temp}°C',
+ speedSet: 'Fan {id} speed set to {speed}%',
+ modeSwitch: 'Fan {id} mode switched to {mode}',
+ setFanFailed: 'Failed to set fan: {msg}',
+ setFanModeFailed: 'Failed to set fan mode: {msg}',
+ setFailed: 'Setting failed: {msg}',
+ clearFailed: 'Clear failed: {msg}'
},
// LED Control
@@ -379,7 +392,8 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
browseImages: 'Browse Images',
touch: 'Touch Buttons',
board: 'Board LED',
- matrix: 'LED Matrix'
+ matrix: 'LED Matrix',
+ selectedFilter: 'Selected: {filter}'
},
// Network
@@ -671,7 +685,9 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
partitionIdle: 'Idle',
disableRollbackDesc: 'Disable auto rollback protection',
rollbackToThis: 'Rollback to this version',
- loadAfterReboot: 'Load this partition after reboot'
+ loadAfterReboot: 'Load this partition after reboot',
+ firmwareDownloadComplete: 'Firmware download complete ({size})',
+ webuiDownloadComplete: 'WebUI download complete ({size})'
},
// Automation
@@ -698,6 +714,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
ruleName: 'Rule Name',
conditions: 'Conditions',
conditionLogic: 'Logic',
+ ruleOptions: 'Rule options',
logicAnd: 'All (AND)',
logicOr: 'Any (OR)',
cooldown: 'Cooldown',
@@ -936,6 +953,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
welcome: 'Welcome to TianShanOS',
username: 'Username',
password: 'Password',
+ passwordPlaceholder: 'Please enter password',
rememberMe: 'Remember Me',
loginButton: 'Login',
loggingIn: 'Logging in...',
@@ -1032,6 +1050,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
imported: 'Import complete',
loggedOut: 'Logged out',
logsLoaded: 'Logs loaded',
+ logsLoadedCount: 'Loaded {count} log entries',
wifiDisconnected: 'WiFi disconnected',
natEnabled: 'NAT enabled',
natDisabled: 'NAT disabled',
@@ -1092,8 +1111,54 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
serviceSuccess: 'Service {name} {action} successful',
// Voltage protection
voltageProtectionEnabled: 'Voltage protection enabled',
+ lowVoltageWarning: 'Low voltage warning: {voltage}V ({countdown}s)',
+ voltageProtectionTriggered: 'Voltage protection triggered',
+ voltageRecovering: 'Voltage recovering: {voltage}V',
voltageProtectionDisabled: 'Voltage protection disabled',
switchFailed: 'Switch failed',
+ switchFailedMsg: 'Switch failed: {msg}',
+ setFailedMsg: 'Setting failed: {msg}',
+ enterHostname: 'Please enter hostname',
+ hostnameSet: 'Hostname set',
+ connectFailedMsg: 'Connection failed: {msg}',
+ disconnectFailedMsg: 'Disconnect failed: {msg}',
+ configFailedMsg: 'Config failed: {msg}',
+ operationFailedMsg: 'Operation failed: {msg}',
+ applyFailedMsg: 'Apply failed: {msg}',
+ saveFailedMsg: 'Save failed: {msg}',
+ deleteFailedMsg: 'Delete failed: {msg}',
+ removeFailedMsg: 'Remove failed: {msg}',
+ exportFailedMsg: 'Export failed: {msg}',
+ updateFailedMsg: 'Update failed: {msg}',
+ clearFailedMsg: 'Clear failed: {msg}',
+ copyFailedMsg: 'Copy failed: {msg}',
+ upgradeFailedMsg: 'Upgrade failed: {msg}',
+ webuiUpgradeStartFailed: 'WebUI upgrade start failed',
+ upgradeStartFailedMsg: 'Upgrade start failed: {msg}',
+ rollbackFailedMsg: 'Rollback failed: {msg}',
+ abortFailedMsg: 'Abort failed: {msg}',
+ otaServerNotSet: 'OTA server address not set',
+ firmwareWriteComplete: 'Firmware write complete!',
+ webuiWriteComplete: 'WebUI write complete!',
+ webuiUpgradeSkipped: 'WebUI upgrade skipped: {msg}',
+ nohupStripped: 'Auto-removed redundant nohup wrapper (backend adds it)',
+ saveCommandFailedMsg: 'Failed to save command: {msg}',
+ deleteCommandFailedMsg: 'Failed to delete command: {msg}',
+ fillServerInfo: 'Please fill in complete server information',
+ publicKeyNotFound: 'Public key not found',
+ oldHostKeyRemoved: 'Old host key removed, reconnect to trust new key',
+ allHostsCleared: 'All known hosts cleared',
+ generateFailedMsg: 'Generate failed: {msg}',
+ configPackDownloaded: 'Config pack downloaded: {filename}',
+ selectAtLeastOneAction: 'Please select at least one action template',
+ cmdNotExist: 'Command does not exist, please reselect',
+ hostFingerprintDeleted: 'Host fingerprint deleted',
+ keyDeleted: 'Key deleted',
+ cannotGetPublicKey: 'Cannot get public key',
+ cannotGetPrivateKey: 'Cannot get private key',
+ keyRevoked: 'Key revoked successfully',
+ hostRemoved: 'Host removed',
+ configPackCopied: 'Config pack copied to clipboard',
// USB switch
usbSwitching: 'Switching USB to {target}...',
usbSwitched: 'USB switched to {target}',
@@ -1104,14 +1169,29 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
widgetSaved: 'Widget saved',
widgetDeleted: 'Deleted {name}',
widgetBound: 'Bound {widget} → {var}',
- // Quick actions
+ // Quick actions / data
+ agxMovedToBinding: 'AGX config moved to variable binding',
+ waitBeforeTrigger: 'Please wait a few seconds before triggering another',
+ operationExecuted: 'Operation executed',
+ execFailed: 'Execution failed',
+ execFailedMsg: 'Execution failed: {msg}',
+ errorMsg: 'Error: {msg}',
processRunning: 'Process is running, please stop it first',
+ confirmTerminateCmd: 'Are you sure you want to stop "{cmdName}"?',
+ terminatingProcess: 'Stopping process...',
cardNotFound: 'Action card not found',
executionFailed: 'Execution failed',
processTerminated: 'Process terminated',
processNotExist: 'Process does not exist',
pidFileNotExist: 'PID file does not exist',
operationComplete: 'Operation complete',
+ // Terminate process results
+ terminateTERMINATED: 'Service stopped',
+ terminateFORCE_KILLED: 'Service force terminated',
+ terminateALREADY_STOPPED: 'Process no longer running, PID file cleaned',
+ terminateNO_PID_FILE: 'PID file does not exist',
+ terminateSTILL_RUNNING: 'Cannot terminate process, please handle manually',
+ terminateDefault: 'Operation complete',
// Time sync
timeSynced: 'Time synced: {datetime}',
timeSyncSyncing: 'Syncing time from browser...',
@@ -1132,12 +1212,30 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
ledQrFailed: 'Failed to generate QR code',
ledTextFailed: 'Failed to display text',
ledTextStopFailed: 'Failed to stop text',
+ allLedsOff: 'All LEDs turned off',
selectAnimation: 'Please select an animation first',
+ enterImagePath: 'Please enter image path',
+ imageDisplayed: 'Image displayed',
+ enterTextToEncode: 'Please enter text to encode',
+ qrGenerated: 'QR code generated',
+ enterTextToDisplay: 'Please enter text to display',
+ textDisplayed: 'Text displayed',
+ textScrollStopped: 'Text scroll stopped',
+ filterStopped: 'Filter stopped',
+ selectFilter: 'Please select a filter first',
ledBrightnessSet: '{device} brightness: {value}',
ledBrightnessFailed: 'Failed to set {device} brightness',
ledTurnedOff: '{device} turned off',
ledTurnedOn: '{device} turned on',
ledOnFailed: 'Failed to turn on',
+ ledOffFailed: 'Failed to turn off',
+ filterApplied: 'Filter {filter} applied',
+ filterApplyFailed: 'Failed to apply filter',
+ filterStopFailed: 'Failed to stop filter',
+ ledConfigSaved: '{device} config saved',
+ ledConfigSavedWithAnim: '{device} config saved: {anim}',
+ ledConfigSaveFailed: 'Failed to save config',
+ enterQrContent: 'Please enter QR code content',
// PKI/Certificates
configPackImported: 'Config pack imported successfully',
keypairGenerated: 'Key pair generated successfully',
@@ -1186,6 +1284,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
ruleTriggered: 'Rule {id} triggered',
toggleRuleFailed: 'Failed to toggle rule',
triggerRuleFailed: 'Failed to trigger rule',
+ toggleSourceFailed: 'Failed to toggle source status',
sourceToggled: 'Source {id} {state}',
actionExecuting: 'Executing action: {id}...',
actionSuccess: 'Execution successful',
@@ -1201,6 +1300,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
shutdownSettingsSaved: 'Shutdown settings saved',
defaultsRestored: 'Defaults restored',
restoreFailed: 'Restore failed',
+ restoreFailedMsg: 'Restore failed: {msg}',
deleteRuleResult: 'Delete rule {id}',
deleteRuleFailed: 'Failed to delete rule',
getRuleDetailFailed: 'Failed to get rule details',
@@ -1209,7 +1309,12 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
createActionFirst: 'Please create action templates first',
ruleCreated: 'Rule {id} {action} successfully',
ruleCreateFailed: 'Failed to {action} rule',
+ actionResult: '{action}: {msg}',
+ actionFailedMsg: '{action} failed: {msg}',
+ ruleUpdate: 'Update',
+ ruleCreate: 'Create',
configExported: 'Exported {type} config: {filename}',
+ ruleConfigExported: 'Exported rule config: {filename}',
selectFile: 'Please select a file first',
configImported: 'Config imported, takes effect after restart',
// Form validation
@@ -1241,14 +1346,32 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
// Network
wifiModeChanged: 'WiFi mode changed to {mode}',
// File operations
+ createDirFailed: 'Failed to create directory: {msg}',
+ enterQrContent: 'Please enter QR code content',
+ enterDisplayText: 'Please enter display text',
+ textStopped: 'Text stopped',
+ selectFileToDelete: 'Please select file(s) to delete first',
+ selectFileToDownload: 'Please select file(s) to download first',
noDownloadableFiles: 'No downloadable files in selection (folders not supported)',
+ batchDownloadComplete: 'Batch download complete',
+ deletingItems: 'Deleting {count} items...',
+ deleteSuccessCount: 'Successfully deleted {count} items',
+ deletePartial: 'Delete complete: {success} success, {fail} failed',
+ selectFileToUpload: 'Please select file(s) to upload',
+ uploadComplete: 'Upload complete',
downloadingFiles: 'Downloading {count} files...',
sdCardUnmounted: 'SD card unmounted',
unmountFailed: 'Unmount failed',
enterFolderName: 'Please enter folder name',
folderCreated: 'Folder created successfully',
+ createFailedMsg: 'Create failed: {msg}',
enterNewName: 'Please enter new name',
renameSuccess: 'Rename successful',
+ renameFailedMsg: 'Rename failed: {msg}',
+ downloadStart: 'Download started',
+ downloadFailedMsg: 'Download failed: {msg}',
+ deleteSuccess: 'Deleted successfully',
+ deleteFailedMsg: 'Delete failed: {msg}',
// SSH page
selectHostFirst: 'Please select a host first',
fillCommandNameAndCmd: 'Please fill in command name and command',
@@ -1268,17 +1391,21 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
processAlreadyStopped: 'Process already stopped',
serviceNotRunning: 'Service not running',
stopServiceFailed: 'Failed to stop service',
+ stopServiceFailedMsg: 'Failed to stop service: {msg}',
commandRunning: 'A command is running, please cancel or wait for completion',
startExecFailed: 'Failed to start execution',
+ startExecFailedMsg: 'Failed to start execution: {msg}',
noRunningCommand: 'No running command',
cancelSent: 'Cancel request sent',
cancelFailed: 'Cancel failed',
+ cancelFailedMsg: 'Cancel failed: {msg}',
patternMatchSuccess: 'Pattern matched successfully',
commandSuccess: 'Command executed successfully',
commandMatchFailed: 'Command completed, pattern match failed',
commandTimeout: 'Command execution timeout',
commandCompletedCode: 'Command completed, exit code: {code}',
execError: 'Execution error',
+ execErrorMsg: 'Execution error: {msg}',
commandCancelled: 'Command cancelled',
hostFingerprintDeleted: 'Host fingerprint deleted',
hostInfoEmpty: 'Host information is empty',
@@ -1313,6 +1440,8 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
selectedJsonFiles: 'Selected all JSON files in current directory',
configPackCopied: 'Config pack copied to clipboard',
configPackDownloaded: 'Config pack downloaded: {filename}',
+ configPackVerifySuccess: 'Config pack verified\nSigner: {signer} {isOfficial}',
+ configPackVerifyFailed: 'Config pack verification failed: {msg}',
// OTA
otaUpgradeFailed: 'Upgrade failed',
webuiUpgradeFailed: 'WebUI upgrade failed to start',
@@ -1536,8 +1665,10 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
statusFailed: 'Failed',
statusIdle: 'Idle',
statusStopped: 'Stopped',
+ statusUnknown: 'Unknown',
// Error messages
hostNotFound: 'Host not found',
+ hostNotExistShort: 'Host does not exist',
cmdNotFound: 'Command not found',
// Match result labels
matchResultTitle: 'Match Results',
@@ -1568,9 +1699,12 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
selectDevice: '-- Select Device --',
noKeysHint: 'No keys yet, click button above to generate',
noDeployedHint: 'No deployed hosts yet, please click "Deploy" in key management above',
+ noDeployedHostsMsg: 'No deployed hosts',
+ deployKeyAtSecurityHint: 'Please go to
Security page to deploy SSH public key first',
noKnownHosts: 'No known host fingerprints',
pleaseConfigSshHost: '-- Please configure SSH host first --',
noVariableData: 'No variable data for this command, please execute it first',
+ getVarFailed: 'Failed to get variables',
// Variable table headers
varTableName: 'Variable Name',
varTableType: 'Type',
@@ -1622,9 +1756,17 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
stopServiceFailed: 'Failed to stop service',
executionFailed: 'Execution failed',
getLogFailed: 'Failed to get log',
+ getLogFailedMsg: 'Failed to fetch log: {msg}',
+ startRealTimeTail: 'Starting real-time tail: {logFile}\n(Click "Stop Tracking" to exit)',
+ viewServiceLogFile: 'View service log: {name}\nFile: {file}',
+ logErrorMsg: '[Error] {msg}',
+ stopServiceName: 'Stop service: {name}',
+ startExecFailedDetail: 'Failed to start execution\n\n{msg}',
patternMatchSuccess: 'Pattern Match Success!',
expectPatternMatch: 'Expect pattern matched: Yes',
failPatternMatch: 'Fail pattern matched: Yes',
+ failMatchedTrue: 'true (error detected)',
+ failMatchedFalse: 'false',
extractedContent: 'Extracted Content',
cancelledExecution: 'Execution cancelled',
error: 'Error',
@@ -2167,6 +2309,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
// OTA states
stateIdle: 'Idle',
stateChecking: 'Checking...',
+ downloading: 'Downloading...',
downloadingWebUI: 'Downloading WebUI...',
downloadingFirmware: 'Downloading firmware...',
stateVerifying: 'Verifying...',
@@ -2204,7 +2347,13 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
refreshPage: 'Refresh Page',
currentVersionUnknown: 'Unknown',
step1: '[1/2] Upgrading Firmware',
- step2: '[2/2] Upgrading WebUI'
+ step2: '[2/2] Upgrading WebUI',
+ step1Connecting: '[1/2] Connecting to server...',
+ preparing: 'Preparing...',
+ writing: 'Writing...',
+ readingFile: 'Reading file...',
+ stateError: 'Error',
+ connecting: 'Connecting...'
},
// Automation Page
@@ -2255,7 +2404,12 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
requestBodyLabel: 'Request Body',
requestBodyHint: 'JSON format, supports variables',
getMemoryDetailFailed: 'Failed to get memory details',
- // Logs
+ // Logs (quick action log tail)
+ logFetchFailed: '[Fetch failed]',
+ logError: '[Error]',
+ deviceBusyRetry: 'Device may be busy, please retry later.',
+ logEmptyBracket: '[Empty]',
+ logFileEmptyBracket: '[Log file does not exist or is empty]',
logFileNotExist: 'Log file does not exist or is empty',
logEmpty: 'No logs',
noRules: 'No rules yet, click "Add" to create',
@@ -2319,6 +2473,10 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
logLevel: 'Level',
logMessage: 'Message',
logMsgHint: 'Supports variables: ${var_name}',
+ varConfig: 'Variable Config',
+ varNameLabel: 'Variable Name',
+ method: 'Method',
+ requestBody: 'Request Body',
indexPlaceholder: 'Index',
pulseMsPlaceholder: 'Pulse ms',
varNamePlaceholder: 'Var name',
@@ -2372,6 +2530,12 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
stopTracking: 'Stop Tracking',
startTracking: 'Start Tracking',
interval: 'Interval',
+ interval1Sec: '1s',
+ interval2Sec: '2s',
+ interval3Sec: '3s',
+ interval5Sec: '5s',
+ interval10Sec: '10s',
+ interval30Sec: '30s',
realTimeUpdating: '● Real-time updating',
trackingStopped: 'Tracking stopped',
// Action editor labels
@@ -2434,6 +2598,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
ledOffHint: 'Turn off LED device, no extra parameters needed',
filterStopHint: 'Stop currently running filter effect, no extra parameters needed',
textStopHint: 'Stop currently running text overlay, no extra parameters needed',
+ textContentLabel: 'Text Content',
textPlaceholder: 'Text to display, supports ${variable}',
scroll: 'Scroll',
scrollNone: 'No Scroll',
@@ -2665,6 +2830,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
secondaryExpressionPlaceholder: 'e.g.: ${max_value}',
displayLines: 'Display Lines',
refreshIntervalMs: 'Refresh Interval (ms)',
+ configLogVariableFirst: 'Please configure log variable first',
logVariable: 'Log Variable',
logVariablePlaceholder: 'Select variable containing log text',
selectVariable: 'Select Variable',
@@ -2740,6 +2906,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
// Modal - Effect
effectTitle: 'Programmatic Animation',
effectNotSelected: 'None',
+ current: 'Current',
speed: 'Speed',
color: 'Color',
start: 'Start',
@@ -2887,17 +3054,30 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
remainingDays: '{days} days remaining',
generatingKeyPair: 'Generating key pair...',
generatingCsr: 'Generating CSR...',
+ ecdsaKeyPairSuccess: 'ECDSA P-256 key pair generated successfully!',
+ installingCert: 'Installing certificate...',
+ certInstalledSuccess: 'Certificate installed successfully!',
installFailed: 'Install failed',
+ installFailedMsg: 'Install failed: {msg}',
+ deployFailedMsg: 'Deploy failed: {msg}',
+ revokeFailedMsg: 'Revoke failed: {msg}',
+ invalidJsonConfigMsg: 'Config file is not valid JSON: {msg}',
// Config Pack
enterConfigName: 'Please enter config name',
selectConfigFile: 'Please select config file',
pasteTargetCert: 'Please paste target device certificate',
invalidJsonConfig: 'Config file is not valid JSON',
generatingPack: 'Generating config pack',
+ generatingPackWithCount: 'Generating config pack ({count} files)...',
savedToDevice: 'Saved to device',
+ installingCaCert: 'Installing CA certificate chain...',
+ caCertInstalledSuccess: 'CA certificate chain installed successfully!',
+ loadFailedMsg: 'Load failed: {msg}',
generationFailed: 'Generation failed',
+ generationFailedMsg: 'Generation failed: {msg}',
verifying: 'Verifying...',
verifyFailed: 'Verification failed',
+ signatureInvalid: 'Invalid signature',
importing: 'Importing...',
importFailed: 'Import failed',
uploadOrPasteContent: 'Please upload file or paste config pack content'
@@ -2926,6 +3106,8 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
turnOn: 'On',
colorSettings: 'Color Settings',
turnOffLight: 'Turn Off',
+ turnOnLight: 'Turn On',
+ noFilterSelected: 'No filter selected',
// OTA
firmwareUpgradeComplete: 'Firmware upgrade complete, preparing WebUI upgrade...',
allUpgradeComplete: 'All upgrades complete',
@@ -2956,6 +3138,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
newCommand: '+ New Command',
editCommand: 'Edit Command',
commandVariables: 'Command Variables',
+ commandVariablesWithName: 'Command variables: {varName}.*',
sourceVariables: '{source} Variables',
editActionTemplate: 'Edit Action Template',
// Actions
@@ -3044,6 +3227,10 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
confirmRemoveKnownHost: 'Are you sure you want to remove the record for "{host}:{port}"?',
confirmClearKnownHosts: 'Are you sure you want to clear all known host records? This action cannot be undone!',
confirmDeletePKI: 'Are you sure you want to delete all PKI credentials?\n\nThis will delete:\n• Private key\n• Device certificate\n• CA certificate chain\n\nThis action cannot be undone!',
+ confirmMarkFirmwareValid: 'Confirm marking current firmware as valid?\nThis will disable automatic rollback protection.',
+ confirmRollback: 'Confirm rollback to previous firmware version?\n\nThe system will immediately reboot and load the firmware from the previous partition.\nPlease ensure the previous firmware is available!',
+ confirmAbortUpgrade: 'Confirm abort current upgrade?',
+ confirmRestoreDefaults: 'Confirm restore default settings?',
// Alert dialogs and other prompts
alertHostFingerprint: 'Host: {host}:{port}\nType: {type}\nFingerprint (SHA256):\n{fingerprint}',
alertEnterWsAddress: 'Please enter WebSocket address',
@@ -3087,7 +3274,29 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
connecting: 'Connecting to device...',
connected: 'Connected to device',
helpHint: 'Enter {help} to view available commands',
+ connectionDisconnected: 'Connection disconnected',
+ reconnectIn: 'Reconnecting in 5 seconds...',
+ reconnecting: 'Reconnecting...',
+ connectionError: 'Connection error',
+ notConnected: 'Not connected to device',
+ sshShellRequiresParams: 'Error: SSH shell requires --host and --user parameters',
+ sshSessionInProgress: 'SSH session already in progress',
+ sshConnectingTo: 'Connecting to {user}@{host}:{port}...',
+ exitSshHint: '(Press Ctrl+\\ to exit SSH shell)',
+ exitSshDisplay: '^\\ (Exit SSH shell)',
+ lowVoltageWarning: '⚠️ Low voltage warning! Voltage: {voltage}V - Shutdown countdown started',
+ countdownTick: '⏱️ Shutdown countdown: {countdown}s | Voltage: {voltage}V',
+ shutdownStart: '🔴 Shutting down... Voltage: {voltage}V',
+ protected: '🛡️ Protection active | Waiting for voltage recovery...',
+ recoveryStart: '🔄 Voltage recovering: {voltage}V | Waiting for stability...',
+ recoveryComplete: '✅ Voltage recovery complete! {voltage}V | System restarting soon',
+ debugTick: '📊 [Debug] {state} | Voltage: {voltage}V | Countdown: {countdown}s',
+ voltageNormal: '✓ Voltage normal: {voltage}V',
+ stateChanged: 'State changed: {state} | Voltage: {voltage}V',
+ powerEvent: '[Power] {event}: state={state}, voltage={voltage}V',
pageTitle: 'Web Terminal',
+ errorLabel: 'Error',
+ unknownError: 'Unknown error',
terminalHint: 'Tip: Type
help for commands |
Ctrl+C interrupt |
Ctrl+L clear |
↑↓ history',
systemLogTitle: 'System Logs',
levelLabel: 'Level',
diff --git a/components/ts_webui/web/js/lang/zh-CN.js b/components/ts_webui/web/js/lang/zh-CN.js
index f59398d..435728f 100644
--- a/components/ts_webui/web/js/lang/zh-CN.js
+++ b/components/ts_webui/web/js/lang/zh-CN.js
@@ -143,6 +143,9 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
device: '设备',
logs: '日志',
loadFailed: '加载失败',
+ loadFailedMsg: '加载失败: {msg}',
+ refreshOnce: '刷新一次',
+ uploadFailedMsg: '失败: {msg}',
copyToClipboard: '复制到剪贴板',
selectFile: '选择文件',
install: '安装'
@@ -344,7 +347,16 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
maxCurvePoints: '最多支持 10 个曲线点',
statusUnavailable: '风扇状态不可用',
importConfig: '导入配置',
- exportConfig: '导出配置'
+ exportConfig: '导出配置',
+ enterValidTemp: '请输入有效温度 (0-100°C)',
+ testTempCleared: '测试温度已清除,恢复正常模式',
+ testTempSet: '测试温度已设置为 {temp}°C',
+ speedSet: '风扇 {id} 速度已设置为 {speed}%',
+ modeSwitch: '风扇 {id} 模式已切换为 {mode}',
+ setFanFailed: '设置风扇失败: {msg}',
+ setFanModeFailed: '设置风扇模式失败: {msg}',
+ setFailed: '设置失败: {msg}',
+ clearFailed: '清除失败: {msg}'
},
// LED 控制
@@ -383,7 +395,8 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
browseImages: '浏览图像',
touch: '触摸按钮',
board: '板载 LED',
- matrix: 'LED 矩阵'
+ matrix: 'LED 矩阵',
+ selectedFilter: '已选择: {filter}'
},
// 网络
@@ -679,7 +692,9 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
partitionIdle: '空闲',
disableRollbackDesc: '取消自动回滚保护',
rollbackToThis: '回滚到此版本',
- loadAfterReboot: '重启后加载此分区'
+ loadAfterReboot: '重启后加载此分区',
+ firmwareDownloadComplete: '固件下载完成 ({size})',
+ webuiDownloadComplete: 'WebUI 下载完成 ({size})'
},
// 自动化
@@ -706,6 +721,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
ruleName: '规则名称',
conditions: '触发条件',
conditionLogic: '条件逻辑',
+ ruleOptions: '规则选项',
logicAnd: '全部满足 (AND)',
logicOr: '任一满足 (OR)',
cooldown: '冷却时间',
@@ -937,6 +953,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
welcome: '欢迎使用 TianShanOS',
username: '用户名',
password: '密码',
+ passwordPlaceholder: '请输入密码',
rememberMe: '记住我',
loginButton: '登录',
loggingIn: '登录中...',
@@ -1033,6 +1050,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
imported: '导入完成',
loggedOut: '已退出登录',
logsLoaded: '加载完成',
+ logsLoadedCount: '加载了 {count} 条历史日志',
wifiDisconnected: 'WiFi 已断开',
natEnabled: 'NAT 已启用',
natDisabled: 'NAT 已禁用',
@@ -1057,8 +1075,54 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
serviceSuccess: '服务 {name} {action} 成功',
// 电压保护
voltageProtectionEnabled: '电压保护已启用',
+ lowVoltageWarning: '低电压警告: {voltage}V ({countdown}s)',
+ voltageProtectionTriggered: '电压保护已触发',
+ voltageRecovering: '电压恢复中: {voltage}V',
voltageProtectionDisabled: '电压保护已禁用',
switchFailed: '切换失败',
+ switchFailedMsg: '切换失败: {msg}',
+ setFailedMsg: '设置失败: {msg}',
+ enterHostname: '请输入主机名',
+ hostnameSet: '主机名已设置',
+ connectFailedMsg: '连接失败: {msg}',
+ disconnectFailedMsg: '断开失败: {msg}',
+ configFailedMsg: '配置失败: {msg}',
+ operationFailedMsg: '操作失败: {msg}',
+ applyFailedMsg: '应用失败: {msg}',
+ saveFailedMsg: '保存失败: {msg}',
+ deleteFailedMsg: '删除失败: {msg}',
+ removeFailedMsg: '移除失败: {msg}',
+ exportFailedMsg: '导出失败: {msg}',
+ updateFailedMsg: '更新失败: {msg}',
+ clearFailedMsg: '清除失败: {msg}',
+ copyFailedMsg: '复制失败: {msg}',
+ upgradeFailedMsg: '升级失败: {msg}',
+ webuiUpgradeStartFailed: 'WebUI 升级启动失败',
+ upgradeStartFailedMsg: '启动升级失败: {msg}',
+ rollbackFailedMsg: '回滚失败: {msg}',
+ abortFailedMsg: '中止失败: {msg}',
+ otaServerNotSet: 'OTA 服务器地址未设置',
+ firmwareWriteComplete: '固件写入完成!',
+ webuiWriteComplete: 'WebUI 写入完成!',
+ webuiUpgradeSkipped: 'WebUI 升级跳过: {msg}',
+ nohupStripped: '已自动去除命令中多余的 nohup 包装(后端会自动添加)',
+ saveCommandFailedMsg: '保存指令失败: {msg}',
+ deleteCommandFailedMsg: '删除指令失败: {msg}',
+ fillServerInfo: '请填写完整的服务器信息',
+ publicKeyNotFound: '公钥未找到',
+ oldHostKeyRemoved: '旧主机密钥已移除,请重新连接以信任新密钥',
+ allHostsCleared: '已清除所有已知主机',
+ generateFailedMsg: '生成失败: {msg}',
+ configPackDownloaded: '配置包已下载: {filename}',
+ selectAtLeastOneAction: '请至少选择一个动作模板',
+ cmdNotExist: '指令不存在,请重新选择',
+ hostFingerprintDeleted: '已删除主机指纹',
+ keyDeleted: '密钥已删除',
+ cannotGetPublicKey: '无法获取公钥',
+ cannotGetPrivateKey: '无法获取私钥',
+ keyRevoked: '密钥撤销成功',
+ hostRemoved: '主机已移除',
+ configPackCopied: '配置包已复制到剪贴板',
// USB 切换
usbSwitching: '切换 USB 到 {target}...',
usbSwitched: 'USB 已切换到 {target}',
@@ -1069,14 +1133,29 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
widgetSaved: '组件已保存',
widgetDeleted: '已删除 {name}',
widgetBound: '已绑定 {widget} → {var}',
- // 快捷操作
+ // 快捷操作 / 数据
+ agxMovedToBinding: 'AGX 配置已移至变量绑定',
+ waitBeforeTrigger: '请等待几秒后再触发其他模型',
+ operationExecuted: '操作已执行',
+ execFailed: '执行失败',
+ execFailedMsg: '执行失败: {msg}',
+ errorMsg: '错误: {msg}',
processRunning: '进程正在运行中,请先停止',
+ confirmTerminateCmd: '确定要终止 "{cmdName}" 吗?',
+ terminatingProcess: '正在终止进程...',
cardNotFound: '无法找到操作卡片',
executionFailed: '执行失败',
processTerminated: '已终止进程',
processNotExist: '进程已不存在',
pidFileNotExist: 'PID 文件不存在',
operationComplete: '操作完成',
+ // 终止进程结果
+ terminateTERMINATED: '服务已停止',
+ terminateFORCE_KILLED: '服务已强制终止',
+ terminateALREADY_STOPPED: '进程已不在运行,已清理 PID 文件',
+ terminateNO_PID_FILE: 'PID 文件不存在',
+ terminateSTILL_RUNNING: '无法终止进程,请手动处理',
+ terminateDefault: '操作完成',
// 时间同步
timeSynced: '时间已同步: {datetime}',
timeSyncSyncing: '正在从浏览器同步时间...',
@@ -1097,12 +1176,30 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
ledQrFailed: '生成 QR 码失败',
ledTextFailed: '显示文本失败',
ledTextStopFailed: '停止文本失败',
+ allLedsOff: '全部 LED 已关闭',
selectAnimation: '请先选择一个动画',
+ enterImagePath: '请输入图像路径',
+ imageDisplayed: '图像已显示',
+ enterTextToEncode: '请输入要编码的文本',
+ qrGenerated: 'QR 码已生成',
+ enterTextToDisplay: '请输入要显示的文本',
+ textDisplayed: '文本已显示',
+ textScrollStopped: '文本滚动已停止',
+ filterStopped: '滤镜已停止',
+ selectFilter: '请先选择滤镜',
ledBrightnessSet: '{device} 亮度: {value}',
ledBrightnessFailed: '设置 {device} 亮度失败',
ledTurnedOff: '{device} 已关闭',
ledTurnedOn: '{device} 已开启',
ledOnFailed: '开启失败',
+ ledOffFailed: '关闭失败',
+ filterApplied: '滤镜 {filter} 已应用',
+ filterApplyFailed: '应用滤镜失败',
+ filterStopFailed: '停止滤镜失败',
+ ledConfigSaved: '{device} 配置已保存',
+ ledConfigSavedWithAnim: '{device} 配置已保存: {anim}',
+ ledConfigSaveFailed: '保存配置失败',
+ enterQrContent: '请输入QR码内容',
// PKI/证书
configPackImported: '配置包导入成功',
importFailed: '导入失败',
@@ -1155,6 +1252,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
ruleTriggered: '触发规则 {id}',
toggleRuleFailed: '切换规则状态失败',
triggerRuleFailed: '触发规则失败',
+ toggleSourceFailed: '切换数据源状态失败',
sourceToggled: '数据源 {id} {state}',
actionExecuting: '正在执行动作: {id}...',
actionSuccess: '执行成功',
@@ -1170,6 +1268,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
shutdownSettingsSaved: '关机设置已保存',
defaultsRestored: '已恢复默认设置',
restoreFailed: '恢复失败',
+ restoreFailedMsg: '恢复失败: {msg}',
deleteRuleResult: '删除规则 {id}',
deleteRuleFailed: '删除规则失败',
getRuleDetailFailed: '获取规则详情失败',
@@ -1178,7 +1277,12 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
createActionFirst: '请先创建动作模板',
ruleCreated: '规则 {id} {action}成功',
ruleCreateFailed: '{action}规则失败',
+ actionResult: '{action}: {msg}',
+ actionFailedMsg: '{action} 失败: {msg}',
+ ruleUpdate: '更新',
+ ruleCreate: '创建',
configExported: '已导出{type}配置: {filename}',
+ ruleConfigExported: '已导出规则配置: {filename}',
selectFile: '请先选择文件',
configImported: '已导入配置,重启后生效',
// 表单验证
@@ -1243,14 +1347,32 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
// 网络
wifiModeChanged: 'WiFi 模式已切换为 {mode}',
// 文件操作
+ createDirFailed: '创建目录失败: {msg}',
+ enterQrContent: '请输入 QR 码内容',
+ enterDisplayText: '请输入显示文本',
+ textStopped: '文本已停止',
+ selectFileToDelete: '请先选择要删除的文件',
+ selectFileToDownload: '请先选择要下载的文件',
noDownloadableFiles: '选中的项目中没有可下载的文件(文件夹不支持下载)',
+ batchDownloadComplete: '批量下载完成',
+ deletingItems: '正在删除 {count} 个项目...',
+ deleteSuccessCount: '成功删除 {count} 个项目',
+ deletePartial: '删除完成: {success} 成功, {fail} 失败',
+ selectFileToUpload: '请选择要上传的文件',
+ uploadComplete: '上传完成',
downloadingFiles: '正在下载 {count} 个文件...',
sdCardUnmounted: 'SD 卡已卸载',
unmountFailed: '卸载失败',
enterFolderName: '请输入文件夹名称',
folderCreated: '文件夹创建成功',
+ createFailedMsg: '创建失败: {msg}',
enterNewName: '请输入新名称',
renameSuccess: '重命名成功',
+ renameFailedMsg: '重命名失败: {msg}',
+ downloadStart: '下载开始',
+ downloadFailedMsg: '下载失败: {msg}',
+ deleteSuccess: '删除成功',
+ deleteFailedMsg: '删除失败: {msg}',
// SSH 页面
selectHostFirst: '请先选择一个主机',
fillCommandNameAndCmd: '请填写指令名称和命令',
@@ -1270,17 +1392,21 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
processAlreadyStopped: '进程已经停止',
serviceNotRunning: '服务未运行',
stopServiceFailed: '停止服务失败',
+ stopServiceFailedMsg: '停止服务失败: {msg}',
commandRunning: '有命令正在执行中,请先取消或等待完成',
startExecFailed: '启动执行失败',
+ startExecFailedMsg: '启动执行失败: {msg}',
noRunningCommand: '没有正在执行的命令',
cancelSent: '取消请求已发送',
cancelFailed: '取消失败',
+ cancelFailedMsg: '取消失败: {msg}',
patternMatchSuccess: '模式匹配成功',
commandSuccess: '命令执行成功',
commandMatchFailed: '命令执行完成,模式匹配失败',
commandTimeout: '命令执行超时',
commandCompletedCode: '命令执行完成,退出码: {code}',
execError: '执行出错',
+ execErrorMsg: '执行出错: {msg}',
commandCancelled: '命令已取消',
hostFingerprintDeleted: '已删除主机指纹',
hostInfoEmpty: '主机信息为空',
@@ -1315,6 +1441,8 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
selectedJsonFiles: '已选择当前目录下的所有 JSON 文件',
configPackCopied: '配置包已复制到剪贴板',
configPackDownloaded: '配置包已下载: {filename}',
+ configPackVerifySuccess: '配置包验证成功\n签名者: {signer} {isOfficial}',
+ configPackVerifyFailed: '配置包验证失败: {msg}',
// OTA
otaUpgradeFailed: '升级失败',
webuiUpgradeFailed: 'WebUI 升级启动失败',
@@ -1539,8 +1667,10 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
statusFailed: '失败',
statusIdle: '未启动',
statusStopped: '已停止',
+ statusUnknown: '未知',
// 错误消息
hostNotFound: '主机信息不存在',
+ hostNotExistShort: '主机不存在',
cmdNotFound: '命令不存在',
// 匹配结果标签
matchResultTitle: '匹配结果',
@@ -1571,9 +1701,12 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
selectDevice: '-- 选择设备 --',
noKeysHint: '暂无密钥,点击上方按钮生成新密钥',
noDeployedHint: '暂无已部署主机,请先在上方密钥管理中点击「部署」',
+ noDeployedHostsMsg: '暂无已部署主机',
+ deployKeyAtSecurityHint: '请先到
安全 页面部署 SSH 公钥',
noKnownHosts: '暂无已知主机指纹',
pleaseConfigSshHost: '-- 请先配置 SSH 主机 --',
noVariableData: '该指令暂无变量数据,请先执行一次',
+ getVarFailed: '获取变量失败',
// 变量表格表头
varTableName: '变量名',
varTableType: '类型',
@@ -1625,9 +1758,17 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
stopServiceFailed: '停止服务失败',
executionFailed: '执行失败',
getLogFailed: '获取日志失败',
+ getLogFailedMsg: '获取日志失败: {msg}',
+ startRealTimeTail: '开始实时跟踪: {logFile}\n(点击"停止跟踪"按钮退出)',
+ viewServiceLogFile: '查看服务日志: {name}\n文件: {file}',
+ logErrorMsg: '[错误] {msg}',
+ stopServiceName: '停止服务: {name}',
+ startExecFailedDetail: '启动执行失败\n\n{msg}',
patternMatchSuccess: '模式匹配成功!',
expectPatternMatch: '期望模式匹配: 是',
failPatternMatch: '失败模式匹配: 是',
+ failMatchedTrue: 'true (检测到错误)',
+ failMatchedFalse: 'false',
extractedContent: '提取内容',
cancelledExecution: '已取消执行',
error: '错误',
@@ -2172,6 +2313,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
// OTA 状态
stateIdle: '空闲',
stateChecking: '检查更新中...',
+ downloading: '下载中...',
downloadingWebUI: '下载 WebUI...',
downloadingFirmware: '下载固件...',
stateVerifying: '验证中...',
@@ -2209,7 +2351,13 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
refreshPage: '刷新页面',
currentVersionUnknown: '未知',
step1: '[1/2] 升级固件',
- step2: '[2/2] 升级 WebUI'
+ step2: '[2/2] 升级 WebUI',
+ step1Connecting: '[1/2] 正在连接服务器...',
+ preparing: '准备中...',
+ writing: '写入中...',
+ readingFile: '正在读取文件...',
+ stateError: '错误',
+ connecting: '正在连接...'
},
// 自动化页面
@@ -2260,7 +2408,12 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
requestBodyLabel: '请求体',
requestBodyHint: 'JSON 格式,支持变量',
getMemoryDetailFailed: '获取内存详情失败',
- // 日志
+ // 日志(快捷操作日志跟踪)
+ logFetchFailed: '[获取失败]',
+ logError: '[错误]',
+ deviceBusyRetry: '若设备繁忙可稍后重试。',
+ logEmptyBracket: '[空]',
+ logFileEmptyBracket: '[日志文件不存在或为空]',
logFileNotExist: '日志文件不存在或为空',
logEmpty: '暂无日志',
noRules: '暂无规则,点击"添加"创建第一条',
@@ -2324,6 +2477,10 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
logLevel: '级别',
logMessage: '消息',
logMsgHint: '支持变量: ${变量名}',
+ varConfig: '变量配置',
+ varNameLabel: '变量名',
+ method: '方法',
+ requestBody: '请求体',
indexPlaceholder: '索引',
pulseMsPlaceholder: '脉冲ms',
varNamePlaceholder: '变量名',
@@ -2377,6 +2534,12 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
stopTracking: '停止跟踪',
startTracking: '▶️ 开始跟踪',
interval: '间隔',
+ interval1Sec: '1秒',
+ interval2Sec: '2秒',
+ interval3Sec: '3秒',
+ interval5Sec: '5秒',
+ interval10Sec: '10秒',
+ interval30Sec: '30秒',
realTimeUpdating: '● 实时更新中',
trackingStopped: '已停止更新',
// 动作编辑器标签
@@ -2439,6 +2602,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
ledOffHint: '关闭 LED 设备,无需额外参数',
filterStopHint: '停止当前运行的滤镜效果,无需额外参数',
textStopHint: '停止当前运行的文本覆盖层,无需额外参数',
+ textContentLabel: '文本内容',
textPlaceholder: '要显示的文本,支持 ${变量名}',
scroll: '滚动',
scrollNone: '无滚动',
@@ -2670,6 +2834,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
secondaryExpressionPlaceholder: '例如: ${max_value}',
displayLines: '显示行数',
refreshIntervalMs: '刷新间隔(毫秒)',
+ configLogVariableFirst: '请先配置日志变量',
logVariable: '日志变量',
logVariablePlaceholder: '选择包含日志文本的变量',
selectVariable: '选择变量',
@@ -2745,6 +2910,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
// Modal - Effect
effectTitle: '程序动画',
effectNotSelected: '未选择',
+ current: '当前',
speed: '速度',
color: '颜色',
start: '启动',
@@ -2892,17 +3058,30 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
remainingDays: '剩余 {days} 天',
generatingKeyPair: '正在生成密钥对...',
generatingCsr: '正在生成 CSR...',
+ ecdsaKeyPairSuccess: 'ECDSA P-256 密钥对生成成功!',
+ installingCert: '正在安装证书...',
+ certInstalledSuccess: '证书安装成功!',
installFailed: '安装失败',
+ installFailedMsg: '安装失败: {msg}',
+ deployFailedMsg: '部署失败: {msg}',
+ revokeFailedMsg: '撤销失败: {msg}',
+ invalidJsonConfigMsg: '配置文件不是有效的 JSON: {msg}',
// 配置包
enterConfigName: '请输入配置名称',
selectConfigFile: '请选择配置文件',
pasteTargetCert: '请粘贴目标设备证书',
invalidJsonConfig: '配置文件不是有效的 JSON',
generatingPack: '生成配置包中',
+ generatingPackWithCount: '生成配置包中 ({count} 个文件)...',
savedToDevice: '已保存到设备',
+ installingCaCert: '正在安装 CA 证书链...',
+ caCertInstalledSuccess: 'CA 证书链安装成功!',
+ loadFailedMsg: '加载失败: {msg}',
generationFailed: '生成失败',
+ generationFailedMsg: '生成失败: {msg}',
verifying: '验证中...',
verifyFailed: '验证失败',
+ signatureInvalid: '签名无效',
importing: '导入中...',
importFailed: '导入失败',
uploadOrPasteContent: '请上传文件或粘贴配置包内容'
@@ -2931,6 +3110,8 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
turnOn: '开启',
colorSettings: '颜色设置',
turnOffLight: '关灯',
+ turnOnLight: '开灯',
+ noFilterSelected: '未选择滤镜',
// OTA
firmwareUpgradeComplete: '固件升级完成,准备升级 WebUI...',
allUpgradeComplete: '全部升级完成',
@@ -2961,6 +3142,7 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
newCommand: '+ 新建指令',
editCommand: '编辑指令',
commandVariables: '指令变量',
+ commandVariablesWithName: '指令变量: {varName}.*',
sourceVariables: '{source} 变量',
editActionTemplate: '编辑动作模板',
// 动作
@@ -3049,6 +3231,15 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
confirmRemoveKnownHost: '确定要移除主机 "{host}:{port}" 的记录吗?',
confirmClearKnownHosts: '确定要清除所有已知主机记录吗?此操作不可撤销!',
confirmDeletePKI: '确定要删除所有 PKI 凭证吗?\n\n这将删除:\n• 私钥\n• 设备证书\n• CA 证书链\n\n此操作不可撤销!',
+ confirmMarkFirmwareValid: '确认将当前固件标记为有效?\n这将取消自动回滚保护。',
+ confirmRollback: '确认回滚到上一版本固件?\n\n系统将立即重启并加载上一个分区的固件。\n请确保上一版本固件可用!',
+ confirmAbortUpgrade: '确认中止当前升级?',
+ confirmRestoreDefaults: '确认恢复默认设置?',
+ confirmDeleteFiles: '确定要删除选中的 {count} 个文件/文件夹吗?此操作不可撤销!',
+ confirmDeleteItem: '确定要删除 "{name}" 吗?',
+ confirmDeleteAction: '确定要删除动作模板 "{id}" 吗?',
+ confirmDeleteSource: '确定要删除数据源 "{id}" 吗?此操作不可撤销。',
+ confirmDeleteRule: '确定要删除规则 "{id}" 吗?此操作不可撤销。',
// Alert 对话框和其他提示
alertHostFingerprint: '主机: {host}:{port}\n类型: {type}\n指纹 (SHA256):\n{fingerprint}',
alertEnterWsAddress: '请输入 WebSocket 地址',
@@ -3092,7 +3283,29 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
connecting: '正在连接到设备...',
connected: '已连接到设备',
helpHint: '输入 {help} 查看可用命令',
+ connectionDisconnected: '连接已断开',
+ reconnectIn: '5秒后尝试重新连接...',
+ reconnecting: '正在重新连接...',
+ connectionError: '连接错误',
+ notConnected: '未连接到设备',
+ sshShellRequiresParams: '错误: SSH shell 需要 --host 和 --user 参数',
+ sshSessionInProgress: 'SSH 会话已在进行中',
+ sshConnectingTo: '正在连接到 {user}@{host}:{port}...',
+ exitSshHint: '(按 Ctrl+\\ 退出 SSH shell)',
+ exitSshDisplay: '^\\ (退出 SSH shell)',
+ lowVoltageWarning: '⚠️ 低电压警告! 电压: {voltage}V - 开始关机倒计时',
+ countdownTick: '⏱️ 关机倒计时: {countdown}秒 | 电压: {voltage}V',
+ shutdownStart: '🔴 正在执行关机... 电压: {voltage}V',
+ protected: '🛡️ 进入保护状态 | 等待电压恢复...',
+ recoveryStart: '🔄 电压恢复中: {voltage}V | 等待稳定...',
+ recoveryComplete: '✅ 电压恢复完成! {voltage}V | 系统即将重启',
+ debugTick: '📊 [调试] {state} | 电压: {voltage}V | 倒计时: {countdown}s',
+ voltageNormal: '✓ 电压状态正常: {voltage}V',
+ stateChanged: '状态变更: {state} | 电压: {voltage}V',
+ powerEvent: '[电源] {event}: 状态={state}, 电压={voltage}V',
pageTitle: 'Web 终端',
+ errorLabel: '错误',
+ unknownError: '未知错误',
terminalHint: '提示: 输入
help 查看命令 |
Ctrl+C 中断 |
Ctrl+L 清屏 |
↑↓ 历史',
systemLogTitle: '系统日志',
levelLabel: '级别',
diff --git a/components/ts_webui/web/js/terminal.js b/components/ts_webui/web/js/terminal.js
index 8bf9134..547e480 100644
--- a/components/ts_webui/web/js/terminal.js
+++ b/components/ts_webui/web/js/terminal.js
@@ -147,7 +147,7 @@ class WebTerminal {
if (this.sshMode) {
// 检查 Ctrl+\ (0x1C) 退出 SSH
if (data.charCodeAt(0) === 0x1C) {
- this.writeln('\r\n^\\ (退出 SSH shell)');
+ this.writeln('\r\n' + (typeof t === 'function' ? t('terminal.exitSshDisplay') : '^\\ (退出 SSH shell)'));
this.exitSshShell();
return;
}
@@ -335,14 +335,14 @@ class WebTerminal {
this.pingInterval = null;
}
- this.writeln('\r\n\x1b[1;31m连接已断开\x1b[0m');
+ this.writeln('\r\n\x1b[1;31m' + (typeof t === 'function' ? t('terminal.connectionDisconnected') : '连接已断开') + '\x1b[0m');
// 尝试重连
if (event.code !== 1000) { // 非正常关闭
- this.writeln('\x1b[33m5秒后尝试重新连接...\x1b[0m');
+ this.writeln('\x1b[33m' + (typeof t === 'function' ? t('terminal.reconnectIn') : '5秒后尝试重新连接...') + '\x1b[0m');
setTimeout(() => {
if (!this.connected) {
- this.writeln('正在重新连接...');
+ this.writeln(typeof t === 'function' ? t('terminal.reconnecting') : '正在重新连接...');
this.connect();
}
}, 5000);
@@ -351,7 +351,7 @@ class WebTerminal {
this.ws.onerror = (error) => {
console.error('Terminal WebSocket error:', error);
- this.writeln('\r\n\x1b[1;31m连接错误\x1b[0m');
+ this.writeln('\r\n\x1b[1;31m' + (typeof t === 'function' ? t('terminal.connectionError') : '连接错误') + '\x1b[0m');
};
}
@@ -382,7 +382,7 @@ class WebTerminal {
break;
case 'error':
- this.writeln('\x1b[1;31m错误: ' + (msg.message || '未知错误') + '\x1b[0m');
+ this.writeln('\x1b[1;31m' + (typeof t === 'function' ? t('terminal.errorLabel') : '错误') + ': ' + (msg.message || (typeof t === 'function' ? t('terminal.unknownError') : '未知错误')) + '\x1b[0m');
this.writePrompt();
break;
@@ -423,10 +423,11 @@ class WebTerminal {
let notification = '';
let color = '\x1b[33m'; // 默认黄色
+ const _t = (key, params, fallback) => (typeof t === 'function' ? t(key, params) : fallback);
switch (event) {
case 'low_voltage':
color = '\x1b[1;31m'; // 亮红色
- notification = `⚠️ 低电压警告! 电压: ${voltage}V - 开始关机倒计时`;
+ notification = _t('terminal.lowVoltageWarning', { voltage }, `⚠️ 低电压警告! 电压: ${voltage}V - 开始关机倒计时`);
break;
case 'countdown_tick':
if (countdown <= 10) {
@@ -436,39 +437,39 @@ class WebTerminal {
} else {
return; // 不显示每秒倒计时,只显示关键时刻
}
- notification = `⏱️ 关机倒计时: ${countdown}秒 | 电压: ${voltage}V`;
+ notification = _t('terminal.countdownTick', { countdown, voltage }, `⏱️ 关机倒计时: ${countdown}秒 | 电压: ${voltage}V`);
break;
case 'shutdown_start':
color = '\x1b[1;31m';
- notification = `🔴 正在执行关机... 电压: ${voltage}V`;
+ notification = _t('terminal.shutdownStart', { voltage }, `🔴 正在执行关机... 电压: ${voltage}V`);
break;
case 'protected':
color = '\x1b[35m'; // 紫色
- notification = `🛡️ 进入保护状态 | 等待电压恢复...`;
+ notification = _t('terminal.protected', {}, '🛡️ 进入保护状态 | 等待电压恢复...');
break;
case 'recovery_start':
color = '\x1b[36m'; // 青色
- notification = `🔄 电压恢复中: ${voltage}V | 等待稳定...`;
+ notification = _t('terminal.recoveryStart', { voltage }, `🔄 电压恢复中: ${voltage}V | 等待稳定...`);
break;
case 'recovery_complete':
color = '\x1b[1;32m'; // 亮绿色
- notification = `✅ 电压恢复完成! ${voltage}V | 系统即将重启`;
+ notification = _t('terminal.recoveryComplete', { voltage }, `✅ 电压恢复完成! ${voltage}V | 系统即将重启`);
break;
case 'debug_tick':
// 调试模式:每秒显示状态
color = '\x1b[36m'; // 青色
- notification = `📊 [调试] ${state} | 电压: ${voltage}V | 倒计时: ${countdown}s`;
+ notification = _t('terminal.debugTick', { state, voltage, countdown }, `📊 [调试] ${state} | 电压: ${voltage}V | 倒计时: ${countdown}s`);
break;
case 'state_changed':
if (state === 'NORMAL') {
color = '\x1b[32m';
- notification = `✓ 电压状态正常: ${voltage}V`;
+ notification = _t('terminal.voltageNormal', { voltage }, `✓ 电压状态正常: ${voltage}V`);
} else {
- notification = `状态变更: ${state} | 电压: ${voltage}V`;
+ notification = _t('terminal.stateChanged', { state, voltage }, `状态变更: ${state} | 电压: ${voltage}V`);
}
break;
default:
- notification = `[电源] ${event}: 状态=${state}, 电压=${voltage}V`;
+ notification = _t('terminal.powerEvent', { event, state, voltage }, `[电源] ${event}: 状态=${state}, 电压=${voltage}V`);
}
if (notification) {
@@ -504,7 +505,7 @@ class WebTerminal {
data: command
}));
} else {
- this.writeln('\x1b[1;31m未连接到设备\x1b[0m');
+ this.writeln('\x1b[1;31m' + (typeof t === 'function' ? t('terminal.notConnected') : '未连接到设备') + '\x1b[0m');
this.writePrompt();
}
}
@@ -527,7 +528,7 @@ class WebTerminal {
const portMatch = argsStr.match(/--port\s+(\d+)/i);
if (!hostMatch || !userMatch) {
- this.writeln('\x1b[1;31m错误: SSH shell 需要 --host 和 --user 参数\x1b[0m');
+ this.writeln('\x1b[1;31m' + (typeof t === 'function' ? t('terminal.sshShellRequiresParams') : '错误: SSH shell 需要 --host 和 --user 参数') + '\x1b[0m');
this.writePrompt();
return true;
}
@@ -548,13 +549,14 @@ class WebTerminal {
*/
startSshShell(params) {
if (this.sshConnecting || this.sshMode) {
- this.writeln('\x1b[1;31mSSH 会话已在进行中\x1b[0m');
+ this.writeln('\x1b[1;31m' + (typeof t === 'function' ? t('terminal.sshSessionInProgress') : 'SSH 会话已在进行中') + '\x1b[0m');
this.writePrompt();
return;
}
this.sshConnecting = true;
- this.writeln(`\x1b[36m正在连接到 ${params.user}@${params.host}:${params.port}...\x1b[0m`);
+ const connMsg = typeof t === 'function' ? t('terminal.sshConnectingTo', { user: params.user, host: params.host, port: params.port }) : `正在连接到 ${params.user}@${params.host}:${params.port}...`;
+ this.writeln(`\x1b[36m${connMsg}\x1b[0m`);
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
@@ -594,7 +596,7 @@ class WebTerminal {
this.sshMode = true;
this.sshConnecting = false;
this.writeln(`\x1b[1;32m${message}\x1b[0m`);
- this.writeln('\x1b[90m(按 Ctrl+\\ 退出 SSH shell)\x1b[0m');
+ this.writeln('\x1b[90m' + (typeof t === 'function' ? t('terminal.exitSshHint') : '(按 Ctrl+\\ 退出 SSH shell)') + '\x1b[0m');
this.writeln('');
break;
case 'closed':
diff --git a/docs/I18N_REMAINING_CHECK.md b/docs/I18N_REMAINING_CHECK.md
new file mode 100644
index 0000000..5522c1e
--- /dev/null
+++ b/docs/I18N_REMAINING_CHECK.md
@@ -0,0 +1,17 @@
+# i18n 收尾检查报告
+
+> 根据 `/Users/massif/Desktop/plan.md` 对项目逐项核查,所有剩余项已于 2025-02-12 完成 i18n 接入。
+
+## 已完成的修改概览
+
+| 类别 | 修改内容 |
+|------|----------|
+| 语言包 | 新增 common.loadFailedMsg、refreshOnce、uploadFailedMsg;toast.terminate*;terminal.errorLabel、unknownError;sshPage.getVarFailed、failMatchedTrue/False |
+| app.js | terminateProcess msgMap、数据组件工具栏/状态、错误文案、变量分组、图标预览、取消/密钥选择、配置包验证、匹配结果等 |
+| terminal.js | 错误输出文案 |
+| index.html | 首屏 Loading 增加 data-i18n |
+
+## 参考
+
+- 计划文档:`/Users/massif/Desktop/plan.md`
+- 语言包:`components/ts_webui/web/js/lang/zh-CN.js`、`en-US.js`