diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index e3b2181..a3fc5b3 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -18,6 +18,8 @@ API 容器首次启动时会自动执行 Prisma `db push`,自动初始化或 curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash ``` +脚本启动后会先询问使用 **中文** 还是 **English**;如果你希望跳过这一步,也可以用 `--lang zh` 或 `--lang en` 显式指定。 + 它会根据你选择的部署方式,自动把需要的文件下载到本地目录。 --- @@ -120,6 +122,7 @@ curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/i --web-port 完整部署前端端口,默认 8080 --web-origin WEB_ORIGIN --log-level LOG_LEVEL,默认 warn +--lang 安装脚本语言;交互模式下默认会先询问 --force 若目录已存在则覆盖 --yes 非交互模式,直接使用默认值 ``` diff --git a/README.md b/README.md index f6ab620..5d4476c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

SubTracker

- 一个现代化的自托管订阅管理工具,用来统一管理多币种订阅、续订提醒、预算分析、Logo 资源,以及 Wallos 数据迁移 + Manage subscriptions, renewal reminders, budgets, logos, and Wallos migrations in one self-hosted dashboard.

@@ -15,27 +15,29 @@

- 本地开发 · - 部署 · - 部署文档 · + 简体中文 · + Local development · + Deployment · + Deployment guide · Releases

-> 当前 `main` 分支提供 **Docker / Docker Compose** 部署;如果你需要 **Cloudflare Worker 无服务器部署**,请前往 [`lite`](https://github.com/Smile-QWQ/SubTracker/tree/lite) 分支,对应的部署说明、工作流与 Worker 适配实现都维护在该分支 +> The current `main` branch ships with **Docker / Docker Compose** deployment. If you need **Cloudflare Worker serverless deployment**, use the [`lite`](https://github.com/Smile-QWQ/SubTracker/tree/lite) branch, where the Worker-specific workflows, deployment notes, and runtime adaptations are maintained. -## 快速开始 +## Quick Start -### 想直接部署 +### Deploy with the install script ```bash curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash ``` -- 推荐使用 **完整部署(full)** -- 支持 **x86 / ARM** 双架构 -- 详细步骤见 [`DEPLOYMENT.md`](./DEPLOYMENT.md) +- **Full mode** is recommended for most users. +- The installer now asks you to choose **中文** or **English** at the start. You can still force it with `--lang zh` or `--lang en`. +- Release artifacts support both **x86** and **ARM**. +- See [`DEPLOYMENT.md`](./DEPLOYMENT.md) for the full deployment flow. -### 想本地开发 +### Run locally ```bash npm install @@ -45,62 +47,66 @@ npm run prisma:seed npm run dev ``` -默认地址: +Default addresses: -- Web:`http://127.0.0.1:5173` -- API:`http://127.0.0.1:3001` +- Web: `http://127.0.0.1:5173` +- API: `http://127.0.0.1:3001` -默认账户: +Default credentials: -- 用户名:`admin` -- 密码:`admin` +- Username: `admin` +- Password: `admin` -## 界面预览 +## Screenshots -### 仪表盘 +### Dashboard -![仪表盘](./screenshot/仪表盘.png) +![Dashboard](./screenshot/仪表盘.png) -### 更多截图 +### More views -| 订阅管理 | 费用统计 | +| Subscriptions | Spending | | --- | --- | -| ![订阅管理](./screenshot/订阅管理.png) | ![费用统计](./screenshot/费用统计.png) | +| ![Subscriptions](./screenshot/订阅管理.png) | ![Spending](./screenshot/费用统计.png) | -| AI 识别 | Wallos 导入 | +| AI recognition | Wallos import | | --- | --- | -| ![AI识别](./screenshot/AI识别.png) | ![导入Wallos](./screenshot/导入Wallos.png) | +| ![AI recognition](./screenshot/AI识别.png) | ![Wallos import](./screenshot/导入Wallos.png) | -## 功能亮点 +## Features -- **多币种订阅管理**:统一维护订阅名称、金额、计费周期、开始日期、下次续订时间、标签、自动续订状态,并支持暂停、停用、恢复、搜索、自定义排序和批量操作 -- **灵活提醒规则**:支持到期前、当天、过期后的自定义提醒规则,按 `天数&时间;` 格式配置,适合同时覆盖提前提醒、当天提醒和过期补提醒 -- **预算与统计视图**:提供月 / 年预算、未来 12 个月趋势、标签占比、状态分布、未来 30 天续订分布、订阅日历等视图,方便从总览和分类两个层面看支出 -- **AI 辅助录入**:支持文本 / 图片识别自动填充订阅信息,并在统计页生成 AI 总结,减少手动录入和整理成本 -- **备份与迁移**:支持原生 ZIP 备份导出、检查、导入与恢复,也兼容 Wallos 的 JSON、SQLite、ZIP 导入,迁移老数据会更顺手 -- **Logo 与品牌资源**:支持上传、本地复用、网络搜索、导入匹配和品牌图展示,方便把订阅列表整理得更直观 -- **登录与通知能力**:支持记住我、默认密码修改提醒、登录失败限流,以及 SMTP / Resend 邮件、PushPlus、Telegram Bot、Server 酱、Gotify、Webhook 等通知渠道 +- **Subscription management**: create, edit, renew, pause, disable, and restore subscriptions; review renewal history; and manage larger collections with tags, multi-tag filtering, custom ordering, search, bulk status updates, and bulk deletion. +- **Reminder rules**: define reminders before renewal, on the renewal day, and after expiration with the `days&time;` format; override defaults per subscription; and preview the resulting trigger schedule before saving. +- **Statistics and budgets**: normalize multi-currency subscriptions into a base currency, track spending totals and trends, inspect tag and status breakdowns, review the next 30 days of renewals, compare auto-renew ratios, and configure monthly, yearly, or per-tag budgets. +- **AI assistance**: extract subscription details from text or images into the form, and generate an AI summary on the statistics page. +- **Calendar and overview**: view subscriptions in a calendar, track upcoming renewals from a dedicated list, and use the dashboard for a consolidated overview. +- **Notifications**: send reminders through Webhook, SMTP / Resend email, PushPlus, Telegram Bot, Server 酱, and Gotify. +- **Logos and assets**: upload logos, reuse saved local logos, search online, and preserve or match logos during Wallos ZIP imports when possible. +- **Backup and migration**: import Wallos JSON, SQLite, and ZIP backups, and export, inspect, import, or restore native SubTracker ZIP backups. +- **Multi-currency tools**: maintain exchange-rate data, convert values into the base currency, and use the built-in currency converter. +- **Login and session controls**: support remember-me sessions, default-password change reminders, and rate limiting after repeated login failures. +- **Interface**: support Simplified Chinese and English, light, dark, and system themes, with a sticky desktop sidebar and independently scrollable content. -## 技术栈 +## Tech Stack -- **前端**:Vue 3、Vite、TypeScript、Naive UI、Pinia、TanStack Query、ECharts -- **后端**:Fastify、Prisma、SQLite、Zod、node-cron +- **Frontend**: Vue 3, Vite, TypeScript, Naive UI, Pinia, TanStack Query, ECharts +- **Backend**: Fastify, Prisma, SQLite, Zod, node-cron -## 本地开发 +## Local Development -### 1. 安装依赖 +### 1. Install dependencies ```bash npm install ``` -### 2. 复制开发环境变量 +### 2. Copy the API environment template ```bash cp apps/api/.env.example apps/api/.env ``` -### 3. 初始化数据库 +### 3. Initialize the database ```bash npm run prisma:generate @@ -108,15 +114,15 @@ npm run prisma:push npm run prisma:seed ``` -### 4. 启动开发环境 +### 4. Start the dev environment ```bash npm run dev ``` -首次登录后建议立即修改默认密码;登录接口在连续失败过多时会触发限流保护 +After the first login, changing the default admin password is strongly recommended. Login attempts are rate-limited after too many failures. -## 常用命令 +## Useful Commands ```bash npm run dev @@ -125,73 +131,66 @@ npm run lint npm test ``` -## 部署 +## Deployment -推荐直接使用安装脚本 +Use the install script for the smoothest setup: ```bash curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash ``` -脚本会按你选择的方式自动下载 Release 产物并生成部署目录: +The script downloads release artifacts, prepares the deployment directory, and lets you choose between: -- **完整部署(full)**:前端 + 后端一起部署,直接使用前端镜像 -- **仅后端部署(api)**:只部署后端 API,前端静态文件由你自己的 Nginx 托管 +- **Full deployment (`full`)**: deploy web and API together with the published frontend image +- **API-only deployment (`api`)**: deploy only the API container and host the web assets yourself -推荐使用**完整部署**,步骤更少 +**Full deployment** is the recommended default. -API 容器首次启动时会自动初始化 SQLite 数据库表结构 +On first startup, the API container initializes the SQLite schema automatically. -### 升级 +### Updating -日常升级直接拉取新镜像并重启: +For routine upgrades: ```bash docker compose pull docker compose up -d ``` -仅后端部署升级时,还需要重新下载并覆盖 `subtracker-web-dist.zip` 解压后的前端静态文件目录 +If you use API-only mode, you also need to download and replace the extracted contents of `subtracker-web-dist.zip`. -只有在这些场景下,才需要重新运行安装脚本: +You only need to rerun the install script when: -- 首次部署 -- 想重建部署目录 -- 想切换部署方式(`仅后端部署 / 完整部署`) -- 部署模板或 `.env` 模板有明显变化 +- deploying for the first time +- rebuilding the deployment directory +- switching between `api` and `full` +- adopting a newer deployment template or updated `.env` defaults -详细部署说明见 [`DEPLOYMENT.md`](./DEPLOYMENT.md) +See [`DEPLOYMENT.md`](./DEPLOYMENT.md) for the complete guide. -当前提供两种方式: +## Release Artifacts -1. **推荐**:完整部署,脚本准备部署目录后直接 `docker compose up -d` -2. **可选**:仅后端部署,外部 Nginx 托管前端静态文件,Docker 仅部署 API +Each release currently provides: -## Release 产物 +- `subtracker-web-dist.zip`: frontend static assets +- `ghcr.io/smile-qwq/subtracker-api`: API Docker image +- `ghcr.io/smile-qwq/subtracker-web`: frontend image used in full deployment -发布 Release 时会提供: +All published images support both x86 and ARM architectures. -- `subtracker-web-dist.zip`:前端静态文件 -- `ghcr.io/smile-qwq/subtracker-api`:API Docker 镜像 -- `ghcr.io/smile-qwq/subtracker-web`:完整部署使用的前端 Docker 镜像 +## License -以上 Docker 镜像均支持 x86 / ARM 双架构,Docker 会根据宿主机架构自动拉取对应变体 +This project is released under the **GNU General Public License v3.0 (GPLv3)**. -适合直接用于服务器部署 +## Acknowledgements -## 许可证 +Thanks to the following projects and ecosystems: -本项目采用 **GNU General Public License v3.0(GPLv3)** 许可证发布 - -## 致谢 - -感谢以下项目和生态为 SubTracker 提供支持: - -- [Wallos](https://github.com/ellite/Wallos) —— 提供了导入兼容方向与迁移参考 -- [Vue 3](https://vuejs.org/) 与 [Vite](https://vitejs.dev/) —— 提供前端开发基础 -- [Naive UI](https://www.naiveui.com/) —— 提供界面组件支持 -- [Fastify](https://fastify.dev/) 与 [Prisma](https://www.prisma.io/) —— 提供后端与数据访问能力 -- [Pinia](https://pinia.vuejs.org/)、[TanStack Query](https://tanstack.com/query/latest) 与 [ECharts](https://echarts.apache.org/) —— 提供状态管理、数据请求与图表展示能力 +- [Wallos](https://github.com/ellite/Wallos) — migration reference and compatibility direction +- [Vue 3](https://vuejs.org/) and [Vite](https://vitejs.dev/) — frontend foundation +- [Naive UI](https://www.naiveui.com/) — UI components +- [Fastify](https://fastify.dev/) and [Prisma](https://www.prisma.io/) — backend and data access +- [Pinia](https://pinia.vuejs.org/), [TanStack Query](https://tanstack.com/query/latest), and [ECharts](https://echarts.apache.org/) — state, data fetching, and charts ## Star History diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..eb93c80 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,125 @@ +

+ SubTracker Logo +

+ +

SubTracker

+ +

+ 一个现代化的自托管订阅管理工具,用来统一管理订阅、提醒、预算、Logo 资源和 Wallos 数据迁移。 +

+ +

+ English · + 本地开发 · + 部署 · + 部署文档 +

+ +> 当前 `main` 分支提供 **Docker / Docker Compose** 部署;如果你需要 **Cloudflare Worker 无服务器部署**,请前往 [`lite`](https://github.com/Smile-QWQ/SubTracker/tree/lite) 分支,对应的部署说明、工作流与 Worker 适配实现都维护在该分支。 + +## 功能亮点 + +- **订阅管理**:支持新增、编辑、续订、暂停、停用、恢复和续订记录查看,并提供标签分类、多标签筛选、自定义排序、搜索、批量状态调整和批量删除。 +- **提醒规则**:支持到期前、到期当天和过期提醒,使用 `天数&时间;` 格式配置;可按订阅覆盖默认规则,并提供提醒规则预览。 +- **统计与预算**:支持多币种统一换算到基准货币,提供费用统计、趋势图表、标签统计、未来 30 天续订分布、状态分布和自动续订占比,并支持月预算、年预算和标签预算。 +- **AI 能力**:支持通过文本或图片识别订阅信息并回填表单,统计页支持生成 AI 总结。 +- **日历与总览**:提供订阅日历、即将续订列表和仪表盘总览。 +- **通知通道**:支持 Webhook、SMTP / Resend 邮件、PushPlus、Telegram Bot、Server 酱和 Gotify。 +- **Logo 与资源管理**:支持 Logo 上传、本地复用、网络搜索,并在导入 Wallos ZIP 时尽量匹配已有 Logo。 +- **备份与迁移**:支持导入 Wallos 的 JSON、SQLite、ZIP 数据,并提供 SubTracker 原生 ZIP 备份的导出、检查、导入和恢复。 +- **多币种工具**:提供汇率数据管理、基准货币换算和内置货币转换器。 +- **登录与会话控制**:支持记住我、登录保留时长、默认密码修改提醒和登录失败限流。 +- **界面体验**:支持简体中文和英文,以及浅色、深色和跟随系统主题,桌面端提供固定侧边栏和独立内容滚动。 + +## 快速开始 + +### 直接部署 + +```bash +curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash +``` + +- 安装脚本启动后会先询问使用 **中文** 还是 **English**,也可以通过 `--lang zh` 或 `--lang en` 直接指定。 + +### 本地开发 + +```bash +npm install +npm run prisma:generate +npm run prisma:push +npm run prisma:seed +npm run dev +``` + +默认地址: + +- Web:`http://127.0.0.1:5173` +- API:`http://127.0.0.1:3001` + +默认账户: + +- 用户名:`admin` +- 密码:`admin` + +## 本地开发 + +### 1. 安装依赖 + +```bash +npm install +``` + +### 2. 复制环境变量 + +```bash +cp apps/api/.env.example apps/api/.env +``` + +### 3. 初始化数据库 + +```bash +npm run prisma:generate +npm run prisma:push +npm run prisma:seed +``` + +### 4. 启动开发环境 + +```bash +npm run dev +``` + +首次登录后建议尽快修改默认密码;登录失败过多时会触发限流保护。 + +## 常用命令 + +```bash +npm run dev +npm run build +npm run lint +npm test +``` + +## 部署 + +推荐直接使用安装脚本: + +```bash +curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash +``` + +可选模式: + +- **完整部署(full)**:前端 + 后端一起部署 +- **仅后端部署(api)**:只部署 API,前端静态文件自行托管 + +推荐优先使用**完整部署**。更多细节见 [`DEPLOYMENT.md`](./DEPLOYMENT.md)。 + +## 技术栈 + +- **前端**:Vue 3、Vite、TypeScript、Naive UI、Pinia、TanStack Query、ECharts +- **后端**:Fastify、Prisma、SQLite、Zod、node-cron + +## 许可证 + +本项目采用 **GNU General Public License v3.0(GPLv3)** 许可证发布。 diff --git a/scripts/install.sh b/scripts/install.sh index faa6099..f51d5c6 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -9,7 +9,9 @@ DEFAULT_WEB_IMAGE="ghcr.io/smile-qwq/subtracker-web:latest" DEFAULT_API_PORT="3001" DEFAULT_WEB_PORT="8080" DEFAULT_LOG_LEVEL="warn" +DEFAULT_SCRIPT_LANG="en" DEPLOYMENT_DOC_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/DEPLOYMENT.md" +SCRIPT_LANG="${SUBTRACKER_LANG:-auto}" MODE="" INSTALL_DIR="" @@ -24,11 +26,68 @@ NON_INTERACTIVE="false" FORCE="false" RESOLVED_REF="" +effective_lang() { + local lang_candidate="${SCRIPT_LANG:-$DEFAULT_SCRIPT_LANG}" + case "$lang_candidate" in + zh|en) ;; + *) lang_candidate="$DEFAULT_SCRIPT_LANG" ;; + esac + printf '%s' "$lang_candidate" +} + +is_en() { + [ "$(effective_lang)" = "en" ] +} + +can_prompt() { + [ "$NON_INTERACTIVE" != "true" ] && [ -r /dev/tty ] && [ -w /dev/tty ] +} + +t() { + local lang + lang="$(effective_lang)" + case "$lang:$1" in + en:mode_full) printf '%s' 'Full deployment' ;; + zh:mode_full) printf '%s' '完整部署' ;; + en:mode_api) printf '%s' 'API-only deployment' ;; + zh:mode_api) printf '%s' '仅后端部署' ;; + en:missing_cmd) printf '%s' 'Missing required command: %s' ;; + zh:missing_cmd) printf '%s' '缺少依赖命令:%s' ;; + en:unknown_arg) printf '%s' 'Unknown argument: %s' ;; + zh:unknown_arg) printf '%s' '未知参数:%s' ;; + en:invalid_mode) printf '%s' '--mode only supports api or full, got: %s' ;; + zh:invalid_mode) printf '%s' '--mode 仅支持 api 或 full,当前是:%s' ;; + en:invalid_lang) printf '%s' '--lang only supports zh, en, or auto, got: %s' ;; + zh:invalid_lang) printf '%s' '--lang 仅支持 zh、en 或 auto,当前是:%s' ;; + en:invalid_lang_choice) printf '%s' 'Invalid choice. Please enter 1, 2, zh, or en.' ;; + zh:invalid_lang_choice) printf '%s' '输入无效,请输入 1、2、zh 或 en。' ;; + en:prompt_install_dir) printf '%s' 'Deployment directory (compose, .env, and data will be created here)' ;; + zh:prompt_install_dir) printf '%s' '部署目录(脚本会在这里生成 compose、.env、data)' ;; + en:prompt_api_port) printf '%s' 'Public API port (API-only deployment)' ;; + zh:prompt_api_port) printf '%s' 'API 对外端口(仅后端部署)' ;; + en:prompt_web_port) printf '%s' 'Public web port (full deployment)' ;; + zh:prompt_web_port) printf '%s' '前端对外端口(完整部署)' ;; + en:prompt_web_origin) printf '%s' 'Final web origin for browser access / CORS, for example https://subtracker.example.com' ;; + zh:prompt_web_origin) printf '%s' '前端最终访问地址(用于浏览器跨域/CORS,例如 https://subtracker.example.com)' ;; + en:need_http_client) printf '%s' 'Either curl or wget is required to download deployment files' ;; + zh:need_http_client) printf '%s' '需要 curl 或 wget 才能下载部署文件' ;; + en:dir_exists) printf '%s' 'Directory already exists: %s. Use --force to overwrite it.' ;; + zh:dir_exists) printf '%s' '目录已存在:%s;如需覆盖请加 --force' ;; + en:latest_release_unresolved) printf '%s' 'Failed to resolve the tag behind the latest Release' ;; + zh:latest_release_unresolved) printf '%s' '无法解析 latest Release 对应的 tag' ;; + en:downloading) printf '%s' 'Downloading: %s' ;; + zh:downloading) printf '%s' '下载文件:%s' ;; + en:env_template_missing) printf '%s' 'API environment template not found: %s' ;; + zh:env_template_missing) printf '%s' '未找到 API 环境变量模板:%s' ;; + *) printf '%s' "$1" ;; + esac +} + mode_label() { if [ "${1:-$MODE}" = "full" ]; then - printf '完整部署' + t mode_full else - printf '仅后端部署' + t mode_api fi } @@ -46,12 +105,36 @@ fail() { } print_help() { - cat <<'EOF' + if is_en; then + cat <<'EOF' SubTracker deployment installer Usage: curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash - curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash -s -- --mode full --dir /opt/subtracker + curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash -s -- --mode full --dir /opt/subtracker --lang en + +Options: + --mode Deployment mode: api = API-only, full = full deployment + --dir Deployment directory, default: ./subtracker- + --release Release tag to use, default: latest + --api-image API image, default: ghcr.io/smile-qwq/subtracker-api:latest + --web-image Web image for full deployment, default: ghcr.io/smile-qwq/subtracker-web:latest + --api-port Public API port; in full mode the API still defaults to internal port 3001 + --web-port Public web port for full deployment, default: 8080 + --web-origin Final web origin for CORS, for example https://subtracker.example.com + --log-level API log level, default: warn + --lang Installer language; auto prompts first when interactive + --force Overwrite the target directory if it already exists + --yes Non-interactive mode, use defaults for missing values + --help Show help +EOF + else + cat <<'EOF' +SubTracker deployment installer + +Usage: + curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash + curl -fsSL https://raw.githubusercontent.com/Smile-QWQ/SubTracker/main/scripts/install.sh | bash -s -- --mode full --dir /opt/subtracker --lang zh Options: --mode 部署方式:api=仅后端部署;full=完整部署 @@ -63,14 +146,16 @@ Options: --web-port 完整部署前端对外端口,默认 8080 --web-origin 前端最终访问地址(用于 CORS),例如 https://subtracker.example.com --log-level API 日志级别,默认 warn + --lang 安装脚本语言;交互模式下 auto 会先询问 --force 若目录已存在则覆盖 --yes 非交互模式,缺省值直接使用默认值 --help 显示帮助 EOF + fi } need_cmd() { - command -v "$1" >/dev/null 2>&1 || fail "缺少依赖命令:$1" + command -v "$1" >/dev/null 2>&1 || fail "$(printf "$(t missing_cmd)" "$1")" } parse_args() { @@ -112,6 +197,10 @@ parse_args() { LOG_LEVEL="${2:-}" shift 2 ;; + --lang) + SCRIPT_LANG="${2:-}" + shift 2 + ;; --force) FORCE="true" shift @@ -125,7 +214,7 @@ parse_args() { exit 0 ;; *) - fail "未知参数:$1" + fail "$(printf "$(t unknown_arg)" "$1")" ;; esac done @@ -141,7 +230,7 @@ prompt_value() { return 0 fi - if [ "$NON_INTERACTIVE" = "true" ] || [ ! -r /dev/tty ]; then + if ! can_prompt; then printf '%s' "$default_value" return 0 fi @@ -156,17 +245,69 @@ prompt_value() { fi } +select_language() { + case "$SCRIPT_LANG" in + zh|en) return 0 ;; + auto|'') ;; + *) fail "$(printf "$(t invalid_lang)" "$SCRIPT_LANG")" ;; + esac + + if ! can_prompt; then + SCRIPT_LANG="$DEFAULT_SCRIPT_LANG" + return 0 + fi + + while true; do + cat > /dev/tty <<'EOF' + +Select installer language / 请选择安装语言: + 1) 简体中文 + 2) English + +EOF + printf 'Language [1/2] (default: 2): ' > /dev/tty + local answer="" + IFS= read -r answer < /dev/tty || true + case "${answer:-2}" in + 1|zh|ZH|zh-CN|zh-cn) + SCRIPT_LANG="zh" + return 0 + ;; + 2|en|EN|en-US|en-us) + SCRIPT_LANG="en" + return 0 + ;; + *) + printf '%s / %s\n' "$(t invalid_lang_choice)" "$(SCRIPT_LANG=zh; t invalid_lang_choice)" > /dev/tty + ;; + esac + done +} + select_mode() { if [ -n "$MODE" ]; then return 0 fi - if [ "$NON_INTERACTIVE" = "true" ] || [ ! -r /dev/tty ]; then + if ! can_prompt; then MODE="full" return 0 fi - cat > /dev/tty <<'EOF' + if is_en; then + cat > /dev/tty <<'EOF' + +Choose a deployment mode: + api = API-only deployment + You host the frontend assets yourself with Nginx, aaPanel, or another static site setup. + + full = Full deployment + Deploy frontend and backend together with the published web image. + +EOF + printf 'Enter deployment mode [api/full] (default: full): ' > /dev/tty + else + cat > /dev/tty <<'EOF' 请选择部署方式: api = 仅后端部署 @@ -176,32 +317,39 @@ select_mode() { 前端 + 后端一起部署,直接使用前端镜像 EOF - printf '请输入部署方式 [api/full](默认 full): ' > /dev/tty + printf '请输入部署方式 [api/full](默认 full): ' > /dev/tty + fi local answer="" IFS= read -r answer < /dev/tty || true MODE="${answer:-full}" } normalize_inputs() { + case "$SCRIPT_LANG" in + auto|zh|en|'') ;; + *) fail "$(printf "$(t invalid_lang)" "$SCRIPT_LANG")" ;; + esac + + select_language select_mode case "$MODE" in api|full) ;; - *) fail "--mode 仅支持 api 或 full,当前是:$MODE" ;; + *) fail "$(printf "$(t invalid_mode)" "$MODE")" ;; esac if [ -z "$INSTALL_DIR" ]; then - INSTALL_DIR="$(prompt_value '部署目录(脚本会在这里生成 compose、.env、data)' "./subtracker-${MODE}" "$INSTALL_DIR")" + INSTALL_DIR="$(prompt_value "$(t prompt_install_dir)" "./subtracker-${MODE}" "$INSTALL_DIR")" fi if [ "$MODE" = "api" ]; then - API_PORT="$(prompt_value 'API 对外端口(仅后端部署)' "$DEFAULT_API_PORT" "$API_PORT")" + API_PORT="$(prompt_value "$(t prompt_api_port)" "$DEFAULT_API_PORT" "$API_PORT")" else API_PORT="${API_PORT:-$DEFAULT_API_PORT}" - WEB_PORT="$(prompt_value '前端对外端口(完整部署)' "$DEFAULT_WEB_PORT" "$WEB_PORT")" + WEB_PORT="$(prompt_value "$(t prompt_web_port)" "$DEFAULT_WEB_PORT" "$WEB_PORT")" fi if [ -z "$WEB_ORIGIN" ]; then - WEB_ORIGIN="$(prompt_value '前端最终访问地址(用于浏览器跨域/CORS,例如 https://subtracker.example.com)' 'https://subtracker.example.com' "$WEB_ORIGIN")" + WEB_ORIGIN="$(prompt_value "$(t prompt_web_origin)" 'https://subtracker.example.com' "$WEB_ORIGIN")" fi } @@ -213,7 +361,7 @@ http_get() { elif command -v wget >/dev/null 2>&1; then wget -qO "$output" "$url" else - fail '需要 curl 或 wget 才能下载部署文件' + fail "$(t need_http_client)" fi } @@ -222,7 +370,7 @@ prepare_dir() { if [ "$FORCE" = "true" ]; then rm -rf "$INSTALL_DIR" else - fail "目录已存在:$INSTALL_DIR;如需覆盖请加 --force" + fail "$(printf "$(t dir_exists)" "$INSTALL_DIR")" fi fi @@ -275,7 +423,7 @@ PY fi rm -f "$metadata_file" - [ -n "$RESOLVED_REF" ] || fail '无法解析 latest Release 对应的 tag' + [ -n "$RESOLVED_REF" ] || fail "$(t latest_release_unresolved)" printf '%s' "$RESOLVED_REF" } @@ -291,7 +439,7 @@ download_repo_file() { local target_path="$2" local url url="$(raw_file_url "$repo_path")" - info "下载文件:$url" + info "$(printf "$(t downloading)" "$url")" mkdir -p "$(dirname "$target_path")" http_get "$url" "$target_path" } @@ -301,7 +449,7 @@ write_env_file() { local env_file="$INSTALL_DIR/.env" if [ ! -f "$template_file" ]; then - fail "未找到 API 环境变量模板:$template_file" + fail "$(printf "$(t env_template_missing)" "$template_file")" fi cp "$template_file" "$env_file" @@ -355,6 +503,101 @@ write_readme() { compose_file='docker-compose.yml' fi + if is_en; then + cat > "$INSTALL_DIR/INSTALL-README.md" <> "$INSTALL_DIR/INSTALL-README.md" <> "$INSTALL_DIR/INSTALL-README.md" <> "$INSTALL_DIR/INSTALL-README.md" <> "$INSTALL_DIR/INSTALL-README.md" < https://your-domain.example +- Outer Nginx -> http://127.0.0.1:${API_PORT} (API) +- Frontend static assets -> hosted by your own Nginx + +EOF + else + cat >> "$INSTALL_DIR/INSTALL-README.md" < https://your-domain.example +- Outer Nginx -> http://127.0.0.1:${WEB_PORT} +- Built-in web container -> API container + +EOF + fi + + cat >> "$INSTALL_DIR/INSTALL-README.md" <> "$INSTALL_DIR/INSTALL-README.md" < "$INSTALL_DIR/INSTALL-README.md" <