`;
@@ -20959,11 +20944,11 @@ async function submitAddRule(originalId = null) {
const manualTrigger = document.getElementById('rule-manual-only')?.checked || false;
if (!id) {
- alert('请输入规则 ID');
+ alert(typeof t === 'function' ? t('automation.pleaseEnterRuleId') : '请输入规则 ID');
return;
}
if (!name) {
- alert('请输入规则名称');
+ alert(typeof t === 'function' ? t('automation.pleaseEnterRuleName') : '请输入规则名称');
return;
}
@@ -21164,20 +21149,20 @@ function showExportSourceModal(sourceId) {
modal.innerHTML = `
-
导出数据源 ${escapeHtml(sourceId)} 的配置为加密配置包
+
${typeof t === 'function' ? t('automation.exportSourceDesc', {id: escapeHtml(sourceId)}) : `导出数据源 ${escapeHtml(sourceId)} 的配置为加密配置包`}
@@ -21197,7 +21182,7 @@ async function doExportSource(sourceId) {
const exportBtn = document.getElementById('export-source-btn');
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在生成配置包...';
+ resultBox.textContent = (typeof t === 'function' ? t('securityPage.generatingPack') : '正在生成配置包...');
exportBtn.disabled = true;
try {
@@ -21205,10 +21190,10 @@ async function doExportSource(sourceId) {
if (certText) params.recipient_cert = certText;
const result = await api.call('automation.sources.export', params);
- if (result.code !== 0) throw new Error(result.message || '导出失败');
+ if (result.code !== 0) throw new Error(result.message || (typeof t === 'function' ? t('toast.exportFailed') : '导出失败'));
const data = result.data;
- if (!data?.tscfg) throw new Error('无效的响应数据');
+ if (!data?.tscfg) throw new Error(typeof t === 'function' ? t('toast.invalidResponse') : '无效的响应数据');
// 下载文件
const blob = new Blob([data.tscfg], { type: 'application/json' });
@@ -21222,8 +21207,8 @@ async function doExportSource(sourceId) {
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.exportedSourceConfig', {filename: data.filename}) : `已导出数据源配置: ${data.filename}`), 'success');
setTimeout(() => hideExportSourceModal(), 1000);
} catch (e) {
resultBox.className = 'result-box error';
@@ -21248,32 +21233,32 @@ function showImportSourceModal() {
modal.innerHTML = `
-
选择 .tscfg 配置包文件以导入数据源
+
${typeof t === 'function' ? t('automation.importSourceDesc') : '选择 .tscfg 配置包文件以导入数据源'}
@@ -21301,7 +21286,7 @@ async function previewSourceImport() {
const file = fileInput.files[0];
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在验证配置包...';
+ resultBox.textContent = (typeof t === 'function' ? t('ssh.verifyingPack') : '正在验证配置包...');
importBtn.disabled = true;
step2.style.display = 'none';
@@ -21375,7 +21360,7 @@ async function confirmSourceImport() {
importBtn.disabled = false;
} else {
resultBox.className = 'result-box success';
- resultBox.innerHTML = `已保存配置:
${escapeHtml(data?.id)}重启系统后生效`;
+ resultBox.innerHTML = `${typeof t === 'function' ? t('securityPage.savedConfig') : 'Saved config'}:
${escapeHtml(data?.id)}${typeof t === 'function' ? t('securityPage.restartToApply') : 'Restart to apply'}`;
showToast(`已导入配置,重启后生效`, 'success');
setTimeout(() => hideImportSourceModal(), 2000);
}
@@ -21490,32 +21475,32 @@ function showImportRuleModal() {
modal.innerHTML = `
-
选择 .tscfg 配置包文件以导入规则
+
${typeof t === 'function' ? t('automation.importRuleDesc') : '选择 .tscfg 配置包文件以导入规则'}
@@ -21543,7 +21528,7 @@ async function previewRuleImport() {
const file = fileInput.files[0];
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在验证配置包...';
+ resultBox.textContent = (typeof t === 'function' ? t('ssh.verifyingPack') : '正在验证配置包...');
importBtn.disabled = true;
step2.style.display = 'none';
@@ -21617,7 +21602,7 @@ async function confirmRuleImport() {
importBtn.disabled = false;
} else {
resultBox.className = 'result-box success';
- resultBox.innerHTML = `已保存配置:
${escapeHtml(data?.id)}重启系统后生效`;
+ resultBox.innerHTML = `${typeof t === 'function' ? t('securityPage.savedConfig') : 'Saved config'}:
${escapeHtml(data?.id)}${typeof t === 'function' ? t('securityPage.restartToApply') : 'Restart to apply'}`;
showToast(`已导入配置,重启后生效`, 'success');
setTimeout(() => hideImportRuleModal(), 2000);
}
@@ -21648,20 +21633,20 @@ function showExportActionModal(actionId) {
modal.innerHTML = `
-
导出动作模板 ${escapeHtml(actionId)} 的配置为加密配置包
+
${typeof t === 'function' ? t('automation.exportActionDesc', {actionId: escapeHtml(actionId)}) : `导出动作模板 ${escapeHtml(actionId)} 的配置为加密配置包`}
@@ -21681,7 +21666,7 @@ async function doExportAction(actionId) {
const exportBtn = document.getElementById('export-action-btn');
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在生成配置包...';
+ resultBox.textContent = (typeof t === 'function' ? t('securityPage.generatingPack') : '正在生成配置包...');
exportBtn.disabled = true;
try {
@@ -21689,10 +21674,10 @@ async function doExportAction(actionId) {
if (certText) params.recipient_cert = certText;
const result = await api.call('automation.actions.export', params);
- if (result.code !== 0) throw new Error(result.message || '导出失败');
+ if (result.code !== 0) throw new Error(result.message || (typeof t === 'function' ? t('toast.exportFailed') : '导出失败'));
const data = result.data;
- if (!data?.tscfg) throw new Error('无效的响应数据');
+ if (!data?.tscfg) throw new Error(typeof t === 'function' ? t('toast.invalidResponse') : '无效的响应数据');
// 下载文件
const blob = new Blob([data.tscfg], { type: 'application/json' });
@@ -21706,8 +21691,8 @@ async function doExportAction(actionId) {
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.exportedActionTemplate', {filename: data.filename}) : `已导出动作模板: ${data.filename}`), 'success');
setTimeout(() => hideExportActionModal(), 1000);
} catch (e) {
resultBox.className = 'result-box error';
@@ -21732,32 +21717,32 @@ function showImportActionModal() {
modal.innerHTML = `
-
选择 .tscfg 配置包文件以导入动作模板
+
${typeof t === 'function' ? t('automation.importActionDesc') : '选择 .tscfg 配置包文件以导入动作模板'}
@@ -21785,7 +21770,7 @@ async function previewActionImport() {
const file = fileInput.files[0];
resultBox.classList.remove('hidden', 'success', 'error');
- resultBox.textContent = '正在验证配置包...';
+ resultBox.textContent = (typeof t === 'function' ? t('ssh.verifyingPack') : '正在验证配置包...');
importBtn.disabled = true;
step2.style.display = 'none';
@@ -21859,7 +21844,7 @@ async function confirmActionImport() {
importBtn.disabled = false;
} else {
resultBox.className = 'result-box success';
- resultBox.innerHTML = `已保存配置:
${escapeHtml(data?.id)}重启系统后生效`;
+ resultBox.innerHTML = `${typeof t === 'function' ? t('securityPage.savedConfig') : 'Saved config'}:
${escapeHtml(data?.id)}${typeof t === 'function' ? t('securityPage.restartToApply') : 'Restart to apply'}`;
showToast(`已导入配置,重启后生效`, 'success');
setTimeout(() => hideImportActionModal(), 2000);
}
diff --git a/components/ts_webui/web/js/i18n.js b/components/ts_webui/web/js/i18n.js
index 40306bf..050f89e 100644
--- a/components/ts_webui/web/js/i18n.js
+++ b/components/ts_webui/web/js/i18n.js
@@ -208,7 +208,7 @@ function selectLanguage(lang) {
}
}
-// 页面加载时初始化语言按钮
+// 页面加载时初始化语言按钮,并翻译整页(使刷新后导航栏等按当前语言显示)
document.addEventListener('DOMContentLoaded', function() {
i18n.init();
const langs = i18n.getSupportedLanguages();
@@ -217,4 +217,5 @@ document.addEventListener('DOMContentLoaded', function() {
if (nameEl && langs[currentLang]) {
nameEl.textContent = currentLang === 'zh-CN' ? '中文' : 'EN';
}
+ i18n.translateDOM();
});
diff --git a/components/ts_webui/web/js/lang/en-US.js b/components/ts_webui/web/js/lang/en-US.js
index af59759..5926356 100644
--- a/components/ts_webui/web/js/lang/en-US.js
+++ b/components/ts_webui/web/js/lang/en-US.js
@@ -1,7 +1,7 @@
/**
* TianShanOS WebUI - English Language Pack
*/
-i18n.registerLanguage('en-US', {
+if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', {
// Common
common: {
confirm: 'Confirm',
@@ -118,6 +118,7 @@ i18n.registerLanguage('en-US', {
timezone: 'Timezone',
customTimezone: 'Or custom timezone string',
timezoneExample: 'e.g.: CST-8',
+ timezoneSetSuccess: 'Timezone set to {timezone}, local time: {localTime}',
tzChinaStandard: 'China Standard Time (UTC+8)',
tzJapanStandard: 'Japan Standard Time (UTC+9)',
tzKoreaStandard: 'Korea Standard Time (UTC+9)',
@@ -139,7 +140,8 @@ i18n.registerLanguage('en-US', {
high: 'High',
low: 'Low',
host: 'Host',
- device: 'Device'
+ device: 'Device',
+ logs: 'Logs'
},
// Status labels
@@ -169,7 +171,7 @@ i18n.registerLanguage('en-US', {
system: 'System',
led: 'LED Control',
network: 'Network',
- files: 'File Manager',
+ files: 'Files',
ssh: 'SSH Commands',
security: 'Security',
ota: 'OTA Update',
@@ -181,8 +183,22 @@ i18n.registerLanguage('en-US', {
// System Overview
system: {
title: 'System Overview',
+ resourceMonitor: 'Resource Monitor',
+ detail: 'Details',
overview: 'System Info',
chip: 'Chip',
+ internal: 'Internal',
+ timeSync: 'Time Sync',
+ devicePanel: 'Device Panel',
+ serviceStatusTitle: 'Service Status',
+ stage: 'Stage',
+ health: 'Health',
+ toggleProtectionTitle: 'Click to toggle protection',
+ switchFailed: 'Switch failed',
+ usbMuxNotConfigured: 'USB MUX not configured',
+ usbSwitchTo: 'Switching USB to {name}...',
+ usbSwitchedTo: 'USB switched to {name}',
+ noDataWidgetsYet: 'No data widgets added yet',
firmware: 'Firmware',
version: 'Version',
idfVersion: 'IDF Version',
@@ -243,6 +259,12 @@ i18n.registerLanguage('en-US', {
currentTime: 'Current Time',
timeStatus: 'Sync Status',
timeSource: 'Time Source',
+ timeSynced: 'Synced',
+ timeNotSynced: 'Not synced',
+ timeSourceNtp: 'NTP',
+ timeSourceHttp: 'Browser',
+ timeSourceManual: 'Manual',
+ timeSourceNone: 'Not synced',
timezone: 'Timezone',
syncTime: 'Sync Time',
@@ -259,7 +281,35 @@ i18n.registerLanguage('en-US', {
quickActions: 'Quick Actions',
reboot: 'Reboot',
confirmReboot: 'Confirm system reboot?',
- rebooting: 'Rebooting...'
+ rebooting: 'Rebooting...',
+ rebootConfirm: 'Reboot system?',
+ rebootSending: 'Sending reboot command...',
+ rebootingPleaseWait: 'System is rebooting, please wait...',
+ rebootFailed: 'Reboot failed',
+ agxPowerOnTitle: 'Click to turn on AGX power',
+ agxPowerOffTitle: 'Click to turn off AGX power',
+ agxPowerOn: 'Power on',
+ agxPowerOff: 'Power off',
+ agxPoweringOn: 'AGX powering on...',
+ agxPoweringOff: 'AGX powering off...',
+ agxPowerOnSuccess: 'AGX powered on',
+ agxPowerOffSuccess: 'AGX powered off',
+ agxPowerFail: 'AGX operation failed',
+ lpmuRunning: 'LPMU Running',
+ lpmuStopped: 'LPMU Stopped',
+ lpmuOnlineTitle: 'LPMU online (ping 10.10.99.99 reachable)\nClick to trigger power button',
+ lpmuOfflineTitle: 'LPMU offline (ping 10.10.99.99 unreachable)\nClick to trigger power button',
+ lpmuDetectingTitle: 'Detecting LPMU status...\nWait up to 80 seconds',
+ lpmuUnknownTitle: 'LPMU status unknown\nClick to trigger power button',
+ statusFetching: 'Fetching status',
+ lpmuTriggerConfirm: 'Trigger LPMU power button?\n\nThis sends a pulse, like pressing the physical power button.',
+ lpmuTriggering: 'LPMU power trigger...',
+ lpmuTriggerSuccess: 'LPMU trigger sent, detecting status...',
+ lpmuTriggerFail: 'LPMU trigger failed',
+ lpmuOnlineSuccess: 'LPMU is online',
+ lpmuOfflineSuccess: 'LPMU is off',
+ lpmuStartupTimeout: 'LPMU startup detection timeout, assumed off',
+ lpmuShutdownTimeout: 'LPMU shutdown timeout, device may still be running'
},
// Fan Control
@@ -287,7 +337,8 @@ i18n.registerLanguage('en-US', {
duty: 'Duty',
invalidCurve: 'Min duty cannot be greater than max duty',
minCurvePoints: 'At least 2 curve points required',
- maxCurvePoints: 'Maximum 10 curve points supported'
+ maxCurvePoints: 'Maximum 10 curve points supported',
+ statusUnavailable: 'Fan status unavailable'
},
// LED Control
@@ -332,6 +383,7 @@ i18n.registerLanguage('en-US', {
// Network
network: {
title: 'Network Settings',
+ connection: 'Network Connection',
status: 'Network Status',
ethernet: 'Ethernet',
wifi: 'WiFi',
@@ -360,7 +412,26 @@ i18n.registerLanguage('en-US', {
linkUp: 'Link Up',
linkDown: 'Link Down',
noIp: 'No IP',
- obtaining: 'Obtaining...'
+ obtaining: 'Obtaining...',
+ interfaceConfig: 'Interface Config',
+ networkServices: 'Network Services',
+ linkStatus: 'Link Status',
+ mode: 'Mode',
+ off: 'Off',
+ sta: 'Station (STA)',
+ ap: 'Access Point (AP)',
+ apsta: 'STA+AP',
+ stationConnect: 'Station',
+ scan: 'Scan',
+ disconnect: 'Disconnect',
+ hotspot: 'Hotspot',
+ config: 'Config',
+ devices: 'Devices',
+ clientCount: 'Clients',
+ newHostname: 'New hostname',
+ set: 'Set',
+ clients: 'Clients',
+ signal: 'Signal'
},
// File Manager
@@ -389,7 +460,24 @@ i18n.registerLanguage('en-US', {
sdcard: 'SD Card',
spiffs: 'SPIFFS',
confirmDeleteFile: 'Delete file "{name}"?',
- confirmDeleteFolder: 'Delete folder "{name}" and all its contents?'
+ confirmDeleteFolder: 'Delete folder "{name}" and all its contents?',
+ selectedCount: 'Selected {n} items',
+ batchDownload: 'Batch Download',
+ batchDelete: 'Batch Delete',
+ clearSelection: 'Clear Selection',
+ uploadTitle: 'Upload File',
+ clickOrDrag: 'Click to select or drag files here',
+ newFolderTitle: 'New Folder',
+ folderName: 'Folder name',
+ folderNamePlaceholder: 'Enter folder name',
+ create: 'Create',
+ renameTitle: 'Rename',
+ newName: 'New name',
+ newNamePlaceholder: 'Enter new name',
+ name: 'Name',
+ size: 'Size',
+ action: 'Action',
+ selectAll: 'Select all'
},
// SSH Commands
@@ -397,6 +485,7 @@ i18n.registerLanguage('en-US', {
title: 'SSH Commands',
hosts: 'Hosts',
commands: 'Commands',
+ commandList: 'Command List',
addHost: 'Add Host',
editHost: 'Edit Host',
deleteHost: 'Delete Host',
@@ -430,7 +519,85 @@ i18n.registerLanguage('en-US', {
connectionSuccess: 'Connection Successful',
connectionFailed: 'Connection Failed',
exportConfig: 'Export Config',
- importConfig: 'Import Config'
+ importConfig: 'Import Config',
+ // Empty states
+ selectHostFirst: 'Please select a host first',
+ createFirstCommand: 'Create your first command',
+ noCommandsForHost: 'No commands for this host',
+ // Command modal
+ newCommand: 'New Command',
+ nohupTitle: 'Background (nohup) Settings',
+ nohupHint: 'Command will run in background with nohup when enabled',
+ varNameLabel: 'Variable Name',
+ varNamePlaceholder: 'e.g.: ping_test',
+ varNameHint: 'Results stored as ${varName}.status, ${varName}.extracted, etc.',
+ timeoutLabel: 'Timeout',
+ timeoutUnit: 'ms',
+ timeoutHint: 'Timeout only applies when success/failure patterns are set or "Stop on Match" is enabled',
+ // Export modal
+ exportCmdTitle: 'Export SSH Command Config',
+ exportCmdDesc: 'Export commands for host {hostId} as encrypted config',
+ includeHostConfig: 'Include host configuration (address, port, credentials)',
+ includeHostHint: 'If unchecked, only command definitions will be exported without host info',
+ targetCert: 'Target Device Certificate (PEM)',
+ targetCertPlaceholder: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
+ targetCertHint: 'Paste target device certificate. Leave empty for self-encryption',
+ generatePack: 'Generate Config Pack',
+ generatedPack: 'Generated Config Pack (.tscfg)',
+ packPlaceholder: 'Config pack will appear here...',
+ copyToClipboard: 'Copy to Clipboard',
+ downloadPack: 'Download',
+ exportStatus: {
+ generating: 'Generating config pack...',
+ success: 'Pack generated successfully',
+ failed: 'Generation failed'
+ },
+ // Import modal
+ importCmdTitle: 'Import SSH Command Config',
+ importCmdDesc: 'Select .tscfg file to import SSH command configuration',
+ selectTscfgFile: 'Select .tscfg File',
+ orPasteJson: 'Or paste JSON content',
+ previewTitle: 'Configuration Preview',
+ previewHostInfo: 'Host Info',
+ previewCommands: 'Commands ({count})',
+ previewHost: 'Host',
+ previewAddress: 'Address',
+ previewPort: 'Port',
+ previewUsername: 'Username',
+ previewName: 'Name',
+ previewCommand: 'Command',
+ noPreview: 'Select a file or paste JSON to preview',
+ verifyingPack: 'Verifying config pack...',
+ verifyOnly: 'Verify Only',
+ confirmImport: 'Confirm Import',
+ // Command editor (nohup/pattern) - referenced as ssh.XXX
+ readyPatternRequired: 'Ready Pattern',
+ readyPatternHint: 'Mark as ready when this string appears in log (supports | for multiple patterns)',
+ cmdReadyPatternPlaceholder: 'e.g.: Running on|Server started',
+ serviceFailPatternHint: 'Mark as failed when this string appears in log (optional, supports | for multiple patterns)',
+ cmdFailPatternPlaceholder: 'e.g.: error|failed|Exception',
+ readyTimeoutLabel: 'Timeout (seconds)',
+ readyTimeoutHint: 'Mark as timeout if ready pattern not matched within this time',
+ readyIntervalLabel: 'Check Interval (ms)',
+ readyIntervalHint: 'How often to check the log file',
+ serviceLogHint: 'After service starts, system will monitor log file:',
+ serviceLogPath: '/tmp/ts_nohup_[cmd_name].log',
+ serviceStatusHint: 'Variable [var_name].status will auto-update based on log matching',
+ varNameLabel: 'Variable Name',
+ cmdVarNamePlaceholder: 'e.g.: ping_test',
+ varNameHint: 'Results stored as ${varName}.status, ${varName}.extracted, etc.',
+ successPatternLabel: 'Success Pattern',
+ cmdExpectPatternPlaceholder: 'e.g.: active (running)',
+ successPatternHint: 'Mark as success when output contains this text',
+ failPatternLabel: 'Fail Pattern',
+ failPatternHint: 'Mark as failed when output contains this text',
+ extractPatternLabel: 'Extract Pattern',
+ cmdExtractPatternPlaceholder: 'e.g.: version: (.*)',
+ extractPatternHint: 'Extract matched content using (.*) capture group',
+ stopOnMatchLabel: 'Stop on Match',
+ stopOnMatchHint: 'For continuous commands like ping, auto-terminate on match',
+ serviceModeLabel: 'Service Mode (monitor ready state)',
+ serviceModeHint: 'Continuously monitor logs after startup, update variable status'
},
// Security
@@ -456,16 +623,29 @@ i18n.registerLanguage('en-US', {
tokenExpiry: 'Expiry',
certificates: 'Certificates',
https: 'HTTPS',
- mtls: 'mTLS Authentication'
+ mtls: 'mTLS Authentication',
+ keyManagement: 'Key Management',
+ httpsCert: 'HTTPS Certificate'
},
// OTA Update
ota: {
title: 'OTA Update',
+ firmwareUpgrade: 'Firmware Upgrade',
currentVersion: 'Current Version',
serverUrl: 'OTA Server',
- saveServer: 'Save Server',
+ saveServer: 'Save',
checkUpdate: 'Check Update',
+ partitionManage: 'Partition Management',
+ manualUpgrade: 'Manual Upgrade',
+ fromUrl: 'Upgrade from URL',
+ fromSd: 'Upgrade from SD Card',
+ includeWebUI: 'Include WebUI',
+ skipVerify: 'Skip Verify',
+ upgrade: 'Upgrade',
+ abort: 'Abort',
+ preparing: 'Preparing...',
+ saveToDevice: 'Save to device',
checking: 'Checking...',
noUpdate: 'Already up to date',
updateAvailable: 'Update Available',
@@ -483,7 +663,13 @@ i18n.registerLanguage('en-US', {
rollbackAvailable: 'Rollback to previous version available',
firmwareInfo: 'Firmware Info',
buildTime: 'Build Time',
- partitionInfo: 'Partition Info'
+ partitionInfo: 'Partition Info',
+ partitionRunning: 'Running',
+ partitionBootable: 'Bootable',
+ partitionIdle: 'Idle',
+ disableRollbackDesc: 'Disable auto rollback protection',
+ rollbackToThis: 'Rollback to this version',
+ loadAfterReboot: 'Load this partition after reboot'
},
// Automation
@@ -509,7 +695,7 @@ i18n.registerLanguage('en-US', {
ruleId: 'Rule ID',
ruleName: 'Rule Name',
conditions: 'Conditions',
- conditionLogic: 'Condition Logic',
+ conditionLogic: 'Logic',
logicAnd: 'All (AND)',
logicOr: 'Any (OR)',
cooldown: 'Cooldown',
@@ -553,7 +739,158 @@ i18n.registerLanguage('en-US', {
exportActionTitle: 'Export Action Template',
exportActionDesc: 'Export action {id} config as encrypted package',
importActionTitle: 'Import Action Template',
- importActionDesc: 'Select .tscfg config file to import action template'
+ importActionDesc: 'Select .tscfg config file to import action template',
+ // Add source modal
+ addSourceTitle: 'Add External Data Source',
+ restTab: 'REST API',
+ wsTab: 'WebSocket',
+ sioTab: 'Socket.IO',
+ variableTab: 'Command Variable',
+ sourceIdPlaceholder: 'e.g. agx_temp',
+ sourceLabelPlaceholder: 'e.g. AGX Temperature',
+ restConfigTitle: 'REST API Configuration',
+ requestUrlReq: 'Request URL',
+ testBtn: 'Test',
+ pollIntervalMs: 'Poll interval (ms)',
+ authHeaderOptional: 'Authorization header (optional)',
+ authPlaceholder: 'Bearer token',
+ selectFieldsToExtract: 'Select fields to extract:',
+ jsonPathLabel: 'JSON path',
+ jsonPathHint: '(click field above to fill)',
+ jsonPathPlaceholderRest: 'e.g. data.temperature (empty = whole response)',
+ wsConfigTitle: 'WebSocket Configuration',
+ wsAddressReq: 'WebSocket URL',
+ wsPlaceholder: 'ws://192.168.1.100:8080/ws',
+ jsonPathPlaceholderWs: 'e.g. data.temperature (empty = whole message)',
+ reconnectIntervalMs: 'Reconnect interval (ms)',
+ sioConfigTitle: 'Socket.IO Configuration',
+ sioServerReq: 'Server URL',
+ sioPlaceholder: 'http://10.10.99.99:59090',
+ sioV4Hint: 'Socket.IO v4 protocol, use HTTP/HTTPS URL',
+ eventNameLabel: 'Event name',
+ eventNameHint: '(empty = auto discover)',
+ eventNamePlaceholder: 'Leave empty to auto-discover on test',
+ timeoutMs: 'Timeout (ms)',
+ autoDiscoverFields: 'Auto-discover all JSON fields as variables',
+ autoDiscoverHint: 'When off, only selected fields above are used as variables',
+ jsonPathPlaceholderSio: 'e.g. cpu.avg_usage (empty = whole event data)',
+ variableConfigTitle: 'SSH Command Variable',
+ sshHostReq: 'SSH Host',
+ sshHostHint: 'Select a configured SSH host (add in SSH page)',
+ selectCmdReq: 'Select Command',
+ selectCmdHint: 'Select command to monitor (create in SSH page)',
+ selectHostFirst: '-- Select host first --',
+ cmdPreviewTitle: 'Command details',
+ commandLabel: 'Command:',
+ descLabel: 'Description:',
+ timeoutLabel: 'Timeout:',
+ seconds: 's',
+ varsPreviewTitle: 'Will monitor these variables (run command first):',
+ selectHostCmdFirst: 'Please select SSH host and command first',
+ pollIntervalSec: 'Poll interval (sec)',
+ pollIntervalHint: 'Interval for reading variable values',
+ enableAfterCreate: 'Enable after creation',
+ // Add/Edit rule modal
+ ruleIdPlaceholder: 'Unique ID',
+ ruleNamePlaceholder: 'Rule display name',
+ iconLabel: 'Icon',
+ iconTab: 'Icon',
+ imageTab: 'Image',
+ customPlaceholder: 'Custom',
+ selectImagePlaceholder: 'Select image...',
+ previewNone: 'None',
+ cooldownMs: 'Cooldown (ms)',
+ enableImmediately: 'Enable immediately',
+ triggerConditions: 'Trigger conditions',
+ manualOnly: 'Manual trigger only',
+ addConditionHint: 'Click "Add" to create trigger conditions, or check "Manual trigger only" for quick action',
+ addConditionHintShort: 'Click "Add" to create trigger conditions',
+ executionActions: 'Execution actions',
+ selectFromTemplatesHint: 'Select actions to run from created action templates',
+ createActionFirstHint: 'Create actions in the "Action Templates" section first, then select them here',
+ pleaseEnterRuleId: 'Please enter Rule ID',
+ pleaseEnterRuleName: 'Please enter Rule name',
+ uniqueIdHint: 'Unique ID: letters, numbers, underscore, hyphen only; cannot start or end with _ or -',
+ // New action template modal
+ newActionTemplate: 'New Action Template',
+ selectActionType: 'Select action type',
+ configParams: 'Configuration',
+ basicInfo: 'Basic info',
+ actionIdPlaceholder: 'Unique ID, e.g. restart_agx',
+ actionIdHint: 'For rule reference, letters, numbers and underscore only',
+ displayNamePlaceholder: 'e.g. Restart AGX',
+ leaveEmptyUseId: 'Leave empty to use ID',
+ actionDescPlaceholder: 'Description (optional)',
+ executionDelay: 'Execution delay',
+ asyncExecute: 'Async execution',
+ asyncExecuteDesc: 'API returns immediately, action runs in background queue',
+ pleaseSelectActionType: 'Please select action type',
+ selectTriggerVarTitle: 'Select trigger condition variable',
+ selectConditionVar: 'Select Condition Variable',
+ systemVariables: 'System Variables',
+ searchVarPlaceholder: 'Search variables...',
+ loadingVarList: 'Loading variable list...',
+ noVariablesAvailable: 'No variables available',
+ actionTypeCli: 'CLI Command',
+ actionTypeCliDesc: 'Execute local console command',
+ actionTypeSsh: 'SSH Command',
+ actionTypeSshDesc: 'Execute configured SSH command',
+ actionTypeLed: 'LED Control',
+ actionTypeLedDesc: 'Control LED color and effects',
+ actionTypeLog: 'Log',
+ actionTypeLogDesc: 'Output log message',
+ actionTypeSetVar: 'Set Variable',
+ actionTypeSetVarDesc: 'Modify system variable value',
+ actionTypeWebhook: 'Webhook',
+ actionTypeWebhookDesc: 'Send HTTP request',
+ // Variable selection
+ selectVariable: '-- Select Variable --',
+ selectVariableTitle: 'Select Variable',
+ searchVariable: 'Search variables...',
+ loadingVariables: 'Loading variable list...',
+ // CLI command config
+ cliCmdLabel: 'Command Line',
+ cliCmdRequired: '(required)',
+ cliCmdPlaceholder: 'e.g.: gpio --set 48 1',
+ cliCmdHint: 'Supports all console commands: gpio, device, fan, led, net, etc.',
+ quickCommands: 'Quick Commands:',
+ quickGpio: 'GPIO',
+ quickAgxPower: 'AGX Power',
+ quickAgxReset: 'AGX Reset',
+ quickFan: 'Fan',
+ quickLed: 'LED',
+ advancedOptions: 'Advanced Options',
+ resultVariable: 'Result Variable',
+ resultVarPlaceholder: 'e.g.: cli.result',
+ resultVarHint: 'Store command output to variable',
+ timeout: 'Timeout',
+ ms: 'ms',
+ // SSH command config
+ sshHostLabel: 'SSH Host',
+ selectSshHost: '-- Select Host --',
+ sshCmdLabel: 'SSH Command',
+ selectSshCmd: '-- Select Command --',
+ cmdDetailsTitle: 'Command Details',
+ // LED control config
+ ledDeviceLabel: 'LED Device',
+ selectLedDevice: '-- Select Device --',
+ ledTypeLabel: 'Operation Type',
+ ledTypeFill: 'Fill Color',
+ ledTypeEffect: 'Animation Effect',
+ ledTypeOff: 'Turn Off',
+ // Save action button
+ saveAction: 'Save Action',
+ // Export modals
+ exportSourceConfigPack: 'Generate Config Pack',
+ exportSourceGeneratedPack: 'Generated Config Pack (.tscfg)',
+ exportSourcePackPlaceholder: 'Config pack will appear here...',
+ copyToClipboardBtn: 'Copy to Clipboard',
+ downloadToLocal: 'Download',
+ exportStatus: {
+ generating: 'Generating config pack...',
+ success: 'Pack generated successfully',
+ failed: 'Generation failed'
+ }
},
// Settings
@@ -601,11 +938,23 @@ i18n.registerLanguage('en-US', {
loginButton: 'Login',
loggingIn: 'Logging in...',
loginFailed: 'Login Failed',
+ welcomeName: 'Welcome, {name}!',
+ networkError: 'Network error',
+ passwordChanged: 'Password changed successfully!',
invalidCredentials: 'Invalid username or password',
firstTimeSetup: 'First time setup, please set a password',
setPassword: 'Set Password',
confirmPassword: 'Confirm Password',
- passwordSet: 'Password set successfully'
+ passwordSet: 'Password set successfully',
+ securityReminder: 'Security Reminder',
+ defaultPasswordHint: 'You are using the default password. Change it now for security.',
+ currentPassword: 'Current Password',
+ newPassword: 'New Password (4-64 chars)',
+ confirmNewPassword: 'Confirm New Password',
+ changeLater: 'Later',
+ changeNow: 'Change Now',
+ passwordMismatch: 'New passwords do not match',
+ passwordMinLength: 'New password must be at least 4 characters'
},
// Time Units
@@ -684,6 +1033,7 @@ i18n.registerLanguage('en-US', {
wifiDisconnected: 'WiFi disconnected',
natEnabled: 'NAT enabled',
natDisabled: 'NAT disabled',
+ natConfigSaved: 'NAT config saved',
hotspotApplied: 'Hotspot config applied',
connecting: 'Connecting...',
connectionFailed: 'Connection failed',
@@ -695,6 +1045,7 @@ i18n.registerLanguage('en-US', {
saveFailed: 'Save failed',
deleteFailed: 'Delete failed',
uploadFailed: 'Upload failed',
+ rootRequired: 'This page requires root access',
downloadFailed: 'Download failed',
renameFailed: 'Rename failed',
createFailed: 'Create failed',
@@ -748,6 +1099,7 @@ i18n.registerLanguage('en-US', {
refreshIntervalSet: 'Refresh interval set to {interval}',
refreshDisabled: 'disabled',
widgetAdded: 'Added {name}',
+ widgetSaved: 'Widget saved',
widgetDeleted: 'Deleted {name}',
widgetBound: 'Bound {widget} → {var}',
// Quick actions
@@ -760,6 +1112,10 @@ i18n.registerLanguage('en-US', {
operationComplete: 'Operation complete',
// Time sync
timeSynced: 'Time synced: {datetime}',
+ timeSyncSyncing: 'Syncing time from browser...',
+ timeSyncFailed: 'Time sync failed',
+ syncFailed: 'Sync failed',
+ setFailed: 'Setting failed',
ntpSyncing: 'Forcing NTP sync...',
ntpStarted: 'NTP sync started, refresh to see results',
ntpFailed: 'NTP sync failed',
@@ -795,6 +1151,16 @@ i18n.registerLanguage('en-US', {
enterKeyId: 'Please enter key ID',
generatingKey: 'Generating key...',
terminalDisconnected: 'Terminal disconnected',
+ // Export/Import
+ exportFailed: 'Export failed',
+ exportSuccess: 'Export successful',
+ invalidResponse: 'Invalid response data',
+ exportedSourceConfig: 'Exported data source config: {filename}',
+ exportedCmdWithHost: 'Exported command config (with host {hostId}): {filename}',
+ exportedCmdConfig: 'Exported command config: {filename}',
+ exportedActionTemplate: 'Exported action template: {filename}',
+ selectFileFirst: 'Please select a file first',
+ importedRestartRequired: 'Imported config, restart required',
// OTA
enterFirmwareUrl: 'Please enter firmware URL',
urlMustHttp: 'URL must start with http:// or https://',
@@ -993,6 +1359,8 @@ i18n.registerLanguage('en-US', {
dynamic: 'Dynamic',
leases: 'leases',
activeLeases: 'active leases',
+ leasesCount: '{n} leases',
+ activeLeasesCount: '{n} active leases',
// WiFi modes
modeOff: 'Off',
modeSta: 'Station (STA)',
@@ -1017,6 +1385,7 @@ i18n.registerLanguage('en-US', {
gateway: 'Gateway',
signal: 'Signal',
clientCount: 'Clients',
+ devicesCount: '{count} devices',
mode: 'Mode',
// STA/AP
staConnection: 'Station Connection',
@@ -1078,9 +1447,19 @@ i18n.registerLanguage('en-US', {
// SD Card
unmountSdCard: 'Unmount SD Card',
mountSdCard: 'Mount SD Card',
+ unmountSdBtn: 'Unmount SD',
+ mountSdBtn: 'Mount SD',
notMounted: 'Not Mounted',
mounted: 'Mounted',
sdCardNotMounted: 'SD Card Not Mounted',
+ mountingSd: 'Mounting SD card...',
+ mountSdSuccess: 'SD card mounted',
+ mountSdFailed: 'Mount failed',
+ confirmUnmountSd: 'Unmount SD card?\n\nYou will not be able to access files on the SD card after unmounting.',
+ unmountingSd: 'Unmounting SD card...',
+ unmountSdSuccess: 'SD card unmounted',
+ unmountSdFailed: 'Unmount failed',
+ loadFailed: 'Load failed',
// Batch operations
batchDownload: 'Batch Download',
batchDelete: 'Batch Delete',
@@ -1471,7 +1850,7 @@ i18n.registerLanguage('en-US', {
generatedConfigPack: 'Generated Config Pack (.tscfg)',
configPackPlaceholder: 'Config pack will be displayed here...',
copyToClipboard: 'Copy to Clipboard',
- downloadToLocal: '💾 Download',
+ downloadToLocal: 'Download',
// File selection status
selectedFiles: '{count} file(s) selected',
filesLoading: 'loading...',
@@ -1482,7 +1861,7 @@ i18n.registerLanguage('en-US', {
revokeHostHint: 'Host will be removed from list after successful revocation',
serverPassword: 'Server Password',
serverPasswordPlaceholder: 'Enter SSH password',
- revokeAndRemove: '🔓 Revoke & Remove',
+ revokeAndRemove: 'Revoke & Remove',
// SSH key management page
sshKeyPairs: 'SSH Key Pairs',
deployedHosts: 'Deployed Hosts',
@@ -1495,7 +1874,8 @@ i18n.registerLanguage('en-US', {
keysTableExportable: 'Exportable',
keysTableActions: 'Actions',
hostsHint: 'After deploying public key via the "Deploy" button above, host will appear in this list',
- importHost: '📥 Import Host',
+ importHost: 'Import Host',
+ host: 'Host',
hostId: 'Host ID',
address: 'Address',
port: 'Port',
@@ -1509,16 +1889,16 @@ i18n.registerLanguage('en-US', {
exportSshHostTitle: 'Export SSH Host Config',
exportSshHostDesc: 'Export host {hostId} config as encrypted config package',
exportSshHostCertHint: 'Paste target device certificate. Leave empty to use local certificate (self-encrypt)',
- importSshHostTitle: '📥 Import SSH Host Config',
+ importSshHostTitle: 'Import SSH Host Config',
importSshHostDesc: 'Select .tscfg config package file to import SSH host config',
- confirmImport: '📥 Confirm Import',
+ confirmImport: 'Confirm Import',
// Deploy key modal
- deployKeyTitle: '🚀 Deploy Public Key to Remote Server',
+ deployKeyTitle: 'Deploy Public Key to Remote Server',
deployKeyDesc: 'Deploy public key {keyId} to remote server authorized_keys',
targetHost: 'Target Host',
authPassword: 'Auth Password (required for first deploy)',
deployHint: 'After successful deployment, host will be added to "Deployed Hosts" list for passwordless login',
- startDeploy: '🚀 Start Deploy',
+ startDeploy: 'Start Deploy',
// Revoke key modal
revokeKeyTitle: 'Revoke Public Key',
revokeKeyDesc: 'Remove public key {keyId} from remote server',
@@ -1594,9 +1974,149 @@ i18n.registerLanguage('en-US', {
deselectAll: 'Deselect All',
selectDir: 'Select Directory',
packListTitle: 'Config Pack List',
+ packListDirPath: 'Directory Path',
+ noTscfgInDir: 'No .tscfg files in directory',
+ packListLoadFailed: 'Load failed',
+ confirmImportPack: 'Import config pack: {path}?',
+ importPackSuccess: 'Config pack imported',
+ importPackFailed: 'Import failed',
deviceType: 'Device Type',
certCN: 'Certificate CN',
- formatVersion: 'Format Version'
+ formatVersion: 'Format Version',
+ // Additional keys for i18n
+ expiredDays: 'Expired {days} days ago',
+ expiringDays: 'Expiring in {days} days',
+ remainingDays: '{days} days remaining',
+ developerDevice: 'Developer Device',
+ normalDevice: 'Device',
+ onlyDeveloperCanExport: 'Only Developer devices can export config packs',
+ // Generate key modal
+ generateNewKey: 'Generate New Key',
+ keyId: 'Key ID',
+ keyIdPlaceholder: 'e.g.: default, mykey',
+ rsaRecommended: 'RSA 2048-bit (Recommended)',
+ ecdsaWarning: 'ECDSA keys do not support SSH public key auth, please use RSA',
+ commentOptional: 'Comment (Optional)',
+ commentPlaceholder: 'e.g.: TianshanOS@device',
+ aliasOptional: 'Alias (Optional)',
+ aliasPlaceholder: 'Used to display instead of Key ID',
+ aliasHint: 'Recommended when "Hide Key ID" is enabled',
+ allowExportPrivateKey: 'Allow Export Private Key',
+ hideKeyId: 'Hide Key ID',
+ hideKeyIdHint: 'When enabled, low-privilege users cannot see the real Key ID',
+ // Deploy key modal
+ deployKeyDescPre: 'Deploy public key',
+ deployKeyDescPost: 'to remote server authorized_keys',
+ hostPlaceholder: '192.168.55.100 or hostname',
+ passwordPlaceholder: 'Enter SSH login password',
+ deployInfoHint: 'After successful deployment, host will be added to "Deployed Hosts" list for passwordless login',
+ // Revoke key modal
+ revokeKeyDescPre: 'Remove public key from remote server',
+ authPasswordRevoke: 'Auth Password',
+ revokeKey: 'Revoke Key',
+ // Host mismatch modal
+ hostKeyMismatchTitle: 'Security Warning: Host Key Mismatch!',
+ hostKeyChangedWarning: 'Host key has changed! This may indicate:',
+ mitmAttack: 'Man-in-the-Middle Attack',
+ serverReinstalled: 'Server reinstalled or key regenerated',
+ ipReassigned: 'IP address assigned to different server',
+ hostLabel: 'Host',
+ suggestion: 'Suggestion',
+ hostKeyMismatchSuggestion: 'If you confirm the server was reinstalled or key was updated, click "Update Host Key" to remove old record, then reconnect to trust new key.',
+ // HTTPS cert modals
+ genHttpsKeyTitle: 'Generate HTTPS Key Pair',
+ genHttpsKeyDesc: 'Generate ECDSA P-256 key pair for mTLS authentication',
+ deviceIdCn: 'Device ID (CN)',
+ leaveEmptyForDefault: 'Leave empty for default config',
+ csrContentLabel: 'CSR Content (copy to CA server for signing)',
+ generateCsrTitle: 'Generate Certificate Signing Request',
+ installCertTitle: 'Install Device Certificate',
+ installCertDesc: 'Paste CA-signed PEM format certificate',
+ certPem: 'Certificate PEM',
+ installCaTitle: 'Install CA Certificate Chain',
+ installCaDesc: 'Paste root and intermediate certificates (PEM format, can concatenate)',
+ caCertPem: 'CA Certificate Chain PEM',
+ viewCertTitle: 'View Device Certificate',
+ // Key table actions
+ publicKey: 'Public Key',
+ privateKey: 'Private Key',
+ cannotExportPrivateKey: 'This key cannot export private key',
+ exportPrivateKey: 'Export Private Key',
+ deployToServer: 'Deploy public key to remote server',
+ revokeFromServer: 'Revoke public key from remote server',
+ deploy: 'Deploy',
+ revoke: 'Revoke',
+ // HTTPS table
+ noCertInstalled: '(No cert installed)',
+ noKeyGenerated: 'Key not generated',
+ noKeysClickToGenerate: 'No keys, click button above to generate new key',
+ // Deployed hosts
+ testConnection: 'Test Connection',
+ exportAsTscfg: 'Export config as .tscfg',
+ revokePubkey: 'Revoke Public Key',
+ removeLocalRecord: 'Remove local record only',
+ remove: 'Remove',
+ noDeployedHostsHint: 'No deployed hosts, please click "Deploy" in key management above',
+ noKnownHostFingerprints: 'No known host fingerprints',
+ // Config pack modals
+ exportCertTitle: 'Export Device Certificate',
+ exportCertDesc: 'Send this certificate to developers who need to send you encrypted configs',
+ certFingerprint: 'Certificate Fingerprint (SHA256)',
+ certCn: 'Certificate CN',
+ importPackTitle: 'Import Config Pack',
+ importPackDesc: 'Upload or paste .tscfg config pack, verify and save to device (encrypted)',
+ orPasteJson: 'Or paste JSON content',
+ configPackInfo: 'Config Pack Info',
+ verifyOnly: 'Verify Only',
+ exportPackTitle: 'Export Encrypted Config Pack',
+ exportPackDesc: 'Select config files and encrypt for target device (multi-select)',
+ multiSelect: 'multi-select',
+ selectDirectory: 'Select Directory',
+ selected: 'selected',
+ configName: 'Config Name',
+ autoFromFilename: 'Auto from filename',
+ descriptionOptional: 'Description (Optional)',
+ pasteTargetCert: 'Paste certificate exported from target device',
+ targetCertHint: 'Paste target device certificate. Leave empty for self-encryption',
+ generatingPack: 'Generating config pack...',
+ packWillShowHere: 'Config pack will appear here...',
+ // Public/Private key export modals
+ pubkeyExport: 'Public Key Export',
+ privkeyExport: 'Private Key Export',
+ keyTypeLabel: 'Type',
+ commentLabel: 'Comment',
+ pubkeyHint: 'Add this public key to remote server\'s ~/.ssh/authorized_keys file for passwordless login',
+ securityWarning: 'Security Warning',
+ privkeyWarning: 'Private key is sensitive information, please keep it safe!',
+ privkeyHint: 'Save as ~/.ssh/{id} and set permissions chmod 600',
+ // HTTPS certificate status
+ notInitialized: 'Not Initialized',
+ keyGeneratedAwaitCsr: 'Key Generated, Awaiting CSR',
+ csrPendingSign: 'CSR Generated, Awaiting Signing',
+ activated: 'Activated',
+ expired: 'Expired',
+ valid: 'Valid',
+ invalid: 'Invalid',
+ // Import SSH host modal
+ importSshHostTitle: 'Import SSH Host Config',
+ importSshHostDesc: 'Select .tscfg config file to import SSH host configuration',
+ configPackContent: 'Config Pack Content',
+ overwriteExisting: 'Overwrite existing config',
+ certFingerprintShort: 'Cert Fingerprint',
+ configExistsCheckOverwrite: 'Config {id} exists, check "Overwrite" option',
+ savedConfig: 'Saved config',
+ restartToApply: 'Restart to apply',
+ // Revoke host modal
+ enterPassword: 'Please enter password',
+ revokingKey: 'Revoking public key...',
+ revokeSuccess: 'Revoked! Removed {count} matching public key(s)',
+ revokedAndRemoved: 'Revoked public key and removed host record',
+ keyNotFoundOnServer: 'No matching public key found on server (may have been removed)\nRemove local record anyway?',
+ removedLocalRecord: 'Removed local host record',
+ revokeFailed: 'Revoke failed',
+ // Known hosts
+ viewFullFingerprint: 'View full fingerprint',
+ deleteFingerprint: 'Delete fingerprint record'
},
// OTA Page
@@ -1700,6 +2220,9 @@ i18n.registerLanguage('en-US', {
sourcesLabel: 'Sources',
triggerCountLabel: 'Triggers',
runtimeLabel: 'Runtime',
+ uptimeSecsOnly: '{n}s',
+ uptimeMinSec: '{m}m {s}s',
+ uptimeHrMin: '{h}h {m}m',
// Table headers (rules)
ruleNameHeader: 'Name',
statusHeader: 'Status',
@@ -1717,6 +2240,7 @@ i18n.registerLanguage('en-US', {
actionNameHeader: 'Name',
actionTypeHeader: 'Type',
actionModeHeader: 'Mode',
+ syncMode: 'Sync',
descriptionHeader: 'Description',
// Action editor
newActionTemplate: 'New Action Template',
@@ -1736,6 +2260,7 @@ i18n.registerLanguage('en-US', {
noSources: 'No data sources yet, click "Add" to create',
noVariables: 'No variable data',
noActions: 'No action templates, click "Add" to create',
+ saveRuleChanges: 'Save changes',
getRulesFailed: 'Failed to get rules',
getSourcesFailed: 'Failed to get data sources',
getVariablesFailed: 'Failed to get variables',
@@ -1755,6 +2280,7 @@ i18n.registerLanguage('en-US', {
noExtraParams: 'No extra parameters for this filter',
selectCmdHint: 'Select command to monitor (created in SSH page)',
selectHostFirst: '-- Select host first --',
+ selectHostPrompt: '-- Please select host --',
// Socket.IO test
eventDataNotJson: 'Event data is not JSON, cannot parse fields',
gotSidNoEvent: 'Got SID but no event data received',
@@ -1828,11 +2354,12 @@ i18n.registerLanguage('en-US', {
// Rule editor
ruleId: 'Rule ID',
ruleName: 'Rule Name',
- conditionLogic: 'Condition Logic',
+ conditionLogic: 'Logic',
triggerConditions: 'Trigger Conditions',
// Quick actions
noQuickActions: 'No quick actions',
quickActionsHint: 'Enable "Manual Trigger" option in automation rules',
+ loadQuickActionsFailed: 'Failed to load quick actions',
processRunning: 'Process running',
processNotRunning: 'Process not running',
viewLog: 'View Log',
@@ -2002,10 +2529,11 @@ i18n.registerLanguage('en-US', {
bindTempVar: 'Bind Temperature Variable',
unbound: 'Unbound',
bound: 'Bound',
+ currentBound: 'Currently bound',
selectVariableHint: 'Select a float variable as temperature source (e.g. agx.cpu_temp)',
- bind: '💾 Bind',
+ bind: 'Bind',
unbind: 'Unbind',
- tempSpeedCurve: '📊 Temperature-Speed Curve',
+ tempSpeedCurve: 'Temperature-Speed Curve',
addPoint: '➕ Add Point',
curveHint: 'Uses min speed below lowest point, max speed above highest point',
curvePreview: '📈 Curve Preview',
@@ -2019,6 +2547,25 @@ i18n.registerLanguage('en-US', {
intervalHint: 'Minimum time between adjustments',
cancel: 'Cancel',
applyCurve: 'Apply Curve',
+ maxCurvePoints: 'Max 10 curve points',
+ minCurvePoints: 'At least 2 curve points required',
+ selectVarToBind: 'Please select a variable to bind',
+ tempBoundToVar: 'Temperature bound to variable: {var}',
+ bindFailed: 'Bind failed',
+ unbindSuccess: 'Temperature variable unbound',
+ unbindFailed: 'Unbind failed',
+ dutyOrderError: 'Min duty cannot be greater than max duty',
+ hysteresisRangeError: 'Temperature hysteresis must be 0-20°C',
+ intervalRangeError: 'Min interval must be 500-30000 ms',
+ curveApplied: 'Fan {id} curve applied and saved',
+ applyCurveFailed: 'Apply curve failed',
+ curveExported: 'Fan {id} curve exported (local + SD {path})',
+ curveExportSdFailed: 'Fan {id} curve saved locally, SD save failed: {msg}',
+ invalidConfigFormat: 'Invalid config file format',
+ invalidCurvePoints: 'Invalid curve points in config',
+ curvePointFormatError: 'Curve point format error',
+ configImported: 'Config imported: {name}',
+ importConfigFailed: 'Import config failed',
// Curve points
tempPlaceholder: 'Temp',
speedPlaceholder: 'Speed'
@@ -2060,6 +2607,18 @@ i18n.registerLanguage('en-US', {
layoutLargeDesc: '3/4 width',
layoutFull: 'Full Row',
layoutFullDesc: 'Occupy entire row',
+ // Widget type names (edit panel title, etc.)
+ widgetTypeRing: 'Circular Progress',
+ widgetTypeGauge: 'Gauge',
+ widgetTypeTemp: 'Temperature',
+ widgetTypeNumber: 'Number',
+ widgetTypeBar: 'Progress Bar',
+ widgetTypeText: 'Text',
+ widgetTypeStatus: 'Status',
+ widgetTypeIcon: 'Icon Status',
+ widgetTypeDual: 'Dual Value',
+ widgetTypePercent: 'Percent',
+ widgetTypeLog: 'Log Stream',
// Preset widgets
presetCpu: 'CPU',
presetMem: 'Memory',
@@ -2086,6 +2645,7 @@ i18n.registerLanguage('en-US', {
seconds10: '10 sec',
seconds30: '30 sec',
minute1: '1 min',
+ noWidgets: 'No widgets',
addedWidgets: 'Added Widgets',
addNewWidget: '➕ Add New Widget',
selectWidgetHint: 'Select a widget to edit
or add a new one',
@@ -2110,6 +2670,7 @@ i18n.registerLanguage('en-US', {
labelName: 'Label Name',
labelPlaceholder: 'Widget name',
icon: 'Icon',
+ iconPlaceholder: 'emoji',
color: 'Color',
layoutWidth: '📐 Layout Width',
unit: 'Unit',
@@ -2120,7 +2681,7 @@ i18n.registerLanguage('en-US', {
dataExpressionPlaceholder: 'Click to select variable or enter expression',
dataExpressionHint: 'Supports: ${var} to reference variables, ${a} + ${b} math, ${a} + "unit" concatenation',
preview: 'Preview',
- save: '💾 Save',
+ save: 'Save',
// Log widget
clickToRead: 'Click "Read" to start fetching logs',
expandLog: 'Expand Log',
@@ -2131,7 +2692,7 @@ i18n.registerLanguage('en-US', {
statusWarning: 'Warning',
// Temperature variable selection
tempVariables: 'Temperature Variables',
- otherNumericVariables: '📊 Other Numeric Variables',
+ otherNumericVariables: 'Other Numeric Variables',
// Error messages
setDutyLimitFailed: 'Failed to set duty cycle limit',
setCurveFailed: 'Failed to set curve'
@@ -2166,8 +2727,11 @@ i18n.registerLanguage('en-US', {
// Button texts
turnOff: 'Off',
turnOn: 'On',
+ clickOff: 'Click to turn off',
+ clickOn: 'Click to turn on',
moreEffects: 'More',
- stopEffect: 'Stop',
+ stopEffect: 'Stop effect',
+ filterEffect: 'Filter effect',
noEffects: 'No effects available',
effects: 'Effects',
selectAnimation: 'Please select an animation first',
@@ -2195,6 +2759,7 @@ i18n.registerLanguage('en-US', {
// Modal - Text
textTitle: 'Text Display',
enterTextToDisplay: 'Enter text to display',
+ fontAndStyle: 'Font & Style',
font: 'Font',
refreshFonts: 'Refresh',
alignment: 'Align',
@@ -2202,6 +2767,7 @@ i18n.registerLanguage('en-US', {
alignCenter: 'Center',
alignRight: 'Right',
autoPosition: 'Auto Position',
+ position: 'Position',
scroll: 'Scroll',
scrollNone: 'None',
scrollLeft: 'Left',
@@ -2340,6 +2906,7 @@ i18n.registerLanguage('en-US', {
chinese: '中文',
english: 'EN',
notLoggedIn: 'Not Logged In',
+ permissionLevel: 'Permission level',
// Button States
stopTracking: 'Stop Tracking',
startTracking: 'Start Tracking',
@@ -2385,11 +2952,11 @@ i18n.registerLanguage('en-US', {
// Modal Titles
newCommand: '➕ New Command',
editCommand: 'Edit Command',
- commandVariables: '📊 Command Variables',
- sourceVariables: '📊 {source} Variables',
+ commandVariables: 'Command Variables',
+ sourceVariables: '{source} Variables',
editActionTemplate: 'Edit Action Template',
// Actions
- updateAction: '💾 Update',
+ updateAction: 'Update',
// Validation Errors
lowVoltageError: 'Low voltage threshold must be less than recovery voltage threshold',
shutdownDelayError: 'Shutdown countdown must be between 10-600 seconds',
@@ -2514,6 +3081,9 @@ i18n.registerLanguage('en-US', {
// Terminal Page
terminal: {
+ connecting: 'Connecting to device...',
+ connected: 'Connected to device',
+ helpHint: 'Enter {help} to view available commands',
pageTitle: 'Web Terminal',
terminalHint: 'Tip: Type
help for commands |
Ctrl+C interrupt |
Ctrl+L clear |
↑↓ history',
systemLogTitle: 'System Logs',
@@ -2582,9 +3152,25 @@ i18n.registerLanguage('en-US', {
// Task count
taskCountLabel: '{count} tasks total',
// Task stack
+ taskStackUsage: 'Task Stack Usage',
taskStackTotal: 'Total Stack Allocated',
totalTaskCount: 'Total Tasks',
- stackHint: 'Remaining stack <256B is danger zone, <512B is warning zone'
+ stackHint: 'Remaining stack <256B is danger zone, <512B is warning zone',
+ state: 'State',
+ cpu: 'CPU',
+ runtimeStats: 'Runtime Statistics',
+ updateTime: 'Updated: ',
+ loadFailed: 'Failed to load memory detail',
+ getDataFailed: 'Failed to get data',
+ staticDataBss: '.data + .bss',
+ iramTitle: 'IRAM (Instruction Memory)',
+ rtcUsed: 'Used',
+ rtcTotal: 'Total',
+ // Memory tips
+ dramFragmented: 'DRAM severely fragmented, recommend system restart',
+ psramSufficient: 'PSRAM space sufficient, available for large buffers',
+ dramLow: 'DRAM running low, consider freeing memory',
+ psramLow: 'PSRAM running low'
},
// Data Source
diff --git a/components/ts_webui/web/js/lang/zh-CN.js b/components/ts_webui/web/js/lang/zh-CN.js
index a2616f2..5122388 100644
--- a/components/ts_webui/web/js/lang/zh-CN.js
+++ b/components/ts_webui/web/js/lang/zh-CN.js
@@ -1,7 +1,7 @@
/**
* TianShanOS WebUI - 简体中文语言包
*/
-i18n.registerLanguage('zh-CN', {
+if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', {
// 通用
common: {
confirm: '确认',
@@ -118,6 +118,7 @@ i18n.registerLanguage('zh-CN', {
timezone: '时区',
customTimezone: '或自定义时区字符串',
timezoneExample: '例如: CST-8',
+ timezoneSetSuccess: '时区已设置为 {timezone},本地时间: {localTime}',
tzChinaStandard: '中国标准时间 (UTC+8)',
tzJapanStandard: '日本标准时间 (UTC+9)',
tzKoreaStandard: '韩国标准时间 (UTC+9)',
@@ -139,7 +140,12 @@ i18n.registerLanguage('zh-CN', {
high: '高电平',
low: '低电平',
host: '主机',
- device: '设备'
+ device: '设备',
+ logs: '日志',
+ loadFailed: '加载失败',
+ copyToClipboard: '复制到剪贴板',
+ selectFile: '选择文件',
+ install: '安装'
},
// 状态标签
@@ -166,10 +172,10 @@ i18n.registerLanguage('zh-CN', {
// 导航菜单
nav: {
home: '首页',
- system: '系统总览',
+ system: '系统',
led: 'LED 控制',
network: '网络',
- files: '文件管理',
+ files: '文件',
ssh: 'SSH 命令',
security: '安全',
ota: 'OTA 升级',
@@ -181,8 +187,22 @@ i18n.registerLanguage('zh-CN', {
// 首页/系统总览
system: {
title: '系统总览',
+ resourceMonitor: '资源监控',
+ detail: '详情',
overview: '系统信息',
chip: '芯片',
+ internal: '内部',
+ timeSync: '时间同步',
+ devicePanel: '设备面板',
+ serviceStatusTitle: '服务状态',
+ stage: '阶段',
+ health: '健康',
+ toggleProtectionTitle: '点击切换保护状态',
+ switchFailed: '切换失败',
+ usbMuxNotConfigured: 'USB MUX 未配置',
+ usbSwitchTo: '切换 USB 到 {name}...',
+ usbSwitchedTo: 'USB 已切换到 {name}',
+ noDataWidgetsYet: '还没有添加数据组件',
firmware: '固件',
version: '版本',
idfVersion: 'IDF 版本',
@@ -243,6 +263,12 @@ i18n.registerLanguage('zh-CN', {
currentTime: '当前时间',
timeStatus: '同步状态',
timeSource: '时间源',
+ timeSynced: '已同步',
+ timeNotSynced: '未同步',
+ timeSourceNtp: 'NTP',
+ timeSourceHttp: '浏览器',
+ timeSourceManual: '手动',
+ timeSourceNone: '未同步',
timezone: '时区',
syncTime: '同步时间',
@@ -259,7 +285,35 @@ i18n.registerLanguage('zh-CN', {
quickActions: '快捷操作',
reboot: '重启',
confirmReboot: '确认重启系统?',
- rebooting: '正在重启...'
+ rebooting: '正在重启...',
+ rebootConfirm: '确定要重启系统吗?',
+ rebootSending: '正在发送重启命令...',
+ rebootingPleaseWait: '系统正在重启,请稍候...',
+ rebootFailed: '重启失败',
+ agxPowerOnTitle: '点击开启 AGX 电源',
+ agxPowerOffTitle: '点击关闭 AGX 电源',
+ agxPowerOn: '上电',
+ agxPowerOff: '断电',
+ agxPoweringOn: 'AGX 上电中...',
+ agxPoweringOff: 'AGX 断电中...',
+ agxPowerOnSuccess: 'AGX 已上电',
+ agxPowerOffSuccess: 'AGX 已断电',
+ agxPowerFail: 'AGX 操作失败',
+ lpmuRunning: 'LPMU 运行中',
+ lpmuStopped: 'LPMU 已关闭',
+ lpmuOnlineTitle: 'LPMU 在线 (ping 10.10.99.99 可达)\n点击触发电源按钮',
+ lpmuOfflineTitle: 'LPMU 离线 (ping 10.10.99.99 不可达)\n点击触发电源按钮',
+ lpmuDetectingTitle: '正在检测 LPMU 状态...\n最多等待 80 秒',
+ lpmuUnknownTitle: 'LPMU 状态未知\n点击触发电源按钮',
+ statusFetching: '状态获取中',
+ lpmuTriggerConfirm: '确定要触发 LPMU 电源按钮吗?\n\n这将发送一个脉冲信号,效果类似按物理电源按钮。',
+ lpmuTriggering: 'LPMU 电源触发中...',
+ lpmuTriggerSuccess: 'LPMU 电源已触发,开始检测状态...',
+ lpmuTriggerFail: 'LPMU 触发失败',
+ lpmuOnlineSuccess: 'LPMU 已上线',
+ lpmuOfflineSuccess: 'LPMU 已关闭',
+ lpmuStartupTimeout: 'LPMU 开机检测超时,认定为已关闭',
+ lpmuShutdownTimeout: 'LPMU 关机检测超时,设备可能仍在运行'
},
// 风扇控制
@@ -287,7 +341,8 @@ i18n.registerLanguage('zh-CN', {
duty: '占空比',
invalidCurve: '最小占空比不能大于最大占空比',
minCurvePoints: '至少需要 2 个曲线点',
- maxCurvePoints: '最多支持 10 个曲线点'
+ maxCurvePoints: '最多支持 10 个曲线点',
+ statusUnavailable: '风扇状态不可用'
},
// LED 控制
@@ -332,6 +387,7 @@ i18n.registerLanguage('zh-CN', {
// 网络
network: {
title: '网络设置',
+ connection: '网络连接',
status: '网络状态',
ethernet: '以太网',
wifi: 'WiFi',
@@ -360,7 +416,26 @@ i18n.registerLanguage('zh-CN', {
linkUp: '链路正常',
linkDown: '链路断开',
noIp: '无 IP',
- obtaining: '获取中...'
+ obtaining: '获取中...',
+ interfaceConfig: '接口配置',
+ networkServices: '网络服务',
+ linkStatus: '链路状态',
+ mode: '模式',
+ off: '关闭',
+ sta: '站点 (STA)',
+ ap: '热点 (AP)',
+ apsta: 'STA+AP',
+ stationConnect: '站点连接',
+ scan: '扫描',
+ disconnect: '断开',
+ hotspot: '热点',
+ config: '配置',
+ devices: '设备',
+ clientCount: '接入数',
+ newHostname: '新主机名',
+ set: '设置',
+ clients: '客户端',
+ signal: '信号'
},
// 文件管理
@@ -389,14 +464,32 @@ i18n.registerLanguage('zh-CN', {
sdcard: 'SD 卡',
spiffs: 'SPIFFS',
confirmDeleteFile: '确定要删除文件 "{name}" 吗?',
- confirmDeleteFolder: '确定要删除文件夹 "{name}" 及其所有内容吗?'
+ confirmDeleteFolder: '确定要删除文件夹 "{name}" 及其所有内容吗?',
+ selectedCount: '已选择 {n} 项',
+ batchDownload: '批量下载',
+ batchDelete: '批量删除',
+ clearSelection: '取消选择',
+ uploadTitle: '上传文件',
+ clickOrDrag: '点击选择文件或拖拽文件到此处',
+ newFolderTitle: '新建文件夹',
+ folderName: '文件夹名称',
+ folderNamePlaceholder: '输入文件夹名称',
+ create: '创建',
+ renameTitle: '重命名',
+ newName: '新名称',
+ newNamePlaceholder: '输入新名称',
+ name: '名称',
+ size: '大小',
+ action: '操作',
+ selectAll: '全选'
},
// SSH 命令
ssh: {
title: 'SSH 命令',
hosts: '主机列表',
- commands: '命令列表',
+ commands: '指令',
+ commandList: '命令列表',
addHost: '添加主机',
editHost: '编辑主机',
deleteHost: '删除主机',
@@ -430,7 +523,45 @@ i18n.registerLanguage('zh-CN', {
connectionSuccess: '连接成功',
connectionFailed: '连接失败',
exportConfig: '导出配置',
- importConfig: '导入配置'
+ importConfig: '导入配置',
+ // New keys for i18n
+ createFirstCommand: '创建第一个指令',
+ selectHostFirst: '请先选择一个主机',
+ noCommandsForHost: '该主机暂无指令',
+ hostNotExistCannotExec: '主机不存在,无法执行',
+ newCommand: '新建指令',
+ matchTimeoutHint: '匹配超时后命令将被终止',
+ timeoutConditionHint: '超时仅在设置了成功/失败模式或勾选了"匹配后停止"时有效',
+ serviceModeVarHint: '服务模式下,状态变量为
${变量名}.status(ready/checking/timeout)',
+ varNameHint: '执行结果将存储为
${变量名}.status、
${变量名}.extracted 等,可在后续命令中引用',
+ // SSH Command Export/Import
+ exportSshCmdTitle: '导出 SSH 指令配置',
+ exportSshCmdDesc: '导出指令
{cmdId} 的配置为加密配置包',
+ includeHostConfig: '同时导出依赖的主机配置',
+ includeHostConfigHint: '推荐勾选,便于在目标设备完整导入',
+ importSshCmdTitle: '导入 SSH 指令配置',
+ importSshCmdDesc: '选择 .tscfg 配置包文件以导入 SSH 指令',
+ configPackContent: '配置包内容',
+ overwriteExisting: '覆盖已存在的配置',
+ importHostConfig: '同时导入包含的主机配置',
+ bindToHost: '绑定到主机(可选)',
+ bindToHostHint: '留空则使用配置包中指定的主机',
+ confirmImport: '确认导入',
+ useConfigHost: '使用配置中的主机',
+ verifyingPack: '正在验证配置包...',
+ configId: '配置 ID',
+ sshCommand: 'SSH 指令',
+ signer: '签名者',
+ official: '官方',
+ note: '备注',
+ restartToLoad: '重启后自动加载',
+ configExistsOverwrite: '该配置已存在,导入将覆盖现有文件',
+ signatureVerified: '签名验证通过',
+ cannotVerifyPack: '无法验证配置包',
+ savingConfig: '正在保存配置...',
+ configExistsCheckOverwrite: '配置 {id} 已存在,请勾选「覆盖」选项',
+ savedConfig: '已保存配置',
+ restartToTakeEffect: '重启系统后生效'
},
// 安全
@@ -456,16 +587,29 @@ i18n.registerLanguage('zh-CN', {
tokenExpiry: '有效期',
certificates: '证书管理',
https: 'HTTPS',
- mtls: 'mTLS 双向认证'
+ mtls: 'mTLS 双向认证',
+ keyManagement: '密钥管理',
+ httpsCert: 'HTTPS 证书'
},
// OTA 升级
ota: {
title: 'OTA 升级',
+ firmwareUpgrade: '固件升级',
currentVersion: '当前版本',
serverUrl: 'OTA 服务器',
- saveServer: '保存服务器',
+ saveServer: '保存',
checkUpdate: '检查更新',
+ partitionManage: '分区管理',
+ manualUpgrade: '手动升级',
+ fromUrl: '从 URL 升级',
+ fromSd: '从 SD 卡升级',
+ includeWebUI: '包含 WebUI',
+ skipVerify: '跳过验证',
+ upgrade: '升级',
+ abort: '中止',
+ preparing: '准备中...',
+ saveToDevice: '保存到设备',
checking: '检查中...',
noUpdate: '已是最新版本',
updateAvailable: '发现新版本',
@@ -483,7 +627,13 @@ i18n.registerLanguage('zh-CN', {
rollbackAvailable: '可回滚到上一版本',
firmwareInfo: '固件信息',
buildTime: '编译时间',
- partitionInfo: '分区信息'
+ partitionInfo: '分区信息',
+ partitionRunning: '运行中',
+ partitionBootable: '可启动',
+ partitionIdle: '空闲',
+ disableRollbackDesc: '取消自动回滚保护',
+ rollbackToThis: '回滚到此版本',
+ loadAfterReboot: '重启后加载此分区'
},
// 自动化
@@ -551,9 +701,153 @@ i18n.registerLanguage('zh-CN', {
importRuleTitle: '导入规则配置',
importRuleDesc: '选择 .tscfg 配置包文件以导入规则',
exportActionTitle: '导出动作模板',
- exportActionDesc: '导出动作 {id} 的配置为加密配置包',
+ exportActionDesc: '导出动作模板
{actionId} 的配置为加密配置包',
importActionTitle: '导入动作模板',
- importActionDesc: '选择 .tscfg 配置包文件以导入动作模板'
+ importActionDesc: '选择 .tscfg 配置包文件以导入动作模板',
+ // Variable selection
+ selectVariable: '选择变量',
+ selectVariableTitle: '选择变量',
+ selectVariablePlaceholder: '选择变量',
+ searchVariable: '搜索变量...',
+ loadingVariables: '加载变量列表...',
+ // CLI command config
+ cliConfig: 'CLI 命令配置',
+ commandLine: '命令行',
+ cliPlaceholder: '如: gpio --set 48 1',
+ cliCmdHint: '支持所有控制台命令: gpio, device, fan, led, net 等',
+ quickCommands: '快捷命令:',
+ agxPowerOn: 'AGX开机',
+ agxRestart: 'AGX重启',
+ fan: '风扇',
+ advancedOptions: '高级选项',
+ resultVariable: '结果变量',
+ resultVarPlaceholder: '如: cli.result',
+ resultVariableHint: '存储命令输出到变量',
+ timeout: '超时时间',
+ // SSH command config
+ sshCmdConfig: 'SSH 命令配置',
+ selectCommand: '选择命令',
+ sshCmdHint: '选择已在 SSH 管理页面配置的命令',
+ commandDetails: '命令详情',
+ hostLabel: '主机:',
+ commandLabel: '命令:',
+ variableLabel: '变量:',
+ // LED control config
+ ledConfig: 'LED 控制配置',
+ device: '设备',
+ selectDevice: '选择设备',
+ selectLedDeviceHint: '选择要控制的 LED 设备',
+ controlType: '控制类型',
+ solidFill: '纯色填充',
+ programEffect: '程序动画',
+ brightnessOnly: '仅调节亮度',
+ turnOff: '关闭',
+ textDisplay: '文本显示',
+ displayImage: '显示图像',
+ displayQrCode: '显示QR码',
+ // 添加数据源弹窗
+ addSourceTitle: '添加外部数据源',
+ restTab: 'REST API',
+ wsTab: 'WebSocket',
+ sioTab: 'Socket.IO',
+ variableTab: '指令变量',
+ sourceIdPlaceholder: '如: agx_temp',
+ sourceLabelPlaceholder: '如: AGX 温度',
+ restConfigTitle: 'REST API 配置',
+ requestUrlReq: '请求地址',
+ testBtn: '测试',
+ pollIntervalMs: '轮询间隔 (ms)',
+ authHeaderOptional: 'Authorization 头(可选)',
+ authPlaceholder: 'Bearer token',
+ selectFieldsToExtract: '选择要提取的字段:',
+ jsonPathLabel: 'JSON 数据路径',
+ jsonPathHint: '(点击上方字段自动填入)',
+ jsonPathPlaceholderRest: '如: data.temperature(留空取整个响应)',
+ wsConfigTitle: 'WebSocket 配置',
+ wsAddressReq: 'WebSocket 地址',
+ wsPlaceholder: 'ws://192.168.1.100:8080/ws',
+ jsonPathPlaceholderWs: '如: data.temperature(留空取整个消息)',
+ reconnectIntervalMs: '断线重连间隔 (ms)',
+ sioConfigTitle: 'Socket.IO 配置',
+ sioServerReq: '服务器地址',
+ sioPlaceholder: 'http://10.10.99.99:59090',
+ sioV4Hint: 'Socket.IO v4 协议,使用 HTTP/HTTPS 地址',
+ eventNameLabel: '事件名称',
+ eventNameHint: '(留空自动发现)',
+ eventNamePlaceholder: '测试时留空可自动发现事件',
+ timeoutMs: '超时时间 (ms)',
+ autoDiscoverFields: '自动发现所有 JSON 字段为变量',
+ autoDiscoverHint: '关闭后仅使用上方选中的字段作为变量',
+ jsonPathPlaceholderSio: '如: cpu.avg_usage(留空取整个事件数据)',
+ variableConfigTitle: 'SSH 指令变量',
+ sshHostReq: 'SSH 主机',
+ sshHostHint: '选择已配置的 SSH 主机(在 SSH 页面添加)',
+ selectCmdReq: '选择指令',
+ selectCmdHint: '选择要监视的指令(在 SSH 页面创建)',
+ selectHostFirst: '-- 先选择主机 --',
+ cmdPreviewTitle: '指令详情',
+ commandLabel: '命令:',
+ descLabel: '描述:',
+ timeoutLabel: '超时:',
+ seconds: '秒',
+ varsPreviewTitle: '将监视以下变量(需先执行指令):',
+ selectHostCmdFirst: '请先选择 SSH 主机和指令',
+ pollIntervalSec: '检测间隔 (秒)',
+ pollIntervalHint: '定期读取变量值的间隔',
+ enableAfterCreate: '创建后立即启用',
+ // 添加/编辑规则弹窗
+ ruleIdPlaceholder: '唯一标识符',
+ ruleNamePlaceholder: '规则显示名称',
+ iconLabel: '图标',
+ iconTab: '图标',
+ imageTab: '图片',
+ customPlaceholder: '自定义',
+ selectImagePlaceholder: '选择图片...',
+ previewNone: '无',
+ cooldownMs: '冷却时间 (ms)',
+ enableImmediately: '立即启用',
+ triggerConditions: '触发条件',
+ manualOnly: '仅手动触发',
+ addConditionHint: '点击"添加"创建触发条件,或勾选"仅手动触发"作为快捷动作',
+ addConditionHintShort: '点击"添加"创建触发条件',
+ executionActions: '执行动作',
+ selectFromTemplatesHint: '从已创建的动作模板中选择要执行的动作',
+ createActionFirstHint: '请先在"动作模板"区域创建动作,然后在这里选择使用',
+ pleaseEnterRuleId: '请输入规则 ID',
+ pleaseEnterRuleName: '请输入规则名称',
+ uniqueIdHint: '唯一标识符,仅限字母、数字、下划线、连字符,不能以 _ 或 - 开头/结尾',
+ // 新建动作模板弹窗
+ newActionTemplate: '新建动作模板',
+ selectActionType: '选择动作类型',
+ configParams: '配置参数',
+ basicInfo: '基本信息',
+ actionIdPlaceholder: '唯一标识,如: restart_agx',
+ actionIdHint: '用于规则引用,只能包含字母、数字和下划线',
+ displayNamePlaceholder: '如: 重启 AGX',
+ leaveEmptyUseId: '留空则使用 ID',
+ actionDescPlaceholder: '动作说明(可选)',
+ executionDelay: '执行延迟',
+ asyncExecute: '异步执行',
+ asyncExecuteDesc: 'API 调用立即返回,动作在后台队列执行',
+ pleaseSelectActionType: '请选择动作类型',
+ selectTriggerVarTitle: '选择触发条件变量',
+ selectConditionVar: '选择条件变量',
+ systemVariables: '系统变量',
+ searchVarPlaceholder: '搜索变量...',
+ loadingVarList: '加载变量列表...',
+ noVariablesAvailable: '没有可用的变量',
+ actionTypeCli: 'CLI 命令',
+ actionTypeCliDesc: '执行本地控制台命令',
+ actionTypeSsh: 'SSH 命令',
+ actionTypeSshDesc: '执行已配置的SSH命令',
+ actionTypeLed: 'LED 控制',
+ actionTypeLedDesc: '控制 LED 颜色和效果',
+ actionTypeLog: '日志记录',
+ actionTypeLogDesc: '输出日志消息',
+ actionTypeSetVar: '设置变量',
+ actionTypeSetVarDesc: '修改系统变量值',
+ actionTypeWebhook: 'Webhook',
+ actionTypeWebhookDesc: '发送 HTTP 请求'
},
// 设置
@@ -601,11 +895,23 @@ i18n.registerLanguage('zh-CN', {
loginButton: '登录',
loggingIn: '登录中...',
loginFailed: '登录失败',
+ welcomeName: '欢迎, {name}!',
+ networkError: '网络错误',
+ passwordChanged: '密码修改成功!',
invalidCredentials: '用户名或密码错误',
firstTimeSetup: '首次使用,请设置密码',
setPassword: '设置密码',
confirmPassword: '确认密码',
- passwordSet: '密码设置成功'
+ passwordSet: '密码设置成功',
+ securityReminder: '安全提醒',
+ defaultPasswordHint: '您正在使用默认密码,建议立即修改以确保系统安全。',
+ currentPassword: '当前密码',
+ newPassword: '新密码 (4-64字符)',
+ confirmNewPassword: '确认新密码',
+ changeLater: '稍后修改',
+ changeNow: '立即修改',
+ passwordMismatch: '两次输入的新密码不一致',
+ passwordMinLength: '新密码至少4个字符'
},
// 时间单位
@@ -684,6 +990,7 @@ i18n.registerLanguage('zh-CN', {
wifiDisconnected: 'WiFi 已断开',
natEnabled: 'NAT 已启用',
natDisabled: 'NAT 已禁用',
+ natConfigSaved: 'NAT 配置已保存',
hotspotApplied: '热点配置已应用',
connecting: '正在连接...',
connectionFailed: '连接失败',
@@ -695,6 +1002,7 @@ i18n.registerLanguage('zh-CN', {
saveFailed: '保存失败',
deleteFailed: '删除失败',
uploadFailed: '上传失败',
+ rootRequired: '此页面需要 root 权限',
// 语言切换
languageSwitched: '已切换到中文',
// 风扇
@@ -712,6 +1020,7 @@ i18n.registerLanguage('zh-CN', {
refreshIntervalSet: '刷新间隔已设置为 {interval}',
refreshDisabled: '禁用',
widgetAdded: '已添加 {name}',
+ widgetSaved: '组件已保存',
widgetDeleted: '已删除 {name}',
widgetBound: '已绑定 {widget} → {var}',
// 快捷操作
@@ -724,6 +1033,10 @@ i18n.registerLanguage('zh-CN', {
operationComplete: '操作完成',
// 时间同步
timeSynced: '时间已同步: {datetime}',
+ timeSyncSyncing: '正在从浏览器同步时间...',
+ timeSyncFailed: '时间同步失败',
+ syncFailed: '同步失败',
+ setFailed: '设置失败',
ntpSyncing: '正在强制NTP同步...',
ntpStarted: 'NTP同步已启动,请稍候刷新查看结果',
ntpFailed: 'NTP同步失败',
@@ -762,6 +1075,17 @@ i18n.registerLanguage('zh-CN', {
keyGenerated: '密钥 "{name}" 生成成功',
generateFailed: '生成失败',
terminalDisconnected: '终端已断开',
+ // Export/Import
+ exportFailed: '导出失败',
+ exportSuccess: '导出成功',
+ invalidResponse: '无效的响应数据',
+ exportedSourceConfig: '已导出数据源配置: {filename}',
+ exportedCmdWithHost: '已导出指令配置(包含主机 {hostId}): {filename}',
+ exportedCmdConfig: '已导出指令配置: {filename}',
+ exportedActionTemplate: '已导出动作模板: {filename}',
+ selectFileFirst: '请先选择文件',
+ importedRestartRequired: '已导入配置,重启后生效',
+ importFailed: '导入失败',
// OTA
enterFirmwareUrl: '请输入固件 URL',
urlMustHttp: 'URL 必须以 http:// 或 https:// 开头',
@@ -994,6 +1318,8 @@ i18n.registerLanguage('zh-CN', {
dynamic: '动态',
leases: '租约',
activeLeases: '活跃租约',
+ leasesCount: '{n} 租约',
+ activeLeasesCount: '{n} 活跃租约',
// WiFi modes
modeOff: '关闭',
modeSta: '站点 (STA)',
@@ -1018,6 +1344,7 @@ i18n.registerLanguage('zh-CN', {
gateway: '网关',
signal: '信号',
clientCount: '接入数',
+ devicesCount: '{count} 设备',
mode: '模式',
// 站点/热点
staConnection: '站点连接',
@@ -1079,9 +1406,19 @@ i18n.registerLanguage('zh-CN', {
// SD 卡
unmountSdCard: '卸载 SD 卡',
mountSdCard: '挂载 SD 卡',
+ unmountSdBtn: '卸载 SD',
+ mountSdBtn: '挂载 SD',
notMounted: '未挂载',
mounted: '已挂载',
sdCardNotMounted: 'SD 卡未挂载',
+ mountingSd: '正在挂载 SD 卡...',
+ mountSdSuccess: 'SD 卡挂载成功',
+ mountSdFailed: '挂载失败',
+ confirmUnmountSd: '确定要卸载 SD 卡吗?\n\n卸载后将无法访问 SD 卡上的文件。',
+ unmountingSd: '正在卸载 SD 卡...',
+ unmountSdSuccess: 'SD 卡已卸载',
+ unmountSdFailed: '卸载失败',
+ loadFailed: '加载失败',
// 批量操作
batchDownload: '批量下载',
batchDelete: '批量删除',
@@ -1425,7 +1762,7 @@ i18n.registerLanguage('zh-CN', {
deleteHttpsKeyAndCert: '删除 HTTPS 密钥和证书',
keyNotGenerated: '未生成密钥',
generateHttpsKey: '生成 HTTPS 密钥对',
- generateKey: '🔑 生成密钥',
+ generateKey: '生成密钥',
// 证书详情
subjectCN: '主体 CN',
issuer: '签发者',
@@ -1460,7 +1797,7 @@ i18n.registerLanguage('zh-CN', {
configFiles: '个配置文件',
savedTo: '已保存到',
// 配置包导出
- configPackExport: '📦 导出配置包',
+ configPackExport: '导出配置包',
selectConfigFiles: '选择配置文件',
configName: '配置名称',
configNamePlaceholder: '自动从文件名获取',
@@ -1468,11 +1805,11 @@ i18n.registerLanguage('zh-CN', {
targetDeviceCert: '目标设备证书 (PEM)',
certPlaceholder: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
certHint: '粘贴目标设备导出的证书',
- generateConfigPack: '📦 生成配置包',
+ generateConfigPack: '生成配置包',
generatedConfigPack: '生成的配置包 (.tscfg)',
configPackPlaceholder: '配置包将在此显示...',
copyToClipboard: '复制到剪贴板',
- downloadToLocal: '💾 下载到本地',
+ downloadToLocal: '下载到本地',
// 文件选择状态
selectedFiles: '已选择 {count} 个文件',
filesLoading: '个加载中...',
@@ -1483,7 +1820,7 @@ i18n.registerLanguage('zh-CN', {
revokeHostHint: '撤销成功后将自动从列表中移除该主机',
serverPassword: '服务器密码',
serverPasswordPlaceholder: '输入 SSH 密码',
- revokeAndRemove: '🔓 撤销并移除',
+ revokeAndRemove: '撤销并移除',
// SSH 密钥管理页面
sshKeyPairs: 'SSH 密钥对',
deployedHosts: '已部署主机',
@@ -1496,7 +1833,8 @@ i18n.registerLanguage('zh-CN', {
keysTableExportable: '可导出',
keysTableActions: '操作',
hostsHint: '通过上方密钥的「部署」按钮将公钥部署到远程服务器后,主机将自动出现在此列表',
- importHost: '📥 导入主机',
+ importHost: '导入主机',
+ host: '主机',
hostId: '主机 ID',
address: '地址',
port: '端口',
@@ -1510,16 +1848,16 @@ i18n.registerLanguage('zh-CN', {
exportSshHostTitle: '导出 SSH 主机配置',
exportSshHostDesc: '导出主机 {hostId} 的配置为加密配置包',
exportSshHostCertHint: '粘贴目标设备的证书。留空则使用本机证书(自加密)',
- importSshHostTitle: '📥 导入 SSH 主机配置',
+ importSshHostTitle: '导入 SSH 主机配置',
importSshHostDesc: '选择 .tscfg 配置包文件以导入 SSH 主机配置',
- confirmImport: '📥 确认导入',
+ confirmImport: '确认导入',
// 部署密钥弹窗
- deployKeyTitle: '🚀 部署公钥到远程服务器',
+ deployKeyTitle: '部署公钥到远程服务器',
deployKeyDesc: '将公钥 {keyId} 部署到远程服务器的 authorized_keys',
targetHost: '目标主机',
authPassword: '认证密码 (首次部署需要)',
deployHint: '部署成功后,该主机将自动添加到「已部署主机」列表,之后可使用此密钥免密登录',
- startDeploy: '🚀 开始部署',
+ startDeploy: '开始部署',
// 撤销密钥弹窗
revokeKeyTitle: '撤销公钥',
revokeKeyDesc: '从远程服务器移除公钥 {keyId}',
@@ -1537,10 +1875,10 @@ i18n.registerLanguage('zh-CN', {
storedFingerprint: '存储的指纹',
currentFingerprint: '当前指纹',
// HTTPS 证书弹窗
- genHttpsKeyPairTitle: '🔑 生成 HTTPS 密钥对',
+ genHttpsKeyPairTitle: '生成 HTTPS 密钥对',
genHttpsKeyPairDesc: '为设备生成 ECDSA P-256 密钥对,用于 mTLS 身份验证',
existingKeyWarning: '已存在密钥对,继续将覆盖现有密钥!',
- generate: '🔑 生成',
+ generate: '生成',
csrTitle: '证书签名请求 (CSR)',
deviceId: '设备 ID (CN)',
deviceIdHint: '留空则使用默认配置',
@@ -1595,9 +1933,151 @@ i18n.registerLanguage('zh-CN', {
deselectAll: '取消全选',
selectDir: '选择整个目录',
packListTitle: '配置包列表',
+ packListDirPath: '目录路径',
+ noTscfgInDir: '目录中没有 .tscfg 文件',
+ packListLoadFailed: '加载失败',
+ confirmImportPack: '确定要导入配置包: {path} ?',
+ importPackSuccess: '配置包导入成功',
+ importPackFailed: '导入失败',
deviceType: '设备类型',
certCN: '证书 CN',
- formatVersion: '格式版本'
+ formatVersion: '格式版本',
+ // Additional keys for i18n
+ expiredDays: '已过期 {days} 天',
+ expiringDays: '{days} 天后过期',
+ remainingDays: '剩余 {days} 天',
+ developerDevice: 'Developer 设备',
+ normalDevice: 'Device 设备',
+ onlyDeveloperCanExport: '仅 Developer 设备可导出配置包',
+ // Generate key modal
+ generateNewKey: '生成新密钥',
+ keyId: '密钥 ID',
+ keyIdPlaceholder: '如: default, mykey',
+ rsaRecommended: 'RSA 2048-bit (推荐)',
+ ecdsaWarning: 'ECDSA 密钥暂不支持 SSH 公钥认证,请使用 RSA',
+ commentOptional: '备注 (可选)',
+ commentPlaceholder: '如: TianshanOS@device',
+ aliasOptional: '别名 (可选)',
+ aliasPlaceholder: '用于替代密钥 ID 显示',
+ aliasHint: '启用「隐藏密钥」时建议填写,用于显示',
+ allowExportPrivateKey: '允许导出私钥',
+ hideKeyId: '隐藏密钥 ID',
+ hideKeyIdHint: '启用后,低权限用户无法看到真实的密钥 ID',
+ // Deploy key modal
+ deployKeyDescPre: '将公钥',
+ deployKeyDescPost: '部署到远程服务器的 authorized_keys',
+ hostPlaceholder: '192.168.55.100 或 hostname',
+ passwordPlaceholder: '输入 SSH 登录密码',
+ deployInfoHint: '部署成功后,该主机将自动添加到「已部署主机」列表,之后可使用此密钥免密登录',
+ // Revoke key modal
+ revokeKeyDescPre: '从远程服务器移除公钥',
+ authPasswordRevoke: '认证密码',
+ revokeKey: '撤销公钥',
+ // Host mismatch modal
+ hostKeyMismatchTitle: '安全警告:主机指纹不匹配!',
+ hostKeyChangedWarning: '主机密钥已更改!这可能表明:',
+ mitmAttack: '中间人攻击(Man-in-the-Middle Attack)',
+ serverReinstalled: '服务器重新安装或密钥重新生成',
+ ipReassigned: 'IP 地址被分配给了不同的服务器',
+ hostLabel: '主机',
+ suggestion: '建议',
+ hostKeyMismatchSuggestion: '如果您确认服务器已重装或密钥已更新,可以点击"更新主机密钥"移除旧记录,然后重新连接以信任新密钥。',
+ // HTTPS cert modals
+ genHttpsKeyTitle: '生成 HTTPS 密钥对',
+ genHttpsKeyDesc: '为设备生成 ECDSA P-256 密钥对,用于 mTLS 身份验证',
+ deviceIdCn: '设备 ID (CN)',
+ leaveEmptyForDefault: '留空则使用默认配置',
+ organization: '组织 (O)',
+ department: '部门 (OU)',
+ csrContentLabel: 'CSR 内容(复制到 CA 服务器签发)',
+ generateCsrTitle: '生成证书签名请求',
+ installCertTitle: '安装设备证书',
+ installCertDesc: '粘贴 CA 签发的 PEM 格式证书',
+ certPem: '证书 PEM',
+ installCaTitle: '安装 CA 证书链',
+ installCaDesc: '粘贴根证书和中间证书(PEM 格式,可拼接多个)',
+ caCertPem: 'CA 证书链 PEM',
+ viewCertTitle: '查看设备证书',
+ // Key table actions
+ publicKey: '公钥',
+ privateKey: '私钥',
+ cannotExportPrivateKey: '此密钥不可导出私钥',
+ exportPrivateKey: '导出私钥',
+ deployToServer: '部署公钥到远程服务器',
+ revokeFromServer: '从远程服务器撤销公钥',
+ deploy: '部署',
+ revoke: '撤销',
+ // HTTPS table
+ noCertInstalled: '(未安装证书)',
+ noKeyGenerated: '未生成密钥',
+ noKeysClickToGenerate: '暂无密钥,点击上方按钮生成新密钥',
+ // Deployed hosts
+ testConnection: '测试连接',
+ exportAsTscfg: '导出配置为 .tscfg',
+ revokePubkey: '撤销公钥',
+ removeLocalRecord: '仅移除本地记录',
+ remove: '移除',
+ noDeployedHostsHint: '暂无已部署主机,请先在上方密钥管理中点击「部署」',
+ noKnownHostFingerprints: '暂无已知主机指纹',
+ // Config pack modals
+ exportCertTitle: '导出设备证书',
+ exportCertDesc: '将此证书发送给需要向您发送加密配置的开发者',
+ certFingerprint: '证书指纹 (SHA256)',
+ certCn: '证书 CN',
+ importPackTitle: '导入配置包',
+ importPackDesc: '上传或粘贴 .tscfg 配置包,验证后保存到设备(加密存储)',
+ orPasteJson: '或粘贴 JSON 内容',
+ configPackInfo: '配置包信息',
+ verifyOnly: '仅验证',
+ exportPackTitle: '导出加密配置包',
+ exportPackDesc: '选择配置文件并加密发送给目标设备(支持多选)',
+ multiSelect: '可多选',
+ selectDirectory: '选择整个目录',
+ selected: '已选择',
+ configName: '配置名称',
+ autoFromFilename: '自动从文件名获取',
+ descriptionOptional: '描述 (可选)',
+ pasteTargetCert: '粘贴目标设备导出的证书',
+ targetCertHint: '粘贴目标设备的证书。留空则使用本机证书(自加密)',
+ generatingPack: '正在生成配置包...',
+ packWillShowHere: '配置包将在此显示...',
+ // Public/Private key export modals
+ pubkeyExport: '公钥导出',
+ privkeyExport: '私钥导出',
+ keyTypeLabel: '类型',
+ commentLabel: '备注',
+ pubkeyHint: '将此公钥添加到远程服务器的 ~/.ssh/authorized_keys 文件中即可实现免密登录',
+ securityWarning: '安全警告',
+ privkeyWarning: '私钥是敏感信息,请妥善保管!',
+ privkeyHint: '保存为 ~/.ssh/{id} 并设置权限 chmod 600',
+ // HTTPS certificate status
+ notInitialized: '未初始化',
+ keyGeneratedAwaitCsr: '密钥已生成,等待 CSR',
+ csrPendingSign: 'CSR 已生成,等待签发',
+ activated: '已激活',
+ expired: '已过期',
+ valid: '有效',
+ invalid: '无效',
+ // Import SSH host modal
+ importSshHostTitle: '导入 SSH 主机配置',
+ importSshHostDesc: '选择 .tscfg 配置包文件以导入 SSH 主机配置',
+ configPackContent: '配置包内容',
+ overwriteExisting: '覆盖已存在的配置',
+ certFingerprintShort: '证书指纹',
+ configExistsCheckOverwrite: '配置 {id} 已存在,请勾选「覆盖」选项',
+ savedConfig: '已保存配置',
+ restartToApply: '重启系统后生效',
+ // Revoke host modal
+ enterPassword: '请输入密码',
+ revokingKey: '正在撤销公钥...',
+ revokeSuccess: '撤销成功!已从服务器移除 {count} 个匹配的公钥',
+ revokedAndRemoved: '已撤销公钥并移除主机记录',
+ keyNotFoundOnServer: '未在服务器上找到匹配的公钥(可能已被移除)\n是否仍要移除本地记录?',
+ removedLocalRecord: '已移除本地主机记录',
+ revokeFailed: '撤销失败',
+ // Known hosts
+ viewFullFingerprint: '查看完整指纹',
+ deleteFingerprint: '删除指纹记录'
},
// OTA 页面
@@ -1701,6 +2181,9 @@ i18n.registerLanguage('zh-CN', {
sourcesLabel: '数据源',
triggerCountLabel: '触发次数',
runtimeLabel: '运行时长',
+ uptimeSecsOnly: '{n}秒',
+ uptimeMinSec: '{m}分{s}秒',
+ uptimeHrMin: '{h}时{m}分',
// 表格标题(规则)
ruleNameHeader: '名称',
statusHeader: '状态',
@@ -1718,6 +2201,7 @@ i18n.registerLanguage('zh-CN', {
actionNameHeader: '名称',
actionTypeHeader: '类型',
actionModeHeader: '模式',
+ syncMode: '同步',
descriptionHeader: '描述',
// 动作编辑器
newActionTemplate: '新建动作模板',
@@ -1737,6 +2221,7 @@ i18n.registerLanguage('zh-CN', {
noSources: '暂无数据源,点击"添加"创建第一个',
noVariables: '暂无变量数据',
noActions: '暂无动作模板,点击"添加"创建',
+ saveRuleChanges: '保存修改',
getRulesFailed: '获取规则失败',
getSourcesFailed: '获取数据源失败',
getVariablesFailed: '获取变量失败',
@@ -1756,6 +2241,7 @@ i18n.registerLanguage('zh-CN', {
noExtraParams: '此滤镜无额外参数',
selectCmdHint: '选择要监视的指令(在 SSH 页面创建)',
selectHostFirst: '-- 先选择主机 --',
+ selectHostPrompt: '-- 请选择主机 --',
// Socket.IO 测试
eventDataNotJson: '事件数据非 JSON 格式,无法解析字段',
gotSidNoEvent: '已获取 SID,但未收到事件数据',
@@ -1834,6 +2320,7 @@ i18n.registerLanguage('zh-CN', {
// 快捷操作
noQuickActions: '暂无快捷操作',
quickActionsHint: '在自动化规则中启用"手动触发"选项',
+ loadQuickActionsFailed: '无法加载快捷操作',
processRunning: '进程运行中',
processNotRunning: '进程未运行',
viewLog: '查看日志',
@@ -2003,10 +2490,11 @@ i18n.registerLanguage('zh-CN', {
bindTempVar: '🌡️ 绑定温度变量',
unbound: '未绑定',
bound: '已绑定',
+ currentBound: '当前绑定',
selectVariableHint: '选择一个浮点类型变量作为温度源(如 agx.cpu_temp)',
- bind: '💾 绑定',
+ bind: '绑定',
unbind: '解绑',
- tempSpeedCurve: '📊 温度-转速曲线',
+ tempSpeedCurve: '温度-转速曲线',
addPoint: '➕ 添加点',
curveHint: '温度低于最小点时使用最小转速,高于最大点时使用最大转速',
curvePreview: '📈 曲线预览',
@@ -2020,6 +2508,25 @@ i18n.registerLanguage('zh-CN', {
intervalHint: '调速最小时间间隔',
cancel: '取消',
applyCurve: '应用曲线',
+ maxCurvePoints: '最多支持 10 个曲线点',
+ minCurvePoints: '至少需要 2 个曲线点',
+ selectVarToBind: '请选择要绑定的变量',
+ tempBoundToVar: '温度已绑定到变量: {var}',
+ bindFailed: '绑定失败',
+ unbindSuccess: '温度变量绑定已解除',
+ unbindFailed: '解绑失败',
+ dutyOrderError: '最小占空比不能大于最大占空比',
+ hysteresisRangeError: '温度迟滞必须在 0-20°C 范围内',
+ intervalRangeError: '最小间隔必须在 500-30000ms 范围内',
+ curveApplied: '风扇 {id} 曲线已应用并保存',
+ applyCurveFailed: '应用曲线失败',
+ curveExported: '风扇 {id} 曲线配置已导出(本地 + SD 卡 {path})',
+ curveExportSdFailed: '风扇 {id} 曲线已保存到本地,SD 卡保存失败: {msg}',
+ invalidConfigFormat: '无效的配置文件格式',
+ invalidCurvePoints: '配置文件中曲线点无效',
+ curvePointFormatError: '曲线点格式错误',
+ configImported: '已导入配置文件: {name}',
+ importConfigFailed: '导入配置失败',
// 曲线点
tempPlaceholder: '温度',
speedPlaceholder: '转速'
@@ -2061,6 +2568,18 @@ i18n.registerLanguage('zh-CN', {
layoutLargeDesc: '3/4 宽度',
layoutFull: '整行',
layoutFullDesc: '独占一整行',
+ // 组件类型名称(编辑面板标题等)
+ widgetTypeRing: '环形进度',
+ widgetTypeGauge: '仪表盘',
+ widgetTypeTemp: '温度计',
+ widgetTypeNumber: '数字',
+ widgetTypeBar: '进度条',
+ widgetTypeText: '文本',
+ widgetTypeStatus: '状态灯',
+ widgetTypeIcon: '图标状态',
+ widgetTypeDual: '双数值',
+ widgetTypePercent: '百分比',
+ widgetTypeLog: '日志流',
// 预设组件
presetCpu: 'CPU',
presetMem: '内存',
@@ -2087,7 +2606,8 @@ i18n.registerLanguage('zh-CN', {
seconds10: '10 秒',
seconds30: '30 秒',
minute1: '1 分钟',
- addedWidgets: '📦 已添加组件',
+ noWidgets: '暂无组件',
+ addedWidgets: '已添加组件',
addNewWidget: '➕ 添加新组件',
selectWidgetHint: '选择左侧组件进行编辑
或添加新组件',
moveUp: '上移',
@@ -2111,6 +2631,7 @@ i18n.registerLanguage('zh-CN', {
labelName: '标签名称',
labelPlaceholder: '组件名称',
icon: '图标',
+ iconPlaceholder: 'emoji',
color: '颜色',
layoutWidth: '📐 布局宽度',
unit: '单位',
@@ -2121,7 +2642,7 @@ i18n.registerLanguage('zh-CN', {
dataExpressionPlaceholder: '点击选择变量或输入表达式',
dataExpressionHint: '支持: ${变量名} 引用变量,${a} + ${b} 数学运算,${a} + "单位" 文本拼接',
preview: '预览',
- save: '💾 保存',
+ save: '保存',
// 日志组件
clickToRead: '点击「读取」开始获取日志',
expandLog: '展开日志',
@@ -2132,7 +2653,7 @@ i18n.registerLanguage('zh-CN', {
statusWarning: '警告',
// 温度变量选择
tempVariables: '🌡️ 温度变量',
- otherNumericVariables: '📊 其他数值变量',
+ otherNumericVariables: '其他数值变量',
// 错误消息
setDutyLimitFailed: '设置占空比限制失败',
setCurveFailed: '设置曲线失败'
@@ -2167,8 +2688,11 @@ i18n.registerLanguage('zh-CN', {
// Button texts
turnOff: '关闭',
turnOn: '开启',
+ clickOff: '点击关闭',
+ clickOn: '点击开启',
moreEffects: '更多动画',
- stopEffect: '停止',
+ stopEffect: '停止动画',
+ filterEffect: '滤镜效果',
noEffects: '暂无可用动画',
effects: '程序动画',
selectAnimation: '请先选择一个动画',
@@ -2196,6 +2720,7 @@ i18n.registerLanguage('zh-CN', {
// Modal - Text
textTitle: '文本显示',
enterTextToDisplay: '输入要显示的文本',
+ fontAndStyle: '字体与样式',
font: '字体',
refreshFonts: '刷新字体',
alignment: '对齐',
@@ -2203,6 +2728,7 @@ i18n.registerLanguage('zh-CN', {
alignCenter: '居中',
alignRight: '右对齐',
autoPosition: '自动位置',
+ position: '位置',
scroll: '滚动',
scrollNone: '无滚动',
scrollLeft: '向左',
@@ -2341,6 +2867,7 @@ i18n.registerLanguage('zh-CN', {
chinese: '中文',
english: 'EN',
notLoggedIn: '未登录',
+ permissionLevel: '权限级别',
// 按钮状态
stopTracking: '停止跟踪',
startTracking: '▶️ 开始跟踪',
@@ -2386,11 +2913,11 @@ i18n.registerLanguage('zh-CN', {
// 弹窗标题
newCommand: '➕ 新建指令',
editCommand: '编辑指令',
- commandVariables: '📊 指令变量',
- sourceVariables: '📊 {source} 变量',
+ commandVariables: '指令变量',
+ sourceVariables: '{source} 变量',
editActionTemplate: '编辑动作模板',
// 动作
- updateAction: '💾 更新',
+ updateAction: '更新',
// 验证错误
lowVoltageError: '低电压阈值必须小于恢复电压阈值',
shutdownDelayError: '关机倒计时必须在 10-600 秒之间',
@@ -2515,6 +3042,9 @@ i18n.registerLanguage('zh-CN', {
// 终端页面
terminal: {
+ connecting: '正在连接到设备...',
+ connected: '已连接到设备',
+ helpHint: '输入 {help} 查看可用命令',
pageTitle: 'Web 终端',
terminalHint: '提示: 输入
help 查看命令 |
Ctrl+C 中断 |
Ctrl+L 清屏 |
↑↓ 历史',
systemLogTitle: '系统日志',
@@ -2583,9 +3113,25 @@ i18n.registerLanguage('zh-CN', {
// 任务计数
taskCountLabel: '共 {count} 个任务',
// 任务栈
+ taskStackUsage: '任务栈使用',
taskStackTotal: '任务栈总分配',
totalTaskCount: '任务总数',
- stackHint: '剩余栈 <256B 为危险区域,<512B 为警告区域'
+ stackHint: '剩余栈 <256B 为危险区域,<512B 为警告区域',
+ state: '状态',
+ cpu: 'CPU',
+ runtimeStats: '运行时统计',
+ updateTime: '更新时间: ',
+ loadFailed: '获取内存详情失败',
+ getDataFailed: '获取数据失败',
+ staticDataBss: '.data + .bss',
+ iramTitle: 'IRAM (指令内存)',
+ rtcUsed: '已用',
+ rtcTotal: '总计',
+ // Memory tips
+ dramFragmented: 'DRAM 碎片化严重,建议重启系统',
+ psramSufficient: 'PSRAM 空间充足,可用于大型缓冲区',
+ dramLow: 'DRAM 内存不足,建议释放内存',
+ psramLow: 'PSRAM 内存不足'
},
// 数据源
diff --git a/components/ts_webui/web/js/router.js b/components/ts_webui/web/js/router.js
index 5ae5593..346fb17 100644
--- a/components/ts_webui/web/js/router.js
+++ b/components/ts_webui/web/js/router.js
@@ -65,7 +65,7 @@ class Router {
}
if (access.reason === 'root_required') {
// 需要 root 权限
- showToast('此页面需要 root 权限', 'error');
+ showToast(t('toast.rootRequired'), 'error');
window.location.hash = '/'; // 重定向到首页
return;
}
@@ -92,6 +92,17 @@ class Router {
}
}
+ /**
+ * 获取当前 hash 对应的 loader,供语言切换后重新渲染当前页
+ * @returns {function|null} 当前页的 loader 或 null
+ */
+ getCurrentLoader() {
+ const hash = window.location.hash.slice(1) || '/';
+ const access = this.checkAccess(hash);
+ if (!access.allowed) return null;
+ return this.routes[hash] || this.routes['/'] || null;
+ }
+
/**
* 根据权限更新导航菜单可见性
*/
diff --git a/components/ts_webui/web/js/terminal.js b/components/ts_webui/web/js/terminal.js
index b5cee16..0af16f9 100644
--- a/components/ts_webui/web/js/terminal.js
+++ b/components/ts_webui/web/js/terminal.js
@@ -87,7 +87,7 @@ class WebTerminal {
this.writeln('\x1b[1;36m║\x1b[0m \x1b[1;33m⛰️ TianshanOS Web Terminal\x1b[0m \x1b[1;36m║\x1b[0m');
this.writeln('\x1b[1;36m╚══════════════════════════════════════════╝\x1b[0m');
this.writeln('');
- this.writeln('正在连接到设备...');
+ this.writeln(typeof t === 'function' ? t('terminal.connecting') : '正在连接到设备...');
return true;
}
@@ -319,8 +319,8 @@ class WebTerminal {
case 'connected':
this.connected = true;
this.prompt = msg.prompt || 'tianshan> ';
- this.writeln('\x1b[1;32m已连接到设备\x1b[0m');
- this.writeln('输入 \x1b[1;33mhelp\x1b[0m 查看可用命令');
+ this.writeln('\x1b[1;32m' + (typeof t === 'function' ? t('terminal.connected') : '已连接到设备') + '\x1b[0m');
+ this.writeln(typeof t === 'function' ? t('terminal.helpHint', { help: '\x1b[1;33mhelp\x1b[0m' }) : '输入 \x1b[1;33mhelp\x1b[0m 查看可用命令');
this.writeln('');
this.writePrompt();
break;
diff --git a/partitions.csv b/partitions.csv
index 2eb16d3..19320ba 100644
--- a/partitions.csv
+++ b/partitions.csv
@@ -11,9 +11,9 @@
# 0x020000 - 0x320000: OTA_0 (3MB)
# 0x320000 - 0x620000: OTA_1 (3MB)
# 0x620000 - 0x6A0000: Storage SPIFFS (512KB)
-# 0x6A0000 - 0x8A0000: WWW SPIFFS (2MB)
-# 0x8A0000 - 0x8B0000: FAT FS (64KB)
-# 0x8B0000 - 0x1000000: Reserved (~7.3MB for future)
+# 0x6A0000 - 0x9A0000: WWW SPIFFS (3MB)
+# 0x9A0000 - 0x9B0000: FAT FS (64KB)
+# 0x9B0000 - 0x1000000: Reserved (~6.3MB for future)
#
# Security Configuration:
# - nvs: 48KB for keystore (8x RSA-4096 + system config)
@@ -27,5 +27,5 @@ phy_init, data, phy, 0x18000, 0x1000,
ota_0, app, ota_0, 0x20000, 0x300000,
ota_1, app, ota_1, 0x320000, 0x300000,
storage, data, spiffs, 0x620000, 0x80000,
-www, data, spiffs, 0x6A0000, 0x200000,
-fatfs, data, fat, 0x8A0000, 0x10000,
+www, data, spiffs, 0x6A0000, 0x300000,
+fatfs, data, fat, 0x9A0000, 0x10000,