`).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) => `
安装完成后会自动生成桌面图标,双击即以独立窗口运行,和普通 Windows 软件一样。 已注册 .apk 关联时,在资源管理器里双击 APK 也会直接装到 ApkStart。
- 选择 APK 文件
+
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.