From 6ac9682ac6eead0695727753e473dce85d488303 Mon Sep 17 00:00:00 2001 From: Jotav Date: Sat, 14 Nov 2026 19:24:08 -0300 Subject: [PATCH] feat(i18n): add Brazilian Portuguese and English interface translations --- README.md | 2 + build/electron-builder-test.cjs | 15 + build/installer.nsh | 18 +- build/pack.js | 2 +- docs/I18N.md | 56 +++ electron-builder.yml | 7 +- package-lock.json | 4 +- package.json | 5 +- src/core/doctor.js | 10 +- src/core/i18n.js | 36 ++ src/core/store.js | 1 + src/locales/en.json | 650 ++++++++++++++++++++++++++++++++ src/locales/pt-BR.json | 650 ++++++++++++++++++++++++++++++++ src/locales/zh-CN.json | 650 ++++++++++++++++++++++++++++++++ src/main.js | 173 +++++---- src/preload.js | 1 + src/ui/app.js | 602 ++++++++++++++--------------- src/ui/i18n.js | 29 ++ src/ui/index.html | 470 ++++++++++++----------- src/ui/shell.html | 13 +- src/ui/shell.js | 6 +- src/ui/style.css | 9 + test/i18n-test.js | 61 +++ test/i18n-ui-preload.js | 28 ++ test/i18n-ui-test.js | 51 +++ 25 files changed, 2920 insertions(+), 629 deletions(-) create mode 100644 build/electron-builder-test.cjs create mode 100644 docs/I18N.md create mode 100644 src/core/i18n.js create mode 100644 src/locales/en.json create mode 100644 src/locales/pt-BR.json create mode 100644 src/locales/zh-CN.json create mode 100644 src/ui/i18n.js create mode 100644 test/i18n-test.js create mode 100644 test/i18n-ui-preload.js create mode 100644 test/i18n-ui-test.js diff --git a/README.md b/README.md index d635389..6b52d01 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # ApkStart +[Interface translations / Traduções da interface](docs/I18N.md) · 简体中文 / English / Português (Brasil) +

ApkStart Logo

[![CI](https://github.com/RoutineMiku/apkstart/actions/workflows/ci.yml/badge.svg)](https://github.com/RoutineMiku/apkstart/actions/workflows/ci.yml) diff --git a/build/electron-builder-test.cjs b/build/electron-builder-test.cjs new file mode 100644 index 0000000..c5f5fb9 --- /dev/null +++ b/build/electron-builder-test.cjs @@ -0,0 +1,15 @@ +'use strict'; +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); + +// Lightweight test installer: reuse the runtime already in the user's data directory. +// A new installation downloads the runtime on demand, as the portable build does. +const config = yaml.load(fs.readFileSync(path.join(__dirname, '../electron-builder.yml'), 'utf8')); +module.exports = { + ...config, + directories: { ...config.directories, output: 'dist/i18n-test' }, + extraMetadata: { version: '0.4.2-i18n.1' }, + extraResources: [], + publish: null, +}; diff --git a/build/installer.nsh b/build/installer.nsh index 79624bc..dd7a7fd 100644 --- a/build/installer.nsh +++ b/build/installer.nsh @@ -11,13 +11,23 @@ ; 用户装完发现桌面上什么都没有。这里用 NSIS 原生的 CreateShortcut 再写一次: ; $DESKTOP 走 SHGetFolderPath,能正确跟随桌面的重定向路径;写成幂等操作, ; 和 electron-builder 的重复执行也只是覆盖同一个 .lnk。 +LangString ApkStartCreateShortcuts 2052 "正在创建桌面快捷方式…" +LangString ApkStartCreateShortcuts 1033 "Creating desktop shortcuts…" +LangString ApkStartCreateShortcuts 1046 "Criando atalhos na área de trabalho…" +LangString ApkStartShortcutDescription 2052 "ApkStart — 在电脑上直接运行安卓应用" +LangString ApkStartShortcutDescription 1033 "ApkStart — Run Android apps on your PC" +LangString ApkStartShortcutDescription 1046 "ApkStart — Execute apps Android no seu computador" +LangString un.ApkStartStopProcesses 2052 "正在结束 ApkStart 残留进程…" +LangString un.ApkStartStopProcesses 1033 "Stopping remaining ApkStart processes…" +LangString un.ApkStartStopProcesses 1046 "Encerrando os processos restantes do ApkStart…" + !macro customInstall - DetailPrint "正在创建桌面快捷方式…" + DetailPrint "$(ApkStartCreateShortcuts)" CreateShortcut "$DESKTOP\ApkStart.lnk" "$INSTDIR\ApkStart.exe" "" "$INSTDIR\ApkStart.exe" 0 \ - SW_SHOWNORMAL "" "ApkStart — 在电脑上直接运行安卓应用" + SW_SHOWNORMAL "" "$(ApkStartShortcutDescription)" CreateDirectory "$SMPROGRAMS\ApkStart" CreateShortcut "$SMPROGRAMS\ApkStart\ApkStart.lnk" "$INSTDIR\ApkStart.exe" "" "$INSTDIR\ApkStart.exe" 0 \ - SW_SHOWNORMAL "" "ApkStart — 在电脑上直接运行安卓应用" + SW_SHOWNORMAL "" "$(ApkStartShortcutDescription)" !macroend !macro customUnInstall @@ -26,7 +36,7 @@ Delete "$SMPROGRAMS\ApkStart\ApkStart.lnk" RMDir "$SMPROGRAMS\ApkStart" - DetailPrint "正在结束 ApkStart 残留进程…" + DetailPrint "$(un.ApkStartStopProcesses)" ; /F 强杀 /T 连带子进程。进程不存在时 taskkill 返回 128,忽略即可。 nsExec::Exec 'taskkill.exe /F /T /IM ApkStart.exe' nsExec::Exec 'taskkill.exe /F /T /IM qemu-system-x86_64.exe' diff --git a/build/pack.js b/build/pack.js index d98c237..5b3bed9 100644 --- a/build/pack.js +++ b/build/pack.js @@ -42,7 +42,7 @@ fs.rmSync(path.join(OUT, 'resources', 'default_app.asar'), { force: true }); // 1.1 语言包瘦身:Electron 自带 55 种语言,合计 41MB,我们只用中文界面。 // 只留 zh-CN / zh-TW / en-US,其余删掉(-39MB,等于整包小 15%)。 -const KEEP_LOCALES = new Set(['zh-CN.pak', 'zh-TW.pak', 'en-US.pak']); +const KEEP_LOCALES = new Set(['zh-CN.pak', 'zh-TW.pak', 'en-US.pak', 'pt-BR.pak']); let localeSaved = 0; try { const locDir = path.join(OUT, 'locales'); diff --git a/docs/I18N.md b/docs/I18N.md new file mode 100644 index 0000000..d258141 --- /dev/null +++ b/docs/I18N.md @@ -0,0 +1,56 @@ +# Interface translations + +The interface supports Simplified Chinese (`zh-CN`), Brazilian Portuguese (`pt-BR`), +and English (`en`). Settings → Language saves the selection immediately and reloads +the main window. Existing Android app windows pick up the new language when reopened. +Automatic mode follows the Windows locale, using English for unsupported languages. + +Messages live in `src/locales/*.json`. Keep the same keys and numbered placeholders +in all three catalogs. Existing `mNNNN` keys identify extracted messages and must not +be renumbered. Use descriptive keys for new messages. Some messages contain trusted +interface HTML; preserve their markup and escape user content before interpolation. +App names, package names, file paths, release notes and raw runtime logs are not +translated. Some diagnostics emitted by the underlying runtime still use Chinese. + +`src/core/i18n.js` handles locale selection and main-process messages. The renderer +loads its catalog through the allowlisted `i18n:get` IPC channel. Static HTML uses +`data-i18n` and `data-i18n-title/placeholder/alt/aria-label`; JavaScript uses `tr()`. +No runtime dependency is added for internationalization. + +## Validation + +```powershell +npm test +npm run test:i18n +npm run test:i18n:ui +``` + +The UI test uses mocked APIs in an isolated Electron window. It checks language +switching, persistence across reloads, app-launch feedback and settings overflow. +Screenshots are written to `test/out/i18n/`. It does not install APKs or alter the +user's installation. Real Android app startup still needs a manual test. + +## Local test installer + +```powershell +npm install +npm run build:exe:test +``` + +Output: `dist/i18n-test/ApkStart-0.4.2-i18n.1-Setup.exe` (Windows x64). +This unsigned, unpublished test build uses the same application ID and data paths +as ApkStart. It omits the bundled Android runtime and reuses an existing runtime in +the user data directory; missing runtime components download on demand. It must be +installed in the same location as the current installation to replace that copy. +The installer language and interface language can be selected independently. + +## Teste em português + +1. Feche o ApkStart pela opção Sair no ícone da bandeja do Windows. +2. Execute o instalador de teste e escolha a mesma pasta da instalação atual. +3. Abra Configurações → Idioma e selecione Português (Brasil). +4. Confira as telas, abra um app Android já instalado e teste a opção English. + +O instalador mantém o mesmo identificador e os mesmos caminhos de dados do projeto. +Ele não inclui a imagem Android completa. Logs brutos e alguns diagnósticos internos +ainda podem aparecer em chinês. A compilação é local e não publica uma versão ou PR. diff --git a/electron-builder.yml b/electron-builder.yml index a21c68e..e7a5866 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -26,6 +26,7 @@ electronLanguages: - zh-CN - zh-TW - en-US + - pt-BR # 完整运行时(emulator + platform-tools + 系统镜像),由 build/prepare-runtime.js 生成。 # 打进包里之后首次启动不再需要任何下载。 @@ -51,7 +52,11 @@ nsis: oneClick: false perMachine: false allowToChangeInstallationDirectory: true - displayLanguageSelector: false + displayLanguageSelector: true + installerLanguages: + - zh_CN + - en_US + - pt_BR # always:即使桌面上已有同名 .lnk 也重建一次。 # 默认的 true 只在「不存在」时才创建,某些机器上(安全软件拦截、桌面被 # OneDrive 重定向、旧版残留了指向已删目录的坏快捷方式)会出现装完没图标, diff --git a/package-lock.json b/package-lock.json index cd182c0..2f4dc24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "apkstart", - "version": "0.3.0", + "version": "0.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "apkstart", - "version": "0.3.0", + "version": "0.4.1", "license": "MIT", "os": [ "win32" diff --git a/package.json b/package.json index 53ab193..9b814f5 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,10 @@ "selftest": "node scripts/selftest.js", "assoc:on": "node scripts/assoc.js on", "assoc:off": "node scripts/assoc.js off", - "test:boot": "node test/boot-test.js" + "test:boot": "node test/boot-test.js", + "test:i18n": "node test/i18n-test.js", + "test:i18n:ui": "electron test/i18n-ui-test.js", + "build:exe:test": "electron-builder --win nsis --x64 --config build/electron-builder-test.cjs --publish never" }, "devDependencies": { "electron": "^33.4.11", diff --git a/src/core/doctor.js b/src/core/doctor.js index 1038560..52075fd 100644 --- a/src/core/doctor.js +++ b/src/core/doctor.js @@ -1,4 +1,5 @@ 'use strict'; +const { t } = require('./i18n'); /** * 设备自适应 / 自动诊断(零配置底座) * @@ -435,9 +436,12 @@ function diagnose(lines) { /** 人类可读的一行摘要,放在设置页让高级用户知道自动选了什么 */ function describeProfile(p) { - return `自动:${(p.memoryMB / 1024).toFixed(1)}GB 内存 · ${p.cores} 核 · ${p.gpu} 渲染 · ` - + `${p.accelOk ? '硬件加速' : '软件模拟'} · ${p.snapshot ? '按需快照' : '冷启动'}` - + `${p.lite ? ' · 轻量容器' : ''} · 端口 ${p.port}`; + return t('profile.summary', [ + (p.memoryMB / 1024).toFixed(1), p.cores, p.gpu, + p.accelOk ? t('profile.accelerated') : t('m0019'), + p.snapshot ? t('profile.snapshot') : t('profile.coldBoot'), + p.lite ? t('profile.lite') : '', p.port, + ]); } /** 导出诊断报告(纯文本,便于发邮件/贴 issue) */ diff --git a/src/core/i18n.js b/src/core/i18n.js new file mode 100644 index 0000000..fd50f2c --- /dev/null +++ b/src/core/i18n.js @@ -0,0 +1,36 @@ +'use strict'; + +const catalogs = { + 'zh-CN': require('../locales/zh-CN.json'), + en: require('../locales/en.json'), + 'pt-BR': require('../locales/pt-BR.json'), +}; +let locale = 'zh-CN'; + +function resolveLocale(preference, systemLocale = 'en') { + if (Object.hasOwn(catalogs, preference)) return preference; + const system = String(systemLocale).toLowerCase().replace(/_/g, '-'); + if (system === 'pt' || system.startsWith('pt-')) return 'pt-BR'; + if (system === 'zh' || system.startsWith('zh-')) return 'zh-CN'; + return 'en'; +} + +function format(message, values = []) { + return message.replace(/\{(\d+)\}/g, (match, index) => ( + index < values.length ? String(values[index]) : match + )); +} + +function getCatalog(language) { + return catalogs[language] || catalogs.en; +} + +function setLocale(language) { + locale = resolveLocale(language); +} + +function t(key, values) { + return format(getCatalog(locale)[key] ?? catalogs.en[key] ?? catalogs['zh-CN'][key] ?? key, values); +} + +module.exports = { resolveLocale, format, getCatalog, setLocale, t }; diff --git a/src/core/store.js b/src/core/store.js index 175b176..74b4e6b 100644 --- a/src/core/store.js +++ b/src/core/store.js @@ -14,6 +14,7 @@ const APPS_FILE = path.join(DATA_DIR, 'apps.json'); const SETTINGS_FILE = path.join(DATA_DIR, 'settings.json'); const DEFAULT_SETTINGS = { + language: 'auto', adbPath: '', device: '127.0.0.1:5555', engine: { diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 0000000..e329328 --- /dev/null +++ b/src/locales/en.json @@ -0,0 +1,650 @@ +{ + "m0000": "Just now", + "m0001": "{0} min ago", + "m0002": "{0} h ago", + "m0003": "{0} days ago", + "m0004": "First use: open Settings → Runtime → Set up automatically, then install your apps.", + "m0005": "Tip: all apps share one Android runtime, so the next app starts almost instantly.", + "m0006": "", + "m0007": "Running", + "m0008": "Bring to front", + "m0009": "Open", + "m0010": "{0} apps", + "m0011": "No apps installed", + "m0012": "Runtime not set up — open Settings → Runtime to set it up automatically", + "m0013": "Setup required", + "m0014": "Runtime ready · Android {0}{1}", + "m0015": " · App windows enabled", + "m0016": "Booting Android…", + "m0017": "Booting", + "m0018": "Hardware acceleration unavailable: using software emulation (first boot may take longer)", + "m0019": "Software emulation", + "m0020": "Android 14 universal image missing — download it in Settings → Runtime (about 1.5 GB, once)", + "m0021": "Image download required", + "m0022": "Runtime engine not installed — open Settings → Runtime for automatic setup", + "m0024": "Runtime ready — install an APK or double-click a desktop shortcut to start", + "m0025": "Ready to start", + "m0026": "Installer running — complete installation in the virtual machine window", + "m0027": "Installing", + "m0028": "Runtime on standby (starts automatically when installing or opening an app)", + "m0029": "Android not installed yet — continue in Settings → Runtime", + "m0030": "Standby", + "m0031": "Installation required", + "m0032": "Status check failed: {0}", + "m0033": "Engine", + "m0034": "System image", + "m0035": "Ready · Android {0}", + "m0037": "Components ready · Starts when you open an app", + "m0038": "Missing {0}{1}", + "m0039": " (no hardware acceleration; software emulation will be used)", + "m0040": "adb tools", + "m0041": "Android runtime", + "m0043": "Everything is ready", + "m0044": "Set up automatically", + "m0045": "Completed automatically", + "m0046": "Automatic", + "m0047": "Android {0} ready · App windows adapt to your screen", + "m0049": "Components ready. Double-click an APK or desktop shortcut to start.", + "m0050": "Still missing {0}. Click “Set up automatically” below to install and start in the background.", + "m0051": "", + "m0052": "Preparing…", + "m0053": "Completed · Click to check again", + "m0055": "Last startup failed: {0}", + "m0056": "Last startup diagnosis: {0}", + "m0057": "Fully automatic: no image, acceleration or memory setup needed", + "m0058": "Setup failed", + "m0059": "Check your connection and try again", + "m0060": "Components ready. Starting automatically…", + "m0061": "First boot takes about 1–3 minutes; later starts take a few seconds.", + "m0062": "Runtime ready", + "m0063": "Drop an APK into the window or double-click a desktop shortcut.", + "m0064": "Startup incomplete", + "m0065": "Export a diagnostic report to investigate", + "m0067": "Setup already in progress", + "m0068": "Installing hardware acceleration driver…", + "m0069": "Click “Yes” in the permission prompt", + "m0070": "Hardware acceleration enabled", + "m0071": "Acceleration not enabled", + "m0072": "Unknown step", + "m0073": "Could not start the installation environment", + "m0074": "Virtual machine window opened", + "m0075": "Follow the six steps below, then click “Installation complete”.", + "m0076": "Installation complete; runtime connected", + "m0077": "You can now drop an APK here to install it.", + "m0078": "Unknown error", + "m0079": "{0}\nAndroid data partition is full: open Settings → Performance and resources, increase the partition size and click “Expand now”.", + "m0080": "{0}\nThis package contains only ARM libraries. Switching to a compatible image and retrying.", + "m0081": "{0}\nThe package name matches an installed app but its signature differs. Uninstall the app from its context menu, then try again.", + "m0082": "{0}\nA newer version is installed. Uninstall it from the context menu first.", + "m0083": "{0}\nThe package is too large or memory is insufficient. Check available disk space and memory, then retry;", + "m0084": "if it still fails, use a short ASCII-only path (for example D:\\a.apk).", + "m0085": "Installation already in progress", + "m0086": "Wait for the current installation to finish.", + "m0087": "Installing {0}", + "m0088": "Reading APK…", + "m0091": "{0} installed", + "m0092": "Desktop shortcut created. Double-click it to open the app.", + "m0093": "Create a desktop shortcut from the app’s context menu.", + "m0094": "{0}: compatibility notice", + "m0095": "Opening {0}", + "m0096": "The first launch needs to warm up the runtime. Please wait…", + "m0097": "Started", + "m0098": "The app is running in its own window (rotate using the top bar).", + "m0099": "Startup failed", + "m0100": "Desktop shortcut created", + "m0101": "Could not create shortcut", + "m0102": "Uninstall “{0}”?\n\nIts app data and desktop shortcut will also be removed.", + "m0103": "Uninstalled", + "m0104": "Uninstall failed", + "m0105": "Open app", + "m0106": "Create desktop shortcut", + "m0107": "Pin to top", + "m0108": "Show APK in folder", + "m0109": "Clear app data", + "m0110": "Uninstall", + "m0111": "Unpin", + "m0113": "Clear all data for “{0}”? This cannot be undone.", + "m0114": "Data cleared", + "m0115": "Could not clear data", + "m0116": "Reading…", + "m0117": "Runtime not ready ({0})", + "m0118": "Runtime not running", + "m0119": "Wi-Fi on", + "m0120": "Wi-Fi off", + "m0121": "{0} · Volume {1}%", + "m0122": " · Airplane mode", + "m0123": "Applied", + "m0124": "Failed: {0}", + "m0125": "Runtime not ready", + "m0126": "Wi-Fi enabled", + "m0127": "Wi-Fi disabled", + "m0128": "Airplane mode enabled", + "m0129": "Airplane mode disabled", + "m0130": "System animations enabled", + "m0131": "System animations disabled for faster navigation", + "m0132": "Font size updated", + "m0133": "Display size updated", + "m0134": "Volume updated", + "m0135": "Optimized: animations off, background limit relaxed, battery exemption enabled", + "m0136": "Optimization failed; runtime may not be running", + "m0137": "Open an app first to start the runtime", + "m0138": "Could not read data partition information", + "m0139": "Not created yet", + "m0140": "Unknown", + "m0141": "Current data image: {0}{1} · Free space on its drive: {2}.", + "m0142": " (automatic)", + "m0143": "If a game reports insufficient storage, increase this value and click “Expand now”. Expansion preserves installed apps and saved games.", + "m0144": "Associated · Click to disable", + "m0145": "Not associated · Click to enable", + "m0146": "Automatic mode", + "m0147": "Total {0}{1}", + "m0148": " · Reclaimable {0} ({1} items)", + "m0149": " · Already optimized", + "m0150": "Statistics unavailable", + "m0152": "Check failed: {0}", + "m0153": "Network unavailable", + "m0154": "Open download page", + "m0155": " (offline cache; may be outdated)", + "m0156": "New version {0}{1}{2}{3}", + "m0157": "; {0} unreachable", + "m0158": "Update now", + "m0160": "Release notes:\n{0}", + "m0161": " (offline cache)", + "m0162": "Up to date (v{0}){1}{2}", + "m0163": " · via {0}", + "m0164": "Update failed: {0}", + "m0165": "Update failed", + "m0166": "Updating…", + "m0167": "Checking…", + "m0168": "Connecting to GitHub…", + "m0169": "New version {0}", + "m0170": "Click “Update now” to download and install automatically", + "m0171": "Up to date", + "m0173": "Updating…", + "m0177": "Downloaded v{0}. Restarting to finish installation…", + "m0178": "Resource policy updated", + "m0179": "Applies the next time you open an app", + "m0180": "Select a capacity first", + "m0181": "“Automatic” chooses a size based on free disk space; no manual expansion needed", + "m0182": "Expand the Android data partition now?", + "m0183": "Resize /data to {0} GB in place, preserving installed apps and saved games.", + "m0184": "Running Android apps must close first. The next launch will boot Android again and take a little longer.", + "m0185": "Start expansion", + "m0186": "Expanding…", + "m0187": "Data partition expanded", + "m0188": "Expansion failed", + "m0189": "About {0} GB now available", + "m0191": "System components restored", + "m0192": "Deep trimming disabled; restoring background packages", + "m0193": "ARM compatibility mode enabled", + "m0194": "Download the Google Play compatibility image (Android 14, about 1.5 GB) using the button below", + "m0196": "Applies after restarting the runtime", + "m0197": "Switched to fullscreen", + "m0198": "Switched to portrait window", + "m0199": "Display size updated", + "m0200": "Applies when opening or switching apps", + "m0201": "Missing shortcuts restored", + "m0202": "All shortcuts already exist", + "m0203": "Desktop and Start menu shortcuts are available", + "m0204": "Diagnostic report exported", + "m0205": "Export failed", + "m0206": "Already optimized", + "m0207": "No old images or snapshots to reclaim.", + "m0208": "Cleanup complete", + "m0209": "Freed about {0} MB ({1} items). Installed apps were preserved.", + "m0210": "Total {0} · Reclaimable {1} ({2} items)", + "m0211": "Cleanup failed", + "m0212": "Reset Android data?", + "m0213": "This restores Android to a clean installation:", + "m0214": "• Deletes all third-party apps (including games), their data and login sessions\n", + "m0215": "• Deletes the current AVD’s Quick Boot snapshot\n", + "m0216": "• Preserves the system image; data is recreated on the next cold boot\n\n", + "m0217": "This cannot be undone. Continue?", + "m0218": "Cancel", + "m0219": "Continue", + "m0220": "Confirm once more", + "m0221": "Installed apps and their data will be permanently deleted. Reset now?", + "m0222": "The system image, other AVDs, local APK cache and icons are preserved.", + "m0224": "Confirm reset", + "m0225": "Reset failed", + "m0226": "Please try again", + "m0227": "Android data reset", + "m0228": "Freed about {0} MB. Installed app records were cleared.", + "m0231": "Repair the virtual machine’s network?", + "m0232": "Blank pages or endless loading in apps often mean the Android virtual machine has no network connection.", + "m0233": "The repair tries these steps, from least to most disruptive:\n\n", + "m0234": "1. Cold boot without loading a snapshot and wait for connectivity — preserves all data\n", + "m0235": "2. Only if needed, resets Android data — keeps the app list and reinstalls apps when opened,\n", + "m0236": " but app login sessions, viewing history and other app data will be lost\n\n", + "m0237": "Only the current AVD is affected. The system image and local APK cache are preserved. This takes about 1–5 minutes. Continue?", + "m0239": "Start repair", + "m0240": "Network still unavailable", + "m0241": "The virtual machine still has no network after the reset. Check the host connection and firewall.", + "m0242": "Network restored", + "m0243": "Connected", + "m0244": " (Android data was reset; apps will reinstall when opened)", + "m0245": " (your data was preserved)", + "m0246": "Repair failed", + "m0247": "Scan failed", + "m0249": "Nothing to clean", + "m0250": "No files left over from old versions.", + "m0251": "…and {0} more items", + "m0252": "Clean up old version files?", + "m0253": "Found {0} items, about {1} MB:", + "m0254": "{0}\n\nOnly old program directories, invalid uninstall entries and temporary update files will be deleted.", + "m0255": "The running version and your app data will be preserved.", + "m0257": "Start cleanup", + "m0260": "; {0} items in use were skipped", + "m0261": "Old version files cleaned up", + "m0262": "Deleted {0} items, freeing {1} MB{2}.", + "m0265": "Shortcuts rebuilt", + "m0266": "All shortcuts exist", + "m0267": "No shortcuts need rebuilding", + "m0268": "New directory selected", + "m0269": "Click “Save and migrate” to move existing files", + "m0270": "Enter or browse for a directory first", + "m0271": "Shared directory updated", + "m0272": "Existing files were moved to the new directory", + "m0273": "Saved", + "m0274": "Save failed", + "m0275": "Uninstall ApkStart?\n\nThis removes the .apk file association and the program. This cannot be undone.", + "m0276": "The portable version cannot uninstall automatically", + "m0277": "Manually delete the program directory ({0}) and data directory ({1})", + "m0279": "Uninstalling…", + "m0280": "Uninstaller started. ApkStart will close shortly.", + "m0281": "Starting ARM compatibility image download", + "m0283": "Download ARM compatibility image", + "m0284": "ARM compatibility image ready", + "m0286": "Compatibility image download failed", + "m0291": "Ready ({0} GB)", + "m0292": "Not downloaded", + "m0293": "Data directory updated", + "m0294": "Restart ApkStart to apply (existing data is not moved automatically)", + "m0295": "Android data directory updated", + "m0296": "Restart ApkStart to apply. You can move the entire apkstart.avd folder to the new directory, then delete the old copy.", + "m0297": "Detecting…", + "m0298": "No running runtime found", + "m0299": "Found: {0}", + "m0300": "Starting…", + "m0301": "Ready: {0}", + "m0302": "Startup failed: {0}", + "m0303": "Built-in virtual machine stopped", + "m0305": ".apk association disabled", + "m0307": ".apk association enabled", + "m0308": "Double-clicking an APK now installs it in ApkStart", + "m0309": "Operation failed", + "m0310": "System image imported", + "m0311": "Import failed", + "m0312": "Virtual disk imported", + "m0313": "You can now start the runtime", + "m0315": "Starting runtime…", + "m0316": "The first launch needs to boot Android", + "m0319": "Runtime session ended", + "m0320": "Shared directory synchronized", + "m0321": "Sync failed", + "m0322": "Synchronization is skipped when the runtime is not ready", + "m0323": "Only .apk files can be dropped here", + "m0324": "Installation failed: {0}", + "m0325": "Installation failed", + "m0327": "Good night", + "m0328": "Good morning", + "m0329": "Good afternoon", + "m0330": "Good afternoon", + "m0331": "Good evening", + "m0332": "{0}, welcome back", + "m0333": "Runtime ready — open an app to begin", + "m0334": "Runtime will be prepared automatically as needed", + "m0335": "Installed apps", + "m0336": "Ready", + "m0337": "Standby", + "m0338": "Android runtime", + "m0339": "Device / port", + "m0340": "New version {0} available. Update in Settings → Software updates.", + "m0341": "Drop an APK into the window to install it and automatically create a desktop shortcut", + "m0342": "Double-click an app card to open its own window; rotate, resize or close it from the top right", + "m0343": "Closing a window closes only that app; Android stays ready for a faster next launch", + "m0344": "{0} issues to fix", + "m0345": "{0} notices", + "m0346": "\n
\n \n Environment check passed. No problems affecting operation were found.\n \n \n
", + "m0347": "\n
\n Environment check\n {0}\n {1}\n \n \n \n
\n
\n {2}\n
", + "m0348": "{0} critical", + "m0349": "{0} notices", + "m0350": "", + "m0351": "Bundled installer not found", + "m0352": "Microsoft’s official download page is open. Download the x64 version.", + "m0353": "VC++ runtime installed", + "m0354": "Installation incomplete", + "m0356": "Enabling hardware acceleration…", + "m0357": "Click “Yes” in the prompt", + "m0359": "Restart your computer for the best performance", + "m0360": "Could not enable", + "m0365": "Freed about {0} MB", + "m0366": "Use a path with ASCII characters only", + "m0367": "Change “Runtime data directory” in Settings → System integration, then restart ApkStart.", + "m0368": "Installing missing runtime components…", + "m0369": "Leftover lock files removed", + "m0371": "Automatic repair is not supported yet", + "m0372": "Initialization failed", + "m0373": "App", + "m0374": "Starting Android runtime…", + "m0375": "Downloading runtime from GitHub…", + "m0376": "Runtime bundle incomplete. Fetching missing components from official sources…", + "m0377": "GitHub runtime unavailable ({0}). Using official sources…", + "m0378": "Preparing adb…", + "m0379": "Preparing Android runtime…", + "m0380": "Preparing Android system image…", + "m0381": "Could not start PowerShell", + "m0382": "ApkStart is still running in the background", + "m0383": "Click the tray icon to reopen; right-click it to quit.", + "m0384": "Open ApkStart", + "m0385": "No apps installed yet", + "m0386": "Switch to portrait window", + "m0387": "Fullscreen (fit to screen)", + "m0388": "Rebuild desktop / Start menu shortcuts", + "m0389": "Synchronize shared directory", + "m0390": "Settings", + "m0392": "Check for updates", + "m0393": "New version available", + "m0394": "ApkStart {0} is available; your current version is v{1}. Update now?", + "m0396": "Later", + "m0398": "Quit", + "m0399": "ApkStart — Android app launcher", + "m0400": "This APK is already being installed. Please wait…", + "m0401": "Installation in progress", + "m0402": "Another installation is in progress. Wait for it to finish.", + "m0404": "Installation complete", + "m0405": "{0} installed{1}", + "m0406": "; desktop shortcut created", + "m0408": "Update in progress", + "m0409": "Already up to date", + "m0410": "This release has no installable package. The GitHub release page has been opened.", + "m0411": "Preparing download…", + "m0412": "Replacing program files and restarting…", + "m0414": "Opening app…", + "m0416": "Select data directory", + "m0417": "Select APK to install", + "m0418": "Android apps", + "m0419": "App not found", + "m0420": "Directory cannot be empty", + "m0421": "VC++ runtime missing (x64)", + "m0422": "Android requires Microsoft VC++ 2015–2022. Without it the emulator may fail to start (0xc0000135).", + "m0423": "Hardware acceleration disabled", + "m0424": "Software emulation will be used (functional but slow). Enable virtualization for the best performance.", + "m0425": "Runtime drive low on space", + "m0426": "The drive containing “{0}” has only {1} MB free. Runtime components and images may fill it.", + "m0427": "Runtime directory contains non-ASCII characters", + "m0428": "Some runtime components cannot handle non-ASCII paths and may fail to start. Use a path with ASCII characters only.", + "m0429": "adb tools missing", + "m0430": "Cannot connect to the Android runtime.", + "m0431": "Runtime engine not installed", + "m0432": "Set up the runtime engine first (about 440 MB).", + "m0433": "System image not ready", + "m0434": "Download the Android system image first (about 1.5 GB).", + "m0435": "Lock files from the previous session found", + "m0436": "These may cause the next launch to report that the AVD is already in use.", + "m0437": "Confirm", + "m0439": "OK", + "m0440": "Data partition resized", + "m0441": "About {0} GB available in Android{1}", + "m0442": " (runtime restarted)", + "m0444": "Clean {0} items", + "m0445": "Confirm runtime cache cleanup", + "m0446": "About {0} MB will be freed", + "m0447": "The runtime will stop before deleting old images, the current AVD’s Quick Boot snapshot, lock files and inactive AVD caches. Installed apps are preserved (only snapshots are removed).", + "m0448": "Only old images, old snapshots, lock files and inactive AVD caches are deleted. Installed apps are preserved.", + "m0449": "This cannot be undone. Export a diagnostic report first if needed.", + "m0450": "AEHD driver package not ready", + "m0451": "Administrator permission canceled (click “Yes” to install the driver)", + "m0452": "Driver installation failed (exit code {0})", + "m0453": "Hardware acceleration already available; no action needed", + "m0454": "Virtual Machine Platform enabled. Restart your computer for best performance.", + "m0455": "Virtual Machine Platform enabled; hardware acceleration is active", + "m0456": "Could not enable system virtualization", + "m0457": "Could not prepare AEHD driver package", + "m0460": "AEHD hardware acceleration enabled", + "m0461": "Select an Android-x86 / Bliss OS system image", + "m0463": "Select a virtual disk with Android installed", + "m0464": "Virtual disk", + "m0465": "Unknown setting {0}", + "m0466": "agent scope is empty: leaving all windows untouched to avoid affecting other software", + "m0467": "Not enough disk space", + "m0468": "Only {0} GB left. Move Android data / runtime to another drive in Settings → System integration, or free disk space first.", + "m0469": "Only {0} GB free; Android may not start (change drives in Settings → System integration)", + "m0470": "Missing shortcuts restored", + "m0471": "Runtime setup completed automatically", + "m0472": "[smoke] Screenshot failed", + "m0473": "Runtime disconnected", + "m0475": "Search installed apps", + "m0476": "Install APK", + "m0477": "Drop an APK here to install", + "m0478": "Installation automatically creates a desktop shortcut. Double-click it to run the app in its own window, like any Windows program.", + "m0479": "When .apk association is enabled, double-clicking an APK in File Explorer also installs it in ApkStart.", + "m0480": "Choose APK files", + "m0481": "Checking runtime…", + "m0482": "Hello", + "m0483": "ApkStart is ready", + "m0484": "Do not show automatically at startup", + "m0485": "Open settings", + "m0486": "Get started", + "m0487": "Release to install", + "m0488": "Supports one or more .apk files", + "m0490": "Runtime and environment", + "m0491": "Runtime status", + "m0492": "Checking…", + "m0494": "Advanced: connect to an existing runtime", + "m0496": "Install Android on a virtual disk", + "m0497": "The virtual machine window is open. Complete these six steps once; ApkStart will then boot from the disk without the installation image.", + "m0498": "Boot menu", + "m0499": ": use", + "m0500": "to select", + "m0501": ", then press Enter", + "m0502": ": select", + "m0503": "; select this when asked about GPT:", + "m0504": "cfdisk partitioning", + "m0505": "→ press Enter for the default size →", + "m0506": "→ type", + "m0507": "Format", + "m0509": "→ when asked to format, select", + "m0510": "Bootloader and system", + "m0511": "select", + "m0513": "Done", + "m0514": ": when progress reaches 100%, click “Installation complete” below (ApkStart restarts and connects automatically)", + "m0515": "If adb is disconnected afterward, press", + "m0516": "in the virtual machine to open a terminal, then run", + "m0517": ", then press", + "m0518": "to return to the graphical interface.", + "m0519": "Installation complete", + "m0520": "Later", + "m0521": "Automatic mode (default; no setup needed)", + "m0522": "Settings adapted to this computer", + "m0523": "Detecting…", + "m0524": "Memory, CPU cores, graphics backend, ports and acceleration are selected automatically. Startup failures trigger fallback attempts without manual changes.", + "m0525": "Total disk usage (runtime + Android data + cache)", + "m0526": "Calculating…", + "m0527": "Clean reclaimable files", + "m0528": "Find and remove old program directories, invalid uninstall entries and temporary update files", + "m0529": "Clean up old versions", + "m0530": "Repair network problems in the Android virtual machine. This may reset Android data; the app list is preserved and apps reinstall when opened.", + "m0531": "Repair network", + "m0532": "Delete Android /data, installed apps and Quick Boot snapshots to restore a clean system", + "m0533": "Reset Android data", + "m0534": "Also delete the current AVD’s Quick Boot snapshot (stops the runtime first and can free several GB)", + "m0535": "The system image is shared and app data uses differencing layers. Old images, snapshots, logs and lock files can be reclaimed. Logs rotate automatically. Resetting Android data deletes user data and preserves only the system.", + "m0536": "Export diagnostic report", + "m0537": "Rebuild shortcuts", + "m0538": "Reports help troubleshoot problems; rebuilding restores desktop and Start menu shortcuts", + "m0539": "Start with Windows (enabled by default)", + "m0540": "Stay in the tray after Windows sign-in so desktop shortcuts can open apps without starting ApkStart manually.", + "m0541": "App windows", + "m0542": "Display mode", + "m0543": "Portrait window (resizable; remembers each app’s size)", + "m0544": "Fullscreen (fits the screen with the phone’s aspect ratio)", + "m0545": "Press", + "m0546": "or use the tray context menu to switch. Fullscreen preserves the aspect ratio and does not restart the app.", + "m0547": "Portrait rendering size", + "m0548": "Small · 720 × 1280", + "m0549": "Medium · 900 × 1600", + "m0550": "Large · 1080 × 1920", + "m0551": "Extra large · 1440 × 2560", + "m0552": "Drag the window border to resize. This setting changes Android’s internal resolution; lower values use fewer resources.", + "m0553": "Mouse controls", + "m0554": "Left button · Confirm / tap", + "m0555": "The emulator maps left-click to a finger tap and click-drag to swiping. No extra setup needed.", + "m0556": "Right button · Android Back", + "m0557": "By default the emulator also treats right-click as a tap. Enable this to make it go Back without triggering a tap.", + "m0558": "Mouse wheel · Swipe", + "m0559": "Smooth (recommended) · Combine wheel events into one swipe", + "m0560": "Native · Let the emulator handle each step", + "m0561": "Off · Do not intercept the wheel", + "m0562": "Smooth mode combines nearby wheel events into one swipe, avoiding stuttering and an adb process for each step.", + "m0563": "Side buttons · Back / forward (off by default)", + "m0564": "Side buttons require a global mouse hook, installed only while an ApkStart app window is in the foreground and removed when focus is lost or the window closes.", + "m0565": "These mappings apply only in app windows. Switching to the desktop or closing the window restores normal Windows mouse behavior.", + "m0566": "Performance and resources", + "m0567": "Resource policy", + "m0568": "Save resources · 2 cores / lower priority / 540p", + "m0569": "Balanced · Half the physical cores / lower priority / 720p (recommended)", + "m0570": "Smooth · All cores / foreground priority (video, games)", + "m0571": "Controls CPU use and picture quality. Choose Smooth for stuttering video or games, or Save resources for lightweight background apps. Applies next time an app opens.", + "m0572": "Picture quality (Android rendering resolution)", + "m0573": "Automatic (based on acceleration)", + "m0574": "Smooth · 540 × 960", + "m0575": "Balanced · 720 × 1280", + "m0576": "HD · 1080 × 1920", + "m0577": "Android data partition (apps and games)", + "m0579": "Expand now", + "m0580": "Expansion preserves installed apps and saved games. The next app launch takes a little longer.", + "m0581": "Reading data partition…", + "m0582": "Network acceleration (recommended)", + "m0583": "Use public DNS servers in China (Alibaba / Tencent) for Android. Host DNS may be slow with VPNs or corporate networks. Disable this if internal domains stop working.", + "m0584": "Android audio", + "m0585": "Play app notifications and video audio. Disable only if you need no sound and want to reduce CPU use.", + "m0586": "Keep Quick Boot snapshot (recommended)", + "m0587": "Subsequent launches take seconds, at the cost of about 2–3 GB on disk for", + "m0588": ". Disable if disk space is low; this affects startup speed, not apps or saved games.", + "m0589": "Container details (advanced)", + "m0590": "Lightweight container", + "m0591": "Limit vCPU allocation and lower process priority to favor the Windows desktop. Resource policy already manages this setting.", + "m0592": "Virtual machine priority", + "m0593": "Balanced (recommended) · Favor the foreground window", + "m0594": "Performance · Same priority as the foreground", + "m0595": "When no app window is open, the container automatically parks at the lowest priority.", + "m0596": "Deep background trimming", + "m0597": "Disable extra background packages such as Play Store, setup wizard and printing services. Disable this setting to restore them if an app reports missing Google Play services.", + "m0598": "Warm up at startup (recommended)", + "m0599": "Boot Android in the background when ApkStart starts, keeping the emulator hidden. Apps then open much faster, at the cost of about 1 GB of idle memory. Disable to cold-boot on the first launch.", + "m0600": "Android boots once and stays ready. Opening another app starts only that app and stops other app processes. Closing the last window clears background processes and parks the container.", + "m0601": "Download and update sources", + "m0602": "Update and download source", + "m0603": "Automatically choose the faster source (recommended)", + "m0604": "Gitee only (usually faster in China)", + "m0605": "GitHub only", + "m0606": "Automatic mode checks GitHub and Gitee, tests each download URL with a 256 KB sample and uses the fastest. An unavailable source does not prevent updates.", + "m0607": "Try public download mirrors automatically", + "m0608": "If GitHub is slow, also test ghfast.top / gh-proxy.com. These third-party services forward public release files without account information. Disable to use direct downloads and your custom prefix only.", + "m0609": "Mirror prefix (optional, speeds up GitHub)", + "m0610": "For example https://ghfast.top/", + "m0611": "If set, only this prefix is used instead of the public mirrors above. Leave blank for automatic selection. The prefix is prepended to the actual download URL.", + "m0612": "Download connections (1–16, default 8)", + "m0613": "Large files download in parallel chunks. Eight connections usually suffice; try sixteen on a stable connection.", + "m0614": "Gitee update source (optional)", + "m0615": "Read-only Gitee token (usually unnecessary)", + "m0616": "Leave blank: public repositories allow anonymous access", + "m0617": "This public repository’s Gitee release list can be read anonymously. A token is needed only for rate limits (403/429) or private branches: gitee.com → Settings → Personal access tokens, with “projects” only. It is used only to read releases; nothing is uploaded.", + "m0618": "Advanced settings (usually no changes needed)", + "m0619": "Runtime", + "m0620": "Engine mode", + "m0621": "Built-in runtime (recommended, QEMU + Android-x86)", + "m0622": "Connect to existing runtime (advanced: WSA / other emulator adb port)", + "m0623": "adb path", + "m0624": "Leave blank to search PATH / Android SDK automatically", + "m0625": "Device address", + "m0626": "Detect", + "m0627": "System image (ISO)", + "m0628": "Android-x86 / Bliss OS ISO or IMG path; leave blank for default", + "m0629": "Memory (MB, 0 = automatic)", + "m0630": "CPU cores (0 = automatic)", + "m0631": "Idle shutdown (minutes, 0 = disabled)", + "m0632": "Universal compatibility image (managed automatically)", + "m0633": "A single Android 14 universal image supports x86 and ARM apps and downloads automatically when first needed. This switch remains only for older settings.", + "m0634": "Compatibility image", + "m0636": "Download compatibility image", + "m0637": "Start runtime", + "m0638": "Stop built-in virtual machine", + "m0639": "Android settings (without opening the Android desktop)", + "m0642": "Refresh", + "m0643": "Optimize performance", + "m0644": "Disable animations, relax background process limits and exempt the current app from battery optimization. Most noticeable on low-end computers.", + "m0645": "Wi-Fi inside the Android virtual machine; turn off to reduce CPU use.", + "m0646": "Airplane mode", + "m0647": "Disconnect all networks, including mobile data.", + "m0648": "System animations", + "m0649": "Disable for faster transitions without affecting functionality.", + "m0650": "Media volume", + "m0651": "Font size", + "m0652": "Small", + "m0653": "Standard", + "m0654": "Large", + "m0655": "Extra large", + "m0656": "Display size", + "m0657": "Compact (uses fewer resources)", + "m0659": "Spacious", + "m0661": "Native Android settings", + "m0662": "Display settings", + "m0663": "Sound settings", + "m0664": "Language settings", + "m0665": "Manage apps", + "m0666": "Desktop and apps", + "m0667": "Create desktop shortcuts after installation", + "m0668": "Shortcuts use real app icons and open apps directly", + "m0669": "Also add to the Start menu", + "m0670": "Find apps in Windows Search; taskbar windows are grouped by app", + "m0671": "Shortcut directory", + "m0672": "Leave blank = desktop", + "m0674": "Restore shortcuts using the current settings if icons were accidentally deleted or blocked by security software.", + "m0675": "File sharing", + "m0676": "Shared directory on this computer", + "m0677": "Browse", + "m0679": "Sync now", + "m0680": "Save and migrate", + "m0681": "Existing files are moved from the old directory automatically; ongoing sharing is not interrupted.", + "m0682": "Corresponding Android directory", + "m0683": "Both directories stay synchronized in both directions. Files saved there by Android apps appear in the shared directory on this computer.", + "m0684": "System integration", + "m0685": "Associate .apk files", + "m0686": "Double-click APKs to install in ApkStart (current user only; no administrator needed)", + "m0688": "Runtime data directory (images · apps · snapshots)", + "m0689": "Default: user data directory on C:", + "m0692": "This uses several GB. Moving it off the system drive can free space on C:. Restart after changing. Existing data is not moved automatically; copy it manually before removing the old copy.", + "m0693": "Android data directory (apps, games and Quick Boot snapshots)", + "m0694": "Default: avd folder inside the runtime directory", + "m0697": "The main disk usage is the data partition (default 6 GB, expandable above) and Quick Boot snapshots (about 2–3 GB). Select another drive, such as D:, and restart to apply. The old", + "m0698": "folder can be moved in full before deleting the old copy.", + "m0699": "Uninstall ApkStart", + "m0700": "Uninstall", + "m0701": "Uninstalling removes the .apk association and the program. For portable versions, follow the instructions to delete the directory manually.", + "m0702": "Software updates", + "m0703": "Current version", + "m0704": "Not checked yet", + "m0707": "Get the latest release from GitHub (RoutineMiku/apkstart). The lightweight version downloads a portable package and replaces program files automatically, preserving the runtime, installed apps and snapshots.", + "m0708": "About", + "m0709": "WeChat official account QR code", + "m0710": "Scan to follow on WeChat", + "m0711": "Douyin QR code", + "m0712": "Douyin · Daily MIKU", + "m0713": "Android app launcher for Windows: APK installation, desktop shortcuts, individual app windows and two-way file sharing.", + "m0714": "Douyin · @Daily MIKU (contact the author)", + "m0715": "Changes apply immediately", + "m0718": "Landscape / portrait (Ctrl+R)", + "m0719": "Minimize", + "m0720": "Maximize", + "m0721": "Close", + "settings.language": "Language", + "settings.systemLanguage": "System language", + "settings.languageNote": "Changing language reloads the main window. Existing app windows use the new language the next time they open.", + "profile.summary": "Automatic: {0} GB RAM · {1} cores · {2} graphics · {3} · {4}{5} · Port {6}", + "profile.accelerated": "Hardware acceleration", + "profile.snapshot": "On-demand snapshots", + "profile.coldBoot": "Cold boot", + "profile.lite": " · Lightweight container" +} diff --git a/src/locales/pt-BR.json b/src/locales/pt-BR.json new file mode 100644 index 0000000..e27ac6c --- /dev/null +++ b/src/locales/pt-BR.json @@ -0,0 +1,650 @@ +{ + "m0000": "Agora mesmo", + "m0001": "Há {0} min", + "m0002": "Há {0} h", + "m0003": "Há {0} dias", + "m0004": "Primeiro uso: abra Configurações → Ambiente Android → Configurar automaticamente. Depois, instale seus apps.", + "m0005": "Dica: todos os apps compartilham o mesmo ambiente Android. Assim, o próximo app inicia quase instantaneamente.", + "m0006": "", + "m0007": "Em execução", + "m0008": "Trazer para frente", + "m0009": "Abrir", + "m0010": "{0} apps", + "m0011": "Nenhum app instalado", + "m0012": "Ambiente Android não configurado — abra Configurações → Ambiente Android para configurar", + "m0013": "Configuração necessária", + "m0014": "Ambiente pronto · Android {0}{1}", + "m0015": " · Janelas individuais ativadas", + "m0016": "Iniciando o Android…", + "m0017": "Iniciando", + "m0018": "Aceleração de hardware indisponível: usando emulação por software (a primeira inicialização pode demorar)", + "m0019": "Emulação por software", + "m0020": "Imagem universal do Android 14 ausente — baixe em Configurações → Ambiente Android (cerca de 1,5 GB, uma única vez)", + "m0021": "É necessário baixar a imagem", + "m0022": "Ambiente Android não instalado — abra Configurações → Ambiente Android para configurar", + "m0024": "Ambiente pronto — instale um APK ou clique duas vezes em um atalho para iniciar", + "m0025": "Pronto para iniciar", + "m0026": "Instalador em execução — conclua a instalação na janela da máquina virtual", + "m0027": "Instalando", + "m0028": "Ambiente em espera (inicia automaticamente ao instalar ou abrir um app)", + "m0029": "Android ainda não instalado — continue em Configurações → Ambiente Android", + "m0030": "Em espera", + "m0031": "Instalação necessária", + "m0032": "Falha ao verificar o status: {0}", + "m0033": "Motor de execução", + "m0034": "Imagem do sistema", + "m0035": "Pronto · Android {0}", + "m0037": "Componentes prontos · Inicia ao abrir um app", + "m0038": "Falta {0}{1}", + "m0039": " (sem aceleração de hardware; será usada emulação por software)", + "m0040": "Ferramentas adb", + "m0041": "Ambiente Android", + "m0043": "Tudo pronto", + "m0044": "Configurar automaticamente", + "m0045": "Concluído automaticamente", + "m0046": "Automático", + "m0047": "Android {0} pronto · As janelas dos apps se ajustam à tela", + "m0049": "Componentes prontos. Clique duas vezes em um APK ou atalho para iniciar.", + "m0050": "Ainda falta {0}. Clique em “Configurar automaticamente” abaixo para instalar e iniciar em segundo plano.", + "m0051": "", + "m0052": "Preparando…", + "m0053": "Concluído · Clique para verificar novamente", + "m0055": "Falha na última inicialização: {0}", + "m0056": "Diagnóstico da última inicialização: {0}", + "m0057": "Tudo automático: não é preciso escolher a imagem, ativar a aceleração ou ajustar a memória", + "m0058": "Falha na configuração", + "m0059": "Verifique sua conexão e tente novamente", + "m0060": "Componentes prontos. Iniciando automaticamente…", + "m0061": "A primeira inicialização leva cerca de 1 a 3 minutos; as próximas levam poucos segundos.", + "m0062": "Ambiente Android pronto", + "m0063": "Arraste um APK para a janela ou clique duas vezes em um atalho da área de trabalho.", + "m0064": "Inicialização incompleta", + "m0065": "Exporte um relatório de diagnóstico para investigar", + "m0067": "Já existe uma configuração em andamento", + "m0068": "Instalando o driver de aceleração de hardware…", + "m0069": "Clique em “Sim” na solicitação de permissão", + "m0070": "Aceleração de hardware ativada", + "m0071": "Aceleração não ativada", + "m0072": "Etapa desconhecida", + "m0073": "Não foi possível iniciar o ambiente de instalação", + "m0074": "Janela da máquina virtual aberta", + "m0075": "Siga as seis etapas abaixo e clique em “Instalação concluída”.", + "m0076": "Instalação concluída; ambiente conectado", + "m0077": "Agora você pode arrastar um APK para cá para instalá-lo.", + "m0078": "Erro desconhecido", + "m0079": "{0}\nA partição de dados do Android está cheia: abra Configurações → Desempenho e recursos, aumente a partição e clique em “Expandir agora”.", + "m0080": "{0}\nEste pacote contém apenas bibliotecas ARM. Alternando para uma imagem compatível e tentando novamente.", + "m0081": "{0}\nO nome do pacote é igual ao de um app instalado, mas a assinatura é diferente. Desinstale o app pelo menu de contexto e tente novamente.", + "m0082": "{0}\nUma versão mais recente está instalada. Desinstale-a pelo menu de contexto primeiro.", + "m0083": "{0}\nO pacote é grande demais ou não há memória suficiente. Verifique o espaço em disco e a memória e tente novamente;", + "m0084": "se a falha persistir, use um caminho curto, sem acentos (por exemplo, D:\\a.apk).", + "m0085": "Já existe uma instalação em andamento", + "m0086": "Aguarde a instalação atual terminar.", + "m0087": "Instalando {0}", + "m0088": "Lendo o APK…", + "m0091": "{0} instalado", + "m0092": "Atalho criado na área de trabalho. Clique duas vezes para abrir o app.", + "m0093": "Crie um atalho na área de trabalho pelo menu de contexto do app.", + "m0094": "{0}: aviso de compatibilidade", + "m0095": "Abrindo {0}", + "m0096": "A primeira inicialização precisa preparar o ambiente. Aguarde…", + "m0097": "Iniciado", + "m0098": "O app está em uma janela própria (gire a tela pela barra superior).", + "m0099": "Falha ao iniciar", + "m0100": "Atalho criado na área de trabalho", + "m0101": "Não foi possível criar o atalho", + "m0102": "Desinstalar “{0}”?\n\nOs dados do app e o atalho da área de trabalho também serão removidos.", + "m0103": "Desinstalado", + "m0104": "Falha ao desinstalar", + "m0105": "Abrir app", + "m0106": "Criar atalho na área de trabalho", + "m0107": "Fixar no topo", + "m0108": "Mostrar APK na pasta", + "m0109": "Limpar dados do app", + "m0110": "Desinstalar", + "m0111": "Desafixar", + "m0113": "Limpar todos os dados de “{0}”? Esta ação não pode ser desfeita.", + "m0114": "Dados apagados", + "m0115": "Não foi possível apagar os dados", + "m0116": "Lendo…", + "m0117": "Ambiente Android indisponível ({0})", + "m0118": "Ambiente Android parado", + "m0119": "Wi-Fi ativado", + "m0120": "Wi-Fi desativado", + "m0121": "{0} · Volume {1}%", + "m0122": " · Modo avião", + "m0123": "Aplicado", + "m0124": "Falha: {0}", + "m0125": "Ambiente Android indisponível", + "m0126": "Wi-Fi ativado", + "m0127": "Wi-Fi desativado", + "m0128": "Modo avião ativado", + "m0129": "Modo avião desativado", + "m0130": "Animações do sistema ativadas", + "m0131": "Animações do sistema desativadas para agilizar a navegação", + "m0132": "Tamanho da fonte atualizado", + "m0133": "Tamanho de exibição atualizado", + "m0134": "Volume atualizado", + "m0135": "Otimizado: animações desativadas, limite de processos ampliado e exceção de bateria ativada", + "m0136": "Falha na otimização; o ambiente Android pode estar parado", + "m0137": "Abra um app primeiro para iniciar o ambiente Android", + "m0138": "Não foi possível ler as informações da partição de dados", + "m0139": "Ainda não criada", + "m0140": "Desconhecido", + "m0141": "Imagem de dados atual: {0}{1} · Espaço livre na unidade: {2}.", + "m0142": " (automático)", + "m0143": "Se um jogo informar falta de armazenamento, aumente este valor e clique em “Expandir agora”. Os apps e jogos salvos são preservados.", + "m0144": "Associado · Clique para desativar", + "m0145": "Não associado · Clique para ativar", + "m0146": "Modo automático", + "m0147": "Total: {0}{1}", + "m0148": " · Recuperável: {0} ({1} itens)", + "m0149": " · Já otimizado", + "m0150": "Estatísticas indisponíveis", + "m0152": "Falha na verificação: {0}", + "m0153": "Rede indisponível", + "m0154": "Abrir página de download", + "m0155": " (cache offline; pode estar desatualizado)", + "m0156": "Nova versão {0}{1}{2}{3}", + "m0157": "; {0} inacessível", + "m0158": "Atualizar agora", + "m0160": "Notas da versão:\n{0}", + "m0161": " (cache offline)", + "m0162": "Versão atualizada (v{0}){1}{2}", + "m0163": " · via {0}", + "m0164": "Falha na atualização: {0}", + "m0165": "Falha na atualização", + "m0166": "Atualizando…", + "m0167": "Verificando…", + "m0168": "Conectando ao GitHub…", + "m0169": "Nova versão {0}", + "m0170": "Clique em “Atualizar agora” para baixar e instalar automaticamente", + "m0171": "Versão atualizada", + "m0173": "Atualizando…", + "m0177": "Versão {0} baixada. Reiniciando para concluir a instalação…", + "m0178": "Política de recursos atualizada", + "m0179": "Será aplicado na próxima vez que você abrir um app", + "m0180": "Selecione uma capacidade primeiro", + "m0181": "“Automático” define o tamanho conforme o espaço livre em disco; não é necessário expandir manualmente", + "m0182": "Expandir a partição de dados do Android agora?", + "m0183": "Expandir /data para {0} GB, preservando os apps instalados e jogos salvos.", + "m0184": "Os apps Android em execução precisam ser fechados. A próxima inicialização reiniciará o Android e levará um pouco mais de tempo.", + "m0185": "Iniciar expansão", + "m0186": "Expandindo…", + "m0187": "Partição de dados expandida", + "m0188": "Falha na expansão", + "m0189": "Cerca de {0} GB disponíveis agora", + "m0191": "Componentes do sistema restaurados", + "m0192": "Redução avançada desativada; restaurando pacotes em segundo plano", + "m0193": "Modo de compatibilidade ARM ativado", + "m0194": "Baixe a imagem de compatibilidade do Google Play (Android 14, cerca de 1,5 GB) pelo botão abaixo", + "m0196": "Será aplicado após reiniciar o ambiente Android", + "m0197": "Modo de tela cheia ativado", + "m0198": "Modo de janela vertical ativado", + "m0199": "Tamanho de exibição atualizado", + "m0200": "Será aplicado ao abrir ou alternar apps", + "m0201": "Atalhos ausentes restaurados", + "m0202": "Todos os atalhos já existem", + "m0203": "Atalhos da área de trabalho e do menu Iniciar disponíveis", + "m0204": "Relatório de diagnóstico exportado", + "m0205": "Falha na exportação", + "m0206": "Já otimizado", + "m0207": "Nenhuma imagem ou snapshot antigo para remover.", + "m0208": "Limpeza concluída", + "m0209": "Cerca de {0} MB liberados ({1} itens). Os apps instalados foram preservados.", + "m0210": "Total: {0} · Recuperável: {1} ({2} itens)", + "m0211": "Falha na limpeza", + "m0212": "Redefinir os dados do Android?", + "m0213": "Isso restaura o Android ao estado de uma instalação limpa:", + "m0214": "• Exclui todos os apps de terceiros (incluindo jogos), seus dados e sessões de login\n", + "m0215": "• Exclui o snapshot de inicialização rápida do AVD atual\n", + "m0216": "• Preserva a imagem do sistema; os dados serão recriados na próxima inicialização a frio\n\n", + "m0217": "Esta ação não pode ser desfeita. Continuar?", + "m0218": "Cancelar", + "m0219": "Continuar", + "m0220": "Confirme mais uma vez", + "m0221": "Os apps instalados e seus dados serão excluídos permanentemente. Redefinir agora?", + "m0222": "A imagem do sistema, os outros AVDs, o cache local de APKs e os ícones serão preservados.", + "m0224": "Confirmar redefinição", + "m0225": "Falha na redefinição", + "m0226": "Tente novamente", + "m0227": "Dados do Android redefinidos", + "m0228": "Cerca de {0} MB liberados. Os registros de apps instalados foram apagados.", + "m0231": "Reparar a rede da máquina virtual?", + "m0232": "Telas vazias ou carregamento sem fim nos apps podem indicar que a máquina virtual Android está sem conexão.", + "m0233": "O reparo tenta estas etapas, da menos para a mais invasiva:\n\n", + "m0234": "1. Inicia sem carregar um snapshot e aguarda a conexão — preserva todos os dados\n", + "m0235": "2. Somente se necessário, redefine os dados do Android — mantém a lista de apps e os reinstala ao abrir,\n", + "m0236": " mas as sessões de login, o histórico e os demais dados dos apps serão perdidos\n\n", + "m0237": "Apenas o AVD atual será afetado. A imagem do sistema e o cache local de APKs serão preservados. Isso leva cerca de 1 a 5 minutos. Continuar?", + "m0239": "Iniciar reparo", + "m0240": "Rede ainda indisponível", + "m0241": "A máquina virtual continua sem rede após a redefinição. Verifique a conexão do computador e o firewall.", + "m0242": "Rede restaurada", + "m0243": "Conectado", + "m0244": " (os dados do Android foram redefinidos; os apps serão reinstalados ao abrir)", + "m0245": " (seus dados foram preservados)", + "m0246": "Falha no reparo", + "m0247": "Falha na verificação", + "m0249": "Nada para limpar", + "m0250": "Nenhum arquivo de versões antigas encontrado.", + "m0251": "…e mais {0} itens", + "m0252": "Limpar arquivos de versões antigas?", + "m0253": "Foram encontrados {0} itens, cerca de {1} MB:", + "m0254": "{0}\n\nSerão excluídos apenas diretórios antigos do programa, registros de desinstalação inválidos e arquivos temporários de atualização.", + "m0255": "A versão em execução e os dados dos seus apps serão preservados.", + "m0257": "Iniciar limpeza", + "m0260": "; {0} itens em uso foram ignorados", + "m0261": "Arquivos de versões antigas removidos", + "m0262": "{0} itens excluídos, liberando {1} MB{2}.", + "m0265": "Atalhos recriados", + "m0266": "Todos os atalhos existem", + "m0267": "Nenhum atalho precisa ser recriado", + "m0268": "Nova pasta selecionada", + "m0269": "Clique em “Salvar e migrar” para mover os arquivos existentes", + "m0270": "Digite ou selecione uma pasta primeiro", + "m0271": "Pasta compartilhada atualizada", + "m0272": "Os arquivos existentes foram movidos para a nova pasta", + "m0273": "Salvo", + "m0274": "Falha ao salvar", + "m0275": "Desinstalar o ApkStart?\n\nIsso remove a associação de arquivos .apk e o programa. Esta ação não pode ser desfeita.", + "m0276": "A versão portátil não pode ser desinstalada automaticamente", + "m0277": "Exclua manualmente a pasta do programa ({0}) e a pasta de dados ({1})", + "m0279": "Desinstalando…", + "m0280": "Desinstalador iniciado. O ApkStart será fechado em breve.", + "m0281": "Iniciando o download da imagem de compatibilidade ARM", + "m0283": "Baixar imagem de compatibilidade ARM", + "m0284": "Imagem de compatibilidade ARM pronta", + "m0286": "Falha ao baixar a imagem de compatibilidade", + "m0291": "Pronto ({0} GB)", + "m0292": "Não baixada", + "m0293": "Pasta de dados atualizada", + "m0294": "Reinicie o ApkStart para aplicar (os dados existentes não são movidos automaticamente)", + "m0295": "Pasta de dados do Android atualizada", + "m0296": "Reinicie o ApkStart para aplicar. Você pode mover a pasta apkstart.avd inteira para a nova pasta e depois excluir a cópia antiga.", + "m0297": "Detectando…", + "m0298": "Nenhum ambiente Android em execução encontrado", + "m0299": "Encontrado: {0}", + "m0300": "Iniciando…", + "m0301": "Pronto: {0}", + "m0302": "Falha ao iniciar: {0}", + "m0303": "Máquina virtual integrada parada", + "m0305": "Associação de .apk desativada", + "m0307": "Associação de .apk ativada", + "m0308": "Agora, clicar duas vezes em um APK o instala no ApkStart", + "m0309": "Falha na operação", + "m0310": "Imagem do sistema importada", + "m0311": "Falha na importação", + "m0312": "Disco virtual importado", + "m0313": "Agora você pode iniciar o ambiente Android", + "m0315": "Iniciando o ambiente Android…", + "m0316": "A primeira inicialização precisa iniciar o Android", + "m0319": "Sessão do ambiente Android encerrada", + "m0320": "Pasta compartilhada sincronizada", + "m0321": "Falha na sincronização", + "m0322": "A sincronização é ignorada quando o ambiente Android está indisponível", + "m0323": "Somente arquivos .apk podem ser arrastados para cá", + "m0324": "Falha na instalação: {0}", + "m0325": "Falha na instalação", + "m0327": "Boa noite", + "m0328": "Bom dia", + "m0329": "Boa tarde", + "m0330": "Boa tarde", + "m0331": "Boa noite", + "m0332": "{0}, bem-vindo de volta", + "m0333": "Ambiente pronto — abra um app para começar", + "m0334": "O ambiente será preparado automaticamente quando necessário", + "m0335": "Apps instalados", + "m0336": "Pronto", + "m0337": "Em espera", + "m0338": "Ambiente Android", + "m0339": "Dispositivo / porta", + "m0340": "Nova versão {0} disponível. Atualize em Configurações → Atualizações.", + "m0341": "Arraste um APK para a janela para instalar e criar um atalho na área de trabalho", + "m0342": "Clique duas vezes no cartão de um app para abri-lo em uma janela própria; gire, redimensione ou feche pelo canto superior direito", + "m0343": "Fechar uma janela encerra apenas aquele app; o Android continua pronto para agilizar a próxima abertura", + "m0344": "{0} problemas para corrigir", + "m0345": "{0} avisos", + "m0346": "\n
\n \n Verificação concluída. Nenhum problema que afete o funcionamento foi encontrado.\n \n \n
", + "m0347": "\n
\n Verificação do ambiente\n {0}\n {1}\n \n \n \n
\n
\n {2}\n
", + "m0348": "{0} graves", + "m0349": "{0} avisos", + "m0350": "", + "m0351": "Instalador integrado não encontrado", + "m0352": "A página oficial de download da Microsoft foi aberta. Baixe a versão x64.", + "m0353": "Bibliotecas de execução do VC++ instaladas", + "m0354": "Instalação incompleta", + "m0356": "Ativando a aceleração de hardware…", + "m0357": "Clique em “Sim” na solicitação", + "m0359": "Reinicie o computador para obter o melhor desempenho", + "m0360": "Não foi possível ativar", + "m0365": "Cerca de {0} MB liberados", + "m0366": "Use um caminho sem acentos ou caracteres especiais", + "m0367": "Altere “Pasta de dados do ambiente Android” em Configurações → Integração com o sistema e reinicie o ApkStart.", + "m0368": "Instalando os componentes ausentes do ambiente Android…", + "m0369": "Arquivos de bloqueio residuais removidos", + "m0371": "O reparo automático ainda não está disponível", + "m0372": "Falha na inicialização", + "m0373": "App", + "m0374": "Iniciando o ambiente Android…", + "m0375": "Baixando o ambiente Android do GitHub…", + "m0376": "Pacote do ambiente Android incompleto. Obtendo componentes ausentes das fontes oficiais…", + "m0377": "Ambiente Android do GitHub indisponível ({0}). Usando fontes oficiais…", + "m0378": "Preparando o adb…", + "m0379": "Preparando o ambiente Android…", + "m0380": "Preparando a imagem do sistema Android…", + "m0381": "Não foi possível iniciar o PowerShell", + "m0382": "O ApkStart continua em execução em segundo plano", + "m0383": "Clique no ícone da bandeja para reabrir; clique com o botão direito para sair.", + "m0384": "Abrir ApkStart", + "m0385": "Nenhum app instalado ainda", + "m0386": "Alternar para janela vertical", + "m0387": "Tela cheia (ajustar à tela)", + "m0388": "Recriar atalhos da área de trabalho e do menu Iniciar", + "m0389": "Sincronizar pasta compartilhada", + "m0390": "Configurações", + "m0392": "Verificar atualizações", + "m0393": "Nova versão disponível", + "m0394": "O ApkStart {0} está disponível; sua versão atual é a {1}. Atualizar agora?", + "m0396": "Mais tarde", + "m0398": "Sair", + "m0399": "ApkStart — Inicializador de apps Android", + "m0400": "Este APK já está sendo instalado. Aguarde…", + "m0401": "Instalação em andamento", + "m0402": "Outra instalação está em andamento. Aguarde a conclusão.", + "m0404": "Instalação concluída", + "m0405": "{0} instalado{1}", + "m0406": "; atalho criado na área de trabalho", + "m0408": "Atualização em andamento", + "m0409": "Já está atualizado", + "m0410": "Esta versão não tem um pacote instalável. A página da versão no GitHub foi aberta.", + "m0411": "Preparando o download…", + "m0412": "Substituindo os arquivos do programa e reiniciando…", + "m0414": "Abrindo o app…", + "m0416": "Selecionar pasta de dados", + "m0417": "Selecionar APK para instalar", + "m0418": "Apps Android", + "m0419": "App não encontrado", + "m0420": "A pasta não pode ficar em branco", + "m0421": "Bibliotecas de execução do VC++ ausentes (x64)", + "m0422": "O Android requer o Microsoft VC++ 2015–2022. Sem ele, o emulador pode não iniciar (0xc0000135).", + "m0423": "Aceleração de hardware desativada", + "m0424": "Será usada emulação por software (funciona, mas é lenta). Ative a virtualização para obter o melhor desempenho.", + "m0425": "Pouco espaço na unidade do ambiente Android", + "m0426": "A unidade que contém “{0}” tem apenas {1} MB livres. Os componentes e as imagens podem esgotar o espaço.", + "m0427": "A pasta do ambiente Android contém caracteres especiais", + "m0428": "Alguns componentes não aceitam caminhos com acentos ou caracteres especiais e podem falhar ao iniciar. Use um caminho sem esses caracteres.", + "m0429": "Ferramentas adb ausentes", + "m0430": "Não é possível conectar ao ambiente Android.", + "m0431": "Ambiente Android não instalado", + "m0432": "Configure o ambiente Android primeiro (cerca de 440 MB).", + "m0433": "Imagem do sistema indisponível", + "m0434": "Baixe a imagem do sistema Android primeiro (cerca de 1,5 GB).", + "m0435": "Arquivos de bloqueio da sessão anterior encontrados", + "m0436": "Esses arquivos podem fazer a próxima inicialização informar que o AVD já está em uso.", + "m0437": "Confirmar", + "m0439": "OK", + "m0440": "Partição de dados redimensionada", + "m0441": "Cerca de {0} GB disponíveis no Android{1}", + "m0442": " (ambiente Android reiniciado)", + "m0444": "Limpar {0} itens", + "m0445": "Confirmar limpeza do cache do ambiente Android", + "m0446": "Cerca de {0} MB serão liberados", + "m0447": "O ambiente Android será parado antes da remoção de imagens antigas, do snapshot de inicialização rápida do AVD atual, dos arquivos de bloqueio e dos caches de AVDs inativos. Os apps instalados serão preservados (apenas os snapshots serão removidos).", + "m0448": "Serão excluídos apenas imagens antigas, snapshots antigos, arquivos de bloqueio e caches de AVDs inativos. Os apps instalados serão preservados.", + "m0449": "Esta ação não pode ser desfeita. Exporte um relatório de diagnóstico antes, se necessário.", + "m0450": "Pacote do driver AEHD indisponível", + "m0451": "Permissão de administrador cancelada (clique em “Sim” para instalar o driver)", + "m0452": "Falha na instalação do driver (código de saída {0})", + "m0453": "A aceleração de hardware já está disponível; nenhuma ação necessária", + "m0454": "Plataforma de Máquina Virtual ativada. Reinicie o computador para obter o melhor desempenho.", + "m0455": "Plataforma de Máquina Virtual ativada; aceleração de hardware em funcionamento", + "m0456": "Não foi possível ativar a virtualização do sistema", + "m0457": "Não foi possível preparar o pacote do driver AEHD", + "m0460": "Aceleração de hardware AEHD ativada", + "m0461": "Selecionar imagem do sistema Android-x86 / Bliss OS", + "m0463": "Selecionar disco virtual com Android instalado", + "m0464": "Disco virtual", + "m0465": "Configuração desconhecida: {0}", + "m0466": "Escopo do agente vazio: nenhuma janela será alterada para evitar afetar outros programas", + "m0467": "Espaço em disco insuficiente", + "m0468": "Restam apenas {0} GB. Mova os dados do Android / ambiente para outra unidade em Configurações → Integração com o sistema ou libere espaço em disco.", + "m0469": "Restam apenas {0} GB; o Android pode não iniciar (altere a unidade em Configurações → Integração com o sistema)", + "m0470": "Atalhos ausentes restaurados", + "m0471": "Configuração automática do ambiente Android concluída", + "m0472": "[smoke] Falha na captura de tela", + "m0473": "Ambiente Android desconectado", + "m0475": "Buscar apps instalados", + "m0476": "Instalar APK", + "m0477": "Arraste um APK para cá para instalar", + "m0478": "A instalação cria um atalho na área de trabalho automaticamente. Clique duas vezes para abrir o app em uma janela própria, como qualquer programa do Windows.", + "m0479": "Com a associação de .apk ativada, clicar duas vezes em um APK no Explorador de Arquivos também o instala no ApkStart.", + "m0480": "Selecionar arquivos APK", + "m0481": "Verificando o ambiente Android…", + "m0482": "Olá", + "m0483": "ApkStart está pronto", + "m0484": "Não mostrar automaticamente ao iniciar", + "m0485": "Abrir configurações", + "m0486": "Começar", + "m0487": "Solte para instalar", + "m0488": "Aceita um ou mais arquivos .apk", + "m0490": "Ambiente Android e sistema", + "m0491": "Status do ambiente Android", + "m0492": "Verificando…", + "m0494": "Avançado: conectar a um ambiente existente", + "m0496": "Instalar Android em um disco virtual", + "m0497": "A janela da máquina virtual está aberta. Siga estas seis etapas uma vez; depois, o ApkStart iniciará pelo disco, sem precisar da imagem de instalação.", + "m0498": "Menu de inicialização", + "m0499": ": use", + "m0500": "para selecionar", + "m0501": " e pressione Enter", + "m0502": ": selecione", + "m0503": "; ao perguntar sobre GPT, selecione", + "m0504": "Particionamento com cfdisk", + "m0505": "→ pressione Enter para aceitar o tamanho →", + "m0506": "→ digite", + "m0507": "Formatar", + "m0509": "→ ao perguntar sobre a formatação, selecione", + "m0510": "Carregador de inicialização e sistema", + "m0511": "selecione", + "m0513": "Concluído", + "m0514": ": quando o progresso chegar a 100%, clique em “Instalação concluída” abaixo (o ApkStart reinicia e se conecta automaticamente)", + "m0515": "Se o adb ficar desconectado depois, pressione", + "m0516": "na máquina virtual para abrir um terminal e execute", + "m0517": " e pressione", + "m0518": "para voltar à interface gráfica.", + "m0519": "Instalação concluída", + "m0520": "Mais tarde", + "m0521": "Modo automático (padrão; sem ajustes necessários)", + "m0522": "Configuração adaptada a este computador", + "m0523": "Detectando…", + "m0524": "Memória, núcleos de CPU, renderização, portas e aceleração são selecionados automaticamente. Em caso de falha ao iniciar, o programa tenta alternativas sem ajustes manuais.", + "m0525": "Uso total do disco (ambiente Android + dados + cache)", + "m0526": "Calculando…", + "m0527": "Limpar arquivos recuperáveis", + "m0528": "Localizar e remover pastas antigas do programa, registros de desinstalação inválidos e arquivos temporários de atualização", + "m0529": "Limpar versões antigas", + "m0530": "Reparar problemas de rede da máquina virtual Android. Isso pode redefinir os dados do Android; a lista de apps será preservada e os apps serão reinstalados ao abrir.", + "m0531": "Reparar rede", + "m0532": "Excluir /data do Android, apps instalados e snapshots de inicialização rápida para restaurar um sistema limpo", + "m0533": "Redefinir dados do Android", + "m0534": "Excluir também o snapshot de inicialização rápida do AVD atual (para o ambiente Android antes e pode liberar vários GB)", + "m0535": "A imagem do sistema é compartilhada e os dados dos apps usam camadas diferenciais. Imagens antigas, snapshots, logs e arquivos de bloqueio podem ser removidos. Os logs são rotacionados automaticamente. Redefinir os dados do Android exclui os dados do usuário e preserva apenas o sistema.", + "m0536": "Exportar relatório de diagnóstico", + "m0537": "Recriar atalhos", + "m0538": "Os relatórios ajudam a investigar problemas; a recriação restaura os atalhos da área de trabalho e do menu Iniciar", + "m0539": "Iniciar com o Windows (ativado por padrão)", + "m0540": "Manter na bandeja após entrar no Windows para abrir apps pelos atalhos sem iniciar o ApkStart manualmente.", + "m0541": "Janelas dos apps", + "m0542": "Modo de exibição", + "m0543": "Janela vertical (redimensionável; lembra o tamanho de cada app)", + "m0544": "Tela cheia (ajusta à tela mantendo a proporção de celular)", + "m0545": "Pressione", + "m0546": "ou use o menu da bandeja para alternar. A tela cheia preserva a proporção e não reinicia o app.", + "m0547": "Resolução de renderização vertical", + "m0548": "Pequena · 720 × 1280", + "m0549": "Média · 900 × 1600", + "m0550": "Grande · 1080 × 1920", + "m0551": "Muito grande · 1440 × 2560", + "m0552": "Arraste a borda da janela para redimensionar. Este ajuste altera a resolução interna do Android; valores menores usam menos recursos.", + "m0553": "Controles do mouse", + "m0554": "Botão esquerdo · Confirmar / tocar", + "m0555": "O emulador mapeia o clique esquerdo como toque e o arrastar como gesto de deslizar. Nenhum ajuste adicional necessário.", + "m0556": "Botão direito · Voltar no Android", + "m0557": "Por padrão, o emulador também interpreta o clique direito como toque. Ative para que ele apenas volte, sem provocar um toque acidental.", + "m0558": "Roda do mouse · Deslizar", + "m0559": "Suave (recomendado) · Agrupar movimentos em um gesto contínuo", + "m0560": "Nativo · Deixar o emulador processar cada movimento", + "m0561": "Desativado · Não interceptar a roda do mouse", + "m0562": "O modo suave agrupa movimentos próximos em um único gesto, evitando travamentos e um processo adb para cada movimento.", + "m0563": "Botões laterais · Voltar / avançar (desativado por padrão)", + "m0564": "Os botões laterais precisam de interceptação global do mouse, ativada apenas enquanto uma janela de app do ApkStart está em primeiro plano e removida ao perder o foco ou fechar.", + "m0565": "Esses mapeamentos funcionam apenas nas janelas dos apps em primeiro plano. Ao voltar para a área de trabalho ou fechar a janela, o mouse volta ao comportamento normal do Windows.", + "m0566": "Desempenho e recursos", + "m0567": "Política de recursos", + "m0568": "Economia · 2 núcleos / prioridade reduzida / 540p", + "m0569": "Equilibrado · Metade dos núcleos físicos / prioridade reduzida / 720p (recomendado)", + "m0570": "Fluidez · Todos os núcleos / prioridade de primeiro plano (vídeos e jogos)", + "m0571": "Controla o uso de CPU e a qualidade da imagem. Escolha Fluidez para vídeos ou jogos com travamentos, ou Economia para apps leves em segundo plano. Será aplicado na próxima abertura de um app.", + "m0572": "Qualidade da imagem (resolução de renderização do Android)", + "m0573": "Automático (conforme a aceleração)", + "m0574": "Fluidez · 540 × 960", + "m0575": "Equilibrado · 720 × 1280", + "m0576": "Alta definição · 1080 × 1920", + "m0577": "Partição de dados do Android (apps e jogos)", + "m0579": "Expandir agora", + "m0580": "A expansão preserva os apps instalados e jogos salvos. A próxima abertura de um app leva um pouco mais de tempo.", + "m0581": "Lendo a partição de dados…", + "m0582": "Aceleração de rede (recomendado)", + "m0583": "Usar servidores DNS públicos na China (Alibaba / Tencent) para o Android. O DNS do computador pode ser lento com VPNs ou redes corporativas. Desative se os domínios internos deixarem de funcionar.", + "m0584": "Áudio do Android", + "m0585": "Reproduzir sons de notificações e vídeos dos apps. Desative apenas se não precisar de áudio e quiser reduzir o uso de CPU.", + "m0586": "Manter snapshot de inicialização rápida (recomendado)", + "m0587": "As próximas inicializações levam segundos, com um custo de cerca de 2 a 3 GB em disco para", + "m0588": ". Desative se houver pouco espaço; isso afeta a velocidade de inicialização, mas preserva os apps e jogos salvos.", + "m0589": "Detalhes do contêiner (avançado)", + "m0590": "Contêiner leve", + "m0591": "Limitar a alocação de vCPU e reduzir a prioridade do processo para favorecer a área de trabalho do Windows. A política de recursos já gerencia este ajuste.", + "m0592": "Prioridade da máquina virtual", + "m0593": "Equilibrado (recomendado) · Favorecer a janela em primeiro plano", + "m0594": "Desempenho · Mesma prioridade do primeiro plano", + "m0595": "Quando nenhuma janela de app está aberta, o contêiner entra automaticamente em espera com a menor prioridade.", + "m0596": "Redução avançada de processos em segundo plano", + "m0597": "Desativar pacotes extras em segundo plano, como Play Store, assistente de configuração e serviços de impressão. Desative este ajuste para restaurá-los se um app informar falta dos serviços do Google Play.", + "m0598": "Pré-carregar ao iniciar (recomendado)", + "m0599": "Iniciar o Android em segundo plano junto com o ApkStart, mantendo o emulador oculto. Os apps abrem mais rápido, com um custo de cerca de 1 GB de memória em espera. Desative para iniciar o Android apenas ao abrir o primeiro app.", + "m0600": "O Android inicia uma vez e permanece pronto. Abrir outro app inicia apenas esse app e encerra os processos dos demais. Fechar a última janela limpa os processos em segundo plano e coloca o contêiner em espera.", + "m0601": "Fontes de download e atualização", + "m0602": "Fonte de atualização e download", + "m0603": "Escolher a fonte mais rápida automaticamente (recomendado)", + "m0604": "Somente Gitee (geralmente mais rápido na China)", + "m0605": "Somente GitHub", + "m0606": "O modo automático consulta o GitHub e o Gitee, testa cada URL com uma amostra de 256 KB e usa a mais rápida. Uma fonte indisponível não impede as atualizações.", + "m0607": "Tentar espelhos públicos automaticamente", + "m0608": "Se o GitHub estiver lento, testar também ghfast.top / gh-proxy.com. Esses serviços de terceiros encaminham arquivos públicos sem informações de conta. Desative para usar apenas downloads diretos e seu prefixo personalizado.", + "m0609": "Prefixo do espelho (opcional, acelera o GitHub)", + "m0610": "Por exemplo, https://ghfast.top/", + "m0611": "Se preenchido, apenas este prefixo será usado em vez dos espelhos públicos acima. Deixe em branco para seleção automática. O prefixo será adicionado antes da URL de download.", + "m0612": "Conexões de download (1 a 16, padrão 8)", + "m0613": "Arquivos grandes são baixados em partes paralelas. Oito conexões costumam ser suficientes; tente dezesseis em uma conexão estável.", + "m0614": "Fonte de atualização Gitee (opcional)", + "m0615": "Token do Gitee somente de leitura (geralmente desnecessário)", + "m0616": "Deixe em branco: repositórios públicos permitem acesso anônimo", + "m0617": "A lista de versões deste repositório público no Gitee permite acesso anônimo. Um token só é necessário para limites de acesso (403/429) ou branches privadas: gitee.com → Configurações → Tokens pessoais, com apenas “projects”. Ele é usado somente para ler versões; nada é enviado.", + "m0618": "Configurações avançadas (geralmente sem necessidade de alteração)", + "m0619": "Ambiente Android", + "m0620": "Modo de execução", + "m0621": "Ambiente integrado (recomendado, QEMU + Android-x86)", + "m0622": "Conectar a um ambiente existente (avançado: porta adb do WSA / outro emulador)", + "m0623": "Caminho do adb", + "m0624": "Deixe em branco para buscar automaticamente no PATH / Android SDK", + "m0625": "Endereço do dispositivo", + "m0626": "Detectar", + "m0627": "Imagem do sistema (ISO)", + "m0628": "Caminho da ISO ou IMG do Android-x86 / Bliss OS; deixe em branco para usar o padrão", + "m0629": "Memória (MB, 0 = automático)", + "m0630": "Núcleos de CPU (0 = automático)", + "m0631": "Desligamento por inatividade (minutos, 0 = desativado)", + "m0632": "Imagem de compatibilidade universal (gerenciada automaticamente)", + "m0633": "Uma única imagem universal do Android 14 aceita apps x86 e ARM e é baixada automaticamente quando necessário. Este controle existe apenas para compatibilidade com configurações antigas.", + "m0634": "Imagem de compatibilidade", + "m0636": "Baixar imagem de compatibilidade", + "m0637": "Iniciar ambiente Android", + "m0638": "Parar máquina virtual integrada", + "m0639": "Configurações do Android (sem abrir a área de trabalho do Android)", + "m0642": "Atualizar", + "m0643": "Otimizar desempenho", + "m0644": "Desativar animações, ampliar limites de processos em segundo plano e isentar o app atual da otimização de bateria. O efeito é mais perceptível em computadores modestos.", + "m0645": "Wi-Fi dentro da máquina virtual Android; desative para reduzir o uso de CPU.", + "m0646": "Modo avião", + "m0647": "Desconectar todas as redes, incluindo os dados móveis.", + "m0648": "Animações do sistema", + "m0649": "Desative para transições mais rápidas sem afetar as funções.", + "m0650": "Volume de mídia", + "m0651": "Tamanho da fonte", + "m0652": "Pequena", + "m0653": "Padrão", + "m0654": "Grande", + "m0655": "Muito grande", + "m0656": "Tamanho de exibição", + "m0657": "Compacto (usa menos recursos)", + "m0659": "Amplo", + "m0661": "Configurações nativas do Android", + "m0662": "Configurações de tela", + "m0663": "Configurações de som", + "m0664": "Configurações de idioma", + "m0665": "Gerenciar apps", + "m0666": "Área de trabalho e apps", + "m0667": "Criar atalhos na área de trabalho após a instalação", + "m0668": "Os atalhos usam os ícones reais e abrem os apps diretamente", + "m0669": "Adicionar também ao menu Iniciar", + "m0670": "Encontre apps na Pesquisa do Windows; as janelas da barra de tarefas são agrupadas por app", + "m0671": "Pasta de atalhos", + "m0672": "Em branco = área de trabalho", + "m0674": "Restaure os atalhos conforme as configurações atuais se os ícones foram apagados sem querer ou bloqueados pelo software de segurança.", + "m0675": "Compartilhamento de arquivos", + "m0676": "Pasta compartilhada neste computador", + "m0677": "Procurar", + "m0679": "Sincronizar agora", + "m0680": "Salvar e migrar", + "m0681": "Os arquivos existentes são movidos automaticamente da pasta antiga; o compartilhamento em andamento não é interrompido.", + "m0682": "Pasta correspondente no Android", + "m0683": "As duas pastas permanecem sincronizadas nos dois sentidos. Arquivos salvos ali pelos apps Android aparecem na pasta compartilhada deste computador.", + "m0684": "Integração com o sistema", + "m0685": "Associar arquivos .apk", + "m0686": "Clique duas vezes em APKs para instalar no ApkStart (somente usuário atual; sem administrador)", + "m0688": "Pasta de dados do ambiente Android (imagens · apps · snapshots)", + "m0689": "Padrão: pasta de dados do usuário em C:", + "m0692": "Esta pasta usa vários GB. Movê-la para outra unidade pode liberar espaço em C:. Reinicie após alterar. Os dados existentes não são movidos automaticamente; copie-os manualmente antes de remover a cópia antiga.", + "m0693": "Pasta de dados do Android (apps, jogos e snapshots de inicialização rápida)", + "m0694": "Padrão: pasta avd dentro da pasta do ambiente Android", + "m0697": "O maior consumo é da partição de dados (padrão de 6 GB, expansível acima) e dos snapshots de inicialização rápida (cerca de 2 a 3 GB). Selecione outra unidade, como D:, e reinicie para aplicar. A pasta antiga", + "m0698": "pode ser movida inteira antes de excluir a cópia antiga.", + "m0699": "Desinstalar ApkStart", + "m0700": "Desinstalar", + "m0701": "A desinstalação remove a associação de .apk e o programa. Na versão portátil, siga as instruções para excluir a pasta manualmente.", + "m0702": "Atualizações", + "m0703": "Versão atual", + "m0704": "Ainda não verificado", + "m0707": "Obter a versão mais recente do GitHub (RoutineMiku/apkstart). A versão leve baixa um pacote portátil e substitui os arquivos do programa automaticamente, preservando o ambiente Android, os apps instalados e os snapshots.", + "m0708": "Sobre", + "m0709": "QR code da conta oficial no WeChat", + "m0710": "Escaneie para seguir no WeChat", + "m0711": "QR code do Douyin", + "m0712": "Douyin · Daily MIKU", + "m0713": "Inicializador de apps Android para Windows: instalação de APKs, atalhos na área de trabalho, janelas individuais e compartilhamento de arquivos nos dois sentidos.", + "m0714": "Douyin · @Daily MIKU (contatar o autor)", + "m0715": "As alterações são aplicadas imediatamente", + "m0718": "Horizontal / vertical (Ctrl+R)", + "m0719": "Minimizar", + "m0720": "Maximizar", + "m0721": "Fechar", + "settings.language": "Idioma", + "settings.systemLanguage": "Idioma do sistema", + "settings.languageNote": "Alterar o idioma recarrega a janela principal. As janelas dos apps usam o novo idioma na próxima abertura.", + "profile.summary": "Automático: {0} GB de RAM · {1} núcleos · Renderização {2} · {3} · {4}{5} · Porta {6}", + "profile.accelerated": "Aceleração de hardware", + "profile.snapshot": "Snapshots sob demanda", + "profile.coldBoot": "Inicialização a frio", + "profile.lite": " · Contêiner leve" +} diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json new file mode 100644 index 0000000..9c552f5 --- /dev/null +++ b/src/locales/zh-CN.json @@ -0,0 +1,650 @@ +{ + "m0000": "刚刚", + "m0001": "{0} 分钟前", + "m0002": "{0} 小时前", + "m0003": "{0} 天前", + "m0004": "首次使用:打开右上角设置 → 运行时 → 一键完成,再回来安装应用即可。", + "m0005": "提示:所有应用共享同一个 Android 运行时,所以第二个应用的启动几乎是瞬时的。", + "m0006": "", + "m0007": "运行中", + "m0008": "切换到前台", + "m0009": "打开", + "m0010": "{0} 个应用", + "m0011": "暂无应用", + "m0012": "运行时未部署 —— 打开设置 → 运行时,一键完成", + "m0013": "需要部署", + "m0014": "运行时已就绪 · Android {0}{1}", + "m0015": " · 独立窗口已启用", + "m0016": "正在引导 Android…", + "m0017": "引导中", + "m0018": "未检测到硬件加速:已自动回退软件模拟(首次开机较慢)", + "m0019": "软件模拟", + "m0020": "缺少 Android 14 通用系统镜像 —— 设置 → 运行时里下载(约 1.5GB,只下一次)", + "m0021": "待下载镜像", + "m0022": "运行时引擎未安装 —— 打开设置 → 运行时一键完成", + "m0024": "运行时已就绪 —— 安装 APK 或双击桌面图标会自动开机", + "m0025": "待开机", + "m0026": "安装程序运行中 —— 请在虚拟机窗口里完成安装", + "m0027": "安装中", + "m0028": "运行时待机(安装或打开应用时会自动拉起)", + "m0029": "运行时尚未安装 Android 系统 —— 打开设置 → 运行时继续", + "m0030": "待机", + "m0031": "待安装", + "m0032": "状态检查失败:{0}", + "m0033": "引擎", + "m0034": "系统镜像", + "m0035": "已就绪 · Android {0}", + "m0037": "组件齐全 · 待开机(打开应用时自动拉起)", + "m0038": "缺少 {0}{1}", + "m0039": "(无硬件加速,将回退软件模拟)", + "m0040": "adb 工具链", + "m0041": "Android 运行时", + "m0043": "一切就绪", + "m0044": "一键完成", + "m0045": "已自动完成", + "m0046": "自动", + "m0047": "Android {0} 已就绪 · 应用窗口会按屏幕自动适配", + "m0049": "组件齐全,双击任意 APK 或桌面图标即会自动开机", + "m0050": "还差 {0};点下面「一键完成」,会在后台一次装好并自动开机", + "m0051": "", + "m0052": "准备中…", + "m0053": "已完成 · 点此复查", + "m0055": "上次启动失败:{0}", + "m0056": "上次启动自动诊断:{0}", + "m0057": "全自动:无需选择镜像、无需启用加速、无需调内存", + "m0058": "准备失败", + "m0059": "请检查网络后重试", + "m0060": "组件已就绪,正在自动开机…", + "m0061": "首次约 1-3 分钟,之后几秒即可。", + "m0062": "运行时已就绪", + "m0063": "现在把 APK 拖进窗口,或双击桌面图标即可。", + "m0064": "开机未完成", + "m0065": "请导出诊断报告查看原因", + "m0067": "已有部署任务进行中", + "m0068": "正在安装硬件加速驱动…", + "m0069": "请在弹出的是否允许窗口点「是」", + "m0070": "硬件加速已启用", + "m0071": "加速未启用", + "m0072": "未知步骤", + "m0073": "无法启动安装环境", + "m0074": "虚拟机窗口已打开", + "m0075": "按下方 6 步完成安装;装好后点「我已安装完成」。", + "m0076": "安装完成,运行时已接管", + "m0077": "现在可以直接把 APK 拖进来安装了。", + "m0078": "未知错误", + "m0079": "{0}\n安卓数据分区不够用:设置 → 性能与资源占用 → 安卓数据分区调大后点「立即扩容」。", + "m0080": "{0}\n这个包只有 ARM 库,正在自动切换兼容镜像后重试。", + "m0081": "{0}\n同包名但签名不同(常见于从别的渠道装过)。在应用卡片右键「卸载」后再装。", + "m0082": "{0}\n已装版本更高,先在右键菜单里卸载再装。", + "m0083": "{0}\n这个安装包过大或内存不足,解析被系统拒绝了。请确认磁盘与内存充足后重试;", + "m0084": "仍失败请把安装包路径换成纯英文短路径(如 D:\\a.apk)。", + "m0085": "已有安装任务在进行", + "m0086": "请等当前应用装完再继续。", + "m0087": "正在安装 {0}", + "m0088": "解析 APK…", + "m0091": "{0} 已安装", + "m0092": "桌面快捷方式已创建,双击即可直接打开。", + "m0093": "可在应用卡片右键创建桌面快捷方式。", + "m0094": "{0} 兼容性提示", + "m0095": "正在打开 {0}", + "m0096": "首次启动需要预热运行时,请稍候…", + "m0097": "已启动", + "m0098": "应用正在独立窗口中运行(顶部可旋转屏幕)。", + "m0099": "启动失败", + "m0100": "桌面快捷方式已创建", + "m0101": "创建快捷方式失败", + "m0102": "确定卸载「{0}」?\n\n应用数据会一并被清除,桌面快捷方式也会被删除。", + "m0103": "已卸载", + "m0104": "卸载失败", + "m0105": "打开应用", + "m0106": "创建桌面快捷方式", + "m0107": "固定到顶部", + "m0108": "打开 APK 所在位置", + "m0109": "清除应用数据", + "m0110": "卸载", + "m0111": "取消固定", + "m0113": "清除「{0}」的全部数据?此操作不可撤销。", + "m0114": "数据已清除", + "m0115": "清除失败", + "m0116": "读取中…", + "m0117": "运行时未就绪({0})", + "m0118": "运行时未启动", + "m0119": "Wi-Fi 已开", + "m0120": "Wi-Fi 已关", + "m0121": "{0} · 音量 {1}%", + "m0122": " · 飞行模式", + "m0123": "已生效", + "m0124": "失败:{0}", + "m0125": "运行时未就绪", + "m0126": "Wi-Fi 已开启", + "m0127": "Wi-Fi 已关闭", + "m0128": "已进入飞行模式", + "m0129": "已退出飞行模式", + "m0130": "系统动画已开启", + "m0131": "系统动画已关闭,界面更快", + "m0132": "字体大小已调整", + "m0133": "显示大小已调整", + "m0134": "音量已调整", + "m0135": "已优化:动画关闭、后台放宽、电池白名单", + "m0136": "优化失败,运行时可能未启动", + "m0137": "需要先打开一次应用,让运行时启动", + "m0138": "读取数据分区信息失败", + "m0139": "尚未创建", + "m0140": "未知", + "m0141": "当前数据分区镜像:{0}{1} · 所在磁盘剩余 {2}。", + "m0142": "(自动)", + "m0143": "游戏提示「存储空间不足」就把这里调大后点「立即扩容」——扩容是就地做的,已装应用与存档都保留。", + "m0144": "已关联 · 点击取消", + "m0145": "未关联 · 点击开启", + "m0146": "自动模式", + "m0147": "共 {0}{1}", + "m0148": " · 可回收 {0}({1} 项)", + "m0149": " · 已是精简状态", + "m0150": "统计不可用", + "m0152": "检查失败:{0}", + "m0153": "网络不可用", + "m0154": "打开下载页", + "m0155": "(离线缓存,可能不是最新)", + "m0156": "发现新版本 {0}{1}{2}{3}", + "m0157": ";{0} 不可达", + "m0158": "立即更新", + "m0160": "更新内容:\n{0}", + "m0161": "(离线缓存)", + "m0162": "已是最新版本(v{0}){1}{2}", + "m0163": " · 经由 {0}", + "m0164": "更新失败:{0}", + "m0165": "更新失败", + "m0166": "正在更新…", + "m0167": "检查中…", + "m0168": "正在连接 GitHub…", + "m0169": "发现新版本 {0}", + "m0170": "点「立即更新」自动下载安装", + "m0171": "已是最新版本", + "m0173": "更新中…", + "m0177": "已下载 v{0},正在重启完成安装…", + "m0178": "资源策略已切换", + "m0179": "下一次打开应用生效", + "m0180": "先选一个容量", + "m0181": "「自动」表示由软件按磁盘余量决定,不需要扩容", + "m0182": "立刻把安卓数据分区扩容?", + "m0183": "将把 /data 调整为 {0} GB(就地扩容,已装应用和存档都会保留)。", + "m0184": "扩容需要先关掉正在运行的安卓应用,完成后下次打开应用会重新引导,约多花十几秒。", + "m0185": "开始扩容", + "m0186": "扩容中…", + "m0187": "数据分区已扩容", + "m0188": "扩容失败", + "m0189": "现在约 {0} GB 可用", + "m0191": "已恢复系统组件", + "m0192": "深度裁剪关闭,后台包正在恢复", + "m0193": "ARM 兼容模式已开启", + "m0194": "还需要下载 Google Play 兼容镜像(Android 14,约 1.5GB),点击下方按钮开始", + "m0196": "重启运行时后生效", + "m0197": "已切全屏", + "m0198": "已切竖屏窗口", + "m0199": "画面大小已更新", + "m0200": "下次打开应用或切换应用时生效", + "m0201": "已补齐快捷方式", + "m0202": "快捷方式都还在", + "m0203": "桌面与开始菜单入口正常", + "m0204": "诊断报告已导出", + "m0205": "导出失败", + "m0206": "已经是精简状态", + "m0207": "没有可回收的旧镜像或快照。", + "m0208": "清理完成", + "m0209": "释放约 {0} MB({1} 项),已安装应用保留。", + "m0210": "共 {0} · 可回收 {1}({2} 项)", + "m0211": "清理失败", + "m0212": "重置安卓数据?", + "m0213": "这会把安卓系统还原成「刚装好系统」的干净状态:", + "m0214": "• 删除全部已安装的第三方 app(含红果、游戏等)及其数据、登录态\n", + "m0215": "• 删除当前 AVD 的快速启动快照\n", + "m0216": "• 系统镜像本身不受影响,重新打开应用会再次冷启动建数据\n\n", + "m0217": "此操作不可撤销。是否继续?", + "m0218": "取消", + "m0219": "继续", + "m0220": "再确认一次", + "m0221": "已安装的 app 和它们的数据将被永久删除,确定要重置吗?", + "m0222": "可放心的是:系统镜像、其它 AVD、本机的 APK 缓存和图标都不会动。", + "m0224": "确定重置", + "m0225": "重置失败", + "m0226": "请重试", + "m0227": "安卓数据已重置", + "m0228": "释放约 {0} MB,已安装应用记录已清空。", + "m0231": "修复虚拟机网络?", + "m0232": "应用里需要联网的页面一直黑屏/转圈,通常是安卓虚拟机没有网络。", + "m0233": "修复会按「代价从小到大」依次尝试:\n\n", + "m0234": "1. 冷启动虚拟机(不加载快照)后等待联网 —— 不影响任何数据\n", + "m0235": "2. 仍不行才重置安卓数据 —— 应用清单保留、点开会自动重装,\n", + "m0236": " 但应用内的登录态、追剧记录等会丢失\n\n", + "m0237": "只影响当前 AVD,系统镜像和本机 APK 缓存都不动。整个过程约 1–5 分钟。是否继续?", + "m0239": "开始修复", + "m0240": "网络仍未恢复", + "m0241": "重置后虚拟机还是没网,请检查本机网络/防火墙是否拦住了模拟器。", + "m0242": "网络已恢复", + "m0243": "已联网", + "m0244": "(为修复网络重置过安卓数据,点开应用会自动重装)", + "m0245": "(未动你的数据)", + "m0246": "修复失败", + "m0247": "扫描失败", + "m0249": "很干净", + "m0250": "没有发现旧版本残留。", + "m0251": "…另有 {0} 项", + "m0252": "清理旧版本残留?", + "m0253": "找到 {0} 项残留,约 {1} MB:", + "m0254": "{0}\n\n只删除「旧版本程序目录 / 已失效的卸载项 / 更新临时文件」,", + "m0255": "当前正在运行的版本和你的应用数据都不会动。", + "m0257": "开始清理", + "m0260": ",{0} 项被占用未删", + "m0261": "已清理旧版本残留", + "m0262": "删除 {0} 项,释放 {1} MB{2}。", + "m0265": "快捷方式已重建", + "m0266": "快捷方式都在", + "m0267": "没有需要重建的入口", + "m0268": "已选新目录", + "m0269": "点「保存并迁移」把旧文件搬过去", + "m0270": "请先填写或浏览目录", + "m0271": "共享目录已更新", + "m0272": "旧目录里已有的文件已迁移到新目录", + "m0273": "已保存", + "m0274": "保存失败", + "m0275": "确定卸载 ApkStart?\n\n将取消 .apk 文件关联并移除程序。此操作不可撤销。", + "m0276": "便携版无法自动卸载", + "m0277": "请手动删除程序目录({0})与数据目录({1})", + "m0279": "正在卸载…", + "m0280": "卸载程序已启动,ApkStart 即将退出", + "m0281": "开始下载 ARM 兼容镜像", + "m0283": "下载 ARM 兼容镜像", + "m0284": "ARM 兼容镜像就绪", + "m0286": "兼容镜像下载失败", + "m0291": "已就绪({0} GB)", + "m0292": "未下载", + "m0293": "数据目录已更新", + "m0294": "请重启 ApkStart 使其生效(旧数据不会自动搬移)", + "m0295": "安卓数据目录已更新", + "m0296": "请重启 ApkStart 使其生效。旧目录里的 apkstart.avd 可以整体搬到新目录,再删掉旧的。", + "m0297": "探测中…", + "m0298": "未发现已运行的运行时", + "m0299": "发现:{0}", + "m0300": "正在启动…", + "m0301": "已就绪:{0}", + "m0302": "启动失败:{0}", + "m0303": "已停止内置虚拟机", + "m0305": ".apk 关联已取消", + "m0307": ".apk 关联已开启", + "m0308": "现在双击 APK 会直接安装到 ApkStart", + "m0309": "操作失败", + "m0310": "系统镜像已导入", + "m0311": "导入失败", + "m0312": "虚拟磁盘已导入", + "m0313": "可直接启动运行时", + "m0315": "正在启动运行时…", + "m0316": "首次启动需要引导 Android 系统", + "m0319": "运行时会话已结束", + "m0320": "已同步共享目录", + "m0321": "同步失败", + "m0322": "运行时未就绪时会跳过同步", + "m0323": "只能拖入 .apk 文件", + "m0324": "安装失败:{0}", + "m0325": "安装失败", + "m0327": "夜深了", + "m0328": "早上好", + "m0329": "中午好", + "m0330": "下午好", + "m0331": "晚上好", + "m0332": "{0},欢迎回来", + "m0333": "运行时就绪,点开即用", + "m0334": "运行时会按需自动准备", + "m0335": "已装应用", + "m0336": "就绪", + "m0337": "待命", + "m0338": "安卓运行时", + "m0339": "设备 / 端口", + "m0340": "发现新版本 {0},可在「设置 → 软件更新」一键更新", + "m0341": "把 APK 拖进窗口就能安装,装完会自动生成桌面图标", + "m0342": "双击应用卡片即可在独立窗口中运行,右上角可旋转 / 缩放 / 关闭", + "m0343": "关窗口只是退出那个应用,安卓环境会留着,下次点开更快", + "m0344": "{0} 项待修复", + "m0345": "{0} 项提示", + "m0346": "\n
\n \n 环境自检通过,没有发现会影响运行的问题。\n \n \n
", + "m0347": "\n
\n 环境自检\n {0}\n {1}\n \n \n \n
\n
\n {2}\n
", + "m0348": "{0} 个严重", + "m0349": "{0} 个提示", + "m0350": "", + "m0351": "未找到内置安装包", + "m0352": "已打开微软官方下载页,请下载 x64 版本", + "m0353": "VC++ 运行库已安装", + "m0354": "安装未完成", + "m0356": "正在开启硬件加速…", + "m0357": "请在弹出的窗口点「是」", + "m0359": "请重启电脑以获得最佳性能", + "m0360": "未能开启", + "m0365": "释放约 {0} MB", + "m0366": "建议改到纯英文目录", + "m0367": "在 设置 → 系统集成 里修改「运行时数据目录」,再重启软件。", + "m0368": "正在补齐缺失的运行时组件…", + "m0369": "已清理残留锁文件", + "m0371": "暂不支持自动修复", + "m0372": "初始化失败", + "m0373": "应用", + "m0374": "正在启动安卓运行时…", + "m0375": "正在从 GitHub 下载运行时…", + "m0376": "运行时整包不完整,改用官方源补齐…", + "m0377": "GitHub 运行时不可用({0}),改用官方源…", + "m0378": "正在准备 adb…", + "m0379": "正在准备 Android 运行时…", + "m0380": "正在准备 Android 系统镜像…", + "m0381": "无法启动 PowerShell", + "m0382": "ApkStart 仍在后台运行", + "m0383": "点击托盘图标可重新打开;右键托盘可退出。", + "m0384": "打开 ApkStart", + "m0385": "还没有安装应用", + "m0386": "切回竖屏窗口", + "m0387": "全屏(自适应屏幕)", + "m0388": "重建桌面/开始菜单快捷方式", + "m0389": "同步共享目录", + "m0390": "设置", + "m0392": "检查更新", + "m0393": "发现新版本", + "m0394": "ApkStart {0} 已发布,当前版本 v{1}。现在更新吗?", + "m0396": "稍后", + "m0398": "退出", + "m0399": "ApkStart — 安卓应用运行器", + "m0400": "这个 APK 正在安装中,请稍等…", + "m0401": "正在安装中", + "m0402": "已有安装任务在进行,请等它装完。", + "m0404": "安装完成", + "m0405": "{0} 已安装{1}", + "m0406": ",桌面快捷方式已创建", + "m0408": "正在更新中", + "m0409": "已经是最新版本", + "m0410": "该版本没有可直接安装的包,已打开 GitHub 发布页", + "m0411": "准备下载…", + "m0412": "正在替换程序并重启…", + "m0414": "正在打开应用…", + "m0416": "选择数据存放目录", + "m0417": "选择要安装的 APK", + "m0418": "Android 应用", + "m0419": "应用不存在", + "m0420": "目录不能为空", + "m0421": "缺少 VC++ 运行库(x64)", + "m0422": "安卓运行时依赖微软 VC++ 2015-2022,缺失会导致模拟器无法启动(0xc0000135)。", + "m0423": "未启用硬件加速", + "m0424": "将回退软件模拟(能跑但很慢)。开启虚拟化后可获得最佳性能。", + "m0425": "运行时磁盘空间紧张", + "m0426": "「{0}」所在盘仅剩 {1} MB,运行时与镜像可能撑爆硬盘。", + "m0427": "运行时目录含中文/特殊字符", + "m0428": "部分运行时组件对中文路径敏感,可能引发启动失败。建议改到纯英文目录。", + "m0429": "adb 工具链缺失", + "m0430": "无法连接安卓运行时。", + "m0431": "运行时引擎未安装", + "m0432": "需要先部署运行时引擎(约 440MB)。", + "m0433": "系统镜像未就绪", + "m0434": "需要先下载 Android 系统镜像(约 1.5GB)。", + "m0435": "检测到上次残留的锁文件", + "m0436": "可能导致下一次启动被判定为「AVD 已被占用」。", + "m0437": "确认", + "m0439": "确定", + "m0440": "数据分区已调整", + "m0441": "安卓可用空间约 {0} GB{1}", + "m0442": "(运行时已重启)", + "m0444": "清理 {0} 项", + "m0445": "确认清理运行时缓存", + "m0446": "将释放约 {0} MB", + "m0447": "会先停止运行时,再删除旧镜像、当前 AVD 的快速启动快照、锁文件和非活动 AVD 的缓存。已安装的应用本身保留(只清快照),", + "m0448": "只删除旧镜像、旧快照、锁文件和非活动 AVD 的缓存,已安装的应用会保留。", + "m0449": "此操作不可撤销,建议先导出诊断报告。", + "m0450": "AEHD 驱动包未就绪", + "m0451": "已取消管理员授权(需要点「是」才能安装驱动)", + "m0452": "驱动安装失败(退出码 {0})", + "m0453": "硬件加速已可用,无需额外操作", + "m0454": "已开启「虚拟机平台」,请重启电脑后获得最佳性能", + "m0455": "「虚拟机平台」已开启,硬件加速即时生效", + "m0456": "无法开启系统虚拟化功能", + "m0457": "AEHD 驱动包准备失败", + "m0460": "AEHD 硬件加速已启用", + "m0461": "选择 Android-x86 / Bliss OS 系统镜像", + "m0463": "选择已安装 Android 的虚拟磁盘", + "m0464": "虚拟磁盘", + "m0465": "未知设置项 {0}", + "m0466": "agent scope 为空:不压制任何窗口(避免误伤其他软件)", + "m0467": "磁盘空间不足", + "m0468": "只剩 {0} GB。请到「设置 → 系统集成」把安卓数据目录/运行时目录改到其他盘,或先清理磁盘。", + "m0469": "磁盘只剩 {0} GB,安卓可能无法启动(设置 → 系统集成 可换盘)", + "m0470": "快捷方式已补齐", + "m0471": "运行时已自动准备完成", + "m0472": "[smoke] 截图失败", + "m0473": "未连接运行时", + "m0475": "搜索已安装应用", + "m0476": "安装 APK", + "m0477": "把 APK 拖进来就能装", + "m0478": "安装完成后会自动生成桌面图标,双击即以独立窗口运行,和普通 Windows 软件一样。", + "m0479": "已注册 .apk 关联时,在资源管理器里双击 APK 也会直接装到 ApkStart。", + "m0480": "选择 APK 文件", + "m0481": "正在检查运行时…", + "m0482": "你好", + "m0483": "ApkStart 已就绪", + "m0484": "下次启动不再自动弹出", + "m0485": "打开设置", + "m0486": "开始使用", + "m0487": "松手开始安装", + "m0488": "支持单个或多个 .apk 文件", + "m0490": "运行时与环境", + "m0491": "运行状态", + "m0492": "正在检查…", + "m0494": "高级:连接已有运行时", + "m0496": "安装 Android 到虚拟磁盘", + "m0497": "虚拟机窗口已打开。按下面 6 步操作一次,之后 ApkStart 会直接从磁盘启动,不再需要镜像。", + "m0498": "启动菜单", + "m0499": ":用", + "m0500": "选到", + "m0501": ",回车", + "m0502": ":选", + "m0503": ";询问是否使用 GPT 时选", + "m0504": "cfdisk 分区", + "m0505": "→ 容量直接回车 →", + "m0506": "→ 输入", + "m0507": "格式化", + "m0509": "→ 提示格式化时选", + "m0510": "引导与系统", + "m0511": "选", + "m0513": "完成", + "m0514": ":进度到 100% 后点下面的「我已安装完成」(ApkStart 会自动重启并接管)", + "m0515": "如果之后提示 adb 未连接:在虚拟机窗口按", + "m0516": "进入终端,执行", + "m0517": ",\n 再按", + "m0518": "回到图形界面。", + "m0519": "我已安装完成", + "m0520": "稍后再说", + "m0521": "自动模式(默认,无需设置)", + "m0522": "本机自适应参数", + "m0523": "正在检测…", + "m0524": "内存、核心数、渲染后端、端口、加速方式全部自动选择;启动失败会自动降级重试,不需要手动改。", + "m0525": "磁盘总占用(运行时 + 安卓数据 + 缓存)", + "m0526": "正在统计…", + "m0527": "清理可回收文件", + "m0528": "扫描并清掉旧版本留下的程序目录、失效的卸载项和更新临时文件", + "m0529": "清理旧版本残留", + "m0530": "安卓虚拟机没有网络时(应用里联网页面一直黑屏/转圈),用这个把安卓数据重置掉来修网络。应用清单会保留,点开应用会自动重装", + "m0531": "修复网络", + "m0532": "清空安卓 /data 用户数据 + 已装应用 + 快速启动快照,还原成刚装好系统的干净状态", + "m0533": "重置安卓数据", + "m0534": "连当前 AVD 的快速启动快照也一起清(会先停运行时,能真正回收几 GB 快照)", + "m0535": "系统镜像全机共享一份,应用数据用差分层保存;旧镜像、旧快照、日志和锁文件都可回收。日志超过上限会自动滚动,不会无限增长。「重置安卓数据」会真实删除数 GB 用户数据,仅保留系统本身。", + "m0536": "导出诊断报告", + "m0537": "重建快捷方式", + "m0538": "诊断报告用于排障;重建会补齐桌面 + 开始菜单入口", + "m0539": "开机自动启动(默认开启)", + "m0540": "随 Windows 登录常驻托盘,双击桌面图标秒开应用,无需手动启动。", + "m0541": "应用窗口", + "m0542": "显示模式", + "m0543": "竖屏窗口(可自由拉伸,记住每个应用的大小)", + "m0544": "全屏(铺满屏幕,保持手机比例两侧留黑)", + "m0545": "随时按", + "m0546": "或右键托盘切换;全屏只放大窗口,不改变画面比例,切换不重启应用。", + "m0547": "竖屏画面大小", + "m0548": "小 · 720 × 1280", + "m0549": "中 · 900 × 1600", + "m0550": "大 · 1080 × 1920", + "m0551": "超大 · 1440 × 2560", + "m0552": "窗口模式下可拖动边框任意缩放;这里调的是 Android 内部渲染尺寸,越小越省性能。", + "m0553": "鼠标键位", + "m0554": "左键 · 确认 / 点击", + "m0555": "模拟器原生把左键当成手指点击,按住拖动就是滑动,无需额外设置。", + "m0556": "右键 · 安卓返回", + "m0557": "模拟器默认把右键也当成一次点击(容易误触)。开启后右键只执行返回,不再触发点击。", + "m0558": "滚轮 · 滑动", + "m0559": "平滑(推荐)· 合并多次滚动为一次流畅滑动", + "m0560": "原生 · 交给模拟器逐格处理", + "m0561": "关闭 · 不拦截滚轮", + "m0562": "平滑模式把一小段时间内的滚动合并成一次滑动手势,避免逐格抽搐,也不会每格都拉起一个 adb 进程。", + "m0563": "鼠标侧键 · 返回 / 前进(默认关闭)", + "m0564": "侧键映射需要全局鼠标钩子。钩子只在 ApkStart 应用窗口处于前台时安装,失焦或关闭立即卸载,不会拖慢桌面。", + "m0565": "以上映射只在应用窗口前台生效;切回桌面或关闭窗口后,鼠标行为完全恢复 Windows 原生。", + "m0566": "性能与资源占用", + "m0567": "资源策略", + "m0568": "省资源 · 2 核 / 让出前台 / 540p(老机器、挂机)", + "m0569": "均衡 · 一半物理核 / 让出前台 / 720p(推荐)", + "m0570": "流畅 · 不限核数 / 与前台同优先级(视频、游戏)", + "m0571": "决定安卓虚拟机吃多少 CPU、画面多细腻。红果/抖音发卡就切「流畅」,只想挂着跑小应用就切「省资源」。改完下一次打开应用生效。", + "m0572": "画质(安卓内部渲染分辨率)", + "m0573": "自动(按加速状态)", + "m0574": "流畅 · 540 × 960", + "m0575": "均衡 · 720 × 1280", + "m0576": "高清 · 1080 × 1920", + "m0577": "安卓数据分区(应用与游戏装这里)", + "m0579": "立即扩容", + "m0580": "扩容是就地做的:已装应用与存档都保留,只是下次打开应用会多花十几秒。", + "m0581": "正在读取数据分区…", + "m0582": "网络加速(推荐)", + "m0583": "给安卓指定国内公共 DNS(阿里 / 腾讯)。安卓默认走主机的 DNS,主机挂着 VPN 或走公司网时解析会拖到几百毫秒,视频类应用首屏发慢。内网域名访问异常时可关掉。", + "m0584": "安卓声音", + "m0585": "让应用提示音、视频声音正常播放。仅在你完全不需要声音、想再省一点 CPU 时关闭。", + "m0586": "保留快速启动快照(推荐)", + "m0587": "第二次打开应用只要几秒,代价是磁盘上多一份约 2–3 GB 的", + "m0588": "。磁盘紧张可以关掉:只影响启动速度,不影响应用与存档。", + "m0589": "容器细节(高级)", + "m0590": "轻量容器", + "m0591": "收敛 vCPU 配额并降低进程优先级,CPU 优先让给 Windows 桌面。上面的「资源策略」已经会自动设置这一项。", + "m0592": "虚拟机优先级", + "m0593": "均衡(推荐)· 运行时让着前台窗口", + "m0594": "性能优先 · 运行时与前台同等优先级", + "m0595": "没有应用窗口时,容器一律自动降到最低优先级停放。", + "m0596": "深度裁剪系统后台", + "m0597": "额外禁用 Play 商店、设置向导、打印服务等后台包,只留打开应用必需的环境。若遇到应用提示缺少 Google Play 服务,关掉此开关会自动恢复。", + "m0598": "开机预热(推荐)", + "m0599": "启动 ApkStart 后就在后台把安卓环境引导好(窗口全程隐藏,不会看到模拟器)。之后点任何应用只剩一次「打开」,实测从 20 多秒降到几乎不用等。代价是空闲时常驻约 1GB 内存,关掉后第一次打开应用需要冷启动。", + "m0600": "安卓环境只开机一次并常驻;之后打开应用不再重跑整个系统,只启动该应用并停掉其他应用进程。关掉最后一个窗口会清后台、停放容器,下次点开仍是秒进。", + "m0601": "下载与更新源", + "m0602": "更新与下载源", + "m0603": "自动选择较快的源(推荐)", + "m0604": "只用 Gitee(国内通常最快)", + "m0605": "只用 GitHub", + "m0606": "「自动」会同时问 GitHub 与 Gitee 的最新版本,把每个下载地址都真下 256KB 测一次速度,再从最快的那个地址下载;其中一个源不通不影响更新。", + "m0607": "自动尝试公共加速镜像", + "m0608": "GitHub 直连太慢时,额外测速 ghfast.top / gh-proxy.com 这两个公开转发服务(只转发公开的发布文件,不涉及账号)。关掉后只用直连和你自己填的前缀。", + "m0609": "下载镜像前缀(可选,加速 GitHub)", + "m0610": "例如 https://ghfast.top/", + "m0611": "填了就只用你填的前缀,不再尝试上面那两个公共镜像;留空则由「自动」逻辑决定。下载器会把它拼在真实地址前面。", + "m0612": "下载线程数(1–16,默认 8)", + "m0613": "大文件按块并行下载。默认 8 线程已经能跑满大多数家宽;网络稳定时可以试 16。", + "m0614": "Gitee 更新源(可选)", + "m0615": "Gitee 只读 token(一般不用填)", + "m0616": "留空即可:公开仓库匿名也能读", + "m0617": "本项目仓库已公开,Gitee 的发行版列表匿名就能读,留空也能用 Gitee 源。\n 只有在「Gitee 被限流(返回 403/429)」或你想读私有分支时才需要填:\n gitee.com → 设置 → 私人令牌,只勾「projects」即可,它只用于读取发行版列表,\n 不会上传任何东西。", + "m0618": "高级参数(一般无需修改)", + "m0619": "运行时", + "m0620": "引擎模式", + "m0621": "自带运行时(推荐,QEMU + Android-x86,无第三方依赖)", + "m0622": "连接已有运行时(高级:复用 WSA / 其他模拟器的 adb 端口)", + "m0623": "adb 路径", + "m0624": "留空则自动在 PATH / Android SDK 中查找", + "m0625": "设备地址", + "m0626": "探测", + "m0627": "系统镜像(ISO)", + "m0628": "Android-x86 / Bliss OS 的 ISO 或 IMG 路径,留空使用默认位置", + "m0629": "内存 (MB,0 = 自动)", + "m0630": "CPU 核心(0 = 自动)", + "m0631": "空闲回收(分钟,0 = 不回收)", + "m0632": "通用兼容镜像(自动管理)", + "m0633": "现在只有一个 Android 14 通用镜像,x86 与 ARM 应用都能跑,首次遇到 ARM 应用时会自动下载,不需要手动切换。此开关仅用于旧配置兼容。", + "m0634": "兼容镜像", + "m0636": "下载兼容镜像", + "m0637": "启动运行时", + "m0638": "停止内置虚拟机", + "m0639": "安卓系统设置(不用进安卓桌面)", + "m0642": "刷新", + "m0643": "一键流畅优化", + "m0644": "关闭系统动画、放开后台进程上限并把当前应用加入电池白名单,低配电脑体感最明显。", + "m0645": "安卓虚拟机内部的无线网,关掉可省一点 CPU。", + "m0646": "飞行模式", + "m0647": "彻底断网(含移动数据),需要安静时打开。", + "m0648": "系统动画", + "m0649": "关掉后界面切换明显更快,功能不受影响。", + "m0650": "媒体音量", + "m0651": "字体大小", + "m0652": "小", + "m0653": "标准", + "m0654": "大", + "m0655": "特大", + "m0656": "显示大小", + "m0657": "紧凑(省性能)", + "m0659": "宽松", + "m0661": "安卓原生设置", + "m0662": "显示设置", + "m0663": "声音设置", + "m0664": "语言设置", + "m0665": "应用管理", + "m0666": "桌面与应用", + "m0667": "安装后自动创建桌面快捷方式", + "m0668": "快捷方式带真实应用图标,双击直接进应用", + "m0669": "同时添加到开始菜单", + "m0670": "Windows 搜索能搜到应用名,任务栏也按应用分组", + "m0671": "快捷方式目录", + "m0672": "留空 = 桌面", + "m0674": "桌面图标被误删或被安全软件拦掉时,点这里按当前设置全部补齐。", + "m0675": "文件互通", + "m0676": "本机共享目录", + "m0677": "浏览", + "m0679": "立即同步", + "m0680": "保存并迁移", + "m0681": "修改后旧目录里已有的文件会自动搬到新目录;正在运行的文件互通不受影响。", + "m0682": "安卓内对应目录", + "m0683": "两侧目录会持续双向同步;在安卓应用里保存到该目录的文件,会出现在本机共享目录中。", + "m0684": "系统集成", + "m0685": "关联 .apk 文件", + "m0686": "双击 APK 直接安装到 ApkStart(仅写入当前用户,无需管理员)", + "m0688": "运行时数据目录(系统镜像 · 已装应用 · 快照)", + "m0689": "默认:C 盘用户数据目录", + "m0692": "这里占用空间最大(数 GB)。改到非系统盘可大幅减轻 C 盘压力;修改后需重启软件,已存在的数据不会自动搬移(请手动复制后删除旧目录)。", + "m0693": "安卓数据目录(应用、游戏与快速启动快照)", + "m0694": "默认:运行时目录下的 avd 文件夹", + "m0697": "真正吃盘的是这一块:数据分区(默认 6 GB,可在上面对大)+ 快速启动快照(约 2–3 GB)。\n 磁盘紧张时把它指到另一个盘(如 D:),重启软件后生效;旧目录里的", + "m0698": "可整体搬过去再删。", + "m0699": "卸载 ApkStart", + "m0700": "一键卸载", + "m0701": "卸载会取消 .apk 关联并移除程序;便携版请按提示手动删除目录。", + "m0702": "软件更新", + "m0703": "当前版本", + "m0704": "尚未检查", + "m0707": "从 GitHub(RoutineMiku/apkstart)获取最新版本;轻量版会下载便携包并自动替换程序文件,\n 运行时、已装应用与快照都不受影响。", + "m0708": "关于", + "m0709": "微信公众号二维码", + "m0710": "扫码关注公众号", + "m0711": "抖音二维码", + "m0712": "抖音 · 日常MIKU", + "m0713": "双击即装的 Windows 安卓应用启动器:APK 安装、桌面快捷方式、独立窗口运行、文件双向互通。", + "m0714": "抖音 · @日常MIKU(联系作者)", + "m0715": "设置修改后立即生效", + "m0718": "横屏 / 竖屏(Ctrl+R)", + "m0719": "最小化", + "m0720": "最大化", + "m0721": "关闭", + "settings.language": "界面语言", + "settings.systemLanguage": "跟随系统", + "settings.languageNote": "切换语言后主窗口会重新加载;已打开的应用窗口下次打开时生效。", + "profile.summary": "自动:{0}GB 内存 · {1} 核 · {2} 渲染 · {3} · {4}{5} · 端口 {6}", + "profile.accelerated": "硬件加速", + "profile.snapshot": "按需快照", + "profile.coldBoot": "冷启动", + "profile.lite": " · 轻量容器" +} diff --git a/src/main.js b/src/main.js index fa3e115..82b6752 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,5 @@ 'use strict'; +const { t, resolveLocale, getCatalog, setLocale } = require('./core/i18n'); /** * ApkStart 主进程 * 职责:窗口 / 托盘 / 单实例 / 命令行分发(双击 apk、桌面快捷方式启动)/ IPC @@ -305,7 +306,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 || t('m0374'), ready: false, landscape: false }); currentAppPkg = pkg; } @@ -341,25 +342,25 @@ async function autoDeploy() { // 整包拿不到(没网 / 还没发过该资产 / 被限流)再逐个从官方源装。 if (needEngine && store.getSettings().runtime.preferGithubRuntime !== false) { try { - progress({ message: '正在从 GitHub 下载运行时…' }); + progress({ message: t('m0375') }); 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: t('m0376') }); } catch (e) { - progress({ message: `GitHub 运行时不可用(${e.message}),改用官方源…` }); + progress({ message: t('m0377', [e.message]) }); } } if (!env.adb.ok) { - progress({ message: '正在准备 adb…' }); + progress({ message: t('m0378') }); await runtime.provisioner.installAdb(progress); } if (!env.emulator.ok) { - progress({ message: '正在准备 Android 运行时…' }); + progress({ message: t('m0379') }); await runtime.provisioner.installEmulator(progress); } if (!env.sysimg.ok) { - progress({ message: '正在准备 Android 系统镜像…' }); + progress({ message: t('m0380') }); await runtime.provisioner.installSystemImage(progress); } const after = await runtime.provisioner.checkEnv(); @@ -402,7 +403,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: t('m0381') })); }); } @@ -455,7 +456,7 @@ function createWindow({ show = true } = {}) { if (quitting) return; e.preventDefault(); mainWindow.hide(); - notify('ApkStart 仍在后台运行', '点击托盘图标可重新打开;右键托盘可退出。'); + notify(t('m0382'), t('m0383')); }); if (IS_DEV) mainWindow.webContents.openDevTools({ mode: 'detach' }); @@ -493,21 +494,21 @@ function buildTray() { const refresh = () => { const apps = store.getApps().slice(0, 8); const menu = Menu.buildFromTemplate([ - { label: '打开 ApkStart', click: () => showWindow() }, + { label: t('m0384'), click: () => showWindow() }, { type: 'separator' }, ...(apps.length ? apps.map((a) => ({ label: a.label, click: () => runLaunch(a.package) })) - : [{ label: '还没有安装应用', enabled: false }]), + : [{ label: t('m0385'), enabled: false }]), { type: 'separator' }, { - label: displaySettings().mode === 'fullscreen' ? '切回竖屏窗口' : '全屏(自适应屏幕)', + label: displaySettings().mode === 'fullscreen' ? t('m0386') : t('m0387'), click: () => { toggleDisplayMode(); }, }, - { label: '重建桌面/开始菜单快捷方式', click: () => { installer.ensureShortcuts().catch(() => {}); } }, - { label: '同步共享目录', click: () => sharer.sync() }, - { label: '设置', click: () => { showWindow(); send('ui:navigate', 'settings'); } }, + { label: t('m0388'), click: () => { installer.ensureShortcuts().catch(() => {}); } }, + { label: t('m0389'), click: () => sharer.sync() }, + { label: t('m0390'), click: () => { showWindow(); send('ui:navigate', 'settings'); } }, { - label: updateInfo && updateInfo.hasUpdate ? `发现新版本 ${updateInfo.tag}` : '检查更新', + label: updateInfo && updateInfo.hasUpdate ? t('m0169', [updateInfo.tag]) : t('m0392'), click: async () => { showWindow(); send('ui:navigate', 'settings'); @@ -516,22 +517,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: t('m0393'), + message: t('m0394', [info.tag, app.getVersion()]), detail: (info.notes || '').slice(0, 600), - buttons: ['立即更新', '稍后'], + buttons: [t('m0158'), t('m0396')], defaultId: 0, cancelId: 1, }); if (r.response === 0) runUpdate(); } else if (info && !info.hasUpdate) { - notify('已是最新版本', `ApkStart v${app.getVersion()}`); + notify(t('m0171'), `ApkStart v${app.getVersion()}`); } }, }, { type: 'separator' }, { - label: '退出', + label: t('m0398'), click: async () => { try { appWin.close({ keepApp: true }); } catch (_) { /* ignore */ } quitting = true; @@ -541,7 +542,7 @@ function buildTray() { ]); tray.setContextMenu(menu); }; - tray.setToolTip('ApkStart — 安卓应用运行器'); + tray.setToolTip(t('m0399')); // Windows 托盘的惯例是单击就打开主窗口(双击当然也可以)。 // 之前只绑了 double-click,用户单击没反应,会以为托盘图标坏了。 // 右键才出菜单,所以这里单击直接 showWindow 不会和菜单冲突。 @@ -561,11 +562,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: t('m0400') }); + return { ok: false, error: t('m0401') }; } - send('toast', { type: 'info', message: '已有安装任务在进行,请等它装完。' }); - return { ok: false, error: '已有安装任务在进行' }; + send('toast', { type: 'info', message: t('m0402') }); + return { ok: false, error: t('m0085') }; } installBusy = filePath; send('install:start', { file: filePath }); @@ -574,7 +575,7 @@ async function runInstall(filePath) { send('install:done', result); // 新建的桌面图标要让资源管理器立刻刷新,否则用户会以为「没创建快捷方式」 windowctl.refreshShell().catch(() => {}); - notify('安装完成', `${result.app.label} 已安装${result.app.shortcutPath ? ',桌面快捷方式已创建' : ''}`); + notify(t('m0404'), t('m0405', [result.app.label, result.app.shortcutPath ? t('m0406') : ''])); return result; } catch (e) { send('install:error', { message: e.message, file: filePath }); @@ -585,7 +586,7 @@ async function runInstall(filePath) { lastInstallErr = { msg: e.message, at: now }; // 窗口在前台时用户已经看到界面里的提示,不必再弹系统通知 if (!mainWindow || !mainWindow.isVisible() || mainWindow.isMinimized()) { - notify('安装失败', e.message.slice(0, 120)); + notify(t('m0325'), e.message.slice(0, 120)); } } throw e; @@ -599,20 +600,20 @@ async function runInstall(filePath) { * 界面与托盘菜单共用这一条路径。 */ async function runUpdate() { - if (updateBusy) return { ok: false, error: '正在更新中' }; + if (updateBusy) return { ok: false, error: t('m0408') }; 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: t('m0409') }; if (!info.asset) { shell.openExternal(info.htmlUrl || Updater.repoUrl()).catch(() => {}); - return { ok: false, error: '该版本没有可直接安装的包,已打开 GitHub 发布页', manual: true }; + return { ok: false, error: t('m0410'), manual: true }; } - sendProgress({ stage: 'download', percent: 0, message: '准备下载…' }); + sendProgress({ stage: 'download', percent: 0, message: t('m0411') }); const job = await updater.prepare(info); - sendProgress({ stage: 'apply', percent: 100, message: '正在替换程序并重启…' }); + sendProgress({ stage: 'apply', percent: 100, message: t('m0412') }); updater.apply(job); // 给界面一点时间显示「即将重启」,然后走正常退出流程(before-quit 会把模拟器窗口藏好) setTimeout(() => { quitting = true; app.quit(); }, 900); @@ -631,7 +632,7 @@ async function runLaunch(pkg) { appLog(`launch start ${pkg}`); try { // 1) 先把「应用窗口」打开显示启动动画:整个过程看不到安卓桌面,也看不到模拟器窗口 - openAppWindow(pkg, '正在启动安卓运行时…'); + openAppWindow(pkg, t('m0374')); // 开机阶段把模拟器主窗 + qemu / netsimd 的控制台黑窗藏起来,避免任何闪现。 // 压制是常驻的(embed 后它会自动跳过被嵌入的那一个窗口,见 appwin.embed)。 // 这里必须 await:不等压制生效就往下走,黑窗会在启动瞬间露出来。 @@ -643,7 +644,7 @@ async function runLaunch(pkg) { onMark: mark, beforeStart: async (rt) => { mark('engine ready'); - appWin.setState({ tip: '正在打开应用…' }); + appWin.setState({ tip: t('m0414') }); // 沉浸式 + 关动画 + 收起锁屏:只做一次,之后每次启动都秒进 await rt.client.prepareForeground().catch(() => {}); mark('foreground ready'); @@ -694,7 +695,7 @@ async function runLaunch(pkg) { // 下次点应用还是走同一条隐藏 → embed 的路径。 if (winAgent) armAgent(winAgent).catch(() => {}); showWindow(); - send('toast', { type: 'error', message: `启动失败:${e.message}` }); + send('toast', { type: 'error', message: t('m0302', [e.message]) }); return { ok: false, error: e.message }; } } @@ -764,7 +765,7 @@ function registerIpc() { // 选文件夹(用于把运行时/镜像/已装应用挪到非系统盘) ipcMain.handle('dialog:openDir', async (_e, defaultPath) => { const r = await dialog.showOpenDialog(mainWindow, { - title: '选择数据存放目录', + title: t('m0416'), defaultPath: defaultPath || store.DATA_DIR, properties: ['openDirectory', 'createDirectory'], }); @@ -794,8 +795,8 @@ function registerIpc() { ipcMain.handle('apk:pick', async () => { const r = await dialog.showOpenDialog(mainWindow, { - title: '选择要安装的 APK', - filters: [{ name: 'Android 应用', extensions: ['apk', 'apks', 'xapk'] }], + title: t('m0417'), + filters: [{ name: t('m0418'), extensions: ['apk', 'apks', 'xapk'] }], properties: ['openFile', 'multiSelections'], }); if (r.canceled) return []; @@ -818,7 +819,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(t('m0419')); const p = installer.createShortcut(appRecord); store.upsertApp({ package: pkg, shortcutPath: p }); return { ok: true, path: p }; @@ -846,9 +847,18 @@ function registerIpc() { shell.openPath(store.APK_DIR); }); + ipcMain.handle('i18n:get', () => ({ + locale: resolveLocale(store.getSettings().language, app.getLocale()), + preference: store.getSettings().language, + messages: getCatalog(resolveLocale(store.getSettings().language, app.getLocale())), + })); ipcMain.handle('settings:get', () => store.getSettings()); ipcMain.handle('settings:set', (_e, patch) => { const next = store.saveSettings(patch); + if (patch && patch.language !== undefined) { + setLocale(resolveLocale(next.language, app.getLocale())); + tray?.setToolTip(t('m0399')); + } if (patch && patch.autoStart !== undefined) applyAutoStart(); if (patch && patch.display && appWin && appWin.isOpen) { // 竖屏基准尺寸变了:只影响后续打开的窗口大小,不打断正在跑的应用 @@ -885,7 +895,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: t('m0420') }; const newDir = dir.trim(); const oldDir = store.getSettings().sharedDir; try { @@ -981,47 +991,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: t('m0421'), + hint: t('m0422'), 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: t('m0423'), + hint: (accel.note || t('m0424')), 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: t('m0425'), + hint: t('m0426', [runtime.provisioner.paths().root, free]), 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: t('m0427'), + hint: t('m0428'), 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: t('m0429'), hint: t('m0430'), 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: t('m0431'), hint: t('m0432'), 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: t('m0433'), hint: t('m0434'), 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: t('m0435'), hint: t('m0436'), fix: 'lock' }); } } catch (_) { /* 自检失败不阻断使用 */ } return { issues, accel, free }; @@ -1063,10 +1073,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 || t('m0437'), message: opts.message || '', detail: opts.detail || '', - buttons: ['取消', opts.okLabel || '确定'], + buttons: [t('m0218'), opts.okLabel || t('m0439')], defaultId: 1, cancelId: 0, noLink: true, @@ -1102,7 +1112,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(t('m0440'), t('m0441', [want, stopped ? t('m0442') : ''])); return { ok: !!(r && r.ok), stopped, ...r, ...runtime.emulator.dataInfo() }; } catch (e) { return { ok: false, error: e.message }; @@ -1116,15 +1126,15 @@ function registerIpc() { const preview = await runtime.cleanup(pass); const { response } = await dialog.showMessageBox(mainWindow, { type: 'warning', - buttons: ['取消', `清理 ${preview.candidates.length} 项`], + buttons: [t('m0218'), t('m0444', [preview.candidates.length])], defaultId: 0, cancelId: 0, - title: '确认清理运行时缓存', - message: `将释放约 ${(preview.candidates.reduce((n, c) => n + c.size, 0) / 1048576).toFixed(0)} MB`, + title: t('m0445'), + message: t('m0446', [(preview.candidates.reduce((n, c) => n + c.size, 0) / 1048576).toFixed(0)]), detail: (opts.includeSnapshots - ? '会先停止运行时,再删除旧镜像、当前 AVD 的快速启动快照、锁文件和非活动 AVD 的缓存。已安装的应用本身保留(只清快照),' - : '只删除旧镜像、旧快照、锁文件和非活动 AVD 的缓存,已安装的应用会保留。') - + '此操作不可撤销,建议先导出诊断报告。', + ? t('m0447') + : t('m0448')) + + t('m0449'), }); if (response !== 1) return { ...preview, cancelled: true }; return runtime.cleanup({ apply: true, aggressive: !!opts.aggressive, includeSnapshots: !!opts.includeSnapshots }); @@ -1246,7 +1256,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 || t('m0450')); } await new Promise((resolve, reject) => { @@ -1257,7 +1267,7 @@ function registerIpc() { windowsHide: true, }); proc.on('exit', (code) => (code === 0 ? resolve() : reject(new Error( - code === 2 ? '已取消管理员授权(需要点「是」才能安装驱动)' : `驱动安装失败(退出码 ${code})`, + code === 2 ? t('m0451') : t('m0452', [code]), )))); proc.on('error', (e) => reject(e)); }); @@ -1277,7 +1287,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: t('m0453') }; const hv = await runtime.provisioner.hypervisor(); if (hv.dll) { @@ -1291,8 +1301,8 @@ function registerIpc() { restartNeeded: r.restart || r2.restart, accel: after, note: ok - ? (r.restart ? '已开启「虚拟机平台」,请重启电脑后获得最佳性能' : '「虚拟机平台」已开启,硬件加速即时生效') - : (r.error || r2.error || '无法开启系统虚拟化功能'), + ? (r.restart ? t('m0454') : t('m0455')) + : (r.error || r2.error || t('m0456')), }; } @@ -1300,7 +1310,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 || t('m0457') }; const bat = path.join(runtime.provisioner.paths().aehdDir, 'silent_install.bat'); await new Promise((resolve, reject) => { const { spawn: sp } = require('child_process'); @@ -1308,12 +1318,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 ? t('m0451') : t('m0452', [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 ? t('m0460') : after.note }; } catch (e) { return { ok: false, error: e.message }; } @@ -1358,8 +1368,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: t('m0461'), + filters: [{ name: t('m0034'), extensions: ['iso', 'img'] }], properties: ['openFile'], }); if (r.canceled) return { ok: false, canceled: true }; @@ -1372,8 +1382,8 @@ function registerIpc() { ipcMain.handle('provision:importDisk', async () => { const r = await dialog.showOpenDialog(mainWindow, { - title: '选择已安装 Android 的虚拟磁盘', - filters: [{ name: '虚拟磁盘', extensions: ['qcow2', 'img', 'vmdk', 'raw'] }], + title: t('m0463'), + filters: [{ name: t('m0464'), extensions: ['qcow2', 'img', 'vmdk', 'raw'] }], properties: ['openFile'], }); if (r.canceled) return { ok: false, canceled: true }; @@ -1539,7 +1549,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: t('m0465', [key]) }; } } catch (e) { return { ok: false, error: e.message }; } }); @@ -1612,6 +1622,7 @@ if (!gotLock) { }); app.whenReady().then(async () => { + setLocale(resolveLocale(store.getSettings().language, app.getLocale())); store.ensureDirs(); runtime = new Runtime(APP_ROOT); // 启动即留一行痕迹:出问题时用户把 app.log 发过来就能看到版本与数据目录 @@ -1679,7 +1690,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(t('m0466')); return agent.bootHide().catch(() => ({ ok: false })); }; @@ -1726,8 +1737,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(t('m0467'), t('m0468', [(freeMB / 1024).toFixed(1)])); + send('toast', { type: 'error', message: t('m0469', [(freeMB / 1024).toFixed(1)]) }); }, 3000).unref?.(); } } @@ -1736,7 +1747,7 @@ if (!gotLock) { installer.ensureShortcuts().then((fixed) => { if (fixed && fixed.length) { windowctl.refreshShell().catch(() => {}); - notify('快捷方式已补齐', fixed.join('、')); + notify(t('m0470'), fixed.join('、')); } }).catch(() => {}); @@ -1786,7 +1797,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: t('m0471') }); }).catch(() => {}); }, 20000).unref?.(); @@ -1872,7 +1883,7 @@ if (!gotLock) { await snap(out.replace(/\.png$/i, '-about.png')); } } catch (e) { - console.error('[smoke] 截图失败', e); + console.error(t('m0472'), e); } quitting = true; app.quit(); diff --git a/src/preload.js b/src/preload.js index a41cc68..b534d19 100644 --- a/src/preload.js +++ b/src/preload.js @@ -5,6 +5,7 @@ const invoke = (channel, payload) => ipcRenderer.invoke(channel, payload); contextBridge.exposeInMainWorld('apkstart', { info: () => invoke('app:info'), + getI18n: () => invoke('i18n:get'), listApps: () => invoke('apps:list'), pickApk: () => invoke('apk:pick'), parseApk: (p) => invoke('apk:parse', p), diff --git a/src/ui/app.js b/src/ui/app.js index 143112f..9aa75e4 100644 --- a/src/ui/app.js +++ b/src/ui/app.js @@ -1,4 +1,5 @@ 'use strict'; +const tr = (key, values) => window.i18n.t(key, values); const api = window.apkstart; const $ = (id) => document.getElementById(id); @@ -35,11 +36,11 @@ 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)} 天前`; - return new Date(ts).toLocaleDateString('zh-CN'); + if (d < 60000) return tr('m0000'); + if (d < 3600000) return tr('m0001', [Math.floor(d / 60000)]); + if (d < 86400000) return tr('m0002', [Math.floor(d / 3600000)]); + if (d < 86400000 * 30) return tr('m0003', [Math.floor(d / 86400000)]); + return new Date(ts).toLocaleDateString(window.i18n.locale); } /** @@ -134,8 +135,8 @@ function renderGrid() { if (!hasAny) { const s = state.status; $('emptyHint').textContent = s && s.adbOk === false - ? '首次使用:打开右上角设置 → 运行时 → 一键完成,再回来安装应用即可。' - : '提示:所有应用共享同一个 Android 运行时,所以第二个应用的启动几乎是瞬时的。'; + ? tr('m0004') + : tr('m0005'); grid.innerHTML = ''; return; } @@ -143,15 +144,15 @@ function renderGrid() { grid.innerHTML = apps.map((a) => `
${a.pinned ? '★' : ''} - ${a.running ? '' : ''} + ${a.running ? tr('m0006') : ''}
${a.iconUrl ? `` : `
${esc(initialOf(a.label))}
`}
${esc(a.label)}
-
${a.running ? '运行中' : `v${esc(a.versionName)}${a.lastLaunchAt ? ' · ' + relTime(a.lastLaunchAt) : ''}`}
-
${a.running ? '切换到前台' : '打开'}
+
${a.running ? tr('m0007') : `v${esc(a.versionName)}${a.lastLaunchAt ? ' · ' + relTime(a.lastLaunchAt) : ''}`}
+
${a.running ? tr('m0008') : tr('m0009')}
`).join(''); } @@ -170,7 +171,7 @@ async function refreshApps() { function updateStats() { const n = state.apps.length; - $('statusStats').textContent = n ? `${n} 个应用` : '暂无应用'; + $('statusStats').textContent = n ? tr('m0010', [n]) : tr('m0011'); } async function refreshStatus() { @@ -183,57 +184,57 @@ async function refreshStatus() { if (!s.adbOk) { dot.className = 'dot err'; - text.textContent = '运行时未部署 —— 打开设置 → 运行时,一键完成'; - tag.textContent = '需要部署'; + text.textContent = tr('m0012'); + tag.textContent = tr('m0013'); tag.className = 'brand-tag'; } else if (s.deviceOnline) { dot.className = 'dot ok'; - text.textContent = `运行时已就绪 · Android ${s.android || '?'}${s.freeform ? ' · 独立窗口已启用' : ''}`; + text.textContent = tr('m0014', [s.android || '?', s.freeform ? tr('m0015') : '']); 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 = tr('m0016'); + tag.textContent = tr('m0017'); tag.className = 'brand-tag'; } else if (s.needsSetup === 'accel') { dot.className = 'dot warn'; - text.textContent = '未检测到硬件加速:已自动回退软件模拟(首次开机较慢)'; - tag.textContent = '软件模拟'; + text.textContent = tr('m0018'); + tag.textContent = tr('m0019'); tag.className = 'brand-tag'; } else if (s.needsSetup === 'sysimg') { dot.className = 'dot err'; - text.textContent = '缺少 Android 14 通用系统镜像 —— 设置 → 运行时里下载(约 1.5GB,只下一次)'; - tag.textContent = '待下载镜像'; + text.textContent = tr('m0020'); + tag.textContent = tr('m0021'); tag.className = 'brand-tag'; } else if (s.needsSetup === 'emulator') { dot.className = 'dot err'; - text.textContent = '运行时引擎未安装 —— 打开设置 → 运行时一键完成'; - tag.textContent = '需要部署'; + text.textContent = tr('m0022'); + tag.textContent = tr('m0013'); tag.className = 'brand-tag'; } else if (s.needsSetup === 'boot') { dot.className = 'dot warn'; - text.textContent = '运行时已就绪 —— 安装 APK 或双击桌面图标会自动开机'; - tag.textContent = '待开机'; + text.textContent = tr('m0024'); + tag.textContent = tr('m0025'); tag.className = 'brand-tag'; } else if (s.installing) { dot.className = 'dot warn'; - text.textContent = '安装程序运行中 —— 请在虚拟机窗口里完成安装'; - tag.textContent = '安装中'; + text.textContent = tr('m0026'); + tag.textContent = tr('m0027'); tag.className = 'brand-tag'; } else { dot.className = 'dot'; text.textContent = s.installed - ? '运行时待机(安装或打开应用时会自动拉起)' - : '运行时尚未安装 Android 系统 —— 打开设置 → 运行时继续'; - tag.textContent = s.installed ? '待机' : '待安装'; + ? tr('m0028') + : tr('m0029'); + tag.textContent = s.installed ? tr('m0030') : tr('m0031'); tag.className = 'brand-tag'; } $('statusDevice').textContent = s.deviceOnline ? (s.serial || '') : ''; renderSetup(s); } catch (e) { - $('statusText').textContent = `状态检查失败:${e.message}`; + $('statusText').textContent = tr('m0032', [e.message]); } } @@ -286,15 +287,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(tr('m0033')); + if (!imgDone0) missing.push(tr('m0034')); stateText.textContent = s.deviceOnline - ? `已就绪 · Android ${s.android || ''}` + ? tr('m0035', [s.android || '']) : s.phase === 'booting' || s.vmStarting - ? '正在引导 Android…' + ? tr('m0016') : allDone0 - ? '组件齐全 · 待开机(打开应用时自动拉起)' - : `缺少 ${missing.join('、')}${accelOk ? '' : '(无硬件加速,将回退软件模拟)'}`; + ? tr('m0037') + : tr('m0038', [missing.join(', '), accelOk ? '' : tr('m0039')]); stateText.title = s.autoProfile || accel.note || hv.note || ''; } @@ -307,39 +308,39 @@ function renderSetup(s) { // 且首次启动会在后台自动跑,用户不需要逐步点击。 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(tr('m0040')); + if (!emuDone) left.push(tr('m0041')); + if (!imgDone) left.push(tr('m0034')); const step = { key: 'all', index: 1, - title: allDone ? '一切就绪' : '一键完成', - tag: allDone ? '已自动完成' : '自动', + title: allDone ? tr('m0043') : tr('m0044'), + tag: allDone ? tr('m0045') : tr('m0046'), done: allDone, busy: !!busy, desc: s.deviceOnline - ? `Android ${s.android || ''} 已就绪 · 应用窗口会按屏幕自动适配` + ? tr('m0047', [s.android || '']) : booting - ? '正在引导 Android…' + ? tr('m0016') : allDone - ? '组件齐全,双击任意 APK 或桌面图标即会自动开机' - : `还差 ${left.join('、')};点下面「一键完成」,会在后台一次装好并自动开机`, + ? tr('m0049') + : tr('m0050', [left.join(', ')]), actions: s.deviceOnline - ? '' + ? tr('m0051') : '', }; $('setupSteps').innerHTML = stepRow(step); $('btnDeployAll').disabled = !!busy; - $('btnDeployAll').textContent = busy ? '准备中…' : (allDone ? '已完成 · 点此复查' : '一键完成'); + $('btnDeployAll').textContent = busy ? tr('m0052') : (allDone ? tr('m0053') : tr('m0044')); // 「上次为什么没起来」优先显示落盘的失败记录:进程重启后内存里的 diagnosis 是空的, // 只显示 autoProfile 会让用户完全看不出原因(issue #20/#25 的报告就是这样)。 const lf = s.lastFailure; const lastMsg = (lf && lf.diagnosis && lf.diagnosis.title) - ? `上次启动失败:${lf.diagnosis.title}` - : (s.diagnosis ? `上次启动自动诊断:${s.diagnosis.title}` : ''); + ? tr('m0055', [lf.diagnosis.title]) + : (s.diagnosis ? tr('m0056', [s.diagnosis.title]) : ''); $('setupNote').textContent = lastMsg - || s.autoProfile || '全自动:无需选择镜像、无需启用加速、无需调内存'; + || s.autoProfile || tr('m0057'); } function store_getDiskSize() { @@ -380,18 +381,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: tr('m0058'), body: (r && r.error) || tr('m0059'), type: 'error', sticky: true }); return; } await refreshStatus(); - toast({ title: '组件已就绪,正在自动开机…', body: '首次约 1-3 分钟,之后几秒即可。', type: 'info' }); + toast({ title: tr('m0060'), body: tr('m0061'), 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: tr('m0062'), body: tr('m0063'), type: 'ok' } + : { title: tr('m0064'), body: (started && started.error) || tr('m0065'), type: 'error', sticky: true }); } catch (e) { - toast({ title: '准备失败', body: e.message, type: 'error', sticky: true }); + toast({ title: tr('m0058'), body: e.message, type: 'error', sticky: true }); } finally { setStepBusy('all', false); await refreshStatus(); @@ -400,7 +401,7 @@ async function deployAll() { } async function runStep(key) { - if (state.setupBusy) return { ok: false, error: '已有部署任务进行中' }; + if (state.setupBusy) return { ok: false, error: tr('m0067') }; setStepBusy(key, true); let r; try { @@ -409,18 +410,18 @@ async function runStep(key) { else if (key === 'sysimg') r = await api.installSysimg(); else if (key === 'accel') { const t = toast({ - title: '正在安装硬件加速驱动…', - body: '请在弹出的是否允许窗口点「是」', + title: tr('m0068'), + body: tr('m0069'), 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: tr('m0070'), type: 'ok' }); + else t.update({ title: tr('m0071'), 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: tr('m0072') }; } catch (e) { r = { ok: false, error: e.message }; } @@ -432,12 +433,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: tr('m0073'), 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: tr('m0074'), body: tr('m0075'), type: 'ok', sticky: true }); } async function markInstalled() { @@ -445,7 +446,7 @@ async function markInstalled() { await api.runtimeStop(); $('setupGuide').hidden = true; await refreshStatus(); - toast({ title: '安装完成,运行时已接管', body: '现在可以直接把 APK 拖进来安装了。', type: 'ok' }); + toast({ title: tr('m0076'), body: tr('m0077'), type: 'ok' }); } @@ -454,18 +455,18 @@ async function markInstalled() { /** 把安装失败翻译成「下一步该做什么」,只对最常见的几种做提示,其它原样显示 */ function friendlyInstallErrorText(msg) { - const s = String(msg || '未知错误'); + const s = String(msg || tr('m0078')); if (/INSUFFICIENT_STORAGE|not enough space|空间不足|ENOSPC/i.test(s)) { - return `${s}\n安卓数据分区不够用:设置 → 性能与资源占用 → 安卓数据分区调大后点「立即扩容」。`; + return tr('m0079', [s]); } - if (/NO_MATCHING_ABIS/i.test(s)) return `${s}\n这个包只有 ARM 库,正在自动切换兼容镜像后重试。`; + if (/NO_MATCHING_ABIS/i.test(s)) return tr('m0080', [s]); if (/INSTALL_FAILED_ALREADY_EXISTS|signatures do not match/i.test(s)) { - return `${s}\n同包名但签名不同(常见于从别的渠道装过)。在应用卡片右键「卸载」后再装。`; + return tr('m0081', [s]); } - if (/INSTALL_FAILED_VERSION_DOWNGRADE/i.test(s)) return `${s}\n已装版本更高,先在右键菜单里卸载再装。`; + if (/INSTALL_FAILED_VERSION_DOWNGRADE/i.test(s)) return tr('m0082', [s]); 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 tr('m0083', [s]) + + tr('m0084'); } return s; } @@ -478,7 +479,7 @@ let installToast = null; async function installFiles(paths) { if (!paths || !paths.length) return; if (state.installing) { - toast({ title: '已有安装任务在进行', body: '请等当前应用装完再继续。' }); + toast({ title: tr('m0085'), body: tr('m0086') }); return; } state.installing = true; @@ -487,21 +488,21 @@ 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: tr('m0087', [name]), body: tr('m0088'), 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 ? tr('m0087', [pr.meta.label]) : tr('m0087', [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: tr('m0091', [r.app.label]), body: r.app.shortcutPath ? tr('m0092') : tr('m0093'), 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: tr('m0094', [r.app.label]), body: r.warnings.join(' '), type: 'error', sticky: false }); } ok++; } catch (e) { @@ -529,20 +530,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: tr('m0095', [app ? app.label : pkg]), + body: tr('m0096'), sticky: true, spinner: true, }); const r = await api.launch(pkg); if (r.ok) { // 应用有自己的窗口,主界面自动收起;这里只留一句提示,然后隐藏窗口 - t.update({ title: '已启动', body: '应用正在独立窗口中运行(顶部可旋转屏幕)。', type: 'ok' }); + t.update({ title: tr('m0097'), body: tr('m0098'), 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: tr('m0099'), body: r.error, type: 'error' }); setTimeout(t.dismiss, 6000); } } @@ -550,38 +551,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: tr('m0100'), body: r.path, type: 'ok' }); refreshApps(); } catch (e) { - toast({ title: '创建快捷方式失败', body: e.message, type: 'error' }); + toast({ title: tr('m0101'), 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(tr('m0102', [app.label])); if (!ok) return; try { await api.uninstall(pkg); - toast({ title: '已卸载', body: app.label, type: 'ok' }); + toast({ title: tr('m0103'), body: app.label, type: 'ok' }); refreshApps(); } catch (e) { - toast({ title: '卸载失败', body: e.message, type: 'error' }); + toast({ title: tr('m0104'), body: e.message, type: 'error' }); } } /* ---------------- 右键菜单 ---------------- */ const MENU_ITEMS = [ - { key: 'open', label: '打开应用', kbd: 'Enter' }, - { key: 'shortcut', label: '创建桌面快捷方式' }, - { key: 'pin', label: '固定到顶部' }, + { key: 'open', label: tr('m0105'), kbd: 'Enter' }, + { key: 'shortcut', label: tr('m0106') }, + { key: 'pin', label: tr('m0107') }, { divider: true }, - { key: 'reveal', label: '打开 APK 所在位置' }, - { key: 'clear', label: '清除应用数据' }, + { key: 'reveal', label: tr('m0108') }, + { key: 'clear', label: tr('m0109') }, { divider: true }, - { key: 'uninstall', label: '卸载', danger: true }, + { key: 'uninstall', label: tr('m0110'), danger: true }, ]; function showMenu(x, y, pkg) { @@ -590,7 +591,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 ? tr('m0111') : tr('m0107')) : it.label; return `
${esc(label)}${it.kbd ? `${it.kbd}` : ''}
`; @@ -618,12 +619,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(tr('m0113', [app.label]))) return; try { await api.clearData(pkg); - toast({ title: '数据已清除', body: app.label, type: 'ok' }); + toast({ title: tr('m0114'), body: app.label, type: 'ok' }); } catch (e) { - toast({ title: '清除失败', body: e.message, type: 'error' }); + toast({ title: tr('m0115'), body: e.message, type: 'error' }); } return; } @@ -641,14 +642,14 @@ let volumeTimer = null; async function refreshAndroid() { const el = $('androidStatus'); if (!el) return; - el.textContent = '读取中…'; + el.textContent = tr('m0116'); 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 ? tr('m0117', [String(r.error).slice(0, 36)]) : tr('m0118'); return; } - el.textContent = `Android ${r.sdk || '?'} · ${r.wifi ? 'Wi-Fi 已开' : 'Wi-Fi 已关'}` + - `${r.airplane ? ' · 飞行模式' : ''} · 音量 ${r.volume}%`; + el.textContent = `Android ${r.sdk || '?'} · ${r.wifi ? tr('m0119') : tr('m0120')}` + + tr('m0121', [r.airplane ? tr('m0122') : '', r.volume]); $('setAndroidWifi').checked = !!r.wifi; $('setAndroidAirplane').checked = !!r.airplane; $('setAndroidAnim').checked = !r.animationsOff; @@ -666,30 +667,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 || tr('m0123')) : tr('m0124', [(r && r.error) || tr('m0125')]), }); 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 ? tr('m0126') : tr('m0127'))); + $('setAndroidAirplane').addEventListener('change', (e) => set('airplane', e.target.checked, e.target.checked ? tr('m0128') : tr('m0129'))); + $('setAndroidAnim').addEventListener('change', (e) => set('animations', e.target.checked, e.target.checked ? tr('m0130') : tr('m0131'))); + $('setAndroidFont').addEventListener('change', (e) => set('fontScale', Number(e.target.value), tr('m0132'))); + $('setAndroidDensity').addEventListener('change', (e) => set('density', Number(e.target.value), tr('m0133'))); $('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), tr('m0134')), 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 ? tr('m0135') : tr('m0136') }); 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: tr('m0137') }); } }; $('btnAndroidSettings').addEventListener('click', () => open('settings')); @@ -745,12 +746,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 = tr('m0138'); 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` : tr('m0139'); + const free = info.freeDiskMB ? `${(info.freeDiskMB / 1024).toFixed(1)} GB` : tr('m0140'); + el.textContent = tr('m0141', [img, auto ? tr('m0142') : '', free]) + + tr('m0143'); } async function openSettings() { @@ -768,17 +769,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 ? tr('m0144') : tr('m0145'); 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 = tr('m0146'); }); api.runtimeUsage().then((u) => { $('usageText').textContent = u - ? `共 ${u.totalText}${u.reclaimable ? ` · 可回收 ${u.reclaimableText}(${u.count} 项)` : ' · 已是精简状态'}` - : '统计不可用'; - }).catch(() => { $('usageText').textContent = '统计不可用'; }); + ? tr('m0147', [u.totalText, u.reclaimable ? tr('m0148', [u.reclaimableText, u.count]) : tr('m0149')]) + : tr('m0150'); + }).catch(() => { $('usageText').textContent = tr('m0150'); }); $('settingsMask').classList.add('on'); refreshAndroid(); @@ -805,11 +806,11 @@ function renderUpdate(info) { if (info.current) cur.textContent = `v${info.current}`; if (info.ok === false) { - st.textContent = `检查失败:${info.error || '网络不可用'}`; + st.textContent = tr('m0152', [info.error || tr('m0153')]); // 检查失败也要给一条出路:直接把发布页打开,用户至少能手动下载。 // 以前这里是「什么都不显示」,用户只会觉得「更新功能坏了」。 showUpdateBtn(true); - btn.textContent = '打开下载页'; + btn.textContent = tr('m0154'); btn.dataset.mode = 'manual'; lastUpdate = { htmlUrl: info.htmlUrl || '' }; if (bar) bar.hidden = true; @@ -819,22 +820,22 @@ function renderUpdate(info) { // 如实说明这次是哪个源给出的版本、另一个源为什么没参与 —— // 用户排查「为什么检查更新很慢」时,这一行比任何日志都直接。 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(', '); + const stale = info.stale ? tr('m0155') : ''; + st.textContent = tr('m0156', [info.tag, src ? `(${src})` : '', bad ? tr('m0157', [bad]) : '', stale]); showUpdateBtn(true); - btn.textContent = info.asset ? '立即更新' : '打开下载页'; + btn.textContent = info.asset ? tr('m0158') : tr('m0154'); 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 = tr('m0160', [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 ? tr('m0161') : ''; + st.textContent = tr('m0162', [info.current, src ? tr('m0163', [src]) : '', stale]); showUpdateBtn(false); if (bar) bar.hidden = true; } @@ -854,12 +855,12 @@ function renderUpdateProgress(p) { const bar = $('updateBar'); const fill = $('updateBarFill'); if (p.stage === 'error') { - if (st) st.textContent = `更新失败:${p.message || ''}`; + if (st) st.textContent = tr('m0164', [p.message || '']); if (bar) bar.hidden = true; - toast({ title: '更新失败', body: p.message || '', type: 'error' }); + toast({ title: tr('m0165'), body: p.message || '', type: 'error' }); return; } - if (st) st.textContent = p.message || '正在更新…'; + if (st) st.textContent = p.message || tr('m0166'); if (bar && fill) { bar.hidden = false; fill.style.width = `${Math.max(0, Math.min(100, p.percent || 0))}%`; @@ -875,15 +876,15 @@ function bindUpdate() { if (!silent) { check.disabled = true; const old = check.textContent; - check.textContent = '检查中…'; - $('updateState').textContent = '正在连接 GitHub…'; + check.textContent = tr('m0167'); + $('updateState').textContent = tr('m0168'); 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: tr('m0169', [info.tag]), body: tr('m0170'), type: 'info' }); + else if (info && info.ok !== false) toast({ title: tr('m0171'), body: `v${info.current}`, type: 'ok' }); } catch (e) { - $('updateState').textContent = `检查失败:${e.message}`; + $('updateState').textContent = tr('m0152', [e.message]); } finally { check.disabled = false; check.textContent = old; @@ -902,17 +903,17 @@ function bindUpdate() { return; } go.disabled = true; - go.textContent = '更新中…'; + go.textContent = tr('m0173'); const r = await api.updateApply().catch((e) => ({ ok: false, error: e.message })); if (!r || !r.ok) { go.disabled = false; - go.textContent = '立即更新'; + go.textContent = tr('m0158'); if (r && r.error && !r.manual) { - $('updateState').textContent = `更新失败:${r.error}`; - toast({ title: '更新失败', body: r.error, type: 'error' }); + $('updateState').textContent = tr('m0164', [r.error]); + toast({ title: tr('m0165'), body: r.error, type: 'error' }); } } else { - $('updateState').textContent = `已下载 v${r.version},正在重启完成安装…`; + $('updateState').textContent = tr('m0177', [r.version]); } }); @@ -948,7 +949,7 @@ function bindSettings() { // 资源策略会连带改 liteMode / priorityMode / renderSize(主进程里翻译) state.settings = await api.setSettings({ engine: { resourceMode: e.target.value } }); fillSettings(state.settings); - toast({ title: '资源策略已切换', body: '下一次打开应用生效', type: 'ok' }); + toast({ title: tr('m0178'), body: tr('m0179'), type: 'ok' }); }); } if ($('setRenderSize')) { @@ -969,24 +970,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: tr('m0180'), body: tr('m0181'), type: 'info' }); return; } const { response } = await api.confirm({ - title: '立刻把安卓数据分区扩容?', - message: `将把 /data 调整为 ${gb} GB(就地扩容,已装应用和存档都会保留)。`, - detail: '扩容需要先关掉正在运行的安卓应用,完成后下次打开应用会重新引导,约多花十几秒。', - okLabel: '开始扩容', + title: tr('m0182'), + message: tr('m0183', [gb]), + detail: tr('m0184'), + okLabel: tr('m0185'), }); if (response !== 1) return; $('btnGrowData').disabled = true; const old = $('btnGrowData').textContent; - $('btnGrowData').textContent = '扩容中…'; + $('btnGrowData').textContent = tr('m0186'); 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 ? tr('m0187') : tr('m0188'), + body: r && r.ok ? tr('m0189', [r.imageGB || gb]) : (r && r.error) || tr('m0078'), }); refreshDataInfo(); }); @@ -1028,7 +1029,7 @@ function bindSettings() { // 关掉深度裁剪必须把之前 pm disable-user 的包恢复回来,否则影响是持久的 if (!e.target.checked) { api.restoreSystemApps().catch(() => {}); - toast({ title: '已恢复系统组件', body: '深度裁剪关闭,后台包正在恢复', type: 'info' }); + toast({ title: tr('m0191'), body: tr('m0192'), type: 'info' }); } }); $('setCompat').addEventListener('change', async (e) => { @@ -1037,8 +1038,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: tr('m0193'), body: tr('m0194'), type: 'info' }); + else if (on) toast({ title: tr('m0193'), body: tr('m0196'), type: 'info' }); }); $('setAutoShortcut').addEventListener('change', (e) => save({ createShortcutOnInstall: e.target.checked })); $('setStartMenuShortcut').addEventListener('change', (e) => save({ createStartMenuShortcut: e.target.checked })); @@ -1049,17 +1050,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' ? tr('m0197') : tr('m0198'), type: 'ok' }); }); $('setPortrait').addEventListener('change', async (e) => { state.settings = await api.setSettings({ display: { portrait: e.target.value } }); - toast({ title: '画面大小已更新', body: '下次打开应用或切换应用时生效', type: 'ok' }); + toast({ title: tr('m0199'), body: tr('m0200'), 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 ? tr('m0201') : tr('m0202'), + body: r && r.fixed && r.fixed.length ? r.fixed.join(', ') : tr('m0203'), type: 'ok', }); }); @@ -1067,9 +1068,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: tr('m0204'), body: `${r.diagnosis.title} · ${r.file}`, type: 'ok', sticky: true }); } catch (e) { - toast({ title: '导出失败', body: e.message, type: 'error' }); + toast({ title: tr('m0205'), body: e.message, type: 'error' }); } }); @@ -1080,20 +1081,20 @@ function bindSettings() { try { const preview = await api.runtimeCleanup({ apply: false, includeSnapshots }); if (!preview.candidates.length) { - toast({ title: '已经是精简状态', body: '没有可回收的旧镜像或快照。', type: 'ok' }); + toast({ title: tr('m0206'), body: tr('m0207'), 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: tr('m0208'), + body: tr('m0209', [(r.saved / 1048576).toFixed(0), r.removed.length]), type: 'ok', }); const u = await api.runtimeUsage(); - $('usageText').textContent = `共 ${u.totalText} · 可回收 ${u.reclaimableText}(${u.count} 项)`; + $('usageText').textContent = tr('m0210', [u.totalText, u.reclaimableText, u.count]); } catch (e) { - toast({ title: '清理失败', body: e.message, type: 'error' }); + toast({ title: tr('m0211'), body: e.message, type: 'error' }); } finally { btn.disabled = false; } @@ -1106,41 +1107,41 @@ function bindSettings() { try { const first = await api.confirm({ type: 'warning', - title: '重置安卓数据?', - message: '这会把安卓系统还原成「刚装好系统」的干净状态:', - detail: '• 删除全部已安装的第三方 app(含红果、游戏等)及其数据、登录态\n' - + '• 删除当前 AVD 的快速启动快照\n' - + '• 系统镜像本身不受影响,重新打开应用会再次冷启动建数据\n\n' - + '此操作不可撤销。是否继续?', - buttons: ['取消', '继续'], + title: tr('m0212'), + message: tr('m0213'), + detail: tr('m0214') + + tr('m0215') + + tr('m0216') + + tr('m0217'), + buttons: [tr('m0218'), tr('m0219')], 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: tr('m0220'), + message: tr('m0221'), + detail: tr('m0222'), + buttons: [tr('m0218'), tr('m0224')], 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: tr('m0225'), body: (r && r.error) || tr('m0226'), type: 'error' }); return; } toast({ - title: '安卓数据已重置', - body: `释放约 ${r.reclaimedMB} MB,已安装应用记录已清空。`, + title: tr('m0227'), + body: tr('m0228', [r.reclaimedMB]), type: 'ok', }); const u = await api.runtimeUsage(); - $('usageText').textContent = `共 ${u.totalText} · 可回收 ${u.reclaimableText}(${u.count} 项)`; + $('usageText').textContent = tr('m0210', [u.totalText, u.reclaimableText, u.count]); } catch (e) { - toast({ title: '重置失败', body: e.message, type: 'error' }); + toast({ title: tr('m0225'), body: e.message, type: 'error' }); } finally { btn.disabled = false; } @@ -1155,14 +1156,14 @@ function bindSettings() { 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: tr('m0231'), + message: tr('m0232'), + detail: tr('m0233') + + tr('m0234') + + tr('m0235') + + tr('m0236') + + tr('m0237'), + buttons: [tr('m0218'), tr('m0239')], defaultId: 0, cancelId: 0, }); @@ -1170,19 +1171,19 @@ function bindSettings() { const r = await api.runtimeRepairNetwork(); if (!r || !r.ok) { toast({ - title: '网络仍未恢复', - body: (r && r.error) || '重置后虚拟机还是没网,请检查本机网络/防火墙是否拦住了模拟器。', + title: tr('m0240'), + body: (r && r.error) || tr('m0241'), type: 'error', }); return; } toast({ - title: '网络已恢复', - body: `${(r.network && r.network.ip) || '已联网'}${r.wiped ? '(为修复网络重置过安卓数据,点开应用会自动重装)' : '(未动你的数据)'}`, + title: tr('m0242'), + body: `${(r.network && r.network.ip) || tr('m0243')}${r.wiped ? tr('m0244') : tr('m0245')}`, type: 'ok', }); } catch (e) { - toast({ title: '修复失败', body: e.message, type: 'error' }); + toast({ title: tr('m0246'), body: e.message, type: 'error' }); } finally { btn.disabled = false; } @@ -1195,43 +1196,43 @@ function bindSettings() { try { const r = await api.leftoversScan(); if (!r || !r.ok) { - toast({ title: '扫描失败', body: (r && r.error) || '请重试', type: 'error' }); + toast({ title: tr('m0247'), body: (r && r.error) || tr('m0226'), type: 'error' }); return; } const items = r.items || []; if (!items.length) { - toast({ title: '很干净', body: '没有发现旧版本残留。', type: 'ok' }); + toast({ title: tr('m0249'), body: tr('m0250'), 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(tr('m0251', [items.length - 8])); const ok = await api.confirm({ type: 'warning', - title: '清理旧版本残留?', - message: `找到 ${items.length} 项残留,约 ${mb} MB:`, - detail: `${lines.join('\n')}\n\n只删除「旧版本程序目录 / 已失效的卸载项 / 更新临时文件」,` - + '当前正在运行的版本和你的应用数据都不会动。', - buttons: ['取消', '开始清理'], + title: tr('m0252'), + message: tr('m0253', [items.length, mb]), + detail: tr('m0254', [lines.join('\n')]) + + tr('m0255'), + buttons: [tr('m0218'), tr('m0257')], 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: tr('m0211'), body: (c && c.error) || tr('m0226'), type: 'error' }); return; } - const failNote = (c.failed && c.failed.length) ? `,${c.failed.length} 项被占用未删` : ''; + const failNote = (c.failed && c.failed.length) ? tr('m0260', [c.failed.length]) : ''; toast({ - title: '已清理旧版本残留', - body: `删除 ${c.removed.length} 项,释放 ${c.freedMB || 0} MB${failNote}。`, + title: tr('m0261'), + body: tr('m0262', [c.removed.length, c.freedMB || 0, failNote]), type: 'ok', }); const u = await api.runtimeUsage(); - $('usageText').textContent = `共 ${u.totalText} · 可回收 ${u.reclaimableText}(${u.count} 项)`; + $('usageText').textContent = tr('m0210', [u.totalText, u.reclaimableText, u.count]); } catch (e) { - toast({ title: '清理失败', body: e.message, type: 'error' }); + toast({ title: tr('m0211'), body: e.message, type: 'error' }); } finally { btn.disabled = false; } @@ -1247,8 +1248,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: tr('m0265'), body: fixed.join(', '), type: 'ok' } + : { title: tr('m0266'), body: tr('m0267'), type: 'info' }); } finally { btn.disabled = false; } @@ -1259,26 +1260,26 @@ function bindSettings() { const picked = await api.openDir(); if (!picked) return; $('setSharedDir').value = picked; - toast({ title: '已选新目录', body: '点「保存并迁移」把旧文件搬过去', type: 'info' }); + toast({ title: tr('m0268'), body: tr('m0269'), type: 'info' }); }); $('btnSaveShared').addEventListener('click', async () => { const dir = $('setSharedDir').value.trim(); - if (!dir) { toast({ title: '请先填写或浏览目录', type: 'error' }); return; } + if (!dir) { toast({ title: tr('m0270'), 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: tr('m0271'), body: moved ? tr('m0272') : tr('m0273'), type: 'ok' }); } else { - toast({ title: '保存失败', body: r.error, type: 'error' }); + toast({ title: tr('m0274'), body: r.error, type: 'error' }); } }); // 一键卸载 ApkStart 本体(取消 .apk 关联 + 启动卸载程序;便携版手动删除) $('btnUninstall').addEventListener('click', async () => { - const ok = window.confirm('确定卸载 ApkStart?\n\n将取消 .apk 文件关联并移除程序。此操作不可撤销。'); + const ok = window.confirm(tr('m0275')); if (!ok) return; const btn = $('btnUninstall'); btn.disabled = true; @@ -1286,44 +1287,44 @@ function bindSettings() { if (r.ok && r.portable) { btn.disabled = false; toast({ - title: '便携版无法自动卸载', - body: `请手动删除程序目录(${r.exeDir})与数据目录(${r.dataDir})`, + title: tr('m0276'), + body: tr('m0277', [r.exeDir, r.dataDir]), type: 'info', sticky: true, }); return; } if (!r.ok) { btn.disabled = false; - toast({ title: '卸载失败', body: r.error, type: 'error' }); + toast({ title: tr('m0104'), body: r.error, type: 'error' }); return; } - toast({ title: '正在卸载…', body: '卸载程序已启动,ApkStart 即将退出', type: 'ok', sticky: true }); + toast({ title: tr('m0279'), body: tr('m0280'), type: 'ok', sticky: true }); }); // 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: tr('m0281'), body: tr('m0052'), 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: tr('m0283'), 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: tr('m0284'), body: tr('m0196'), progress: 100 }); setTimeout(() => t.dismiss(), 3000); $('compatInstallField').hidden = true; refreshStatus(); } else { - t.update({ title: '兼容镜像下载失败', body: (r && r.error) || '未知错误', progress: null }); + t.update({ title: tr('m0286'), body: (r && r.error) || tr('m0078'), progress: null }); t.dismiss(); - toast({ title: '兼容镜像下载失败', body: (r && r.error) || '未知错误', type: 'error' }); + toast({ title: tr('m0286'), body: (r && r.error) || tr('m0078'), type: 'error' }); } } catch (e) { t.dismiss(); - toast({ title: '兼容镜像下载失败', body: e.message, type: 'error' }); + toast({ title: tr('m0286'), body: e.message, type: 'error' }); } finally { un(); btn.disabled = false; @@ -1339,7 +1340,7 @@ function bindSettings() { $('compatInstallField').hidden = !on || !!ok; if (env.playImage) { $('compatImgStatus').textContent = ok - ? `已就绪(${(env.playImage.size / 1073741824).toFixed(2)} GB)` : '未下载'; + ? tr('m0291', [(env.playImage.size / 1073741824).toFixed(2)]) : tr('m0292'); } })(); $('btnOpenData').addEventListener('click', async () => { @@ -1353,7 +1354,7 @@ function bindSettings() { if (!picked) return; await api.setSettings({ runtime: { dir: picked } }); $('setRuntimeDir').value = picked; - toast({ title: '数据目录已更新', body: '请重启 ApkStart 使其生效(旧数据不会自动搬移)', type: 'ok', sticky: true }); + toast({ title: tr('m0293'), body: tr('m0294'), type: 'ok', sticky: true }); }); // 安卓数据目录(AVD + 快照):真正吃盘的那块,单独指到别的盘 @@ -1364,8 +1365,8 @@ function bindSettings() { await api.setSettings({ runtime: { avdDir: picked } }); $('setAvdDir').value = picked; toast({ - title: '安卓数据目录已更新', - body: '请重启 ApkStart 使其生效。旧目录里的 apkstart.avd 可以整体搬到新目录,再删掉旧的。', + title: tr('m0295'), + body: tr('m0296'), type: 'ok', sticky: true, }); @@ -1381,13 +1382,13 @@ function bindSettings() { $('btnProbe').addEventListener('click', async () => { const el = $('probeResult'); - el.textContent = '探测中…'; + el.textContent = tr('m0297'); const found = await api.runtimeProbe(); if (!found.length) { - el.textContent = '未发现已运行的运行时'; + el.textContent = tr('m0298'); return; } - el.textContent = `发现:${found.map((f) => f.name).join('、')}`; + el.textContent = tr('m0299', [found.map((f) => f.name).join(', ')]); await api.setSettings({ device: found[0].serial }); $('setDevice').value = found[0].serial; refreshStatus(); @@ -1395,15 +1396,15 @@ function bindSettings() { $('btnRuntimeStart').addEventListener('click', async () => { const el = $('probeResult'); - el.textContent = '正在启动…'; + el.textContent = tr('m0300'); const r = await api.runtimeStart(); - el.textContent = r.ok ? `已就绪:${r.engine}` : `启动失败:${r.error}`; + el.textContent = r.ok ? tr('m0301', [r.engine]) : tr('m0302', [r.error]); refreshStatus(); }); $('btnRuntimeStop').addEventListener('click', async () => { await api.runtimeStop(); - $('probeResult').textContent = '已停止内置虚拟机'; + $('probeResult').textContent = tr('m0303'); refreshStatus(); }); @@ -1411,14 +1412,14 @@ function bindSettings() { const btn = $('btnAssoc'); if (btn.dataset.on === '1') { await api.assocUnregister(); - btn.textContent = '未关联 · 点击开启'; + btn.textContent = tr('m0145'); btn.dataset.on = '0'; - toast({ title: '.apk 关联已取消' }); + toast({ title: tr('m0305') }); } else { const r = await api.assocRegister(); - btn.textContent = '已关联 · 点击取消'; + btn.textContent = tr('m0144'); btn.dataset.on = '1'; - toast({ title: '.apk 关联已开启', body: r.ok ? '现在双击 APK 会直接安装到 ApkStart' : '', type: r.ok ? 'ok' : 'error' }); + toast({ title: tr('m0307'), body: r.ok ? tr('m0308') : '', type: r.ok ? 'ok' : 'error' }); } }); } @@ -1471,33 +1472,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: tr('m0309'), 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: tr('m0310'), body: r.path, type: 'ok' }); refreshStatus(); } + else if (!r.canceled) toast({ title: tr('m0311'), 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: tr('m0312'), body: tr('m0313'), type: 'ok' }); refreshStatus(); } + else if (!r.canceled) toast({ title: tr('m0311'), 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: tr('m0315'), body: tr('m0316'), 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: tr('m0062'), type: 'ok' }); setTimeout(t.dismiss, 2200); } + else t.update({ title: tr('m0099'), body: r.error, type: 'error' }); refreshStatus(); return; } if (act === 'vmStop') { await api.runtimeStop(); - toast({ title: '运行时会话已结束' }); + toast({ title: tr('m0319') }); refreshStatus(); } }); @@ -1514,9 +1515,9 @@ function bindEvents() { btnSync.disabled = true; try { await api.syncShared(); - toast({ title: '已同步共享目录', type: 'ok' }); + toast({ title: tr('m0320'), type: 'ok' }); } catch (_) { - toast({ title: '同步失败', body: '运行时未就绪时会跳过同步', type: 'error' }); + toast({ title: tr('m0321'), body: tr('m0322'), type: 'error' }); } finally { btnSync.disabled = false; } @@ -1586,7 +1587,7 @@ 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: tr('m0323'), type: 'error' }); }); // 主进程事件 @@ -1595,8 +1596,8 @@ function bindEvents() { 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 ? tr('m0324', [name]) : tr('m0325'), + body: friendlyInstallErrorText((p && p.message) || tr('m0078')), type: 'error', }); }); @@ -1621,6 +1622,8 @@ function bindEvents() { /* ---------------- 启动 ---------------- */ async function init() { + await window.i18n.ready; + await bindLanguage(); bindEvents(); bindSettings(); bindUpdate(); @@ -1642,11 +1645,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 tr('m0327'); + if (h < 11) return tr('m0328'); + if (h < 13) return tr('m0329'); + if (h < 18) return tr('m0330'); + return tr('m0331'); } function bindWelcome() { @@ -1676,17 +1679,17 @@ async function maybeShowWelcome() { if (!(startupInfo && startupInfo.autostart) && cfg.lastDate === today) return; const h = new Date().getHours(); - $('welcomeHi').textContent = `${greetingOf(h)},欢迎回来`; + $('welcomeHi').textContent = tr('m0332', [greetingOf(h)]); 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 ? tr('m0333') : tr('m0334')}`; 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: tr('m0335') }, + { k: ready ? tr('m0336') : tr('m0337'), v: tr('m0338') }, + { k: dev ? dev.replace(/^.*?(\d+)/, '$1') : '—', v: tr('m0339') }, ]; $('welcomeStats').innerHTML = chips .map((c) => `
${esc(c.k)}${esc(c.v)}
`) @@ -1706,14 +1709,14 @@ function renderWelcomeTips() { const apps = (state.apps || []).length; const tips = []; if (lastUpdate && lastUpdate.hasUpdate) { - tips.push(`发现新版本 ${esc(lastUpdate.tag || '')},可在「设置 → 软件更新」一键更新`); + tips.push(tr('m0340', [esc(lastUpdate.tag || '')])); } if (!apps) { - tips.push('把 APK 拖进窗口就能安装,装完会自动生成桌面图标'); + tips.push(tr('m0341')); } else { - tips.push('双击应用卡片即可在独立窗口中运行,右上角可旋转 / 缩放 / 关闭'); + tips.push(tr('m0342')); } - tips.push('关窗口只是退出那个应用,安卓环境会留着,下次点开更快'); + tips.push(tr('m0343')); box.innerHTML = tips .map((t, i) => `
${i + 1}${t}
`) .join(''); @@ -1746,20 +1749,14 @@ 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 ? tr('m0344', [errs]) : tr('m0345', [issues.length]); chip.className = `status-chip${errs ? ' err' : ''}`; chip.title = issues.map((i) => i.title).join(';'); } if (!issues.length) { box.hidden = false; - box.innerHTML = ` -
- - 环境自检通过,没有发现会影响运行的问题。 - - -
`; + box.innerHTML = tr('m0346'); const re = $('btnEnvRecheck'); if (re) re.addEventListener('click', selfCheck); return; @@ -1767,26 +1764,15 @@ function renderEnvCheck(r) { const errs = issues.filter((i) => i.severity === 'error').length; const warns = issues.length - errs; box.hidden = false; - box.innerHTML = ` -
- 环境自检 - ${errs ? `${errs} 个严重` : ''} - ${warns ? `${warns} 个提示` : ''} - - - -
-
- ${issues.map((i) => ` + box.innerHTML = tr('m0347', [errs ? tr('m0348', [errs]) : '', warns ? tr('m0349', [warns]) : '', issues.map((i) => `
${esc(i.title)}
${esc(i.hint || '')}
- ${i.fix ? `` : ''} -
`).join('')} -
`; + ${i.fix ? tr('m0350', [esc(i.code)]) : ''} + `).join('')]); const re = $('btnEnvRecheck'); if (re) re.addEventListener('click', selfCheck); const fixAll = $('btnEnvFixAll'); @@ -1805,50 +1791,50 @@ async function fixIssue(issue) { 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' }); + toast({ title: tr('m0351'), body: tr('m0352'), type: 'info' }); break; } toast(r.ok - ? { title: 'VC++ 运行库已安装', type: 'ok' } - : { title: '安装未完成', body: r.error || '请重试', type: 'error' }); + ? { title: tr('m0353'), type: 'ok' } + : { title: tr('m0354'), body: r.error || tr('m0226'), type: 'error' }); break; } case 'virtualization': { - const t = toast({ title: '正在开启硬件加速…', body: '请在弹出的窗口点「是」', sticky: true, spinner: true }); + const t = toast({ title: tr('m0356'), body: tr('m0357'), 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: tr('m0070'), body: r.restartNeeded ? tr('m0359') : (r.note || ''), type: 'ok' }); setTimeout(t.dismiss, 3200); } else { - t.update({ title: '未能开启', body: r.error || '请重试', type: 'error', sticky: true }); + t.update({ title: tr('m0360'), body: r.error || tr('m0226'), type: 'error', sticky: true }); } break; } case 'disk': { // 主进程会弹出原生确认框(含可释放空间预览),这里不再重复 confirm const r = await api.runtimeCleanup({ apply: true }).catch(() => null); - if (!r) { toast({ title: '清理失败', body: '请重试', type: 'error' }); break; } + if (!r) { toast({ title: tr('m0211'), body: tr('m0226'), type: 'error' }); break; } if (r.cancelled) break; - toast({ title: '清理完成', body: `释放约 ${((r.saved || 0) / 1048576).toFixed(0)} MB`, type: 'ok' }); + toast({ title: tr('m0208'), body: tr('m0365', [((r.saved || 0) / 1048576).toFixed(0)]), type: 'ok' }); break; } case 'path': { - toast({ title: '建议改到纯英文目录', body: '在 设置 → 系统集成 里修改「运行时数据目录」,再重启软件。', type: 'info', sticky: true }); + toast({ title: tr('m0366'), body: tr('m0367'), type: 'info', sticky: true }); openSettings(); break; } case 'deploy': { - toast({ title: '正在补齐缺失的运行时组件…', type: 'info', sticky: true, spinner: true }); + toast({ title: tr('m0368'), 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: tr('m0369'), type: 'ok' } : { title: tr('m0211'), body: r.error, type: 'error' }); break; } default: - toast({ title: '暂不支持自动修复', body: issue.hint || '', type: 'info' }); + toast({ title: tr('m0371'), body: issue.hint || '', type: 'info' }); } // 修复后重新自检,刷新横幅与状态 setTimeout(selfCheck, 600); @@ -1867,5 +1853,21 @@ async function fixAllIssues(issues) { init().catch((e) => { console.error(e); - toast({ title: '初始化失败', body: e.message, type: 'error', sticky: true }); + toast({ title: tr('m0372'), body: e.message, type: 'error', sticky: true }); }); + +async function bindLanguage() { + const select = $('setLanguage'); + select.value = window.i18n.preference; + select.addEventListener('change', async () => { + select.disabled = true; + try { + await api.setSettings({ language: select.value }); + window.location.reload(); + } catch (error) { + select.disabled = false; + select.value = window.i18n.preference; + toast({ title: tr('m0274'), body: error.message, type: 'error' }); + } + }); +} diff --git a/src/ui/i18n.js b/src/ui/i18n.js new file mode 100644 index 0000000..10c91bd --- /dev/null +++ b/src/ui/i18n.js @@ -0,0 +1,29 @@ +'use strict'; + +window.i18n = { + locale: 'zh-CN', + preference: 'auto', + messages: {}, + t(key, values = []) { + const message = this.messages[key] ?? key; + return message.replace(/\{(\d+)\}/g, (match, index) => ( + index < values.length ? String(values[index]) : match + )); + }, + apply(root = document) { + root.querySelectorAll('[data-i18n]').forEach((el) => { + el.textContent = this.t(el.dataset.i18n); + }); + for (const attr of ['title', 'placeholder', 'alt', 'aria-label']) { + root.querySelectorAll(`[data-i18n-${attr}]`).forEach((el) => { + el.setAttribute(attr, this.t(el.getAttribute(`data-i18n-${attr}`))); + }); + } + document.documentElement.lang = this.locale; + }, +}; + +window.i18n.ready = window.apkstart.getI18n().then((config) => { + Object.assign(window.i18n, config); + window.i18n.apply(); +}); diff --git a/src/ui/index.html b/src/ui/index.html index 78845ca..e365ce0 100644 --- a/src/ui/index.html +++ b/src/ui/index.html @@ -31,11 +31,11 @@ ApkStart - 未连接运行时 + 未连接运行时
-
@@ -45,12 +45,12 @@
@@ -68,9 +68,9 @@ -

把 APK 拖进来就能装

-

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

- +

把 APK 拖进来就能装

+

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

+

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

把 APK 拖进来就能装