const Lang = { _current: 'zh', _strings: { zh: { // 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 测试模式', 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', 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', } }, init() { 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); location.reload(); } }; window.Lang = Lang;