feat: full i18n support - all pages use Lang.t() for zh/en

This commit is contained in:
2026-07-27 20:31:41 +08:00
parent 860bc015e8
commit de58a1c42f
10 changed files with 578 additions and 282 deletions
+336 -41
View File
@@ -2,50 +2,332 @@ const Lang = {
_current: 'zh',
_strings: {
zh: {
home: '首页', dashboard: '仪表盘', scene: '场景', settings: '设置',
debug: '数据测试', selectGame: '选择游戏', collapsed: '折叠侧边栏',
connected: '已连接', disconnected: '未连接', speed: '实时速度', rpm: '实时转速',
gear: '当前档位', server: '服务器状态', telemetry: '遥测监听',
connections: 'WebSocket 连接', packets: '数据包接收', lastPacket: '最后数据包',
currentGame: '当前游戏', systemOverview: '系统概览', running: '运行中',
stopped: '已停止', notStarted: '未启动', noData: '暂无数据', client: '个客户端',
notSelected: '未选择', packetsUnit: '包', secAgo: '秒前',
importTsd: '导入 .tsd', importTss: '导入 .tss', importTsp: '导入 .tsp',
export: '导出', delete: '删除', edit: '编辑', render: '渲染', save: '保存',
cancel: '取消', create: '新建场景', sceneName: '场景名称',
search: '搜索仪表盘...', sort: '排序', all: '全部', none: '暂无',
forward: '数据转发', addTarget: '+ 添加目标', saveActive: '保存并生效',
connectionSettings: '连接设置', telemetryPort: '遥测监听端口',
unifiedPort: '统一端口', perGamePort: '各游戏独立端口',
restart: '重启监听', pluginSettings: '游戏插件管理',
plugins: '游戏插件', reload: '重新加载', backup: '数据备份',
exportBackup: '下载 .tsb', importBackup: '导入 .tsb',
// Nav
home: '首页', dashboard: '仪表盘', scene: '场景', market: '市场',
record: '录制回放', debug: '数据测试', settings: '设置',
selectGame: '选择游戏', collapseSidebar: '折叠侧边栏',
// Connection
connected: '已连接', disconnected: '未连接', connectionError: '连接错误',
// Status cards
serverStatus: '服务器状态', telemetryStatus: '遥测监听',
wsConnections: 'WebSocket 连接', packetCount: '数据包接收',
lastPacket: '最后数据包', currentGame: '当前游戏',
liveSpeed: '实时速度', liveRpm: '实时转速', liveGear: '当前档位',
systemOverview: '系统概览', overviewDesc: '实时监控 TurboSu 运行状态与游戏遥测连接',
running: '运行中', stopped: '已停止', notStarted: '未启动',
noData: '暂无数据', notSelected: '未选择',
listening: '监听中',
// Dashboard
searchDashboards: '搜索仪表盘...', sortByName: '名称', sortByCategory: '分类',
sortByAuthor: '作者', importTsd: '导入 .tsd', newDashboard: '+ 新建',
noDashboards: '暂无仪表盘主题', createFirstDash: '创建或导入你的第一个仪表盘主题',
noCompatibleDash: '当前游戏没有兼容的仪表盘',
allCategory: '全部', builtin: '内置', custom: '自定义',
fullCompatible: '全', limitedCompatible: '限',
// Scene
sceneManagement: '场景管理', noGameSelected: '请先在侧边栏选择一个游戏',
newScene: '+ 新建场景', importTss: '导入 .tss',
noScenes: '暂无场景', createFirstScene: '点击"新建场景"创建你的第一个场景布局',
render: '渲染', edit: '编辑', delete: '删除', export: '导出',
sceneName: '场景名称', sceneDescription: '场景描述(可选)',
sceneCanvasHint: '提示:保存后可在场景编辑器中添加仪表盘、调整布局和画布比例。',
save: '保存', cancel: '取消', editScene: '编辑场景',
canvasesCount: '个画布', backToList: '← 返回', saveScene: '保存场景',
dashListDrag: '仪表盘列表 (拖到画布)', noCompatibleDashForScene: '无兼容仪表盘',
// Market
marketPage: '仪表盘市场', marketDesc: '浏览并安装社区分享的仪表盘和场景',
notLoggedIn: '未登录', loggedIn: '已登录',
login: '登录', register: '注册', logout: '退出',
publish: '发布你的作品', install: '安装', installed: '已安装',
installScene: '安装场景', uninstall: '卸载',
pending: '待审核', approved: '已通过',
marketLogin: '市场账户',
email: '邮箱', password: '密码', username: '用户名',
loginSuccess: '登录成功', loginFailed: '登录失败',
registerSuccess: '注册成功,请查收验证邮件(点击关闭)', registerFailed: '注册失败',
publishTitle: '发布作品', publishDesc: '选择要发布的仪表盘或场景',
chooseFile: '📁 选择文件 (.tsd / .tss)',
orChooseLocal: '— 或从本地选择 —',
localDashboards: '本地仪表盘', localScenes: '本地场景',
noPublishable: '没有可发布的内容', publishFailed: '发布失败',
publishSuccess: '发布成功',
uploadPreview: '上传预览图', dropPreview: '点击或拖放图片到此处 (最大 6MB)',
previewTooLarge: '预览图不能超过 6MB', imageOnly: '仅支持图片格式',
updateItem: '更新', confirmDelete: '确定删除?',
myItems: '我的', myDashboards: '我的仪表盘', myScenes: '我的场景',
noUploads: '你还没有上传任何内容',
refresh: '🔄', retry: '重试', loadFailed: '加载失败',
dashboardsTab: '仪表盘', scenesTab: '场景', myTab: '我的',
installedHint: '安装成功!请在仪表盘页面查看',
installFailed: '安装失败',
needLogin: '请先登录',
confirmUninstall: '确定卸载 "$1"',
// Record
recordPage: '录制与回放', recordDesc: '录制原始 UDP 流到 .tsr 文件,回放调试仪表盘',
recordControl: '录制控制', startRecord: '开始录制', stopRecord: '停止录制',
ready: '就绪', recording: '录制中...', autoDetect: '自动检测',
playbackActive: '正在回放: ', playbackHint: '打开仪表盘页面即可看到数据',
stopPlayback: '停止', recordFiles: '录制文件', importTsr: '导入 .tsr',
noRecordings: '暂无录制文件', play: '播放',
// Debug
debugPage: '数据测试', debugToolbar: '',
parsedFields: '解析后的数据字段', rawJsonStream: '原始 JSON 数据流',
waitingData: '等待数据...', pause: '⏸ 暂停', resume: '▶ 继续',
clear: '清空', autoScroll: '自动滚动', viewLog: '📄 日志',
// Settings
connectionSettings: '连接设置',
telemetryPort: '遥测监听端口', telemetryPortDesc: '全局统一端口',
tcpPort: 'TCP 默认端口', tcpPortDesc: 'TCP 协议默认监听端口',
unifiedPort: '统一端口', unifiedPortDesc: '关闭后切游戏自动切换到各游戏的默认端口',
unifiedPortToggle: '使用游戏独立端口',
perGamePort: '各游戏独立端口', savePorts: '保存端口',
restartListener: '重启遥测监听', restartListenerDesc: '修改端口或切换游戏后',
restartBtn: '重启监听',
dataForwarding: '数据转发', dataForwardingDesc: '将收到的游戏原始 UDP 数据包完整转发到下游物理外设',
addTarget: '+ 添加目标', saveEffective: '保存并生效',
noForwardTargets: '暂无转发目标,点击下方添加',
forwardEnabled: '启用',
dashManagement: '仪表盘管理', rightClickExport: '右键卡片导出',
pluginManagement: '游戏插件管理', importTsp: '导入 .tsp',
reloadPlugins: '重新加载', noPlugins: '暂无游戏插件',
dataBackup: '数据备份', exportBackup: '导出全量备份',
backupDesc: '打包仪表盘+场景+配置为 .tsb', downloadTsb: '下载 .tsb',
restoreBackup: '恢复备份', restoreBackupDesc: '从 .tsb 文件恢复',
importTsb: '导入 .tsb',
devTools: '开发工具', testMode: 'UI 测试模式',
system: '系统', restartServer: '重启服务',
about: '关于', author: 'Yei.J. (AskaEth)',
testModeDesc: '本地随机模拟遥测数据',
testModeOn: '测试模式已开启', testModeOff: '测试模式已关闭',
systemSection: '系统', restartServer: '重启 TurboSu',
restartServerDesc: '重新加载所有代码和配置', restartBtnSys: '重启服务',
confirmRestart: '确定重启 TurboSu 服务?',
restarting: '服务正在重启,3秒后自动刷新...',
about: '关于', aboutDesc: '赛车遥测仪表盘 · 极速中枢 · Yei.J. (AskaEth)',
// General
loading: '加载中...', confirm: '确定', success: '成功', error: '错误',
clickClose: '点击关闭', saved: '已保存', updated: '已更新',
deleted: '已删除', portUpdated: '端口已更新,重启后生效',
configSaved: '已保存', telemetryRestarted: '遥测监听已重启',
pluginInstalled: '插件安装成功', pluginRemoved: '插件已移除',
pluginReloaded: '插件已重新加载',
// Counters & formats
connectionsCount: '%d 个客户端', packetsCount: '%d 包',
secondsAgo: '%d秒前', gearLabel: '档位 %s',
downloadCount: '%d 次下载', downloadCountShort: '%d 次',
canvasesLabel: '%d 个画布', forwardsSaved: '已保存 %d 个转发目标',
defaultPortLabel: '默认: ',
// More UI
noGamePlugins: '暂无可用的游戏插件',
noGame: '未关联游戏', currentGamePrefix: '当前游戏: ',
community: '社区',
noScenesMarket: '暂无场景', noDashboardsMarket: '暂无仪表盘',
// Prompts / modals
dashboardNamePrompt: '仪表盘名称:',
marketLoginPrompt: '请先登录市场账户',
enterEmailPassword: '请输入邮箱和密码',
selectFileOrPreview: '请选择文件或预览图',
confirmRemove: '移除?',
// Toast: import / create / upload
importSuccess: '导入成功', importFailed: '导入失败',
createSuccess: '创建成功', createFailed: '创建失败',
uploadSuccess: '上传成功', uploadFailed: '上传失败',
uploadFailedMsg: '上传失败: ',
installSuccess: '安装成功', installFailedMsg: '安装失败: ',
remove: '移除',
uninstallSuccess: '已卸载', uninstallFailed: '卸载失败',
uninstallNotFound: '未找到本地安装',
deleteFailed: '删除失败', loggedOut: '已退出',
sceneSaveSuccess: '场景保存成功', saveFailed: '保存失败: ',
sceneImportSuccess: '场景导入成功',
playbackStarted: '回放已开始', playbackStartedHint: '回放已开始,打开仪表盘查看数据',
restoreSuccess: '恢复成功', restoreFailed: '恢复失败: ',
removeSuccess: '已移除',
exportTsdHint: '正在下载 .tsd 文件...',
linkCopied: '链接已复制,可在局域网设备访问',
linkCopiedShort: '链接已复制',
portsSaved: '端口已保存',
marketEmpty: '市场暂无仪表盘', marketLoadFailed: '市场加载失败',
installDataIncomplete: '安装失败:数据不完整',
installNetworkError: '安装失败:网络错误',
restartRequestSent: '重启请求已发送,请手动刷新',
marketNoItems: '暂无',
confirmUpdate: '确认更新',
},
en: {
home: 'Home', dashboard: 'Dashboard', scene: 'Scene', settings: 'Settings',
debug: 'Debug', selectGame: 'Select Game', collapsed: 'Collapse',
connected: 'Connected', disconnected: 'Disconnected', speed: 'Speed', rpm: 'RPM',
gear: 'Gear', server: 'Server', telemetry: 'Telemetry',
connections: 'Connections', packets: 'Packets', lastPacket: 'Last Packet',
currentGame: 'Game', systemOverview: 'Overview', running: 'Running',
stopped: 'Stopped', notStarted: 'Off', noData: 'No Data', client: ' clients',
notSelected: 'None', packetsUnit: 'pkt', secAgo: 's ago',
importTsd: 'Import .tsd', importTss: 'Import .tss', importTsp: 'Import .tsp',
export: 'Export', delete: 'Delete', edit: 'Edit', render: 'Render', save: 'Save',
cancel: 'Cancel', create: 'New Scene', sceneName: 'Name',
search: 'Search...', sort: 'Sort', all: 'All', none: 'None',
forward: 'Forward', addTarget: '+ Add', saveActive: 'Save',
connectionSettings: 'Connection', telemetryPort: 'Port',
unifiedPort: 'Unified', perGamePort: 'Per Game',
restart: 'Restart', pluginSettings: 'Plugins',
plugins: 'Plugins', reload: 'Reload', backup: 'Backup',
exportBackup: 'Download .tsb', importBackup: 'Import .tsb',
devTools: 'Dev Tools', testMode: 'Test Mode',
system: 'System', restartServer: 'Restart',
about: 'About', author: 'Yei.J. (AskaEth)',
home: 'Home', dashboard: 'Dashboard', scene: 'Scene', market: 'Market',
record: 'Record', debug: 'Debug', settings: 'Settings',
selectGame: 'Select Game', collapseSidebar: 'Collapse',
connected: 'Connected', disconnected: 'Disconnected', connectionError: 'Error',
serverStatus: 'Server', telemetryStatus: 'Telemetry',
wsConnections: 'Connections', packetCount: 'Packets',
lastPacket: 'Last Packet', currentGame: 'Game',
liveSpeed: 'Speed', liveRpm: 'RPM', liveGear: 'Gear',
systemOverview: 'System Overview', overviewDesc: 'Real-time monitoring',
running: 'Running', stopped: 'Stopped', notStarted: 'Off',
noData: 'No Data', notSelected: 'None',
listening: 'Listening',
searchDashboards: 'Search...', sortByName: 'Name', sortByCategory: 'Category',
sortByAuthor: 'Author', importTsd: 'Import .tsd', newDashboard: '+ New',
noDashboards: 'No dashboards', createFirstDash: 'Create or import your first dashboard',
noCompatibleDash: 'No compatible dashboards for current game',
allCategory: 'All', builtin: 'Built-in', custom: 'Custom',
fullCompatible: 'All', limitedCompatible: 'Lim',
sceneManagement: 'Scene Manager', noGameSelected: 'Select a game first',
newScene: '+ New Scene', importTss: 'Import .tss',
noScenes: 'No scenes', createFirstScene: 'Create your first scene',
render: 'Render', edit: 'Edit', delete: 'Delete', export: 'Export',
sceneName: 'Name', sceneDescription: 'Description (optional)',
sceneCanvasHint: 'Tip: Save first, then edit layout in scene editor.',
save: 'Save', cancel: 'Cancel', editScene: 'Edit Scene',
canvasesCount: 'canvases', backToList: '← Back', saveScene: 'Save Scene',
dashListDrag: 'Dashboards (drag to canvas)', noCompatibleDashForScene: 'No compatible dashboards',
marketPage: 'Marketplace', marketDesc: 'Browse and install community dashboards & scenes',
notLoggedIn: 'Not logged in', loggedIn: 'Logged in',
login: 'Login', register: 'Register', logout: 'Logout',
publish: 'Publish', install: 'Install', installed: 'Installed',
installScene: 'Install Scene', uninstall: 'Uninstall',
pending: 'Pending', approved: 'Approved',
marketLogin: 'Account',
email: 'Email', password: 'Password', username: 'Username',
loginSuccess: 'Login successful', loginFailed: 'Login failed',
registerSuccess: 'Registered! Check email to verify (click to close)', registerFailed: 'Registration failed',
publishTitle: 'Publish', publishDesc: 'Select a dashboard or scene',
chooseFile: '📁 Choose file (.tsd / .tss)',
orChooseLocal: '— or select from local —',
localDashboards: 'Local Dashboards', localScenes: 'Local Scenes',
noPublishable: 'Nothing to publish', publishFailed: 'Publish failed',
publishSuccess: 'Published successfully',
uploadPreview: 'Upload Preview', dropPreview: 'Click or drop image (max 6MB)',
previewTooLarge: 'Preview too large (6MB max)', imageOnly: 'Images only',
updateItem: 'Update', confirmDelete: 'Confirm delete?',
myItems: 'Mine', myDashboards: 'My Dashboards', myScenes: 'My Scenes',
noUploads: 'Nothing uploaded yet',
refresh: '🔄', retry: 'Retry', loadFailed: 'Load failed',
dashboardsTab: 'Dashboards', scenesTab: 'Scenes', myTab: 'Mine',
installedHint: 'Installed! Check dashboard page',
installFailed: 'Install failed',
needLogin: 'Please login first',
confirmUninstall: 'Uninstall "$1"?',
recordPage: 'Record & Playback', recordDesc: 'Record raw UDP to .tsr, replay for debugging',
recordControl: 'Record Control', startRecord: 'Start', stopRecord: 'Stop',
ready: 'Ready', recording: 'Recording...', autoDetect: 'Auto Detect',
playbackActive: 'Playing: ', playbackHint: 'Open a dashboard to see data',
stopPlayback: 'Stop', recordFiles: 'Recordings', importTsr: 'Import .tsr',
noRecordings: 'No recordings', play: 'Play',
debugPage: 'Debug', debugToolbar: '',
parsedFields: 'Parsed Fields', rawJsonStream: 'Raw JSON Stream',
waitingData: 'Waiting...', pause: '⏸ Pause', resume: '▶ Resume',
clear: 'Clear', autoScroll: 'Auto Scroll', viewLog: '📄 Log',
connectionSettings: 'Connection',
telemetryPort: 'Telemetry Port', telemetryPortDesc: 'Global port',
tcpPort: 'TCP Port', tcpPortDesc: 'Default TCP port',
unifiedPort: 'Unified Port', unifiedPortDesc: 'Turn off for per-game ports',
unifiedPortToggle: 'Use per-game ports',
perGamePort: 'Per-Game Ports', savePorts: 'Save Ports',
restartListener: 'Restart Listener', restartListenerDesc: 'After port/game change',
restartBtn: 'Restart',
dataForwarding: 'Data Forwarding', dataForwardingDesc: 'Forward raw UDP to downstream devices',
addTarget: '+ Add', saveEffective: 'Save',
noForwardTargets: 'No targets, add below',
forwardEnabled: 'Enabled',
dashManagement: 'Dashboard Management', rightClickExport: 'Right-click to export',
pluginManagement: 'Plugin Management', importTsp: 'Import .tsp',
reloadPlugins: 'Reload', noPlugins: 'No plugins',
dataBackup: 'Backup', exportBackup: 'Export Backup',
backupDesc: 'Pack dashboards+scenes+config as .tsb', downloadTsb: 'Download .tsb',
restoreBackup: 'Restore Backup', restoreBackupDesc: 'Restore from .tsb',
importTsb: 'Import .tsb',
devTools: 'Dev Tools', testMode: 'UI Test Mode',
testModeDesc: 'Simulate telemetry locally',
testModeOn: 'Test mode on', testModeOff: 'Test mode off',
systemSection: 'System', restartServer: 'Restart TurboSu',
restartServerDesc: 'Reload all code and config', restartBtnSys: 'Restart',
confirmRestart: 'Restart TurboSu?',
restarting: 'Restarting, will refresh in 3s...',
about: 'About', aboutDesc: 'Racing Telemetry Dashboard · Yei.J. (AskaEth)',
loading: 'Loading...', confirm: 'Confirm', success: 'Success', error: 'Error',
clickClose: 'Click to close', saved: 'Saved', updated: 'Updated',
deleted: 'Deleted', portUpdated: 'Port updated, restart to apply',
configSaved: 'Saved', telemetryRestarted: 'Listener restarted',
pluginInstalled: 'Plugin installed', pluginRemoved: 'Plugin removed',
pluginReloaded: 'Plugins reloaded',
connectionsCount: '%d clients', packetsCount: '%d packets',
secondsAgo: '%ds ago', gearLabel: 'Gear %s',
downloadCount: '%d downloads', downloadCountShort: '%d',
canvasesLabel: '%d canvases', forwardsSaved: '%d targets saved',
defaultPortLabel: 'Default: ',
noGamePlugins: 'No game plugins available',
noGame: 'No game', currentGamePrefix: 'Current game: ',
community: 'Community',
noScenesMarket: 'No scenes', noDashboardsMarket: 'No dashboards',
dashboardNamePrompt: 'Dashboard name:',
marketLoginPrompt: 'Please login first',
enterEmailPassword: 'Enter email and password',
selectFileOrPreview: 'Select file or preview',
confirmRemove: 'Remove?',
importSuccess: 'Imported', importFailed: 'Import failed',
createSuccess: 'Created', createFailed: 'Create failed',
uploadSuccess: 'Uploaded', uploadFailed: 'Upload failed',
uploadFailedMsg: 'Upload failed: ',
installSuccess: 'Installed', installFailedMsg: 'Install failed: ',
remove: 'Remove',
uninstallSuccess: 'Uninstalled', uninstallFailed: 'Uninstall failed',
uninstallNotFound: 'Not found locally',
deleteFailed: 'Delete failed', loggedOut: 'Logged out',
sceneSaveSuccess: 'Scene saved', saveFailed: 'Save failed: ',
sceneImportSuccess: 'Scene imported',
playbackStarted: 'Playback started', playbackStartedHint: 'Playback started, open dashboard to view',
restoreSuccess: 'Restored', restoreFailed: 'Restore failed: ',
removeSuccess: 'Removed',
exportTsdHint: 'Downloading .tsd...',
linkCopied: 'Link copied, accessible on LAN',
linkCopiedShort: 'Link copied',
portsSaved: 'Ports saved',
marketEmpty: 'No dashboards on market', marketLoadFailed: 'Market load failed',
installDataIncomplete: 'Incomplete data',
installNetworkError: 'Network error',
restartRequestSent: 'Restart sent, refresh manually',
marketNoItems: 'None',
confirmUpdate: 'Confirm Update',
}
},
@@ -53,12 +335,25 @@ const Lang = {
this._current = localStorage.getItem('turbosu-lang') || 'zh';
const btn = document.getElementById('lang-toggle');
if (btn) btn.textContent = this._current === 'zh' ? 'EN' : '中';
this._scan();
},
t(key) {
return (this._strings[this._current] || this._strings.zh)[key] || key;
},
_scan() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
const text = this.t(key);
if (el.tagName === 'INPUT' && (el.type === 'text' || el.type === 'search' || el.type === 'email' || el.type === 'password')) {
el.placeholder = text;
} else {
el.textContent = text;
}
});
},
toggle() {
this._current = this._current === 'zh' ? 'en' : 'zh';
localStorage.setItem('turbosu-lang', this._current);