From b01603727015d9bc98ca8795ea333e11bb8d48fb Mon Sep 17 00:00:00 2001 From: oliver Date: Wed, 7 Oct 2026 10:15:40 +0800 Subject: [PATCH] Make Windows Setup bilingual, offline-capable, and use NetX branding. Ship WinSW + icon assets, default first-run without network, EN/ZH installer UI. Co-authored-by: Cursor --- packaging/README.md | 4 + packaging/assets/README.md | 6 + packaging/assets/gen_icon.py | 56 +++ packaging/assets/netx-256.png | Bin 0 -> 2374 bytes packaging/assets/netx-64.png | Bin 0 -> 575 bytes packaging/assets/netx.ico | Bin 0 -> 16065 bytes packaging/build_release.ps1 | 43 +- packaging/install_service.ps1 | 20 +- .../installer/Languages/ChineseSimplified.isl | 417 ++++++++++++++++++ packaging/installer/netx.iss | 56 ++- packaging/netx_tray.ps1 | 40 +- packaging/setup_first_run.ps1 | 27 +- 12 files changed, 622 insertions(+), 47 deletions(-) create mode 100644 packaging/assets/README.md create mode 100644 packaging/assets/gen_icon.py create mode 100644 packaging/assets/netx-256.png create mode 100644 packaging/assets/netx-64.png create mode 100644 packaging/assets/netx.ico create mode 100644 packaging/installer/Languages/ChineseSimplified.isl diff --git a/packaging/README.md b/packaging/README.md index a71f0de..3da2d65 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -17,6 +17,10 @@ This directory builds a Windows deliverable with: | `NetX-x.y.z-win64.zip` | `build_release.ps1` | | `NetX-Setup-x.y.z.exe` | Compile `installer/netx.iss` with [Inno Setup](https://jrsoftware.org/isinfo.php) after staging | +**Installer:** English + 简体中文 (language dialog). Icons use `packaging/assets/netx.ico`. + +**Offline:** Setup ships portable PostgreSQL, `.venv` (when built with `-CreateVenv`), and WinSW. First-run uses `-NonInteractive -DbMode bundled` — **no GitHub/EDB download on the target PC**. Service install uses bundled WinSW (pass `-AllowDownload` only on a build/dev machine if the binary is missing). Auto-update still needs network later, and is unchecked by default. + ## Build (developer machine) Prerequisites: Python 3.11+, Node 20+, PowerShell, network (to download PG binaries once). diff --git a/packaging/assets/README.md b/packaging/assets/README.md new file mode 100644 index 0000000..2310893 --- /dev/null +++ b/packaging/assets/README.md @@ -0,0 +1,6 @@ +# NetX packaging assets + +- `netx.ico` — installer / shortcuts / tray (regenerate with `python gen_icon.py`) +- `netx-256.png` / `netx-64.png` — previews + +Mark matches `web/public/favicon.svg` (network “N” nodes on navy). diff --git a/packaging/assets/gen_icon.py b/packaging/assets/gen_icon.py new file mode 100644 index 0000000..f6e556c --- /dev/null +++ b/packaging/assets/gen_icon.py @@ -0,0 +1,56 @@ +"""Generate NetX Windows .ico from brand mark (matches web/public/favicon.svg).""" +from __future__ import annotations + +import os +from PIL import Image, ImageDraw + +OUT_DIR = os.path.dirname(os.path.abspath(__file__)) +BG = (15, 39, 68, 255) +LINE = (126, 182, 232, 255) +NODE = (232, 242, 252, 255) +ACCENT = (61, 130, 247, 255) + + +def make(size: int) -> Image.Image: + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + radius = max(2, int(size * 0.22)) + d.rounded_rectangle([0, 0, size - 1, size - 1], radius=radius, fill=BG) + s = size / 32.0 + + def xy(x: float, y: float) -> tuple[float, float]: + return (x * s, y * s) + + stroke = max(2, int(round(2.2 * s))) + for a, b in ( + (xy(9.5, 8.5), xy(9.5, 23.5)), + (xy(9.5, 8.5), xy(22.5, 23.5)), + (xy(22.5, 8.5), xy(22.5, 23.5)), + ): + d.line([a, b], fill=LINE, width=stroke) + + def circle(cx: float, cy: float, rad: float, fill: tuple[int, int, int, int]) -> None: + x, y = xy(cx, cy) + rr = rad * s + d.ellipse([x - rr, y - rr, x + rr, y + rr], fill=fill) + + circle(9.5, 8.5, 2.35, NODE) + circle(9.5, 23.5, 2.35, NODE) + circle(22.5, 8.5, 2.35, NODE) + circle(22.5, 23.5, 2.35, NODE) + circle(16, 16, 1.7, ACCENT) + return img + + +def main() -> None: + sizes = [16, 24, 32, 48, 64, 128, 256] + images = [make(s) for s in sizes] + ico_path = os.path.join(OUT_DIR, "netx.ico") + images[-1].save(ico_path, format="ICO", sizes=[(s, s) for s in sizes]) + images[-1].save(os.path.join(OUT_DIR, "netx-256.png")) + make(64).save(os.path.join(OUT_DIR, "netx-64.png")) + print(f"wrote {ico_path} ({os.path.getsize(ico_path)} bytes)") + + +if __name__ == "__main__": + main() diff --git a/packaging/assets/netx-256.png b/packaging/assets/netx-256.png new file mode 100644 index 0000000000000000000000000000000000000000..167327b1e26e931f2013740f1fc462b1e4fbf0e7 GIT binary patch literal 2374 zcmY*bcUTko7QHhJywKK!fCK~*0~iZcWCbaS8iXi|QB)9V0*Y7=1p$R;CKeI|rMQA3 z0M8+oDT0WKg*76!5KThfM4zwxHFM9ozqxbocjlhS<_Gy38(10u zfH5zCyA%K+ZV@2q5ts0-XVwDf8}qnxm!+4!dE`^*;BSU^%*Mt&9?st$dh6i)R@&B3 zYbFvEB#**jD7F(Ii%O?Q!TQ>`2fo|tbKwCHtBC(mc(v3aQ456r);83FxTwgTMZL23-9jPF#sIHpx5{7T4p>9WgxK`B z6>qhP)%{*Ikve#*uMt7#4wvK^Mt|2dl=EHP7_v(Zr5x!?|bqy-Q}sCD6jIT5fU zF{LX%fe^J{g>>A3Ct`1duuTnD zR55R*h3N|RnhBgYhS7hHdg<2omD_(FE%)|ucD~qX?%F36&e+neQATOP;~d?lU1i%m z*6;*qdWL_GTSKMb%7cQ(JcfMy?$axc=H!mge(&#vMD?qsMXCF6ujT#fBSOfm7}K>w ztH8_W~gBKyY zjmg5oi-Q_v@#}H> zacXmaDNOOz-Do|Y9Hf&>#d)^;7)1!v#Thy5g4&A&_Gq#Rew)dU)q-?{_VWaTCA%Qd zR9KRxITS1j*GdED1tE?j<1;Gp?Ig*!ZXsJ=JE{D`gfviXa8%*?ESZe+YE>CZfkOSR zSTj-hP9N&Uk{&FY=@^8(exxZS_YVlNWx&*`&I5r`!6kE|SbzwPB+2nV%%AB}%y=etUdH*J!zwZ1D)x)X#;y0 z1^#Ox8JG zKBW)&-axryv)XuxU;4%(*syNea=?d>S1T18(~2Esi&qV!?XQ_`s%mXlobta39`l?nsekiRhTlkyOZ$4u+JGrCmu+JZi;O^p1fs)cjh3C=4fqATLbk! zUqQLf;5NARB(v+7R^oW;PBU1n6P>s@zMn9V40EW2!=0ykQ@?pfJxxnGb+3I$koA^Y zvgQ2BrtK&hq4oK@$)UyMDUE$jx|?@F$PNhv+GE|%9MjKvV3alUb_<~wEj9`j`+cssjeA*1H{pFRS)i( zWlZ8@75fWoR(wnbCTu%YR{fNYnIK<{TeItBS#fc9Nfdpi#|oNi3SBqV67O+arhbcx zq4n6YzaI~lR$}DWr+a6?jLA5y^0|}1x~CHryHX>XhCM5hnO>=uDi_3qr5BeZ^Zxkq zhQm-P+2urRMRU~NRqd-{B)1MAqVe!ozeg}h+*Bjq(apw}dp2-|HV1YaEA2J9Hnc@+ UQ)jzp5&uWP^9$l$m>0e4U(vMeasU7T literal 0 HcmV?d00001 diff --git a/packaging/assets/netx-64.png b/packaging/assets/netx-64.png new file mode 100644 index 0000000000000000000000000000000000000000..afeb76f450a04897dc213711af3b3f61bfa5fc9d GIT binary patch literal 575 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 a1d_xBBR^2Rz>wg{o4avyhW+nZ)c*jlM8dZK literal 0 HcmV?d00001 diff --git a/packaging/build_release.ps1 b/packaging/build_release.ps1 index d62968a..07ad260 100644 --- a/packaging/build_release.ps1 +++ b/packaging/build_release.ps1 @@ -51,6 +51,20 @@ if (-not $SkipPostgresDownload) { } } +# Bundle WinSW so offline installs can register a Windows Service without GitHub. +$winswCache = Join-Path $PSScriptRoot "cache\WinSW-x64.exe" +$winswShip = Join-Path $PSScriptRoot "winsw\WinSW-x64.exe" +if (-not (Test-Path $winswShip)) { + if (-not (Test-Path $winswCache)) { + $winswUrl = "https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe" + Write-Host "==> Downloading WinSW for offline bundle: $winswUrl" + New-Item -ItemType Directory -Path (Split-Path $winswCache -Parent) -Force | Out-Null + Invoke-WebRequest -Uri $winswUrl -OutFile $winswCache -UseBasicParsing + } + New-Item -ItemType Directory -Path (Split-Path $winswShip -Parent) -Force | Out-Null + Copy-Item -Path $winswCache -Destination $winswShip -Force +} + # Flat layout = same as repo so scripts\start_netx.ps1 works unchanged. foreach ($d in @("netx_api", "alembic", "scripts")) { Copy-Item -Path (Join-Path $repo $d) -Destination (Join-Path $stage $d) -Recurse -Force @@ -81,13 +95,36 @@ foreach ($name in @( } Copy-Item -Path (Join-Path $PSScriptRoot "config") -Destination (Join-Path $packOut "config") -Recurse -Force Copy-Item -Path (Join-Path $PSScriptRoot "installer") -Destination (Join-Path $packOut "installer") -Recurse -Force +$assetsSrc = Join-Path $PSScriptRoot "assets" +if (Test-Path $assetsSrc) { + Copy-Item -Path $assetsSrc -Destination (Join-Path $packOut "assets") -Recurse -Force +} +$winswOut = Join-Path $packOut "winsw" +New-Item -ItemType Directory -Path $winswOut -Force | Out-Null +if (Test-Path $winswShip) { + Copy-Item -Path $winswShip -Destination (Join-Path $winswOut "WinSW-x64.exe") -Force + Write-Host "==> Bundled WinSW for offline service install" +} else { + Write-Host "[WARN] WinSW missing — offline service install will fail" -ForegroundColor Yellow +} New-Item -ItemType Directory -Path (Join-Path $packOut "postgres") -Force | Out-Null Copy-Item -Path (Join-Path $PSScriptRoot "postgres\README.md") -Destination (Join-Path $packOut "postgres\README.md") -Force if (Test-Path (Join-Path $pgsql "bin\pg_ctl.exe")) { - Write-Host "==> Copying bundled PostgreSQL" - New-Item -ItemType Directory -Path (Join-Path $stage "postgres") -Force | Out-Null - Copy-Item -Path $pgsql -Destination (Join-Path $stage "postgres\pgsql") -Recurse -Force + Write-Host "==> Copying bundled PostgreSQL (bin/lib/share; skip doc/)" + $pgStage = Join-Path $stage "postgres\pgsql" + New-Item -ItemType Directory -Path $pgStage -Force | Out-Null + foreach ($sub in @("bin", "lib", "share")) { + $srcSub = Join-Path $pgsql $sub + if (Test-Path $srcSub) { + Copy-Item -Path $srcSub -Destination (Join-Path $pgStage $sub) -Recurse -Force + } + } + Get-ChildItem -Path $pgsql -File -ErrorAction SilentlyContinue | ForEach-Object { + Copy-Item -Path $_.FullName -Destination (Join-Path $pgStage $_.Name) -Force + } +} else { + throw "bundled_postgres_missing: Setup.exe must ship postgres for offline install. Run download_postgres.ps1 (build machine only)." } $builtAt = (Get-Date).ToUniversalTime().ToString("o") diff --git a/packaging/install_service.ps1 b/packaging/install_service.ps1 index 21c58cb..ff97f17 100644 --- a/packaging/install_service.ps1 +++ b/packaging/install_service.ps1 @@ -4,11 +4,14 @@ param( [ValidateSet("winsw", "task")] [string]$Mode = "winsw", [switch]$Uninstall = $false, - [switch]$Start = $false + [switch]$Start = $false, + # Offline-safe by default: use WinSW shipped in the package. Opt-in to download from GitHub. + [switch]$AllowDownload = $false ) # Install NetX as a Windows Service (WinSW) or as a SYSTEM startup Scheduled Task. # Requires elevation for winsw / task modes that run as SYSTEM. +# WinSW binary is bundled at packaging\winsw\WinSW-x64.exe by build_release.ps1 — no network needed. $ErrorActionPreference = "Stop" . "$PSScriptRoot\_common.ps1" @@ -38,13 +41,24 @@ function Ensure-WinSW { if (-not (Test-Path $cacheDir)) { New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null } + $bundled = Join-Path $PSScriptRoot "winsw\WinSW-x64.exe" $dl = Join-Path $cacheDir "WinSW-x64.exe" - if (-not (Test-Path $dl)) { + $src = $null + if (Test-Path $bundled) { + $src = $bundled + Write-Host "==> Using bundled WinSW: $bundled" + } elseif (Test-Path $dl) { + $src = $dl + Write-Host "==> Using cached WinSW: $dl" + } elseif ($AllowDownload) { $url = "https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe" Write-Host "==> Downloading WinSW: $url" Invoke-WebRequest -Uri $url -OutFile $dl -UseBasicParsing + $src = $dl + } else { + throw "winsw_missing_offline: expected $bundled (rebuild with build_release.ps1). Or pass -AllowDownload when online." } - Copy-Item -Path $dl -Destination $winswExe -Force + Copy-Item -Path $src -Destination $winswExe -Force $runPs1 = ConvertTo-WinSWPath (Join-Path $PSScriptRoot "service_run.ps1") $stopPs1 = ConvertTo-WinSWPath (Join-Path $PSScriptRoot "stop_netx_app.ps1") diff --git a/packaging/installer/Languages/ChineseSimplified.isl b/packaging/installer/Languages/ChineseSimplified.isl new file mode 100644 index 0000000..30d9973 --- /dev/null +++ b/packaging/installer/Languages/ChineseSimplified.isl @@ -0,0 +1,417 @@ +; *** Inno Setup version 6.5.0+ Chinese Simplified messages *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). +; +; Maintainer: Zhenghan Yang (Kira) +; Email: 847320916@QQ.com +; Github: https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation +; Encoding: UTF-8 +; Translation based on network resource +; + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=简体中文 +; About LanguageID, to reference link: +; https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c +LanguageID=$0804 +; LanguageCodePage should always be set if possible, even if this file is Unicode +; For English it's set to zero anyway because English only uses ASCII characters +LanguageCodePage=936 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=9 +;DialogFontBaseScaleWidth=7 +;DialogFontBaseScaleHeight=15 +;WelcomeFontName=Segoe UI +;WelcomeFontSize=14 + +[Messages] + +; *** Application titles +SetupAppTitle=安装 +SetupWindowTitle=安装 - %1 +UninstallAppTitle=卸载 +UninstallAppFullTitle=%1 卸载 + +; *** Misc. common +InformationTitle=信息 +ConfirmTitle=确认 +ErrorTitle=错误 + +; *** SetupLdr messages +SetupLdrStartupMessage=现在将安装 %1。您想要继续吗? +LdrCannotCreateTemp=无法创建临时文件。安装程序已中止 +LdrCannotExecTemp=无法执行临时目录中的文件。安装程序已中止 +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1。%n%n错误 %2: %3 +SetupFileMissing=安装目录中缺少文件 %1。请修正这个问题或者获取程序的新副本。 +SetupFileCorrupt=安装文件已损坏。请获取程序的新副本。 +SetupFileCorruptOrWrongVer=安装文件已损坏,或是与这个安装程序的版本不兼容。请修正这个问题或获取新的程序副本。 +InvalidParameter=无效的命令行参数:%n%n%1 +SetupAlreadyRunning=安装程序已在运行。 +WindowsVersionNotSupported=此程序不支持当前计算机运行的 Windows 版本。 +WindowsServicePackRequired=此程序需要 %1 服务包 %2 或更高版本。 +NotOnThisPlatform=此程序不能在 %1 上运行。 +OnlyOnThisPlatform=此程序只能在 %1 上运行。 +OnlyOnTheseArchitectures=此程序只能安装到为下列处理器架构设计的 Windows 版本中:%n%n%1 +WinVersionTooLowError=此程序需要 %1 版本 %2 或更高。 +WinVersionTooHighError=此程序不能安装于 %1 版本 %2 或更高。 +AdminPrivilegesRequired=在安装此程序时您必须以管理员身份登录。 +PowerUserPrivilegesRequired=在安装此程序时您必须以管理员身份或高级用户组身份登录。 +SetupAppRunningError=安装程序检测到 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。 +UninstallAppRunningError=卸载程序检测到 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。 + +; *** Startup questions +PrivilegesRequiredOverrideTitle=选择安装程序安装模式 +PrivilegesRequiredOverrideInstruction=选择安装模式 +PrivilegesRequiredOverrideText1=%1 可以为所有用户安装(需要管理员权限),或仅为您安装。 +PrivilegesRequiredOverrideText2=%1 可以仅为您安装,或为所有用户安装(需要管理员权限)。 +PrivilegesRequiredOverrideAllUsers=为所有用户安装(&A) +PrivilegesRequiredOverrideAllUsersRecommended=为所有用户安装(&A)(推荐) +PrivilegesRequiredOverrideCurrentUser=仅为我安装(&M) +PrivilegesRequiredOverrideCurrentUserRecommended=仅为我安装(&M)(推荐) + +; *** Misc. errors +ErrorCreatingDir=安装程序无法创建目录“%1” +ErrorTooManyFilesInDir=无法在目录“%1”中创建文件,因为里面包含太多文件。 + +; *** Setup common messages +ExitSetupTitle=退出安装程序 +ExitSetupMessage=安装程序尚未完成。如果现在退出,将不会安装该程序。%n%n您之后可以再次运行安装程序完成安装。%n%n现在退出安装程序吗? +AboutSetupMenuItem=关于安装程序(&A)... +AboutSetupTitle=关于安装程序 +AboutSetupMessage=%1 版本 %2%n%3%n%n%1 主页:%n%4 +AboutSetupNote= +TranslatorNote=简体中文翻译由 Kira(847320916@qq.com)维护。项目地址:https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation + +; *** Buttons +ButtonBack=< 上一步(&B) +ButtonNext=下一步(&N) > +ButtonInstall=安装(&I) +ButtonOK=确定 +ButtonCancel=取消 +ButtonYes=是(&Y) +ButtonYesToAll=全是(&A) +ButtonNo=否(&N) +ButtonNoToAll=全否(&O) +ButtonFinish=完成(&F) +ButtonBrowse=浏览(&B)... +ButtonWizardBrowse=浏览(&R)... +ButtonNewFolder=新建文件夹(&M) + +; *** "Select Language" dialog messages +SelectLanguageTitle=选择安装语言 +SelectLanguageLabel=选择安装时使用的语言。 + +; *** Common wizard text +ClickNext=点击“下一步”继续,或点击“取消”退出安装程序。 +BeveledLabel= +BrowseDialogTitle=浏览文件夹 +BrowseDialogLabel=在下面的列表中选择一个文件夹,然后点击“确定”。 +NewFolderName=新建文件夹 + +; *** "Welcome" wizard page +WelcomeLabel1=欢迎使用 [name] 安装向导 +WelcomeLabel2=即将在您的计算机上安装 [name/ver]。%n%n建议您在继续安装前关闭所有其他应用程序。 + +; *** "Password" wizard page +WizardPassword=密码 +PasswordLabel1=此安装程序需要密码验证。 +PasswordLabel3=请输入密码,然后点击“下一步”继续。密码区分大小写。 +PasswordEditLabel=密码(&P): +IncorrectPassword=您输入的密码不正确,请重新输入。 + +; *** "License Agreement" wizard page +WizardLicense=许可协议 +LicenseLabel=请在继续安装前阅读以下重要信息。 +LicenseLabel3=请阅读下列许可协议。在继续安装前您必须同意这些协议条款。 +LicenseAccepted=我同意此协议(&A) +LicenseNotAccepted=我不同意此协议(&D) + +; *** "Information" wizard pages +WizardInfoBefore=信息 +InfoBeforeLabel=请在继续安装前阅读以下重要信息。 +InfoBeforeClickLabel=准备好继续安装后,点击“下一步”。 +WizardInfoAfter=信息 +InfoAfterLabel=请在继续安装前阅读以下重要信息。 +InfoAfterClickLabel=准备好继续安装后,点击“下一步”。 + +; *** "User Information" wizard page +WizardUserInfo=用户信息 +UserInfoDesc=请输入您的信息。 +UserInfoName=用户名(&U): +UserInfoOrg=组织(&O): +UserInfoSerial=序列号(&S): +UserInfoNameRequired=请输入用户名。 + +; *** "Select Destination Location" wizard page +WizardSelectDir=选择目标位置 +SelectDirDesc=您想将 [name] 安装在哪里? +SelectDirLabel3=安装程序将安装 [name] 到下面的文件夹中。 +SelectDirBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。 +DiskSpaceGBLabel=至少需要有 [gb] GB 的可用磁盘空间。 +DiskSpaceMBLabel=至少需要有 [mb] MB 的可用磁盘空间。 +CannotInstallToNetworkDrive=安装程序无法安装到一个网络驱动器。 +CannotInstallToUNCPath=安装程序无法安装到一个 UNC 路径。 +InvalidPath=您必须输入一个带驱动器盘符的完整路径,例如:%n%nC:\App%n%n或UNC路径:%n%n\\server\share +InvalidDrive=您选定的驱动器或 UNC 共享不存在或不能访问。请选择其他位置。 +DiskSpaceWarningTitle=磁盘空间不足 +DiskSpaceWarning=安装程序至少需要 %1 KB 的可用空间才能安装,但选定驱动器只有 %2 KB 的可用空间。%n%n您确定要继续吗? +DirNameTooLong=文件夹名称或路径太长。 +InvalidDirName=文件夹名称无效。 +BadDirName32=文件夹名称不能包含下列任何字符:%n%n%1 +DirExistsTitle=文件夹已存在 +DirExists=文件夹:%n%n%1%n%n已经存在。您确定安装到这个文件夹中吗? +DirDoesntExistTitle=文件夹不存在 +DirDoesntExist=文件夹:%n%n%1%n%n不存在。您想要创建此文件夹吗? + +; *** "Select Components" wizard page +WizardSelectComponents=选择组件 +SelectComponentsDesc=您想安装哪些程序组件? +SelectComponentsLabel2=选中您想安装的组件;取消您不想安装的组件。然后点击“下一步”继续。 +FullInstallation=完全安装 +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=简洁安装 +CustomInstallation=自定义安装 +NoUninstallWarningTitle=组件已存在 +NoUninstallWarning=安装程序检测到下列组件已安装在您的计算机中:%n%n%1%n%n取消选中这些组件不会卸载它们。%n%n您确定要继续吗? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=当前选择的组件需要至少 [gb] GB 的磁盘空间。 +ComponentsDiskSpaceMBLabel=当前选择的组件需要至少 [mb] MB 的磁盘空间。 + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=选择附加任务 +SelectTasksDesc=您想要安装程序执行哪些附加任务? +SelectTasksLabel2=选择您想要安装程序在安装 [name] 时执行的附加任务,然后点击“下一步”。 + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=选择开始菜单文件夹 +SelectStartMenuFolderDesc=安装程序应该在哪里放置程序的快捷方式? +SelectStartMenuFolderLabel3=安装程序将在下列“开始”菜单文件夹中创建程序的快捷方式。 +SelectStartMenuFolderBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。 +MustEnterGroupName=您必须输入一个文件夹名称。 +GroupNameTooLong=文件夹名称或路径太长。 +InvalidGroupName=文件夹名称无效。 +BadGroupName=文件夹名称不能包含下列任何字符:%n%n%1 +NoProgramGroupCheck2=不创建开始菜单文件夹(&D) + +; *** "Ready to Install" wizard page +WizardReady=准备安装 +ReadyLabel1=安装程序准备就绪,现在可以开始安装 [name] 到您的计算机。 +ReadyLabel2a=点击“安装”继续此安装程序。如果您想重新查看或修改任何设置,点击“上一步”。 +ReadyLabel2b=点击“安装”继续此安装程序。 +ReadyMemoUserInfo=用户信息: +ReadyMemoDir=目标位置: +ReadyMemoType=安装类型: +ReadyMemoComponents=已选择组件: +ReadyMemoGroup=开始菜单文件夹: +ReadyMemoTasks=附加任务: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel2=正在下载文件... +ButtonStopDownload=停止下载(&S) +StopDownload=您确定要停止下载吗? +ErrorDownloadAborted=下载已中止。 +ErrorDownloadFailed=下载失败:%1 %2。 +ErrorDownloadSizeFailed=获取大小失败:%1 %2。 +ErrorProgress=无效的进度:%1 / %2。 +ErrorFileSize=文件大小错误:预期 %1,实际 %2。 + +; *** TExtractionWizardPage wizard page and ExtractArchive +ExtractingLabel=正在提取文件... +ButtonStopExtraction=停止提取(&S) +StopExtraction=您确定要停止提取吗? +ErrorExtractionAborted=提取已中止。 +ErrorExtractionFailed=提取失败:%1 + +; *** Archive extraction failure details +ArchiveIncorrectPassword=密码不正确。 +ArchiveIsCorrupted=压缩包已损坏。 +ArchiveUnsupportedFormat=不支持的压缩包格式。 + +; *** "Preparing to Install" wizard page +WizardPreparing=正在准备安装 +PreparingDesc=安装程序正在准备安装 [name] 到您的计算机。 +PreviousInstallNotCompleted=先前的程序安装或卸载未完成,需要您重启计算机以完成该安装。%n%n在重启计算机后,再次运行安装程序以完成 [name] 的安装。 +CannotContinue=安装程序不能继续。请点击“取消”退出。 +ApplicationsFound=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。 +ApplicationsFound2=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。安装完成后,安装程序将尝试重新启动这些应用程序。 +CloseApplications=自动关闭应用程序(&A) +DontCloseApplications=不要关闭应用程序(&D) +ErrorCloseApplications=安装程序无法自动关闭所有应用程序。建议您在继续之前,关闭所有在使用需要由安装程序更新的文件的应用程序。 +PrepareToInstallNeedsRestart=安装程序必须重启您的计算机。计算机重启后,请再次运行安装程序以完成 [name] 的安装。%n%n要立即重启吗? + +; *** "Installing" wizard page +WizardInstalling=正在安装 +InstallingLabel=安装程序正在安装 [name] 到您的计算机,请稍候。 + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=完成 [name] 安装向导 +FinishedLabelNoIcons=安装程序已在您的计算机中安装了 [name]。 +FinishedLabel=安装程序已在您的计算机中安装了 [name]。您可以通过已安装的快捷方式运行此应用程序。 +ClickFinish=点击“完成”退出安装程序。 +FinishedRestartLabel=为完成 [name] 的安装,安装程序必须重新启动您的计算机。要立即重启吗? +FinishedRestartMessage=为完成 [name] 的安装,安装程序必须重新启动您的计算机。%n%n要立即重启吗? +ShowReadmeCheck=是,我想查阅自述文件 +YesRadio=是,立即重启计算机(&Y) +NoRadio=否,稍后重启计算机(&N) +; used for example as 'Run MyProg.exe' +RunEntryExec=运行 %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=查阅 %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=安装程序需要下一张磁盘 +SelectDiskLabel2=请插入磁盘 %1 并点击“确定”。%n%n如果这个磁盘中的文件可以在下列文件夹之外的文件夹中找到,请输入正确的路径或点击“浏览”。 +PathLabel=路径(&P): +FileNotInDir2=“%2”中找不到文件“%1”。请插入正确的磁盘或选择其他文件夹。 +SelectDirectoryLabel=请指定下一张磁盘的位置。 + +; *** Installation phase messages +SetupAborted=安装程序未完成安装。%n%n请修正这个问题并重新运行安装程序。 +AbortRetryIgnoreSelectAction=选择操作 +AbortRetryIgnoreRetry=重试(&T) +AbortRetryIgnoreIgnore=忽略错误并继续(&I) +AbortRetryIgnoreCancel=取消安装 +RetryCancelSelectAction=选择操作 +RetryCancelRetry=重试(&T) +RetryCancelCancel=取消 + +; *** Installation status messages +StatusClosingApplications=正在关闭应用程序... +StatusCreateDirs=正在创建目录... +StatusExtractFiles=正在提取文件... +StatusDownloadFiles=正在下载文件... +StatusCreateIcons=正在创建快捷方式... +StatusCreateIniEntries=正在创建 INI 条目... +StatusCreateRegistryEntries=正在创建注册表条目... +StatusRegisterFiles=正在注册文件... +StatusSavingUninstall=正在保存卸载信息... +StatusRunProgram=正在完成安装... +StatusRestartingApplications=正在重启应用程序... +StatusRollback=正在撤销更改... + +; *** Misc. errors +ErrorInternal2=内部错误:%1。 +ErrorFunctionFailedNoCode=%1 失败。 +ErrorFunctionFailed=%1 失败;错误代码 %2。 +ErrorFunctionFailedWithMessage=%1 失败;错误代码 %2。%n%3 +ErrorExecutingProgram=无法执行文件:%n%1 + +; *** Registry errors +ErrorRegOpenKey=打开注册表项时出错:%n%1\%2 +ErrorRegCreateKey=创建注册表项时出错:%n%1\%2 +ErrorRegWriteKey=写入注册表项时出错:%n%1\%2 + +; *** INI errors +ErrorIniEntry=在文件“%1”中创建 INI 条目时出错。 + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=跳过此文件(&S)(不推荐) +FileAbortRetryIgnoreIgnoreNotRecommended=忽略错误并继续(&I)(不推荐) +SourceIsCorrupted=源文件已损坏。 +SourceDoesntExist=源文件“%1”不存在。 +SourceVerificationFailed=源文件验证失败:%1 +VerificationSignatureDoesntExist=签名文件“%1”不存在。 +VerificationSignatureInvalid=签名文件“%1”无效。 +VerificationKeyNotFound=签名文件“%1”使用了未知的密钥。 +VerificationFileNameIncorrect=文件名不正确。 +VerificationFileTagIncorrect=文件标签不正确。 +VerificationFileSizeIncorrect=文件大小不正确。 +VerificationFileHashIncorrect=文件哈希值不正确。 +ExistingFileReadOnly2=无法替换已存在的文件,它是只读的。 +ExistingFileReadOnlyRetry=移除只读属性并重试(&R) +ExistingFileReadOnlyKeepExisting=保留已存在的文件(&K) +ErrorReadingExistingDest=尝试读取已存在的文件时出错: +FileExistsSelectAction=选择操作 +FileExists2=文件已经存在。 +FileExistsOverwriteExisting=覆盖已存在的文件(&O) +FileExistsKeepExisting=保留已存在的文件(&K) +FileExistsOverwriteOrKeepAll=为接下来的冲突文件执行此操作(&D) +ExistingFileNewerSelectAction=选择操作 +ExistingFileNewer2=已存在的文件比安装程序将要安装的文件还要新。 +ExistingFileNewerOverwriteExisting=覆盖已存在的文件(&O) +ExistingFileNewerKeepExisting=保留已存在的文件(&K)(推荐) +ExistingFileNewerOverwriteOrKeepAll=为接下来的冲突文件执行此操作(&D) +ErrorChangingAttr=尝试更改下列已存在的文件属性时出错: +ErrorCreatingTemp=尝试在目标目录创建文件时出错: +ErrorReadingSource=尝试读取下列源文件时出错: +ErrorCopying=尝试复制下列文件时出错: +ErrorDownloading=尝试下载文件时出错: +ErrorExtracting=尝试提取压缩包时出错: +ErrorReplacingExistingFile=尝试替换已存在的文件时出错: +ErrorRestartReplace=重启并替换失败: +ErrorRenamingTemp=尝试重命名下列目标目录中的一个文件时出错: +ErrorRegisterServer=无法注册 DLL/OCX:%1 +ErrorRegSvr32Failed=RegSvr32 失败;退出代码 %1。 +ErrorRegisterTypeLib=无法注册类型库:%1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32 位 +UninstallDisplayNameMark64Bit=64 位 +UninstallDisplayNameMarkAllUsers=所有用户 +UninstallDisplayNameMarkCurrentUser=当前用户 + +; *** Post-installation errors +ErrorOpeningReadme=尝试打开自述文件时出错。 +ErrorRestartingComputer=安装程序无法重启计算机,请手动重启。 + +; *** Uninstaller messages +UninstallNotFound=文件“%1”不存在。无法卸载。 +UninstallOpenError=文件“%1”不能被打开。无法卸载 +UninstallUnsupportedVer=此版本的卸载程序无法识别卸载日志文件“%1”的格式。无法卸载。 +UninstallUnknownEntry=卸载日志中遇到一个未知条目(%1)。 +ConfirmUninstall=您确认要完全移除 %1 及其所有组件吗? +UninstallOnlyOnWin64=仅允许在 64 位 Windows 中卸载此程序。 +OnlyAdminCanUninstall=仅使用管理员权限的用户能完成此卸载。 +UninstallStatusLabel=正在从您的计算机中移除 %1,请稍候。 +UninstalledAll=已顺利从您的计算机中移除 %1。 +UninstalledMost=%1 卸载完成。%n%n有部分内容未能被删除,但您可以手动删除它们。 +UninstalledAndNeedsRestart=为完成 %1 的卸载,需要重启您的计算机。%n%n要立即重启吗? +UninstallDataCorrupted=文件“%1”已损坏。无法卸载。 + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=删除共享文件? +ConfirmDeleteSharedFile2=系统表示下列共享文件已不再有任何程序使用。您希望卸载程序删除此共享文件吗?%n%n如果仍有程序正在使用此文件,删除后这些程序可能无法正常运行。如果您不能确定,请选择“否”,保留此文件在系统中不会造成任何损害。 +SharedFileNameLabel=文件名: +SharedFileLocationLabel=位置: +WizardUninstalling=卸载状态 +StatusUninstalling=正在卸载 %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=正在安装 %1。 +ShutdownBlockReasonUninstallingApp=正在卸载 %1。 + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 版本 %2 +AdditionalIcons=附加快捷方式: +CreateDesktopIcon=创建桌面快捷方式(&D) +CreateQuickLaunchIcon=创建快速启动栏快捷方式(&Q) +ProgramOnTheWeb=%1 网站 +UninstallProgram=卸载 %1 +LaunchProgram=运行 %1 +AssocFileExtension=将 %2 文件扩展名与 %1 建立关联(&A) +AssocingFileExtension=正在将 %2 文件扩展名与 %1 建立关联... +AutoStartProgramGroupDescription=启动: +AutoStartProgram=自动启动 %1 +AddonHostProgramNotFound=您选择的文件夹中无法找到 %1。%n%n您确定要继续吗? diff --git a/packaging/installer/netx.iss b/packaging/installer/netx.iss index 63a1a5c..1275081 100644 --- a/packaging/installer/netx.iss +++ b/packaging/installer/netx.iss @@ -12,6 +12,7 @@ ; Stage directory produced by build_release.ps1 (relative to this .iss) #define StageDir "..\release\netx-win64" +#define IconFile "..\assets\netx.ico" [Setup] AppId={{A7E3C2D1-9F40-4B8E-9C1A-NETXWIN64001} @@ -25,6 +26,8 @@ DisableProgramGroupPage=yes LicenseFile={#StageDir}\LICENSE OutputDir=..\release OutputBaseFilename=NetX-Setup-{#MyAppVersion} +SetupIconFile={#IconFile} +UninstallDisplayIcon={app}\packaging\assets\netx.ico Compression=lzma2 SolidCompression=yes WizardStyle=modern @@ -33,13 +36,31 @@ ArchitecturesInstallIn64BitMode=x64compatible PrivilegesRequired=admin ; Data lives under {commonappdata}\NetX — not overwritten by upgrades CloseApplications=yes +ShowLanguageDialog=yes [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "chinesesimplified"; MessagesFile: "Languages\ChineseSimplified.isl" + +[CustomMessages] +english.CreateDesktopIcon=Create a desktop shortcut +english.SetupOptions=Setup options: +english.RunFirstSetup=Run first-time setup after install (bundled database, offline) +english.StartTrayNow=Start NetX tray now +english.EnableAutostart=Start NetX tray at Windows logon +english.EnableAutoUpdate=Enable daily silent auto-update (needs network later) +english.InstallService=Install as Windows Service (uses bundled WinSW, offline) +chinesesimplified.CreateDesktopIcon=创建桌面快捷方式 +chinesesimplified.SetupOptions=安装选项: +chinesesimplified.RunFirstSetup=安装后运行首次配置(内置数据库,可离线) +chinesesimplified.StartTrayNow=立即启动 NetX 托盘 +chinesesimplified.EnableAutostart=开机时自动启动 NetX 托盘 +chinesesimplified.EnableAutoUpdate=启用每日静默自动更新(以后需要联网) +chinesesimplified.InstallService=安装为 Windows 服务(使用已捆绑的 WinSW,可离线) [Tasks] -Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional icons:"; Flags: unchecked -Name: "firstrun"; Description: "Run first-time setup (database mode) after install"; GroupDescription: "Setup:"; Flags: checkedonce +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +Name: "firstrun"; Description: "{cm:RunFirstSetup}"; GroupDescription: "{cm:SetupOptions}"; Flags: checkedonce [Files] ; Entire release stage → {app}. Exclude .portable so installed builds use ProgramData. @@ -54,23 +75,24 @@ Name: "{commonappdata}\NetX\pgdata" Name: "{commonappdata}\NetX\backups" [Icons] -Name: "{group}\NetX Tray"; Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\netx_tray.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -StartOnLaunch"; WorkingDir: "{app}" -Name: "{group}\Start NetX"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\start_netx_app.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}" -Name: "{group}\Stop NetX"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\stop_netx_app.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}" -Name: "{group}\Check for updates"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\check_update.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}" -Name: "{group}\Open NetX UI"; Filename: "http://127.0.0.1:8890/" -Name: "{group}\First-time setup"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\setup_first_run.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}" -Name: "{group}\Install Windows Service (admin)"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_service.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -Start"; WorkingDir: "{app}" -Name: "{group}\Enable silent auto-update (daily)"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_update_task.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}" -Name: "{group}\Enable start at logon"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_autostart.ps1"" -ProgramRoot ""{app}"""; WorkingDir: "{app}" -Name: "{autodesktop}\NetX"; Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\netx_tray.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -StartOnLaunch"; WorkingDir: "{app}"; Tasks: desktopicon +Name: "{group}\NetX Tray"; Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\netx_tray.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -StartOnLaunch"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\Start NetX"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\start_netx_app.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\Stop NetX"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\stop_netx_app.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\Check for updates"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\check_update.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\Open NetX UI"; Filename: "http://127.0.0.1:8890/"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\First-time setup"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\setup_first_run.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\Install Windows Service (admin)"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_service.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -Start"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\Enable silent auto-update (daily)"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_update_task.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\Enable start at logon"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_autostart.ps1"" -ProgramRoot ""{app}"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{autodesktop}\NetX"; Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\netx_tray.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -StartOnLaunch"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico"; Tasks: desktopicon [Run] -Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\setup_first_run.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; Flags: postinstall skipifsilent; Tasks: firstrun -Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\netx_tray.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -StartOnLaunch"; WorkingDir: "{app}"; Description: "Start NetX tray now"; Flags: postinstall nowait skipifsilent unchecked -Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_autostart.ps1"" -ProgramRoot ""{app}"""; WorkingDir: "{app}"; Description: "Start NetX tray at Windows logon"; Flags: postinstall skipifsilent unchecked -Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_update_task.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; Description: "Enable daily silent auto-update"; Flags: postinstall skipifsilent unchecked -Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_service.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -Start"; WorkingDir: "{app}"; Description: "Install as Windows Service (admin)"; Flags: postinstall skipifsilent unchecked +; First-run is NonInteractive + bundled DB so offline machines succeed without prompts or downloads. +Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\setup_first_run.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -NonInteractive -DbMode bundled"; WorkingDir: "{app}"; Flags: postinstall skipifsilent; Tasks: firstrun +Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\netx_tray.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -StartOnLaunch"; WorkingDir: "{app}"; Description: "{cm:StartTrayNow}"; Flags: postinstall nowait skipifsilent unchecked +Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_autostart.ps1"" -ProgramRoot ""{app}"""; WorkingDir: "{app}"; Description: "{cm:EnableAutostart}"; Flags: postinstall skipifsilent unchecked +Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_update_task.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; Description: "{cm:EnableAutoUpdate}"; Flags: postinstall skipifsilent unchecked +Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_service.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -Start"; WorkingDir: "{app}"; Description: "{cm:InstallService}"; Flags: postinstall skipifsilent unchecked [UninstallDelete] ; Do NOT delete {commonappdata}\NetX — preserves DB and secrets across reinstall diff --git a/packaging/netx_tray.ps1 b/packaging/netx_tray.ps1 index c21b07c..5652f86 100644 --- a/packaging/netx_tray.ps1 +++ b/packaging/netx_tray.ps1 @@ -56,31 +56,43 @@ $form.WindowState = "Minimized" $form.Visible = $false $form.Size = New-Object System.Drawing.Size(0, 0) +$zh = ([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)') +function T([string]$En, [string]$Zh) { + if ($zh) { return $Zh } else { return $En } +} + $notify = New-Object System.Windows.Forms.NotifyIcon $notify.Text = "NetX $ver" $notify.Visible = $true +$iconPath = Join-Path $PSScriptRoot "assets\netx.ico" try { - $notify.Icon = [System.Drawing.SystemIcons]::Application -} catch {} + if (Test-Path $iconPath) { + $notify.Icon = New-Object System.Drawing.Icon $iconPath + } else { + $notify.Icon = [System.Drawing.SystemIcons]::Application + } +} catch { + try { $notify.Icon = [System.Drawing.SystemIcons]::Application } catch {} +} $menu = New-Object System.Windows.Forms.ContextMenuStrip -$miOpen = $menu.Items.Add("Open UI ($uiUrl)") +$miOpen = $menu.Items.Add((T "Open UI ($uiUrl)" "打开界面 ($uiUrl)")) $miOpen.add_Click({ Start-Process $uiUrl }) -$miStart = $menu.Items.Add("Start NetX") +$miStart = $menu.Items.Add((T "Start NetX" "启动 NetX")) $miStart.add_Click({ - $notify.ShowBalloonTip(3000, "NetX", "Starting…", [System.Windows.Forms.ToolTipIcon]::Info) + $notify.ShowBalloonTip(3000, "NetX", (T "Starting…" "正在启动…"), [System.Windows.Forms.ToolTipIcon]::Info) Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") }) -$miStop = $menu.Items.Add("Stop NetX") +$miStop = $menu.Items.Add((T "Stop NetX" "停止 NetX")) $miStop.add_Click({ - $notify.ShowBalloonTip(3000, "NetX", "Stopping…", [System.Windows.Forms.ToolTipIcon]::Info) + $notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info) Invoke-NetxScript -File $stopPs1 }) -$miUpdate = $menu.Items.Add("Check for updates") +$miUpdate = $menu.Items.Add((T "Check for updates" "检查更新")) $miUpdate.add_Click({ try { $p = Start-Process -FilePath "powershell.exe" -ArgumentList @( @@ -89,8 +101,8 @@ $miUpdate.add_Click({ ) -Wait -PassThru -WindowStyle Normal if ($p.ExitCode -eq 10) { $ans = [System.Windows.Forms.MessageBox]::Show( - "A newer NetX is available. Download and apply now?", - "NetX update", + (T "A newer NetX is available. Download and apply now?" "发现新版本,是否立即下载并更新?"), + (T "NetX update" "NetX 更新"), [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question ) @@ -101,16 +113,16 @@ $miUpdate.add_Click({ ) -WorkingDirectory $prog } } elseif ($p.ExitCode -eq 0) { - [System.Windows.Forms.MessageBox]::Show("NetX is up to date ($ver).", "NetX update") + [System.Windows.Forms.MessageBox]::Show((T "NetX is up to date ($ver)." "已是最新版本 ($ver)。"), (T "NetX update" "NetX 更新")) } } catch { - [System.Windows.Forms.MessageBox]::Show("Update check failed: $($_.Exception.Message)", "NetX") + [System.Windows.Forms.MessageBox]::Show((T "Update check failed: $($_.Exception.Message)" "检查更新失败:$($_.Exception.Message)"), "NetX") } }) [void]$menu.Items.Add("-") -$miExit = $menu.Items.Add("Exit tray (services keep running)") +$miExit = $menu.Items.Add((T "Exit tray (services keep running)" "退出托盘(服务继续运行)")) $miExit.add_Click({ $notify.Visible = $false $form.Close() @@ -122,7 +134,7 @@ $notify.add_DoubleClick({ Start-Process $uiUrl }) $form.add_Shown({ if ($AutoUpdate) { - $notify.ShowBalloonTip(4000, "NetX", "Checking for updates…", [System.Windows.Forms.ToolTipIcon]::Info) + $notify.ShowBalloonTip(4000, "NetX", (T "Checking for updates…" "正在检查更新…"), [System.Windows.Forms.ToolTipIcon]::Info) Start-Process -FilePath "powershell.exe" -ArgumentList @( "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $checkPs1, "-ProgramRoot", $prog, "-DataRoot", $data, "-Apply", "-Quiet", "-AutoOnly" diff --git a/packaging/setup_first_run.ps1 b/packaging/setup_first_run.ps1 index 6a9e9c3..8889308 100644 --- a/packaging/setup_first_run.ps1 +++ b/packaging/setup_first_run.ps1 @@ -15,10 +15,15 @@ $ErrorActionPreference = "Stop" $prog = Get-NetxProgramRoot -Override $ProgramRoot $data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot $envPath = Join-Path $data ".env" +$zh = ([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)') -Write-Host "==> Program root: $prog" -Write-Host "==> Data root: $data" -Write-Host "==> Env file: $envPath" +function T([string]$En, [string]$Zh) { + if ($zh) { return $Zh } else { return $En } +} + +Write-Host ("==> " + (T "Program root:" "程序目录:") + " $prog") +Write-Host ("==> " + (T "Data root:" "数据目录:") + " $data") +Write-Host ("==> " + (T "Env file:" "环境文件:") + " $envPath") Ensure-NetxDataDirectories -DataRoot $data @@ -34,10 +39,10 @@ if (-not $DbMode) { } } else { Write-Host "" - Write-Host "Choose database mode:" - Write-Host " 1) bundled — use NetX portable PostgreSQL (default for new installs)" - Write-Host " 2) external — connect to an existing PostgreSQL (Linux / already deployed)" - $choice = Read-Host "Enter 1 or 2" + Write-Host (T "Choose database mode:" "选择数据库模式:") + Write-Host (T " 1) bundled — use NetX portable PostgreSQL (default for new installs)" " 1) bundled — 使用 NetX 内置便携 PostgreSQL(新安装默认)") + Write-Host (T " 2) external — connect to an existing PostgreSQL (Linux / already deployed)" " 2) external — 连接已有 PostgreSQL") + $choice = Read-Host (T "Enter 1 or 2" "请输入 1 或 2") if ($choice -eq "2") { $DbMode = "external" } else { $DbMode = "bundled" } } } @@ -69,14 +74,16 @@ if ($DbMode -eq "bundled") { if (Test-Path (Join-Path $alt "bin\initdb.exe")) { $pgsql = $alt } else { - throw "bundled_postgres_missing: run packaging\download_postgres.ps1 first (expected $pgsql)" + throw (T ` + "bundled_postgres_missing: incomplete package (expected $pgsql). Offline Setup must include postgres; rebuild with build_release.ps1 on a machine that already ran download_postgres.ps1." ` + "缺少内置 PostgreSQL(期望路径 $pgsql)。离线安装包必须自带数据库;请在已运行过 download_postgres.ps1 的机器上重新 build_release。") } } if (-not $BundledPassword) { if ($NonInteractive) { $BundledPassword = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 24 | ForEach-Object { [char]$_ }) } else { - $sec = Read-Host -Prompt "Password for bundled role 'netx' (empty = auto-generate)" -AsSecureString + $sec = Read-Host -Prompt (T "Password for bundled role 'netx' (empty = auto-generate)" "内置数据库用户 netx 的密码(回车=自动生成)") -AsSecureString $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec) try { $BundledPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr) @@ -85,7 +92,7 @@ if ($DbMode -eq "bundled") { } if (-not $BundledPassword) { $BundledPassword = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 24 | ForEach-Object { [char]$_ }) - Write-Host "Generated password (saved in .env only)." + Write-Host (T "Generated password (saved in .env only)." "已自动生成密码(仅保存在 .env)。") } } }