diff --git a/src/main.js b/src/main.js index fa3e115..4d7fecc 100644 --- a/src/main.js +++ b/src/main.js @@ -305,7 +305,7 @@ function openAppWindow(pkg, tip) { fullscreen: displaySettings().mode === 'fullscreen', }); appWin.setIcon(rec && rec.icoPath); - appWin.setState({ tip: tip || '正在启动安卓运行时…', ready: false, landscape: false }); + appWin.setState({ tip: tip || 'Starting Android runtime…', ready: false, landscape: false }); currentAppPkg = pkg; } @@ -341,25 +341,25 @@ async function autoDeploy() { // 整包拿不到(没网 / 还没发过该资产 / 被限流)再逐个从官方源装。 if (needEngine && store.getSettings().runtime.preferGithubRuntime !== false) { try { - progress({ message: '正在从 GitHub 下载运行时…' }); + progress({ message: 'Downloading runtime from GitHub…' }); await runtime.provisioner.installRuntimePack(progress); const afterPack = await runtime.provisioner.checkEnv(); if (afterPack.emulator.ok && afterPack.sysimg.ok) return { ok: true, source: 'github' }; - progress({ message: '运行时整包不完整,改用官方源补齐…' }); + progress({ message: 'Runtime package is incomplete; downloading missing components from official sources…' }); } catch (e) { - progress({ message: `GitHub 运行时不可用(${e.message}),改用官方源…` }); + progress({ message: `GitHub runtime unavailable (${e.message}); switching to official sources…` }); } } if (!env.adb.ok) { - progress({ message: '正在准备 adb…' }); + progress({ message: 'Preparing adb…' }); await runtime.provisioner.installAdb(progress); } if (!env.emulator.ok) { - progress({ message: '正在准备 Android 运行时…' }); + progress({ message: 'Preparing Android runtime…' }); await runtime.provisioner.installEmulator(progress); } if (!env.sysimg.ok) { - progress({ message: '正在准备 Android 系统镜像…' }); + progress({ message: 'Preparing Android system image…' }); await runtime.provisioner.installSystemImage(progress); } const after = await runtime.provisioner.checkEnv(); @@ -402,7 +402,7 @@ function enableWindowsFeature(feature) { const proc = spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }); proc.on('exit', (code) => resolve({ ok: code === 0, restart: true, code })); - proc.on('error', () => resolve({ ok: false, error: '无法启动 PowerShell' })); + proc.on('error', () => resolve({ ok: false, error: 'Unable to start PowerShell' })); }); } @@ -455,7 +455,7 @@ function createWindow({ show = true } = {}) { if (quitting) return; e.preventDefault(); mainWindow.hide(); - notify('ApkStart 仍在后台运行', '点击托盘图标可重新打开;右键托盘可退出。'); + notify('ApkStart is still running in the background', 'Click the tray icon to reopen; right-click it to quit.'); }); if (IS_DEV) mainWindow.webContents.openDevTools({ mode: 'detach' }); @@ -493,21 +493,21 @@ function buildTray() { const refresh = () => { const apps = store.getApps().slice(0, 8); const menu = Menu.buildFromTemplate([ - { label: '打开 ApkStart', click: () => showWindow() }, + { label: 'Open ApkStart', click: () => showWindow() }, { type: 'separator' }, ...(apps.length ? apps.map((a) => ({ label: a.label, click: () => runLaunch(a.package) })) - : [{ label: '还没有安装应用', enabled: false }]), + : [{ label: 'No apps installed yet', enabled: false }]), { type: 'separator' }, { - label: displaySettings().mode === 'fullscreen' ? '切回竖屏窗口' : '全屏(自适应屏幕)', + label: displaySettings().mode === 'fullscreen' ? 'Switch to portrait window' : 'Fullscreen (fit to screen)', click: () => { toggleDisplayMode(); }, }, - { label: '重建桌面/开始菜单快捷方式', click: () => { installer.ensureShortcuts().catch(() => {}); } }, - { label: '同步共享目录', click: () => sharer.sync() }, - { label: '设置', click: () => { showWindow(); send('ui:navigate', 'settings'); } }, + { label: 'Rebuild Desktop / Start menu shortcuts', click: () => { installer.ensureShortcuts().catch(() => {}); } }, + { label: 'Sync shared folder', click: () => sharer.sync() }, + { label: 'Settings', click: () => { showWindow(); send('ui:navigate', 'settings'); } }, { - label: updateInfo && updateInfo.hasUpdate ? `发现新版本 ${updateInfo.tag}` : '检查更新', + label: updateInfo && updateInfo.hasUpdate ? `New version available: ${updateInfo.tag}` : 'Check for updates', click: async () => { showWindow(); send('ui:navigate', 'settings'); @@ -516,22 +516,22 @@ function buildTray() { if (info && info.hasUpdate && info.asset) { const r = await dialog.showMessageBox(mainWindow || undefined, { type: 'question', - title: '发现新版本', - message: `ApkStart ${info.tag} 已发布,当前版本 v${app.getVersion()}。现在更新吗?`, + title: 'Update available', + message: `ApkStart ${info.tag} is available. Current version: v${app.getVersion()}. Update now?`, detail: (info.notes || '').slice(0, 600), - buttons: ['立即更新', '稍后'], + buttons: ['Update now', 'Later'], defaultId: 0, cancelId: 1, }); if (r.response === 0) runUpdate(); } else if (info && !info.hasUpdate) { - notify('已是最新版本', `ApkStart v${app.getVersion()}`); + notify('Already up to date', `ApkStart v${app.getVersion()}`); } }, }, { type: 'separator' }, { - label: '退出', + label: 'Quit', click: async () => { try { appWin.close({ keepApp: true }); } catch (_) { /* ignore */ } quitting = true; @@ -541,7 +541,7 @@ function buildTray() { ]); tray.setContextMenu(menu); }; - tray.setToolTip('ApkStart — 安卓应用运行器'); + tray.setToolTip('ApkStart — Android App Launcher'); // Windows 托盘的惯例是单击就打开主窗口(双击当然也可以)。 // 之前只绑了 double-click,用户单击没反应,会以为托盘图标坏了。 // 右键才出菜单,所以这里单击直接 showWindow 不会和菜单冲突。 @@ -561,11 +561,11 @@ async function runInstall(filePath) { // 同一个包正在装(用户连点了几次图标 / 拖了好几次)→ 只提示一次,别重复跑一遍安装 if (installBusy) { if (installBusy === filePath) { - send('toast', { type: 'info', message: '这个 APK 正在安装中,请稍等…' }); - return { ok: false, error: '正在安装中' }; + send('toast', { type: 'info', message: 'This APK is already being installed. Please wait…' }); + return { ok: false, error: 'Installation in progress' }; } - send('toast', { type: 'info', message: '已有安装任务在进行,请等它装完。' }); - return { ok: false, error: '已有安装任务在进行' }; + send('toast', { type: 'info', message: 'An installation is already in progress. Please wait for it to finish.' }); + return { ok: false, error: 'An installation is already in progress' }; } installBusy = filePath; send('install:start', { file: filePath }); @@ -574,7 +574,7 @@ async function runInstall(filePath) { send('install:done', result); // 新建的桌面图标要让资源管理器立刻刷新,否则用户会以为「没创建快捷方式」 windowctl.refreshShell().catch(() => {}); - notify('安装完成', `${result.app.label} 已安装${result.app.shortcutPath ? ',桌面快捷方式已创建' : ''}`); + notify('Installation complete', `${result.app.label} installed${result.app.shortcutPath ? '; desktop shortcut created' : ''}`); return result; } catch (e) { send('install:error', { message: e.message, file: filePath }); @@ -585,7 +585,7 @@ async function runInstall(filePath) { lastInstallErr = { msg: e.message, at: now }; // 窗口在前台时用户已经看到界面里的提示,不必再弹系统通知 if (!mainWindow || !mainWindow.isVisible() || mainWindow.isMinimized()) { - notify('安装失败', e.message.slice(0, 120)); + notify('Installation failed', e.message.slice(0, 120)); } } throw e; @@ -599,20 +599,20 @@ async function runInstall(filePath) { * 界面与托盘菜单共用这一条路径。 */ async function runUpdate() { - if (updateBusy) return { ok: false, error: '正在更新中' }; + if (updateBusy) return { ok: false, error: 'Update in progress' }; updateBusy = true; const sendProgress = (p) => send('update:progress', p); try { const info = updateInfo && updateInfo.hasUpdate ? updateInfo : await updater.check(); updateInfo = info; - if (!info.hasUpdate) return { ok: false, error: '已经是最新版本' }; + if (!info.hasUpdate) return { ok: false, error: 'Already up to date' }; if (!info.asset) { shell.openExternal(info.htmlUrl || Updater.repoUrl()).catch(() => {}); - return { ok: false, error: '该版本没有可直接安装的包,已打开 GitHub 发布页', manual: true }; + return { ok: false, error: 'No installable package is available for this version. The GitHub release page has been opened', manual: true }; } - sendProgress({ stage: 'download', percent: 0, message: '准备下载…' }); + sendProgress({ stage: 'download', percent: 0, message: 'Preparing download…' }); const job = await updater.prepare(info); - sendProgress({ stage: 'apply', percent: 100, message: '正在替换程序并重启…' }); + sendProgress({ stage: 'apply', percent: 100, message: 'Replacing the application and restarting…' }); updater.apply(job); // 给界面一点时间显示「即将重启」,然后走正常退出流程(before-quit 会把模拟器窗口藏好) setTimeout(() => { quitting = true; app.quit(); }, 900); @@ -631,7 +631,7 @@ async function runLaunch(pkg) { appLog(`launch start ${pkg}`); try { // 1) 先把「应用窗口」打开显示启动动画:整个过程看不到安卓桌面,也看不到模拟器窗口 - openAppWindow(pkg, '正在启动安卓运行时…'); + openAppWindow(pkg, 'Starting Android runtime…'); // 开机阶段把模拟器主窗 + qemu / netsimd 的控制台黑窗藏起来,避免任何闪现。 // 压制是常驻的(embed 后它会自动跳过被嵌入的那一个窗口,见 appwin.embed)。 // 这里必须 await:不等压制生效就往下走,黑窗会在启动瞬间露出来。 @@ -643,7 +643,7 @@ async function runLaunch(pkg) { onMark: mark, beforeStart: async (rt) => { mark('engine ready'); - appWin.setState({ tip: '正在打开应用…' }); + appWin.setState({ tip: 'Opening app…' }); // 沉浸式 + 关动画 + 收起锁屏:只做一次,之后每次启动都秒进 await rt.client.prepareForeground().catch(() => {}); mark('foreground ready'); @@ -694,7 +694,7 @@ async function runLaunch(pkg) { // 下次点应用还是走同一条隐藏 → embed 的路径。 if (winAgent) armAgent(winAgent).catch(() => {}); showWindow(); - send('toast', { type: 'error', message: `启动失败:${e.message}` }); + send('toast', { type: 'error', message: `Launch failed: ${e.message}` }); return { ok: false, error: e.message }; } } @@ -764,7 +764,7 @@ function registerIpc() { // 选文件夹(用于把运行时/镜像/已装应用挪到非系统盘) ipcMain.handle('dialog:openDir', async (_e, defaultPath) => { const r = await dialog.showOpenDialog(mainWindow, { - title: '选择数据存放目录', + title: 'Choose data folder', defaultPath: defaultPath || store.DATA_DIR, properties: ['openDirectory', 'createDirectory'], }); @@ -794,8 +794,8 @@ function registerIpc() { ipcMain.handle('apk:pick', async () => { const r = await dialog.showOpenDialog(mainWindow, { - title: '选择要安装的 APK', - filters: [{ name: 'Android 应用', extensions: ['apk', 'apks', 'xapk'] }], + title: 'Select APK to install', + filters: [{ name: 'Android app', extensions: ['apk', 'apks', 'xapk'] }], properties: ['openFile', 'multiSelections'], }); if (r.canceled) return []; @@ -818,7 +818,7 @@ function registerIpc() { ipcMain.handle('app:shortcut', async (_e, pkg) => { const appRecord = store.getApp(pkg); - if (!appRecord) throw new Error('应用不存在'); + if (!appRecord) throw new Error('App not found'); const p = installer.createShortcut(appRecord); store.upsertApp({ package: pkg, shortcutPath: p }); return { ok: true, path: p }; @@ -885,7 +885,7 @@ function registerIpc() { * 再保存设置。Sharer 每次同步都动态读 store.getSettings().sharedDir,无需重启即可生效。 */ ipcMain.handle('settings:setSharedDir', async (_e, dir) => { - if (!dir || !dir.trim()) return { ok: false, error: '目录不能为空' }; + if (!dir || !dir.trim()) return { ok: false, error: 'Folder path cannot be empty' }; const newDir = dir.trim(); const oldDir = store.getSettings().sharedDir; try { @@ -981,47 +981,47 @@ function registerIpc() { try { if (!doctor.vcRedistOk()) { issues.push({ - code: 'vcRedist', severity: 'error', title: '缺少 VC++ 运行库(x64)', - hint: '安卓运行时依赖微软 VC++ 2015-2022,缺失会导致模拟器无法启动(0xc0000135)。', fix: 'vcRedist', + code: 'vcRedist', severity: 'error', title: 'Missing VC++ Runtime (x64)', + hint: 'The Android runtime requires Microsoft VC++ 2015–2022. Without it, the emulator cannot start (0xc0000135).', fix: 'vcRedist', }); } accel = await runtime.provisioner.emulatorAccel().catch(() => ({ ok: false })); if (!accel.ok) { issues.push({ - code: 'accel', severity: 'warn', title: '未启用硬件加速', - hint: (accel.note || '将回退软件模拟(能跑但很慢)。开启虚拟化后可获得最佳性能。'), fix: 'virtualization', + code: 'accel', severity: 'warn', title: 'Hardware acceleration is not enabled', + hint: (accel.note || 'Software emulation will be used (it works, but is slow). Enable virtualization for best performance.'), fix: 'virtualization', }); } free = doctor.freeDiskMB(runtime.provisioner.paths().root); if (free && free < 8192) { issues.push({ - code: 'disk', severity: free < 2048 ? 'error' : 'warn', title: '运行时磁盘空间紧张', - hint: `「${runtime.provisioner.paths().root}」所在盘仅剩 ${free} MB,运行时与镜像可能撑爆硬盘。`, fix: 'disk', + code: 'disk', severity: free < 2048 ? 'error' : 'warn', title: 'Low disk space for runtime', + hint: `Only ${free} MB remain on the drive containing “${runtime.provisioner.paths().root}”. The runtime and system image may fill the disk.`, fix: 'disk', }); } const rtDir = runtime.provisioner.paths().root; if (!doctor.isAsciiPath(rtDir)) { issues.push({ - code: 'pathRuntime', severity: 'warn', title: '运行时目录含中文/特殊字符', - hint: '部分运行时组件对中文路径敏感,可能引发启动失败。建议改到纯英文目录。', fix: 'path', + code: 'pathRuntime', severity: 'warn', title: 'Runtime folder path contains non-English or special characters', + hint: 'Some runtime components are sensitive to non-English paths, which may prevent startup. Use a folder path containing only English characters.', fix: 'path', }); } const env = await runtime.provisioner.checkEnv().catch(() => ({})); if (!env.adb || !env.adb.ok) { - issues.push({ code: 'adb', severity: 'error', title: 'adb 工具链缺失', hint: '无法连接安卓运行时。', fix: 'deploy' }); + issues.push({ code: 'adb', severity: 'error', title: 'Missing adb tools', hint: 'Cannot connect to the Android runtime.', fix: 'deploy' }); } if (!env.emulator || !env.emulator.ok) { - issues.push({ code: 'emulator', severity: 'error', title: '运行时引擎未安装', hint: '需要先部署运行时引擎(约 440MB)。', fix: 'deploy' }); + issues.push({ code: 'emulator', severity: 'error', title: 'Runtime engine not installed', hint: 'The runtime engine must be set up first (about 440 MB).', fix: 'deploy' }); } if (!env.sysimg || !env.sysimg.ok) { - issues.push({ code: 'sysimg', severity: 'warn', title: '系统镜像未就绪', hint: '需要先下载 Android 系统镜像(约 1.5GB)。', fix: 'deploy' }); + issues.push({ code: 'sysimg', severity: 'warn', title: 'System image not ready', hint: 'The Android system image must be downloaded first (about 1.5 GB).', fix: 'deploy' }); } // 残留锁文件:会让下一次启动被判定为「AVD 已被占用」而 FATAL const avdHome = runtime.provisioner.paths().avdHome; let locked = false; try { for (const f of fs.readdirSync(avdHome)) { if (/\.lock$/i.test(f)) { locked = true; break; } } } catch (_) { /* ignore */ } if (locked) { - issues.push({ code: 'lock', severity: 'warn', title: '检测到上次残留的锁文件', hint: '可能导致下一次启动被判定为「AVD 已被占用」。', fix: 'lock' }); + issues.push({ code: 'lock', severity: 'warn', title: 'Found a leftover lock file', hint: 'This may cause the next startup to report that the AVD is already in use.', fix: 'lock' }); } } catch (_) { /* 自检失败不阻断使用 */ } return { issues, accel, free }; @@ -1063,10 +1063,10 @@ function registerIpc() { ipcMain.handle('app:confirm', async (_e, opts = {}) => { const { response } = await dialog.showMessageBox(mainWindow || undefined, { type: opts.type || 'question', - title: opts.title || '确认', + title: opts.title || 'Confirm', message: opts.message || '', detail: opts.detail || '', - buttons: ['取消', opts.okLabel || '确定'], + buttons: ['Cancel', opts.okLabel || 'OK'], defaultId: 1, cancelId: 0, noLink: true, @@ -1102,7 +1102,7 @@ function registerIpc() { try { const r = runtime.emulator.resizeDataPartition(mb); appLog(`data partition -> ${want}GB: ${JSON.stringify(r)}`); - if (r && r.ok) notify('数据分区已调整', `安卓可用空间约 ${want} GB${stopped ? '(运行时已重启)' : ''}`); + if (r && r.ok) notify('Data partition resized', `About ${want} GB available on Android${stopped ? ' (runtime restarted)' : ''}`); return { ok: !!(r && r.ok), stopped, ...r, ...runtime.emulator.dataInfo() }; } catch (e) { return { ok: false, error: e.message }; @@ -1116,15 +1116,15 @@ function registerIpc() { const preview = await runtime.cleanup(pass); const { response } = await dialog.showMessageBox(mainWindow, { type: 'warning', - buttons: ['取消', `清理 ${preview.candidates.length} 项`], + buttons: ['Cancel', `Clean up ${preview.candidates.length} items`], defaultId: 0, cancelId: 0, - title: '确认清理运行时缓存', - message: `将释放约 ${(preview.candidates.reduce((n, c) => n + c.size, 0) / 1048576).toFixed(0)} MB`, + title: 'Confirm runtime cache cleanup', + message: `About ${(preview.candidates.reduce((n, c) => n + c.size, 0) / 1048576).toFixed(0)} MB will be freed`, detail: (opts.includeSnapshots - ? '会先停止运行时,再删除旧镜像、当前 AVD 的快速启动快照、锁文件和非活动 AVD 的缓存。已安装的应用本身保留(只清快照),' - : '只删除旧镜像、旧快照、锁文件和非活动 AVD 的缓存,已安装的应用会保留。') - + '此操作不可撤销,建议先导出诊断报告。', + ? 'The runtime will stop first, then old images, the current AVD quick-boot snapshot, lock files, and inactive AVD caches will be removed. Installed apps are kept (only snapshots are cleared).' + : 'Only old images, snapshots, lock files, and inactive AVD caches will be removed. Installed apps are kept.') + + 'This action cannot be undone. Export a diagnostic report first.', }); if (response !== 1) return { ...preview, cancelled: true }; return runtime.cleanup({ apply: true, aggressive: !!opts.aggressive, includeSnapshots: !!opts.includeSnapshots }); @@ -1246,7 +1246,7 @@ function registerIpc() { const r = await runtime.provisioner.installAehd( (x) => send('provision:progress', { ...x, component: 'aehd' }), ); - if (!fs.existsSync(bat)) throw new Error(r.error || 'AEHD 驱动包未就绪'); + if (!fs.existsSync(bat)) throw new Error(r.error || 'AEHD driver package is not ready'); } await new Promise((resolve, reject) => { @@ -1257,7 +1257,7 @@ function registerIpc() { windowsHide: true, }); proc.on('exit', (code) => (code === 0 ? resolve() : reject(new Error( - code === 2 ? '已取消管理员授权(需要点「是」才能安装驱动)' : `驱动安装失败(退出码 ${code})`, + code === 2 ? 'Administrator permission was denied (click “Yes” to install the driver)' : `Driver installation failed (exit code ${code})`, )))); proc.on('error', (e) => reject(e)); }); @@ -1277,7 +1277,7 @@ function registerIpc() { ipcMain.handle('provision:enableVirtualization', async () => { try { const accel = await runtime.provisioner.emulatorAccel(); - if (accel.ok) return { ok: true, already: true, accel, note: '硬件加速已可用,无需额外操作' }; + if (accel.ok) return { ok: true, already: true, accel, note: 'Hardware acceleration is already available; no action is needed' }; const hv = await runtime.provisioner.hypervisor(); if (hv.dll) { @@ -1291,8 +1291,8 @@ function registerIpc() { restartNeeded: r.restart || r2.restart, accel: after, note: ok - ? (r.restart ? '已开启「虚拟机平台」,请重启电脑后获得最佳性能' : '「虚拟机平台」已开启,硬件加速即时生效') - : (r.error || r2.error || '无法开启系统虚拟化功能'), + ? (r.restart ? 'Virtual Machine Platform is enabled. Restart your PC for best performance' : 'Virtual Machine Platform is enabled; hardware acceleration is now active') + : (r.error || r2.error || 'Unable to enable system virtualization'), }; } @@ -1300,7 +1300,7 @@ function registerIpc() { const ae = await runtime.provisioner.installAehd( (x) => send('provision:progress', { ...x, component: 'aehd' }), ).catch((e) => ({ ok: false, error: e.message })); - if (!ae.ok) return { ok: false, error: ae.error || 'AEHD 驱动包准备失败' }; + if (!ae.ok) return { ok: false, error: ae.error || 'Failed to prepare the AEHD driver package' }; const bat = path.join(runtime.provisioner.paths().aehdDir, 'silent_install.bat'); await new Promise((resolve, reject) => { const { spawn: sp } = require('child_process'); @@ -1308,12 +1308,12 @@ function registerIpc() { const ps = `try { $p = Start-Process -FilePath '${esc}' -WorkingDirectory '${path.dirname(bat).replace(/'/g, "''")}' -Verb RunAs -Wait -PassThru; exit $p.ExitCode } catch { exit 2 }`; const proc = sp('powershell', ['-NoProfile', '-NonInteractive', '-Command', ps], { windowsHide: true }); proc.on('exit', (code) => (code === 0 ? resolve() : reject(new Error( - code === 2 ? '已取消管理员授权(需要点「是」才能安装驱动)' : `驱动安装失败(退出码 ${code})`, + code === 2 ? 'Administrator permission was denied (click “Yes” to install the driver)' : `Driver installation failed (exit code ${code})`, )))); proc.on('error', (e) => reject(e)); }); const after = await runtime.provisioner.emulatorAccel(); - return { ok: after.ok, accel: after, note: after.ok ? 'AEHD 硬件加速已启用' : after.note }; + return { ok: after.ok, accel: after, note: after.ok ? 'AEHD hardware acceleration enabled' : after.note }; } catch (e) { return { ok: false, error: e.message }; } @@ -1358,8 +1358,8 @@ function registerIpc() { ipcMain.handle('provision:importIso', async () => { const r = await dialog.showOpenDialog(mainWindow, { - title: '选择 Android-x86 / Bliss OS 系统镜像', - filters: [{ name: '系统镜像', extensions: ['iso', 'img'] }], + title: 'Select Android-x86 / Bliss OS system image', + filters: [{ name: 'System image', extensions: ['iso', 'img'] }], properties: ['openFile'], }); if (r.canceled) return { ok: false, canceled: true }; @@ -1372,8 +1372,8 @@ function registerIpc() { ipcMain.handle('provision:importDisk', async () => { const r = await dialog.showOpenDialog(mainWindow, { - title: '选择已安装 Android 的虚拟磁盘', - filters: [{ name: '虚拟磁盘', extensions: ['qcow2', 'img', 'vmdk', 'raw'] }], + title: 'Select a virtual disk with Android installed', + filters: [{ name: 'Virtual disk', extensions: ['qcow2', 'img', 'vmdk', 'raw'] }], properties: ['openFile'], }); if (r.canceled) return { ok: false, canceled: true }; @@ -1539,7 +1539,7 @@ function registerIpc() { case 'animations': return { ok: true, ...(await androidSet.setAnimations(!!value)) }; case 'fontScale': return { ok: true, ...(await androidSet.setFontScale(value)) }; case 'density': return { ok: true, ...(await androidSet.setDensity(value)) }; - default: return { ok: false, error: `未知设置项 ${key}` }; + default: return { ok: false, error: `Unknown setting: ${key}` }; } } catch (e) { return { ok: false, error: e.message }; } }); @@ -1679,7 +1679,7 @@ if (!gotLock) { const dirs = agentScope(); // 第二个参数是本进程 pid:代理据此拒绝把窗口嵌进「不是我们的」窗口 await agent.scope(dirs, process.pid).catch(() => null); - if (!dirs.length) appLog('agent scope 为空:不压制任何窗口(避免误伤其他软件)'); + if (!dirs.length) appLog('Agent scope is empty; no windows will be suppressed (to avoid affecting other applications)'); return agent.bootHide().catch(() => ({ ok: false })); }; @@ -1726,8 +1726,8 @@ if (!gotLock) { if (freeMB && freeMB < 3072) { appLog(`LOW_DISK free=${freeMB}MB dir=${store.DATA_DIR}`); setTimeout(() => { - notify('磁盘空间不足', `只剩 ${(freeMB / 1024).toFixed(1)} GB。请到「设置 → 系统集成」把安卓数据目录/运行时目录改到其他盘,或先清理磁盘。`); - send('toast', { type: 'error', message: `磁盘只剩 ${(freeMB / 1024).toFixed(1)} GB,安卓可能无法启动(设置 → 系统集成 可换盘)` }); + notify('Low disk space', `Only ${(freeMB / 1024).toFixed(1)} GB remain. Go to Settings → System Integration to move the Android data/runtime folder to another drive, or free up disk space.`); + send('toast', { type: 'error', message: `Only ${(freeMB / 1024).toFixed(1)} GB remain. Android may not start (change drives in Settings → System Integration).` }); }, 3000).unref?.(); } } @@ -1736,7 +1736,7 @@ if (!gotLock) { installer.ensureShortcuts().then((fixed) => { if (fixed && fixed.length) { windowctl.refreshShell().catch(() => {}); - notify('快捷方式已补齐', fixed.join('、')); + notify('Shortcuts restored', fixed.join('、')); } }).catch(() => {}); @@ -1786,7 +1786,7 @@ if (!gotLock) { // 再并发下载解压只会让桌面更卡。真有应用要打开时 ensure() 自会准备。 setTimeout(() => { autoDeploy().then((r) => { - if (r && r.ok) send('toast', { type: 'ok', message: '运行时已自动准备完成' }); + if (r && r.ok) send('toast', { type: 'ok', message: 'Runtime setup completed automatically' }); }).catch(() => {}); }, 20000).unref?.(); diff --git a/src/ui/app.js b/src/ui/app.js index 143112f..e28d853 100644 --- a/src/ui/app.js +++ b/src/ui/app.js @@ -35,10 +35,10 @@ function fmtSize(bytes) { function relTime(ts) { if (!ts) return ''; const d = Date.now() - ts; - if (d < 60000) return '刚刚'; - if (d < 3600000) return `${Math.floor(d / 60000)} 分钟前`; - if (d < 86400000) return `${Math.floor(d / 3600000)} 小时前`; - if (d < 86400000 * 30) return `${Math.floor(d / 86400000)} 天前`; + if (d < 60000) return 'just now'; + if (d < 3600000) return `${Math.floor(d / 60000)} min ago`; + if (d < 86400000) return `${Math.floor(d / 3600000)} hr ago`; + if (d < 86400000 * 30) return `${Math.floor(d / 86400000)} days ago`; return new Date(ts).toLocaleDateString('zh-CN'); } @@ -52,7 +52,7 @@ function relTime(ts) { let _toastSeq = 0; function toast({ title, body = '', type = 'info', progress = null, sticky = false, spinner = false }) { const wrap = $('toastWrap'); - // ① 去重:同样的标题 + 正文(4 秒内)只刷新那一条,不叠加 + // ① 去重:同样的标题 + 正文 (4 秒内)只刷新那一条,不叠加 const key = `${title}|${body}`; const dup = [...wrap.children].find((n) => n.dataset.key === key && Date.now() - Number(n.dataset.at || 0) < 4000); if (dup) { @@ -134,8 +134,8 @@ function renderGrid() { if (!hasAny) { const s = state.status; $('emptyHint').textContent = s && s.adbOk === false - ? '首次使用:打开右上角设置 → 运行时 → 一键完成,再回来安装应用即可。' - : '提示:所有应用共享同一个 Android 运行时,所以第二个应用的启动几乎是瞬时的。'; + ? 'First-time setup: open Settings in the top-right corner → Runtime → Set up automatically, then return to install apps.' + : 'Tip: All apps share one Android runtime, so launching additional apps is almost instant.'; grid.innerHTML = ''; return; } @@ -143,15 +143,15 @@ function renderGrid() { grid.innerHTML = apps.map((a) => `
${a.pinned ? '★' : ''} - ${a.running ? '' : ''} + ${a.running ? '' : ''}
${a.iconUrl ? `` : `
${esc(initialOf(a.label))}
`}
${esc(a.label)}
-
${a.running ? '运行中' : `v${esc(a.versionName)}${a.lastLaunchAt ? ' · ' + relTime(a.lastLaunchAt) : ''}`}
-
${a.running ? '切换到前台' : '打开'}
+
${a.running ? 'Running' : `v${esc(a.versionName)}${a.lastLaunchAt ? ' · ' + relTime(a.lastLaunchAt) : ''}`}
+
${a.running ? 'Switch to foreground' : 'Open'}
`).join(''); } @@ -170,7 +170,7 @@ async function refreshApps() { function updateStats() { const n = state.apps.length; - $('statusStats').textContent = n ? `${n} 个应用` : '暂无应用'; + $('statusStats').textContent = n ? `${n} apps` : 'No apps installed yet'; } async function refreshStatus() { @@ -183,64 +183,64 @@ async function refreshStatus() { if (!s.adbOk) { dot.className = 'dot err'; - text.textContent = '运行时未部署 —— 打开设置 → 运行时,一键完成'; - tag.textContent = '需要部署'; + text.textContent = 'Runtime not deployed — open Settings → Runtime and click Set up automatically'; + tag.textContent = 'Setup required'; tag.className = 'brand-tag'; } else if (s.deviceOnline) { dot.className = 'dot ok'; - text.textContent = `运行时已就绪 · Android ${s.android || '?'}${s.freeform ? ' · 独立窗口已启用' : ''}`; + text.textContent = `Runtime ready · Android ${s.android || '?'}${s.freeform ? ' · separate app windows enabled' : ''}`; tag.textContent = `Android ${s.android || ''}`.trim(); tag.className = 'brand-tag ok'; } else if (s.phase === 'booting' || s.vmStarting) { dot.className = 'dot warn'; - text.textContent = '正在引导 Android…'; - tag.textContent = '引导中'; + text.textContent = 'Starting Android…'; + tag.textContent = 'Starting'; tag.className = 'brand-tag'; } else if (s.needsSetup === 'accel') { dot.className = 'dot warn'; - text.textContent = '未检测到硬件加速:已自动回退软件模拟(首次开机较慢)'; - tag.textContent = '软件模拟'; + text.textContent = 'Hardware acceleration not detected; using software emulation (first boot may be slow)'; + tag.textContent = 'Software emulation'; tag.className = 'brand-tag'; } else if (s.needsSetup === 'sysimg') { dot.className = 'dot err'; - text.textContent = '缺少 Android 14 通用系统镜像 —— 设置 → 运行时里下载(约 1.5GB,只下一次)'; - tag.textContent = '待下载镜像'; + text.textContent = 'Android 14 system image missing — download it in Settings → Runtime (about 1.5 GB, one-time download)'; + tag.textContent = 'Image required'; tag.className = 'brand-tag'; } else if (s.needsSetup === 'emulator') { dot.className = 'dot err'; - text.textContent = '运行时引擎未安装 —— 打开设置 → 运行时一键完成'; - tag.textContent = '需要部署'; + text.textContent = 'Runtime engine not installed — open Settings → Runtime and click Set up automatically'; + tag.textContent = 'Setup required'; tag.className = 'brand-tag'; } else if (s.needsSetup === 'boot') { dot.className = 'dot warn'; - text.textContent = '运行时已就绪 —— 安装 APK 或双击桌面图标会自动开机'; - tag.textContent = '待开机'; + text.textContent = 'Runtime ready — install an APK or double-click a desktop shortcut to boot'; + tag.textContent = 'Ready to boot'; tag.className = 'brand-tag'; } else if (s.installing) { dot.className = 'dot warn'; - text.textContent = '安装程序运行中 —— 请在虚拟机窗口里完成安装'; - tag.textContent = '安装中'; + text.textContent = 'Installer is running — finish the installation in the virtual machine window'; + tag.textContent = 'Installing'; tag.className = 'brand-tag'; } else { dot.className = 'dot'; text.textContent = s.installed - ? '运行时待机(安装或打开应用时会自动拉起)' - : '运行时尚未安装 Android 系统 —— 打开设置 → 运行时继续'; - tag.textContent = s.installed ? '待机' : '待安装'; + ? 'Runtime idle (starts automatically when you install or open an app)' + : 'Android system not installed yet — continue in Settings → Runtime'; + tag.textContent = s.installed ? 'Idle' : 'Not installed'; tag.className = 'brand-tag'; } $('statusDevice').textContent = s.deviceOnline ? (s.serial || '') : ''; renderSetup(s); } catch (e) { - $('statusText').textContent = `状态检查失败:${e.message}`; + $('statusText').textContent = `Status check failed: ${e.message}`; } } -/* ---------------- 运行时部署(固定在设置里) ---------------- */ +/* ---------------- 运行时部署 (固定在设置里) ---------------- */ /** - * 运行时/部署/自检面板现在都收在「设置」里,主界面只留已安装应用。 + * 运行时/部署/自检面板现在都收在「设置」里,主界面只留Installed应用。 * 保留 showSetup 给托盘菜单与 ui:navigate 复用,语义改为「打开设置并定位到运行时」。 */ function showSetup(on) { @@ -275,7 +275,7 @@ function renderSetup(s) { const hv = env.hypervisor || {}; const accel = env.accel || s.accel || {}; - // 启动即见已安装应用:不再用部署向导整屏接管主界面, + // 启动即见Installed应用:不再用部署向导整屏接管主界面, // 状态只在设置里的运行时面板 + 底部状态条上体现。 const accelOk = accel.ok || hv.whpx; const adbDone = !!(env.adb && env.adb.ok); @@ -286,15 +286,15 @@ function renderSetup(s) { if (stateText) { const missing = []; if (!adbDone) missing.push('adb'); - if (!emuDone0) missing.push('引擎'); - if (!imgDone0) missing.push('系统镜像'); + if (!emuDone0) missing.push('Engine'); + if (!imgDone0) missing.push('System image'); stateText.textContent = s.deviceOnline - ? `已就绪 · Android ${s.android || ''}` + ? `Ready · Android ${s.android || ''}` : s.phase === 'booting' || s.vmStarting - ? '正在引导 Android…' + ? 'Starting Android…' : allDone0 - ? '组件齐全 · 待开机(打开应用时自动拉起)' - : `缺少 ${missing.join('、')}${accelOk ? '' : '(无硬件加速,将回退软件模拟)'}`; + ? 'All components ready · waiting to boot (starts when you open an app)' + : `Missing ${missing.join(' and ')}${accelOk ? '' : ' (software emulation will be used without hardware acceleration)'}`; stateText.title = s.autoProfile || accel.note || hv.note || ''; } @@ -303,43 +303,43 @@ function renderSetup(s) { const busy = state.setupBusy; const booting = s.phase === 'booting'; - // 只保留一个步骤:下载、参数自适应、开机全部由「一键完成」串起来, + // 只保留一个步骤:下载 and 参数自适应 and 开机全部由「一键完成」串起来, // 且首次启动会在后台自动跑,用户不需要逐步点击。 const allDone = !!(env.adb && env.adb.ok) && emuDone && imgDone; const left = []; - if (!(env.adb && env.adb.ok)) left.push('adb 工具链'); - if (!emuDone) left.push('Android 运行时'); - if (!imgDone) left.push('系统镜像'); + if (!(env.adb && env.adb.ok)) left.push('ADB tools'); + if (!emuDone) left.push('Android runtime'); + if (!imgDone) left.push('System image'); const step = { key: 'all', index: 1, - title: allDone ? '一切就绪' : '一键完成', - tag: allDone ? '已自动完成' : '自动', + title: allDone ? 'All set' : 'Set up automatically', + tag: allDone ? 'Completed automatically' : 'Automatic', done: allDone, busy: !!busy, desc: s.deviceOnline - ? `Android ${s.android || ''} 已就绪 · 应用窗口会按屏幕自动适配` + ? `Android ${s.android || ''} is ready · app windows adapt to your screen` : booting - ? '正在引导 Android…' + ? 'Starting Android…' : allDone - ? '组件齐全,双击任意 APK 或桌面图标即会自动开机' - : `还差 ${left.join('、')};点下面「一键完成」,会在后台一次装好并自动开机`, + ? 'All components ready. Double-click any APK or desktop shortcut to boot' + : `Still needed: ${left.join(' and ')}; click “Set up automatically” below to install everything in the background and boot`, actions: s.deviceOnline - ? '' + ? '' : '', }; $('setupSteps').innerHTML = stepRow(step); $('btnDeployAll').disabled = !!busy; - $('btnDeployAll').textContent = busy ? '准备中…' : (allDone ? '已完成 · 点此复查' : '一键完成'); + $('btnDeployAll').textContent = busy ? 'Preparing…' : (allDone ? 'Complete · click to check again' : 'Set up automatically'); // 「上次为什么没起来」优先显示落盘的失败记录:进程重启后内存里的 diagnosis 是空的, - // 只显示 autoProfile 会让用户完全看不出原因(issue #20/#25 的报告就是这样)。 + // 只显示 autoProfile 会让用户完全看不出原因 (issue #20/#25 的报告就是这样)。 const lf = s.lastFailure; const lastMsg = (lf && lf.diagnosis && lf.diagnosis.title) - ? `上次启动失败:${lf.diagnosis.title}` - : (s.diagnosis ? `上次启动自动诊断:${s.diagnosis.title}` : ''); + ? `Last startup failed: ${lf.diagnosis.title}` + : (s.diagnosis ? `Last startup diagnosis: ${s.diagnosis.title}` : ''); $('setupNote').textContent = lastMsg - || s.autoProfile || '全自动:无需选择镜像、无需启用加速、无需调内存'; + || s.autoProfile || 'Fully automatic: no image selection, acceleration setup, or memory tuning needed'; } function store_getDiskSize() { @@ -380,18 +380,18 @@ async function deployAll() { // 一次调用把 adb / 引擎 / 系统镜像全装完,进度统一显示在唯一那一行 const r = await api.autoDeploy(); if (!r || !r.ok) { - toast({ title: '准备失败', body: (r && r.error) || '请检查网络后重试', type: 'error', sticky: true }); + toast({ title: 'Setup failed', body: (r && r.error) || 'Check your internet connection and try again', type: 'error', sticky: true }); return; } await refreshStatus(); - toast({ title: '组件已就绪,正在自动开机…', body: '首次约 1-3 分钟,之后几秒即可。', type: 'info' }); + toast({ title: 'Components ready. Starting Android…', body: 'First boot takes about 1–3 minutes; subsequent starts take seconds.', type: 'info' }); const started = await api.runtimeStart(); await refreshStatus(); toast(started && started.ok - ? { title: '运行时已就绪', body: '现在把 APK 拖进窗口,或双击桌面图标即可。', type: 'ok' } - : { title: '开机未完成', body: (started && started.error) || '请导出诊断报告查看原因', type: 'error', sticky: true }); + ? { title: 'Runtime ready', body: 'Drag an APK into the window or double-click a desktop shortcut.', type: 'ok' } + : { title: 'Boot incomplete', body: (started && started.error) || 'Export a diagnostic report to see why', type: 'error', sticky: true }); } catch (e) { - toast({ title: '准备失败', body: e.message, type: 'error', sticky: true }); + toast({ title: 'Setup failed', body: e.message, type: 'error', sticky: true }); } finally { setStepBusy('all', false); await refreshStatus(); @@ -400,7 +400,7 @@ async function deployAll() { } async function runStep(key) { - if (state.setupBusy) return { ok: false, error: '已有部署任务进行中' }; + if (state.setupBusy) return { ok: false, error: 'A setup task is already running' }; setStepBusy(key, true); let r; try { @@ -409,18 +409,18 @@ async function runStep(key) { else if (key === 'sysimg') r = await api.installSysimg(); else if (key === 'accel') { const t = toast({ - title: '正在安装硬件加速驱动…', - body: '请在弹出的是否允许窗口点「是」', + title: 'Installing the hardware acceleration driver…', + body: 'Click “Yes” in the permission prompt', sticky: true, spinner: true, }); r = await api.enableAccel(); - if (r.ok) t.update({ title: '硬件加速已启用', type: 'ok' }); - else t.update({ title: '加速未启用', body: r.error, type: 'error', sticky: true }); + if (r.ok) t.update({ title: 'Hardware acceleration enabled', type: 'ok' }); + else t.update({ title: 'Hardware acceleration not enabled', body: r.error, type: 'error', sticky: true }); } else if (key === 'qemu') r = await api.installQemu(); else if (key === 'iso') r = await api.downloadIso(); else if (key === 'disk') r = await api.createDisk(store_getDiskSize()); - else r = { ok: false, error: '未知步骤' }; + else r = { ok: false, error: 'Unknown step' }; } catch (e) { r = { ok: false, error: e.message }; } @@ -432,12 +432,12 @@ async function runStep(key) { async function startInstall() { const r = await api.startInstaller(); if (!r.ok) { - toast({ title: '无法启动安装环境', body: r.error, type: 'error', sticky: true }); + toast({ title: 'Unable to start the installation environment', body: r.error, type: 'error', sticky: true }); return; } $('setupGuide').hidden = false; $('setupGuide').scrollIntoView({ behavior: 'smooth', block: 'start' }); - toast({ title: '虚拟机窗口已打开', body: '按下方 6 步完成安装;装好后点「我已安装完成」。', type: 'ok', sticky: true }); + toast({ title: 'Virtual machine window opened', body: 'Follow the six steps below. When finished, click “Installation complete”.', type: 'ok', sticky: true }); } async function markInstalled() { @@ -445,7 +445,7 @@ async function markInstalled() { await api.runtimeStop(); $('setupGuide').hidden = true; await refreshStatus(); - toast({ title: '安装完成,运行时已接管', body: '现在可以直接把 APK 拖进来安装了。', type: 'ok' }); + toast({ title: 'Installation complete; runtime is ready', body: 'You can now drag APK files here to install them.', type: 'ok' }); } @@ -454,18 +454,18 @@ async function markInstalled() { /** 把安装失败翻译成「下一步该做什么」,只对最常见的几种做提示,其它原样显示 */ function friendlyInstallErrorText(msg) { - const s = String(msg || '未知错误'); + const s = String(msg || 'Unknown error'); if (/INSUFFICIENT_STORAGE|not enough space|空间不足|ENOSPC/i.test(s)) { - return `${s}\n安卓数据分区不够用:设置 → 性能与资源占用 → 安卓数据分区调大后点「立即扩容」。`; + return `${s}\nThe Android data partition is too small. Go to Settings → Performance & Resource Usage, increase the partition size, then click “Expand now”.`; } - if (/NO_MATCHING_ABIS/i.test(s)) return `${s}\n这个包只有 ARM 库,正在自动切换兼容镜像后重试。`; + if (/NO_MATCHING_ABIS/i.test(s)) return `${s}\nThis package contains only ARM libraries. Switching to a compatible image and retrying automatically.`; if (/INSTALL_FAILED_ALREADY_EXISTS|signatures do not match/i.test(s)) { - return `${s}\n同包名但签名不同(常见于从别的渠道装过)。在应用卡片右键「卸载」后再装。`; + return `${s}\nThe package name matches but the signature differs (often caused by installing from another source). Right-click the app card, choose “Uninstall”, then try again.`; } - if (/INSTALL_FAILED_VERSION_DOWNGRADE/i.test(s)) return `${s}\n已装版本更高,先在右键菜单里卸载再装。`; + if (/INSTALL_FAILED_VERSION_DOWNGRADE/i.test(s)) return `${s}\nA newer version is already installed. Uninstall it from the context menu before trying again.`; if (/ERR_FS_FILE_TOO_LARGE|greater than possible Buffer|Cannot allocate|ENOMEM|out of memory/i.test(s)) { - return `${s}\n这个安装包过大或内存不足,解析被系统拒绝了。请确认磁盘与内存充足后重试;` - + '仍失败请把安装包路径换成纯英文短路径(如 D:\\a.apk)。'; + return `${s}\nThe APK may be too large or memory may be insufficient. Check available disk space and memory, then try again; ` + + 'if it still fails, move the APK to a short path containing only English characters (e.g. D:\\a.apk).'; } return s; } @@ -478,7 +478,7 @@ let installToast = null; async function installFiles(paths) { if (!paths || !paths.length) return; if (state.installing) { - toast({ title: '已有安装任务在进行', body: '请等当前应用装完再继续。' }); + toast({ title: 'An installation is already in progress', body: 'Wait for the current installation to finish before continuing.' }); return; } state.installing = true; @@ -487,28 +487,28 @@ async function installFiles(paths) { for (const p of paths) { const name = p.split(/[\\/]/).pop(); - const t = toast({ title: `正在安装 ${name}`, body: '解析 APK…', progress: 8, sticky: true, spinner: true }); + const t = toast({ title: `Installing ${name}`, body: 'Parsing APK…', progress: 8, sticky: true, spinner: true }); installToast = t; let off = () => {}; try { off = api.on('install:progress', (pr) => { const base = { parse: 12, 'parse-done': 24, runtime: 38, install: 62, shortcut: 88, done: 100 }[pr.stage] || 50; const pct = pr.percent != null ? Math.min(60, Math.round((pr.percent / 100) * 60)) : base; - t.update({ title: pr.meta ? `正在安装 ${pr.meta.label}` : `正在安装 ${name}`, body: pr.message, progress: pct }); + t.update({ title: pr.meta ? `Installing ${pr.meta.label}` : `Installing ${name}`, body: pr.message, progress: pct }); }); const r = await api.installApk(p); - t.update({ title: `${r.app.label} 已安装`, body: r.app.shortcutPath ? '桌面快捷方式已创建,双击即可直接打开。' : '可在应用卡片右键创建桌面快捷方式。', progress: 100, type: 'ok' }); + t.update({ title: `${r.app.label} Installed`, body: r.app.shortcutPath ? 'Desktop shortcut created. Double-click to launch the app.' : 'Right-click the app card to create a desktop shortcut.', progress: 100, type: 'ok' }); t.el.querySelector('.spinner')?.remove(); setTimeout(t.dismiss, 3600); if (r.warnings && r.warnings.length) { - toast({ title: `${r.app.label} 兼容性提示`, body: r.warnings.join(' '), type: 'error', sticky: false }); + toast({ title: `${r.app.label} Compatibility notice`, body: r.warnings.join(' '), type: 'error', sticky: false }); } ok++; } catch (e) { failed++; - // 先把那条「正在安装…」的 sticky 提示收掉,否则它会一直转圈、还继续被 + // 先把那条「正在安装…」的 sticky 提示收掉,否则它会一直转圈 and 还继续被 // 运行时日志刷新 —— 用户看到的就是「失败信息一直在循环」。 - // 错误提示统一由下面的 install:error 监听器负责(带文件名与中文原因), + // 错误提示统一由下面的 install:error 监听器负责 (带文件名与中文原因), // 这里不再重复弹一条。 try { off(); } catch (_) { /* ignore */ } t.dismiss(); @@ -529,20 +529,20 @@ async function launchApp(pkg) { const app = state.apps.find((a) => a.package === pkg); state.launchPkg = pkg; const t = toast({ - title: `正在打开 ${app ? app.label : pkg}`, - body: '首次启动需要预热运行时,请稍候…', + title: `Opening ${app ? app.label : pkg}`, + body: 'Warming up the runtime for the first launch. Please wait…', sticky: true, spinner: true, }); const r = await api.launch(pkg); if (r.ok) { - // 应用有自己的窗口,主界面自动收起;这里只留一句提示,然后隐藏窗口 - t.update({ title: '已启动', body: '应用正在独立窗口中运行(顶部可旋转屏幕)。', type: 'ok' }); + // 应用有自己的窗口,主界面自动收起; 这里只留一句提示,然后隐藏窗口 + t.update({ title: 'Launched', body: 'The app is running in a separate window (use the top bar to rotate the screen).', type: 'ok' }); setTimeout(t.dismiss, 1600); setTimeout(() => api.winHide(), 900); setTimeout(refreshApps, 1200); } else { - t.update({ title: '启动失败', body: r.error, type: 'error' }); + t.update({ title: 'Launch failed', body: r.error, type: 'error' }); setTimeout(t.dismiss, 6000); } } @@ -550,38 +550,38 @@ async function launchApp(pkg) { async function createShortcut(pkg) { try { const r = await api.createShortcut(pkg); - toast({ title: '桌面快捷方式已创建', body: r.path, type: 'ok' }); + toast({ title: 'Desktop shortcut created', body: r.path, type: 'ok' }); refreshApps(); } catch (e) { - toast({ title: '创建快捷方式失败', body: e.message, type: 'error' }); + toast({ title: 'Failed to create shortcut', body: e.message, type: 'error' }); } } async function uninstall(pkg) { const app = state.apps.find((a) => a.package === pkg); if (!app) return; - const ok = window.confirm(`确定卸载「${app.label}」?\n\n应用数据会一并被清除,桌面快捷方式也会被删除。`); + const ok = window.confirm(`Uninstall “${app.label}”?\n\nThe app’s data and desktop shortcut will also be deleted.`); if (!ok) return; try { await api.uninstall(pkg); - toast({ title: '已卸载', body: app.label, type: 'ok' }); + toast({ title: 'Uninstalled', body: app.label, type: 'ok' }); refreshApps(); } catch (e) { - toast({ title: '卸载失败', body: e.message, type: 'error' }); + toast({ title: 'Uninstall failed', body: e.message, type: 'error' }); } } /* ---------------- 右键菜单 ---------------- */ const MENU_ITEMS = [ - { key: 'open', label: '打开应用', kbd: 'Enter' }, - { key: 'shortcut', label: '创建桌面快捷方式' }, - { key: 'pin', label: '固定到顶部' }, + { key: 'open', label: 'Open app', kbd: 'Enter' }, + { key: 'shortcut', label: 'Create desktop shortcut' }, + { key: 'pin', label: 'Pin to top' }, { divider: true }, - { key: 'reveal', label: '打开 APK 所在位置' }, - { key: 'clear', label: '清除应用数据' }, + { key: 'reveal', label: 'Show APK location' }, + { key: 'clear', label: 'Clear app data' }, { divider: true }, - { key: 'uninstall', label: '卸载', danger: true }, + { key: 'uninstall', label: 'Uninstall', danger: true }, ]; function showMenu(x, y, pkg) { @@ -590,7 +590,7 @@ function showMenu(x, y, pkg) { const app = state.apps.find((a) => a.package === pkg); menu.innerHTML = MENU_ITEMS.map((it) => { if (it.divider) return '
'; - const label = it.key === 'pin' ? (app && app.pinned ? '取消固定' : '固定到顶部') : it.label; + const label = it.key === 'pin' ? (app && app.pinned ? 'Unpin' : 'Pin to top') : it.label; return `
${esc(label)}${it.kbd ? `${it.kbd}` : ''}
`; @@ -618,12 +618,12 @@ async function handleMenu(act) { case 'pin': return api.pin(pkg, !app.pinned).then(refreshApps); case 'reveal': return api.revealApk(pkg); case 'clear': { - if (!window.confirm(`清除「${app.label}」的全部数据?此操作不可撤销。`)) return; + if (!window.confirm(`Clear all data for “${app.label}”? This action cannot be undone.`)) return; try { await api.clearData(pkg); - toast({ title: '数据已清除', body: app.label, type: 'ok' }); + toast({ title: 'App data cleared', body: app.label, type: 'ok' }); } catch (e) { - toast({ title: '清除失败', body: e.message, type: 'error' }); + toast({ title: 'Failed to clear data', body: e.message, type: 'error' }); } return; } @@ -641,14 +641,14 @@ let volumeTimer = null; async function refreshAndroid() { const el = $('androidStatus'); if (!el) return; - el.textContent = '读取中…'; + el.textContent = 'Loading…'; const r = await api.android.status().catch(() => null); if (!r || !r.ok) { - el.textContent = r && r.error ? `运行时未就绪(${String(r.error).slice(0, 36)})` : '运行时未启动'; + el.textContent = r && r.error ? `Runtime not ready (${String(r.error).slice(0, 36)})` : 'Runtime is not running'; return; } - el.textContent = `Android ${r.sdk || '?'} · ${r.wifi ? 'Wi-Fi 已开' : 'Wi-Fi 已关'}` + - `${r.airplane ? ' · 飞行模式' : ''} · 音量 ${r.volume}%`; + el.textContent = `Android ${r.sdk || '?'} · ${r.wifi ? 'Wi-Fi on' : 'Wi-Fi off'}` + + `${r.airplane ? ' · Airplane mode' : ''} · Volume ${r.volume}%`; $('setAndroidWifi').checked = !!r.wifi; $('setAndroidAirplane').checked = !!r.airplane; $('setAndroidAnim').checked = !r.animationsOff; @@ -666,30 +666,30 @@ function bindAndroid() { const r = await api.android.set(key, value).catch(() => null); toast({ type: r && r.ok ? 'ok' : 'error', - title: r && r.ok ? (msg || '已生效') : `失败:${(r && r.error) || '运行时未就绪'}`, + title: r && r.ok ? (msg || 'Applied') : `Failed: ${(r && r.error) || 'Runtime is not ready'}`, }); if (r && r.ok) refreshAndroid(); }; - $('setAndroidWifi').addEventListener('change', (e) => set('wifi', e.target.checked, e.target.checked ? 'Wi-Fi 已开启' : 'Wi-Fi 已关闭')); - $('setAndroidAirplane').addEventListener('change', (e) => set('airplane', e.target.checked, e.target.checked ? '已进入飞行模式' : '已退出飞行模式')); - $('setAndroidAnim').addEventListener('change', (e) => set('animations', e.target.checked, e.target.checked ? '系统动画已开启' : '系统动画已关闭,界面更快')); - $('setAndroidFont').addEventListener('change', (e) => set('fontScale', Number(e.target.value), '字体大小已调整')); - $('setAndroidDensity').addEventListener('change', (e) => set('density', Number(e.target.value), '显示大小已调整')); + $('setAndroidWifi').addEventListener('change', (e) => set('wifi', e.target.checked, e.target.checked ? 'Wi-Fi enabled' : 'Wi-Fi disabled')); + $('setAndroidAirplane').addEventListener('change', (e) => set('airplane', e.target.checked, e.target.checked ? 'Airplane mode enabled' : 'Airplane mode disabled')); + $('setAndroidAnim').addEventListener('change', (e) => set('animations', e.target.checked, e.target.checked ? 'System animations enabled' : 'System animations disabled; UI is faster')); + $('setAndroidFont').addEventListener('change', (e) => set('fontScale', Number(e.target.value), 'Font size adjusted')); + $('setAndroidDensity').addEventListener('change', (e) => set('density', Number(e.target.value), 'Display size adjusted')); $('setAndroidVolume').addEventListener('input', (e) => { $('androidVolumeText').textContent = `${e.target.value}%`; clearTimeout(volumeTimer); - volumeTimer = setTimeout(() => set('volume', Number(e.target.value), '音量已调整'), 260); + volumeTimer = setTimeout(() => set('volume', Number(e.target.value), 'Volume adjusted'), 260); }); $('btnAndroidRefresh').addEventListener('click', () => refreshAndroid()); $('btnAndroidTune').addEventListener('click', async () => { const r = await api.android.tune(state.launchPkg || '').catch(() => null); - toast({ type: r && r.ok ? 'ok' : 'error', title: r && r.ok ? '已优化:动画关闭、后台放宽、电池白名单' : '优化失败,运行时可能未启动' }); + toast({ type: r && r.ok ? 'ok' : 'error', title: r && r.ok ? 'Optimized: animations off, background limits relaxed, battery whitelist enabled' : 'Optimization failed; the runtime may not be running' }); if (r && r.ok) refreshAndroid(); }); const open = async (action) => { const r = await api.android.open(action).catch(() => null); if (!r || !r.ok) { - toast({ type: 'error', title: '需要先打开一次应用,让运行时启动' }); + toast({ type: 'error', title: 'Open an app once to start the runtime' }); } }; $('btnAndroidSettings').addEventListener('click', () => open('settings')); @@ -745,12 +745,12 @@ async function refreshDataInfo() { const el = $('dataNote'); if (!el) return; const info = await api.dataInfo().catch(() => null); - if (!info) { el.textContent = '读取数据分区信息失败'; return; } + if (!info) { el.textContent = 'Failed to read data partition information'; return; } const auto = Number(($('setDataGB') || {}).value || 0) === 0; - const img = info.imageGB ? `${info.imageGB} GB` : '尚未创建'; - const free = info.freeDiskMB ? `${(info.freeDiskMB / 1024).toFixed(1)} GB` : '未知'; - el.textContent = `当前数据分区镜像:${img}${auto ? '(自动)' : ''} · 所在磁盘剩余 ${free}。` - + '游戏提示「存储空间不足」就把这里调大后点「立即扩容」——扩容是就地做的,已装应用与存档都保留。'; + const img = info.imageGB ? `${info.imageGB} GB` : 'Not created'; + const free = info.freeDiskMB ? `${(info.freeDiskMB / 1024).toFixed(1)} GB` : 'Unknown'; + el.textContent = `Current data partition image: ${img}${auto ? '(Auto)' : ''} · free disk space: ${free}。` + + 'If a game reports “Insufficient storage”, increase this value and click “Expand now”. Expansion is in place; installed apps and saves are preserved.'; } async function openSettings() { @@ -768,17 +768,17 @@ async function openSettings() { if (curVerEl && info.version) curVerEl.textContent = `v${info.version}`; const assoc = await api.assocStatus(); const btn = $('btnAssoc'); - btn.textContent = assoc.registered ? '已关联 · 点击取消' : '未关联 · 点击开启'; + btn.textContent = assoc.registered ? 'Associated · click to remove' : 'Not associated · click to enable'; btn.dataset.on = assoc.registered ? '1' : '0'; api.doctorProfile().then((r) => { if (r && r.text) $('autoProfileText').textContent = r.text; - }).catch(() => { $('autoProfileText').textContent = '自动模式'; }); + }).catch(() => { $('autoProfileText').textContent = 'Automatic mode'; }); api.runtimeUsage().then((u) => { $('usageText').textContent = u - ? `共 ${u.totalText}${u.reclaimable ? ` · 可回收 ${u.reclaimableText}(${u.count} 项)` : ' · 已是精简状态'}` - : '统计不可用'; - }).catch(() => { $('usageText').textContent = '统计不可用'; }); + ? `Total ${u.totalText}${u.reclaimable ? ` · reclaimable ${u.reclaimableText} (${u.count} more)` : ' · Already optimized'}` + : 'Statistics unavailable'; + }).catch(() => { $('usageText').textContent = 'Statistics unavailable'; }); $('settingsMask').classList.add('on'); refreshAndroid(); @@ -793,7 +793,7 @@ function closeSettings() { let lastUpdate = null; -/** 把一次检查结果画到设置面板(也用于启动时静默检查推送过来的状态) */ +/** 把一次检查结果画到设置面板 (也用于启动时静默检查推送过来的状态) */ function renderUpdate(info) { lastUpdate = info || null; const cur = $('updateCurrent'); @@ -805,36 +805,36 @@ function renderUpdate(info) { if (info.current) cur.textContent = `v${info.current}`; if (info.ok === false) { - st.textContent = `检查失败:${info.error || '网络不可用'}`; + st.textContent = `Check failed: ${info.error || 'Network unavailable'}`; // 检查失败也要给一条出路:直接把发布页打开,用户至少能手动下载。 // 以前这里是「什么都不显示」,用户只会觉得「更新功能坏了」。 showUpdateBtn(true); - btn.textContent = '打开下载页'; + btn.textContent = 'Open download page'; btn.dataset.mode = 'manual'; lastUpdate = { htmlUrl: info.htmlUrl || '' }; if (bar) bar.hidden = true; return; } if (info.hasUpdate) { - // 如实说明这次是哪个源给出的版本、另一个源为什么没参与 —— + // 如实说明这次是哪个源给出的版本 and 另一个源为什么没参与 —— // 用户排查「为什么检查更新很慢」时,这一行比任何日志都直接。 const src = (info.sources || []).map((x) => `${x.name} ${x.tag || ''}`.trim()).join(' / '); - const bad = (info.unreachable || []).map((x) => x.name).join('、'); - const stale = info.stale ? '(离线缓存,可能不是最新)' : ''; - st.textContent = `发现新版本 ${info.tag}${src ? `(${src})` : ''}${bad ? `;${bad} 不可达` : ''}${stale}`; + const bad = (info.unreachable || []).map((x) => x.name).join(' and '); + const stale = info.stale ? '(offline cache; may not be current)' : ''; + st.textContent = `New version available ${info.tag}${src ? ` (${src})` : ''}${bad ? `; ${bad} unreachable` : ''}${stale}`; showUpdateBtn(true); - btn.textContent = info.asset ? '立即更新' : '打开下载页'; + btn.textContent = info.asset ? 'Update now' : 'Open download page'; btn.dataset.mode = info.asset ? 'auto' : 'manual'; const note = $('updateNote'); if (note && info.notes) { const brief = String(info.notes).split('\n').slice(0, 8).join('\n').trim(); - if (brief) note.textContent = `更新内容:\n${brief}`; + if (brief) note.textContent = `Release notes:\n${brief}`; } if (bar) bar.hidden = true; } else { const src = (info.sources || []).map((x) => x.name).join(' / '); - const stale = info.stale ? '(离线缓存)' : ''; - st.textContent = `已是最新版本(v${info.current})${src ? ` · 经由 ${src}` : ''}${stale}`; + const stale = info.stale ? '(offline cache)' : ''; + st.textContent = `Up to date (v${info.current})${src ? ` · via ${src}` : ''}${stale}`; showUpdateBtn(false); if (bar) bar.hidden = true; } @@ -854,12 +854,12 @@ function renderUpdateProgress(p) { const bar = $('updateBar'); const fill = $('updateBarFill'); if (p.stage === 'error') { - if (st) st.textContent = `更新失败:${p.message || ''}`; + if (st) st.textContent = `Update failed: ${p.message || ''}`; if (bar) bar.hidden = true; - toast({ title: '更新失败', body: p.message || '', type: 'error' }); + toast({ title: 'Update failed', body: p.message || '', type: 'error' }); return; } - if (st) st.textContent = p.message || '正在更新…'; + if (st) st.textContent = p.message || 'Updating…'; if (bar && fill) { bar.hidden = false; fill.style.width = `${Math.max(0, Math.min(100, p.percent || 0))}%`; @@ -875,15 +875,15 @@ function bindUpdate() { if (!silent) { check.disabled = true; const old = check.textContent; - check.textContent = '检查中…'; - $('updateState').textContent = '正在连接 GitHub…'; + check.textContent = 'Checking…'; + $('updateState').textContent = 'Connecting to GitHub…'; try { const info = await api.updateCheck(); renderUpdate(info); - if (info && info.hasUpdate) toast({ title: `发现新版本 ${info.tag}`, body: '点「立即更新」自动下载安装', type: 'info' }); - else if (info && info.ok !== false) toast({ title: '已是最新版本', body: `v${info.current}`, type: 'ok' }); + if (info && info.hasUpdate) toast({ title: `New version available ${info.tag}`, body: 'Click “Update now” to download and install automatically', type: 'info' }); + else if (info && info.ok !== false) toast({ title: 'Already up to date', body: `v${info.current}`, type: 'ok' }); } catch (e) { - $('updateState').textContent = `检查失败:${e.message}`; + $('updateState').textContent = `Check failed: ${e.message}`; } finally { check.disabled = false; check.textContent = old; @@ -902,21 +902,21 @@ function bindUpdate() { return; } go.disabled = true; - go.textContent = '更新中…'; + go.textContent = 'Updating…'; const r = await api.updateApply().catch((e) => ({ ok: false, error: e.message })); if (!r || !r.ok) { go.disabled = false; - go.textContent = '立即更新'; + go.textContent = 'Update now'; if (r && r.error && !r.manual) { - $('updateState').textContent = `更新失败:${r.error}`; - toast({ title: '更新失败', body: r.error, type: 'error' }); + $('updateState').textContent = `Update failed: ${r.error}`; + toast({ title: 'Update failed', body: r.error, type: 'error' }); } } else { - $('updateState').textContent = `已下载 v${r.version},正在重启完成安装…`; + $('updateState').textContent = `Downloaded v${r.version}, restarting to finish installation…`; } }); - // 打开设置时如果还没结果,就顺手查一次(放后台,不打扰) + // 打开设置时如果还没结果,就顺手查一次 (放后台,不打扰) runCheck(true).catch(() => {}); } @@ -945,10 +945,10 @@ function bindSettings() { }); if ($('setResourceMode')) { $('setResourceMode').addEventListener('change', async (e) => { - // 资源策略会连带改 liteMode / priorityMode / renderSize(主进程里翻译) + // 资源策略会连带改 liteMode / priorityMode / renderSize (主进程里翻译) state.settings = await api.setSettings({ engine: { resourceMode: e.target.value } }); fillSettings(state.settings); - toast({ title: '资源策略已切换', body: '下一次打开应用生效', type: 'ok' }); + toast({ title: 'Resource profile updated', body: 'Takes effect the next time you open an app', type: 'ok' }); }); } if ($('setRenderSize')) { @@ -969,24 +969,24 @@ function bindSettings() { if ($('btnGrowData')) { $('btnGrowData').addEventListener('click', async () => { const gb = Number($('setDataGB').value) || 0; - if (!gb) { toast({ title: '先选一个容量', body: '「自动」表示由软件按磁盘余量决定,不需要扩容', type: 'info' }); return; } + if (!gb) { toast({ title: 'Choose a size first', body: '“Auto” lets the app choose based on available disk space; no expansion needed', type: 'info' }); return; } const { response } = await api.confirm({ - title: '立刻把安卓数据分区扩容?', - message: `将把 /data 调整为 ${gb} GB(就地扩容,已装应用和存档都会保留)。`, - detail: '扩容需要先关掉正在运行的安卓应用,完成后下次打开应用会重新引导,约多花十几秒。', - okLabel: '开始扩容', + title: 'Expand the Android data partition now?', + message: `The /data partition will be resized to ${gb} GB (installed apps and saved data will be preserved).`, + detail: 'Running Android apps must close first. The next launch will boot again and may take a few extra seconds.', + okLabel: 'Start expansion', }); if (response !== 1) return; $('btnGrowData').disabled = true; const old = $('btnGrowData').textContent; - $('btnGrowData').textContent = '扩容中…'; + $('btnGrowData').textContent = 'Expanding…'; const r = await api.setDataSize(gb).catch((e) => ({ ok: false, error: e.message })); $('btnGrowData').disabled = false; $('btnGrowData').textContent = old; toast({ type: r && r.ok ? 'ok' : 'error', - title: r && r.ok ? '数据分区已扩容' : '扩容失败', - body: r && r.ok ? `现在约 ${r.imageGB || gb} GB 可用` : (r && r.error) || '未知错误', + title: r && r.ok ? 'Data partition expanded' : 'Expansion failed', + body: r && r.ok ? `About ${r.imageGB || gb} GB available` : (r && r.error) || 'Unknown error', }); refreshDataInfo(); }); @@ -1028,7 +1028,7 @@ function bindSettings() { // 关掉深度裁剪必须把之前 pm disable-user 的包恢复回来,否则影响是持久的 if (!e.target.checked) { api.restoreSystemApps().catch(() => {}); - toast({ title: '已恢复系统组件', body: '深度裁剪关闭,后台包正在恢复', type: 'info' }); + toast({ title: 'System components restored', body: 'Deep trimming is off; background packages are being restored', type: 'info' }); } }); $('setCompat').addEventListener('change', async (e) => { @@ -1037,8 +1037,8 @@ function bindSettings() { const env = ((state.status = await api.runtimeStatus()).env) || {}; const ok = env.playImage && env.playImage.ok; $('compatInstallField').hidden = !on || !!ok; - if (on && !ok) toast({ title: 'ARM 兼容模式已开启', body: '还需要下载 Google Play 兼容镜像(Android 14,约 1.5GB),点击下方按钮开始', type: 'info' }); - else if (on) toast({ title: 'ARM 兼容模式已开启', body: '重启运行时后生效', type: 'info' }); + if (on && !ok) toast({ title: 'ARM compatibility mode enabled', body: 'The Google Play compatibility image (Android 14, about 1.5 GB) is still needed. Click the button below to download it.', type: 'info' }); + else if (on) toast({ title: 'ARM compatibility mode enabled', body: 'Takes effect after restarting the runtime', type: 'info' }); }); $('setAutoShortcut').addEventListener('change', (e) => save({ createShortcutOnInstall: e.target.checked })); $('setStartMenuShortcut').addEventListener('change', (e) => save({ createStartMenuShortcut: e.target.checked })); @@ -1049,17 +1049,17 @@ function bindSettings() { const mode = e.target.value; $('portraitField').hidden = mode === 'fullscreen'; state.settings = await api.setSettings({ display: { mode } }); - toast({ title: mode === 'fullscreen' ? '已切全屏' : '已切竖屏窗口', type: 'ok' }); + toast({ title: mode === 'fullscreen' ? 'Switched to fullscreen' : 'Switched to portrait window', type: 'ok' }); }); $('setPortrait').addEventListener('change', async (e) => { state.settings = await api.setSettings({ display: { portrait: e.target.value } }); - toast({ title: '画面大小已更新', body: '下次打开应用或切换应用时生效', type: 'ok' }); + toast({ title: 'Display size updated', body: 'Takes effect the next time you open or switch apps', type: 'ok' }); }); $('btnRebuildShortcuts').addEventListener('click', async () => { const r = await api.rebuildShortcuts(); toast({ - title: r && r.fixed && r.fixed.length ? '已补齐快捷方式' : '快捷方式都还在', - body: r && r.fixed && r.fixed.length ? r.fixed.join('、') : '桌面与开始菜单入口正常', + title: r && r.fixed && r.fixed.length ? 'Shortcuts restored' : 'All shortcuts are already present', + body: r && r.fixed && r.fixed.length ? r.fixed.join(' and ') : 'Desktop and Start menu shortcuts are working', type: 'ok', }); }); @@ -1067,9 +1067,9 @@ function bindSettings() { $('btnDiagnostics').addEventListener('click', async () => { try { const r = await api.doctorReport(); - toast({ title: '诊断报告已导出', body: `${r.diagnosis.title} · ${r.file}`, type: 'ok', sticky: true }); + toast({ title: 'Diagnostic report exported', body: `${r.diagnosis.title} · ${r.file}`, type: 'ok', sticky: true }); } catch (e) { - toast({ title: '导出失败', body: e.message, type: 'error' }); + toast({ title: 'Export failed', body: e.message, type: 'error' }); } }); @@ -1080,89 +1080,89 @@ function bindSettings() { try { const preview = await api.runtimeCleanup({ apply: false, includeSnapshots }); if (!preview.candidates.length) { - toast({ title: '已经是精简状态', body: '没有可回收的旧镜像或快照。', type: 'ok' }); + toast({ title: 'Already optimized', body: 'No old images or snapshots to reclaim.', type: 'ok' }); return; } const r = await api.runtimeCleanup({ apply: true, includeSnapshots }); if (r.cancelled) return; toast({ - title: '清理完成', - body: `释放约 ${(r.saved / 1048576).toFixed(0)} MB(${r.removed.length} 项),已安装应用保留。`, + title: 'Cleanup complete', + body: `Freed about ${(r.saved / 1048576).toFixed(0)} MB (${r.removed.length} items removed; installed apps are preserved.`, type: 'ok', }); const u = await api.runtimeUsage(); - $('usageText').textContent = `共 ${u.totalText} · 可回收 ${u.reclaimableText}(${u.count} 项)`; + $('usageText').textContent = `Total ${u.totalText} · reclaimable ${u.reclaimableText} (${u.count} more)`; } catch (e) { - toast({ title: '清理失败', body: e.message, type: 'error' }); + toast({ title: 'Cleanup failed', body: e.message, type: 'error' }); } finally { btn.disabled = false; } }); - // 重置安卓数据:二级确认(先确认意图,再确认不可撤销),清完刷新占用统计 + // 重置安卓数据:二级确认 (先确认意图,再确认不可撤销),清完刷新占用统计 $('btnResetData').addEventListener('click', async () => { const btn = $('btnResetData'); btn.disabled = true; try { const first = await api.confirm({ type: 'warning', - title: '重置安卓数据?', - message: '这会把安卓系统还原成「刚装好系统」的干净状态:', - detail: '• 删除全部已安装的第三方 app(含红果、游戏等)及其数据、登录态\n' - + '• 删除当前 AVD 的快速启动快照\n' - + '• 系统镜像本身不受影响,重新打开应用会再次冷启动建数据\n\n' - + '此操作不可撤销。是否继续?', - buttons: ['取消', '继续'], + title: 'Reset Android data?', + message: 'This will restore Android to a clean, freshly installed state:', + detail: '• Remove all installed third-party apps, their data, and sign-in sessions\n' + + '• Delete the current AVD quick-boot snapshot\n' + + '• The system image is not affected; the next app launch will create fresh data\n\n' + + 'This action cannot be undone. Continue?', + buttons: ['Cancel', 'Continue'], defaultId: 0, cancelId: 0, }); if (!first || first.response !== 1) return; const second = await api.confirm({ type: 'warning', - title: '再确认一次', - message: '已安装的 app 和它们的数据将被永久删除,确定要重置吗?', - detail: '可放心的是:系统镜像、其它 AVD、本机的 APK 缓存和图标都不会动。', - buttons: ['取消', '确定重置'], + title: 'Confirm once more', + message: 'Installed apps and their data will be permanently deleted. Are you sure you want to reset?', + detail: 'The system image, other AVDs, local APK cache, and icons will not be affected.', + buttons: ['Cancel', 'Reset now'], defaultId: 0, cancelId: 0, }); if (!second || second.response !== 1) return; const r = await api.runtimeResetData(); if (!r || !r.ok) { - toast({ title: '重置失败', body: (r && r.error) || '请重试', type: 'error' }); + toast({ title: 'Reset failed', body: (r && r.error) || 'Please try again', type: 'error' }); return; } toast({ - title: '安卓数据已重置', - body: `释放约 ${r.reclaimedMB} MB,已安装应用记录已清空。`, + title: 'Android data reset', + body: `Freed about ${r.reclaimedMB} MB; installed app records were cleared.`, type: 'ok', }); const u = await api.runtimeUsage(); - $('usageText').textContent = `共 ${u.totalText} · 可回收 ${u.reclaimableText}(${u.count} 项)`; + $('usageText').textContent = `Total ${u.totalText} · reclaimable ${u.reclaimableText} (${u.count} more)`; } catch (e) { - toast({ title: '重置失败', body: e.message, type: 'error' }); + toast({ title: 'Reset failed', body: e.message, type: 'error' }); } finally { btn.disabled = false; } }); - // 修复网络:安卓里联网页面一直黑屏(红果点「同意并继续」后整屏黑)时用。 - // 根因是虚拟机 /data 里的 Wi-Fi 配置坏了,wlan0 没起来、没有默认路由。 - // Play Store 镜像不允许 root,只能整体重置 /data;应用清单保留,点开时会自动重装。 + // Fix网络:安卓里联网页面一直黑屏 (红果点「同意并继续」后整屏黑)时用。 + // 根因是虚拟机 /data 里的 Wi-Fi 配置坏了,wlan0 没起来 and 没有默认路由。 + // Play Store 镜像不允许 root,只能整体重置 /data; 应用清单保留,点开时会自动重装。 $('btnRepairNetwork').addEventListener('click', async () => { const btn = $('btnRepairNetwork'); btn.disabled = true; try { const ok = await api.confirm({ type: 'warning', - title: '修复虚拟机网络?', - message: '应用里需要联网的页面一直黑屏/转圈,通常是安卓虚拟机没有网络。', - detail: '修复会按「代价从小到大」依次尝试:\n\n' - + '1. 冷启动虚拟机(不加载快照)后等待联网 —— 不影响任何数据\n' - + '2. 仍不行才重置安卓数据 —— 应用清单保留、点开会自动重装,\n' - + ' 但应用内的登录态、追剧记录等会丢失\n\n' - + '只影响当前 AVD,系统镜像和本机 APK 缓存都不动。整个过程约 1–5 分钟。是否继续?', - buttons: ['取消', '开始修复'], + title: 'Repair virtual machine network?', + message: 'If online pages in apps stay blank or keep loading, the Android VM may have no network connection.', + detail: 'Repair tries the following steps, from least to most disruptive:\n\n' + + '1. Cold-boot the VM without loading a snapshot and wait for networking — no data is affected\n' + + '2. If that fails, reset Android data — your app list is kept and apps are reinstalled when opened,\n' + + ' but in-app sign-ins, watch history, and similar data will be lost\n\n' + + 'Only the current AVD is affected; the system image and local APK cache remain untouched. This takes about 1–5 minutes. Continue?', + buttons: ['Cancel', 'Start repair'], defaultId: 0, cancelId: 0, }); @@ -1170,19 +1170,19 @@ function bindSettings() { const r = await api.runtimeRepairNetwork(); if (!r || !r.ok) { toast({ - title: '网络仍未恢复', - body: (r && r.error) || '重置后虚拟机还是没网,请检查本机网络/防火墙是否拦住了模拟器。', + title: 'Network not restored', + body: (r && r.error) || 'The VM is still offline after reset. Check whether your network or firewall is blocking the emulator.', type: 'error', }); return; } toast({ - title: '网络已恢复', - body: `${(r.network && r.network.ip) || '已联网'}${r.wiped ? '(为修复网络重置过安卓数据,点开应用会自动重装)' : '(未动你的数据)'}`, + title: 'Network restored', + body: `${(r.network && r.network.ip) || 'Connected'}${r.wiped ? '(Android data was reset to repair networking; apps will be reinstalled when opened)' : '(Your data was not changed)'}`, type: 'ok', }); } catch (e) { - toast({ title: '修复失败', body: e.message, type: 'error' }); + toast({ title: 'Repair failed', body: e.message, type: 'error' }); } finally { btn.disabled = false; } @@ -1195,43 +1195,43 @@ function bindSettings() { try { const r = await api.leftoversScan(); if (!r || !r.ok) { - toast({ title: '扫描失败', body: (r && r.error) || '请重试', type: 'error' }); + toast({ title: 'Scan failed', body: (r && r.error) || 'Please try again', type: 'error' }); return; } const items = r.items || []; if (!items.length) { - toast({ title: '很干净', body: '没有发现旧版本残留。', type: 'ok' }); + toast({ title: 'All clean', body: 'No leftovers from older versions were found.', type: 'ok' }); return; } const mb = Math.round((r.totalBytes || 0) / 1048576); const lines = items.slice(0, 8).map((x) => `• ${x.note}:${x.path}`); - if (items.length > 8) lines.push(`…另有 ${items.length - 8} 项`); + if (items.length > 8) lines.push(`…plus ${items.length - 8} more`); const ok = await api.confirm({ type: 'warning', - title: '清理旧版本残留?', - message: `找到 ${items.length} 项残留,约 ${mb} MB:`, - detail: `${lines.join('\n')}\n\n只删除「旧版本程序目录 / 已失效的卸载项 / 更新临时文件」,` - + '当前正在运行的版本和你的应用数据都不会动。', - buttons: ['取消', '开始清理'], + title: 'Remove leftovers from older versions?', + message: `Found ${items.length} leftover items, about ${mb} MB:`, + detail: `${lines.join('\n')}\n\nOnly old application folders, obsolete uninstall entries, and temporary update files will be removed.` + + 'The currently running version and your app data will not be affected.', + buttons: ['Cancel', 'Start cleanup'], defaultId: 1, cancelId: 0, }); if (!ok || ok.response !== 1) return; const c = await api.leftoversClean(items); if (!c || !c.ok) { - toast({ title: '清理失败', body: (c && c.error) || '请重试', type: 'error' }); + toast({ title: 'Cleanup failed', body: (c && c.error) || 'Please try again', type: 'error' }); return; } - const failNote = (c.failed && c.failed.length) ? `,${c.failed.length} 项被占用未删` : ''; + const failNote = (c.failed && c.failed.length) ? `, ${c.failed.length} items could not be deleted because they are in use` : ''; toast({ - title: '已清理旧版本残留', - body: `删除 ${c.removed.length} 项,释放 ${c.freedMB || 0} MB${failNote}。`, + title: 'Removed old-version leftovers', + body: `Removed ${c.removed.length} items and freed ${c.freedMB || 0} MB${failNote}.`, type: 'ok', }); const u = await api.runtimeUsage(); - $('usageText').textContent = `共 ${u.totalText} · 可回收 ${u.reclaimableText}(${u.count} 项)`; + $('usageText').textContent = `Total ${u.totalText} · reclaimable ${u.reclaimableText} (${u.count} more)`; } catch (e) { - toast({ title: '清理失败', body: e.message, type: 'error' }); + toast({ title: 'Cleanup failed', body: e.message, type: 'error' }); } finally { btn.disabled = false; } @@ -1247,8 +1247,8 @@ function bindSettings() { const r = await api.rebuildShortcuts().catch(() => ({ ok: false })); const fixed = (r && r.fixed) || []; toast(fixed.length - ? { title: '快捷方式已重建', body: fixed.join('、'), type: 'ok' } - : { title: '快捷方式都在', body: '没有需要重建的入口', type: 'info' }); + ? { title: 'Shortcuts rebuilt', body: fixed.join(' and '), type: 'ok' } + : { title: 'All shortcuts are present', body: 'No shortcuts needed rebuilding', type: 'info' }); } finally { btn.disabled = false; } @@ -1259,26 +1259,26 @@ function bindSettings() { const picked = await api.openDir(); if (!picked) return; $('setSharedDir').value = picked; - toast({ title: '已选新目录', body: '点「保存并迁移」把旧文件搬过去', type: 'info' }); + toast({ title: 'New folder selected', body: 'Click “Save and move” to move existing files', type: 'info' }); }); $('btnSaveShared').addEventListener('click', async () => { const dir = $('setSharedDir').value.trim(); - if (!dir) { toast({ title: '请先填写或浏览目录', type: 'error' }); return; } + if (!dir) { toast({ title: 'Enter or browse for a folder first', type: 'error' }); return; } const btn = $('btnSaveShared'); btn.disabled = true; const r = await api.setSharedDir(dir).catch(() => ({ ok: false })); btn.disabled = false; if (r.ok) { const moved = r.oldDir && r.oldDir !== r.newDir; - toast({ title: '共享目录已更新', body: moved ? '旧目录里已有的文件已迁移到新目录' : '已保存', type: 'ok' }); + toast({ title: 'Shared folder updated', body: moved ? 'Existing files were moved from the old folder to the new folder' : 'Saved', type: 'ok' }); } else { - toast({ title: '保存失败', body: r.error, type: 'error' }); + toast({ title: 'Save failed', body: r.error, type: 'error' }); } }); - // 一键卸载 ApkStart 本体(取消 .apk 关联 + 启动卸载程序;便携版手动删除) + // 一键卸载 ApkStart 本体 (取消 .apk 关联 + 启动卸载程序; 便携版手动删除) $('btnUninstall').addEventListener('click', async () => { - const ok = window.confirm('确定卸载 ApkStart?\n\n将取消 .apk 文件关联并移除程序。此操作不可撤销。'); + const ok = window.confirm('Uninstall ApkStart?\n\nThis will remove the .apk file association and uninstall the app. This cannot be undone.'); if (!ok) return; const btn = $('btnUninstall'); btn.disabled = true; @@ -1286,44 +1286,44 @@ function bindSettings() { if (r.ok && r.portable) { btn.disabled = false; toast({ - title: '便携版无法自动卸载', - body: `请手动删除程序目录(${r.exeDir})与数据目录(${r.dataDir})`, + title: 'Portable version cannot be uninstalled automatically', + body: `Please manually delete the application folder (${r.exeDir}) and data folder (${r.dataDir})`, type: 'info', sticky: true, }); return; } if (!r.ok) { btn.disabled = false; - toast({ title: '卸载失败', body: r.error, type: 'error' }); + toast({ title: 'Uninstall failed', body: r.error, type: 'error' }); return; } - toast({ title: '正在卸载…', body: '卸载程序已启动,ApkStart 即将退出', type: 'ok', sticky: true }); + toast({ title: 'Uninstalling…', body: 'The uninstaller has started. ApkStart will now exit', type: 'ok', sticky: true }); }); - // ARM 兼容镜像下载(带进度 toast) + // ARM 兼容镜像下载 (带进度 toast) $('btnInstallPlay').addEventListener('click', async () => { const btn = $('btnInstallPlay'); btn.disabled = true; - const t = toast({ title: '开始下载 ARM 兼容镜像', body: '准备中…', sticky: true, spinner: true, progress: 3 }); + const t = toast({ title: 'Downloading ARM compatibility image', body: 'Preparing…', sticky: true, spinner: true, progress: 3 }); const un = api.on('provision:progress', (p) => { if (p.component !== 'playimg') return; - t.update({ title: '下载 ARM 兼容镜像', body: p.message || '', progress: p.percent }); + t.update({ title: 'Download ARM compatibility image', body: p.message || '', progress: p.percent }); }); try { const r = await api.installPlayImage(); if (r && r.ok) { - t.update({ title: 'ARM 兼容镜像就绪', body: '重启运行时后生效', progress: 100 }); + t.update({ title: 'ARM compatibility image ready', body: 'Takes effect after restarting the runtime', progress: 100 }); setTimeout(() => t.dismiss(), 3000); $('compatInstallField').hidden = true; refreshStatus(); } else { - t.update({ title: '兼容镜像下载失败', body: (r && r.error) || '未知错误', progress: null }); + t.update({ title: 'Compatibility image download failed', body: (r && r.error) || 'Unknown error', progress: null }); t.dismiss(); - toast({ title: '兼容镜像下载失败', body: (r && r.error) || '未知错误', type: 'error' }); + toast({ title: 'Compatibility image download failed', body: (r && r.error) || 'Unknown error', type: 'error' }); } } catch (e) { t.dismiss(); - toast({ title: '兼容镜像下载失败', body: e.message, type: 'error' }); + toast({ title: 'Compatibility image download failed', body: e.message, type: 'error' }); } finally { un(); btn.disabled = false; @@ -1339,7 +1339,7 @@ function bindSettings() { $('compatInstallField').hidden = !on || !!ok; if (env.playImage) { $('compatImgStatus').textContent = ok - ? `已就绪(${(env.playImage.size / 1073741824).toFixed(2)} GB)` : '未下载'; + ? `Ready (${(env.playImage.size / 1073741824).toFixed(2)} GB)` : 'Not downloaded'; } })(); $('btnOpenData').addEventListener('click', async () => { @@ -1353,10 +1353,10 @@ function bindSettings() { if (!picked) return; await api.setSettings({ runtime: { dir: picked } }); $('setRuntimeDir').value = picked; - toast({ title: '数据目录已更新', body: '请重启 ApkStart 使其生效(旧数据不会自动搬移)', type: 'ok', sticky: true }); + toast({ title: 'Data folder updated', body: 'Restart ApkStart for this change to take effect (existing data is not moved automatically)', type: 'ok', sticky: true }); }); - // 安卓数据目录(AVD + 快照):真正吃盘的那块,单独指到别的盘 + // 安卓数据目录 (AVD + 快照):真正吃盘的那块,单独指到别的盘 if ($('btnBrowseAvdDir')) { $('btnBrowseAvdDir').addEventListener('click', async () => { const picked = await api.openDir(); @@ -1364,8 +1364,8 @@ function bindSettings() { await api.setSettings({ runtime: { avdDir: picked } }); $('setAvdDir').value = picked; toast({ - title: '安卓数据目录已更新', - body: '请重启 ApkStart 使其生效。旧目录里的 apkstart.avd 可以整体搬到新目录,再删掉旧的。', + title: 'Android data folder updated', + body: 'Restart ApkStart for this change to take effect. You can move the apkstart.avd folder from the old location to the new one, then delete the old folder.', type: 'ok', sticky: true, }); @@ -1381,13 +1381,13 @@ function bindSettings() { $('btnProbe').addEventListener('click', async () => { const el = $('probeResult'); - el.textContent = '探测中…'; + el.textContent = 'Checking…'; const found = await api.runtimeProbe(); if (!found.length) { - el.textContent = '未发现已运行的运行时'; + el.textContent = 'No running runtime found'; return; } - el.textContent = `发现:${found.map((f) => f.name).join('、')}`; + el.textContent = `Found: ${found.map((f) => f.name).join(' and ')}`; await api.setSettings({ device: found[0].serial }); $('setDevice').value = found[0].serial; refreshStatus(); @@ -1395,15 +1395,15 @@ function bindSettings() { $('btnRuntimeStart').addEventListener('click', async () => { const el = $('probeResult'); - el.textContent = '正在启动…'; + el.textContent = 'Starting…'; const r = await api.runtimeStart(); - el.textContent = r.ok ? `已就绪:${r.engine}` : `启动失败:${r.error}`; + el.textContent = r.ok ? `Ready: ${r.engine}` : `Startup failed: ${r.error}`; refreshStatus(); }); $('btnRuntimeStop').addEventListener('click', async () => { await api.runtimeStop(); - $('probeResult').textContent = '已停止内置虚拟机'; + $('probeResult').textContent = 'Built-in virtual machine stopped'; refreshStatus(); }); @@ -1411,14 +1411,14 @@ function bindSettings() { const btn = $('btnAssoc'); if (btn.dataset.on === '1') { await api.assocUnregister(); - btn.textContent = '未关联 · 点击开启'; + btn.textContent = 'Not associated · click to enable'; btn.dataset.on = '0'; - toast({ title: '.apk 关联已取消' }); + toast({ title: '.apk association removed' }); } else { const r = await api.assocRegister(); - btn.textContent = '已关联 · 点击取消'; + btn.textContent = 'Associated · click to remove'; btn.dataset.on = '1'; - toast({ title: '.apk 关联已开启', body: r.ok ? '现在双击 APK 会直接安装到 ApkStart' : '', type: r.ok ? 'ok' : 'error' }); + toast({ title: '.apk association enabled', body: r.ok ? 'Double-clicking an APK will now install it directly in ApkStart' : '', type: r.ok ? 'ok' : 'error' }); } }); } @@ -1471,33 +1471,33 @@ function bindEvents() { if (act === 'emulator' || act === 'sysimg' || act === 'accel' || act === 'adb' || act === 'qemu' || act === 'iso' || act === 'disk') { const r = await runStep(act); - if (!r.ok) toast({ title: '操作失败', body: r.error, type: 'error', sticky: true }); + if (!r.ok) toast({ title: 'Operation failed', body: r.error, type: 'error', sticky: true }); return; } if (act === 'isoImport') { const r = await api.importIso(); - if (r.ok) { toast({ title: '系统镜像已导入', body: r.path, type: 'ok' }); refreshStatus(); } - else if (!r.canceled) toast({ title: '导入失败', body: r.error, type: 'error' }); + if (r.ok) { toast({ title: 'System image imported', body: r.path, type: 'ok' }); refreshStatus(); } + else if (!r.canceled) toast({ title: 'Import failed', body: r.error, type: 'error' }); return; } if (act === 'diskImport') { const r = await api.importDisk(); - if (r.ok) { toast({ title: '虚拟磁盘已导入', body: '可直接启动运行时', type: 'ok' }); refreshStatus(); } - else if (!r.canceled) toast({ title: '导入失败', body: r.error, type: 'error' }); + if (r.ok) { toast({ title: 'Virtual disk imported', body: 'The runtime can now be started directly', type: 'ok' }); refreshStatus(); } + else if (!r.canceled) toast({ title: 'Import failed', body: r.error, type: 'error' }); return; } if (act === 'install') { await startInstall(); return; } if (act === 'vmStart') { - const t = toast({ title: '正在启动运行时…', body: '首次启动需要引导 Android 系统', sticky: true, spinner: true }); + const t = toast({ title: 'Starting runtime…', body: 'The Android system must be initialized on first launch', sticky: true, spinner: true }); const r = await api.runtimeStart(); - if (r.ok) { t.update({ title: '运行时已就绪', type: 'ok' }); setTimeout(t.dismiss, 2200); } - else t.update({ title: '启动失败', body: r.error, type: 'error' }); + if (r.ok) { t.update({ title: 'Runtime ready', type: 'ok' }); setTimeout(t.dismiss, 2200); } + else t.update({ title: 'Launch failed', body: r.error, type: 'error' }); refreshStatus(); return; } if (act === 'vmStop') { await api.runtimeStop(); - toast({ title: '运行时会话已结束' }); + toast({ title: 'Runtime session ended' }); refreshStatus(); } }); @@ -1514,9 +1514,9 @@ function bindEvents() { btnSync.disabled = true; try { await api.syncShared(); - toast({ title: '已同步共享目录', type: 'ok' }); + toast({ title: 'Shared folder synced', type: 'ok' }); } catch (_) { - toast({ title: '同步失败', body: '运行时未就绪时会跳过同步', type: 'error' }); + toast({ title: 'Sync failed', body: 'Sync is skipped when the runtime is not ready', type: 'error' }); } finally { btnSync.disabled = false; } @@ -1586,17 +1586,17 @@ function bindEvents() { .map((f) => { try { return api.pathForFile(f); } catch (_) { return null; } }) .filter((p) => p && /\.apk$/i.test(p)); if (paths.length) installFiles(paths); - else toast({ title: '只能拖入 .apk 文件', type: 'error' }); + else toast({ title: 'Only .apk files can be dropped here', type: 'error' }); }); // 主进程事件 api.on('install:error', (p) => { - // 报错了就别再让「正在安装…」那条一直转圈(可能来自双击 APK 的另一条链路) + // 报错了就别再让「正在安装…」那条一直转圈 (可能来自双击 APK 的另一条链路) if (installToast) { try { installToast.dismiss(); } catch (_) { /* ignore */ } installToast = null; } const name = p && p.file ? String(p.file).split(/[\\/]/).pop() : ''; toast({ - title: name ? `安装失败:${name}` : '安装失败', - body: friendlyInstallErrorText((p && p.message) || '未知错误'), + title: name ? `Installation failed: ${name}` : 'Installation failed', + body: friendlyInstallErrorText((p && p.message) || 'Unknown error'), type: 'error', }); }); @@ -1642,11 +1642,11 @@ async function init() { let startupInfo = null; function greetingOf(h) { - if (h < 5) return '夜深了'; - if (h < 11) return '早上好'; - if (h < 13) return '中午好'; - if (h < 18) return '下午好'; - return '晚上好'; + if (h < 5) return 'It's late'; + if (h < 11) return 'Good morning'; + if (h < 13) return 'Hello'; + if (h < 18) return 'Good afternoon'; + return 'Good evening'; } function bindWelcome() { @@ -1663,7 +1663,7 @@ function bindWelcome() { window.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !mask.hidden) close(); }); } -/** 决定要不要弹:开机自启必弹;手动打开则每天最多一次,且用户可永久关闭 */ +/** 决定要不要弹:开机自启必弹; 手动打开则每天最多一次,且用户可永久关闭 */ async function maybeShowWelcome() { const mask = $('welcomeMask'); if (!mask) return; @@ -1676,17 +1676,17 @@ async function maybeShowWelcome() { if (!(startupInfo && startupInfo.autostart) && cfg.lastDate === today) return; const h = new Date().getHours(); - $('welcomeHi').textContent = `${greetingOf(h)},欢迎回来`; + $('welcomeHi').textContent = `${greetingOf(h)},Welcome back`; const ver = (startupInfo && startupInfo.version) || (state.status && state.status.version) || ''; const ready = state.status && state.status.ready; - $('welcomeSub').textContent = `ApkStart${ver ? ` v${ver}` : ''} · ${ready ? '运行时就绪,点开即用' : '运行时会按需自动准备'}`; + $('welcomeSub').textContent = `ApkStart${ver ? ` v${ver}` : ''} · ${ready ? 'Runtime ready — open apps instantly' : 'Runtime will be prepared automatically when needed'}`; const apps = (state.apps || []).length; const dev = $('statusDevice') ? $('statusDevice').textContent.trim() : ''; const chips = [ - { k: String(apps), v: '已装应用' }, - { k: ready ? '就绪' : '待命', v: '安卓运行时' }, - { k: dev ? dev.replace(/^.*?(\d+)/, '$1') : '—', v: '设备 / 端口' }, + { k: String(apps), v: 'Installed apps' }, + { k: ready ? 'Ready' : 'Idle', v: 'Android runtime' }, + { k: dev ? dev.replace(/^.*?(\d+)/, '$1') : '—', v: 'Device / port' }, ]; $('welcomeStats').innerHTML = chips .map((c) => `
${esc(c.k)}${esc(c.v)}
`) @@ -1706,20 +1706,20 @@ function renderWelcomeTips() { const apps = (state.apps || []).length; const tips = []; if (lastUpdate && lastUpdate.hasUpdate) { - tips.push(`发现新版本 ${esc(lastUpdate.tag || '')},可在「设置 → 软件更新」一键更新`); + tips.push(`New version available ${esc(lastUpdate.tag || '')}; update with one click in Settings → Software Updates`); } if (!apps) { - tips.push('把 APK 拖进窗口就能安装,装完会自动生成桌面图标'); + tips.push('Drag an APK into the window to install it. A desktop shortcut is created automatically.'); } else { - tips.push('双击应用卡片即可在独立窗口中运行,右上角可旋转 / 缩放 / 关闭'); + tips.push('Double-click an app card to run it in a separate window. Rotate, resize, or close it from the top-right corner.'); } - tips.push('关窗口只是退出那个应用,安卓环境会留着,下次点开更快'); + tips.push('Closing a window only exits that app. Android stays running, so the next launch is faster.'); box.innerHTML = tips .map((t, i) => `
${i + 1}${t}
`) .join(''); } -/* ---------------- 环境自检 ---------------- */ +/* ---------------- Environment check ---------------- */ let envBusy = false; @@ -1746,9 +1746,9 @@ function renderEnvCheck(r) { if (chip) { const errs = issues.filter((i) => i.severity === 'error').length; chip.hidden = issues.length === 0; - chip.textContent = errs ? `${errs} 项待修复` : `${issues.length} 项提示`; + chip.textContent = errs ? `${errs} issues to fix` : `${issues.length} notices`; chip.className = `status-chip${errs ? ' err' : ''}`; - chip.title = issues.map((i) => i.title).join(';'); + chip.title = issues.map((i) => i.title).join('; '); } if (!issues.length) { @@ -1756,9 +1756,9 @@ function renderEnvCheck(r) { box.innerHTML = `
- 环境自检通过,没有发现会影响运行的问题。 + Environment check passed. No issues affecting operation were found. - +
`; const re = $('btnEnvRecheck'); if (re) re.addEventListener('click', selfCheck); @@ -1769,12 +1769,12 @@ function renderEnvCheck(r) { box.hidden = false; box.innerHTML = `
- 环境自检 - ${errs ? `${errs} 个严重` : ''} - ${warns ? `${warns} 个提示` : ''} + Environment check + ${errs ? `${errs} critical` : ''} + ${warns ? `${warns} notices` : ''} - - + +
${issues.map((i) => ` @@ -1784,7 +1784,7 @@ function renderEnvCheck(r) {
${esc(i.title)}
${esc(i.hint || '')}
- ${i.fix ? `` : ''} + ${i.fix ? `` : ''} `).join('')} `; const re = $('btnEnvRecheck'); @@ -1804,53 +1804,53 @@ async function fixIssue(issue) { case 'vcRedist': { const r = await api.installVcRedist().catch(() => ({ ok: false })); if (!r.ok && r.reason === 'no-installer') { - api.openExternal('https://learn.microsoft.com/zh-CN/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022'); - toast({ title: '未找到内置安装包', body: '已打开微软官方下载页,请下载 x64 版本', type: 'info' }); + api.openExternal('https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022'); + toast({ title: 'Built-in installer not found', body: 'Microsoft’s download page has been opened. Download the x64 version.', type: 'info' }); break; } toast(r.ok - ? { title: 'VC++ 运行库已安装', type: 'ok' } - : { title: '安装未完成', body: r.error || '请重试', type: 'error' }); + ? { title: 'VC++ Runtime installed', type: 'ok' } + : { title: 'Installation incomplete', body: r.error || 'Please try again', type: 'error' }); break; } case 'virtualization': { - const t = toast({ title: '正在开启硬件加速…', body: '请在弹出的窗口点「是」', sticky: true, spinner: true }); + const t = toast({ title: 'Enabling hardware acceleration…', body: 'Click “Yes” in the permission window', sticky: true, spinner: true }); const r = await api.enableVirtualization().catch(() => ({ ok: false })); if (r.ok) { - t.update({ title: '硬件加速已启用', body: r.restartNeeded ? '请重启电脑以获得最佳性能' : (r.note || ''), type: 'ok' }); + t.update({ title: 'Hardware acceleration enabled', body: r.restartNeeded ? 'Restart your PC for best performance' : (r.note || ''), type: 'ok' }); setTimeout(t.dismiss, 3200); } else { - t.update({ title: '未能开启', body: r.error || '请重试', type: 'error', sticky: true }); + t.update({ title: 'Could not enable', body: r.error || 'Please try again', type: 'error', sticky: true }); } break; } case 'disk': { - // 主进程会弹出原生确认框(含可释放空间预览),这里不再重复 confirm + // 主进程会弹出原生确认框 (含可释放空间预览),这里不再重复 confirm const r = await api.runtimeCleanup({ apply: true }).catch(() => null); - if (!r) { toast({ title: '清理失败', body: '请重试', type: 'error' }); break; } + if (!r) { toast({ title: 'Cleanup failed', body: 'Please try again', type: 'error' }); break; } if (r.cancelled) break; - toast({ title: '清理完成', body: `释放约 ${((r.saved || 0) / 1048576).toFixed(0)} MB`, type: 'ok' }); + toast({ title: 'Cleanup complete', body: `Freed about ${((r.saved || 0) / 1048576).toFixed(0)} MB`, type: 'ok' }); break; } case 'path': { - toast({ title: '建议改到纯英文目录', body: '在 设置 → 系统集成 里修改「运行时数据目录」,再重启软件。', type: 'info', sticky: true }); + toast({ title: 'Use a folder path containing only English characters', body: 'Change the Runtime data folder in Settings → System Integration, then restart the app.', type: 'info', sticky: true }); openSettings(); break; } case 'deploy': { - toast({ title: '正在补齐缺失的运行时组件…', type: 'info', sticky: true, spinner: true }); + toast({ title: 'Installing missing runtime components…', type: 'info', sticky: true, spinner: true }); await deployAll(); break; } case 'lock': { const r = await api.clearLocks().catch(() => ({ ok: false })); - toast(r.ok ? { title: '已清理残留锁文件', type: 'ok' } : { title: '清理失败', body: r.error, type: 'error' }); + toast(r.ok ? { title: 'Removed stale lock files', type: 'ok' } : { title: 'Cleanup failed', body: r.error, type: 'error' }); break; } default: - toast({ title: '暂不支持自动修复', body: issue.hint || '', type: 'info' }); + toast({ title: 'Automatic fix is not supported yet', body: issue.hint || '', type: 'info' }); } - // 修复后重新自检,刷新横幅与状态 + // Fix后重新自检,刷新横幅与状态 setTimeout(selfCheck, 600); setTimeout(refreshStatus, 600); } @@ -1867,5 +1867,5 @@ async function fixAllIssues(issues) { init().catch((e) => { console.error(e); - toast({ title: '初始化失败', body: e.message, type: 'error', sticky: true }); + toast({ title: 'Initialization failed', body: e.message, type: 'error', sticky: true }); }); diff --git a/src/ui/index.html b/src/ui/index.html index 78845ca..5ca3371 100644 --- a/src/ui/index.html +++ b/src/ui/index.html @@ -1,5 +1,5 @@ - + @@ -31,11 +31,11 @@ ApkStart - 未连接运行时 + Runtime disconnected
-
@@ -45,12 +45,12 @@
@@ -68,9 +68,9 @@ -

把 APK 拖进来就能装

-

安装完成后会自动生成桌面图标,双击即以独立窗口运行,和普通 Windows 软件一样。
已注册 .apk 关联时,在资源管理器里双击 APK 也会直接装到 ApkStart。

- +

Drag and drop an APK to install

+

A desktop shortcut is created automatically after installation. Double-click it to run the app in its own window, just like a regular Windows application.
If the .apk file association is enabled, double-click an APK in File Explorer to install it in ApkStart.

+

@@ -78,7 +78,7 @@

把 APK 拖进来就能装