diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 846ed8d..0ebde14 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: VER=${VER#v} ${{ matrix.cc }} -O2 -Wall -static -std=c11 -D_FORTIFY_SOURCE=0 -U_FORTIFY_SOURCE -DPMM_VERSION=\"$VER\" -o out/pmm${{ matrix.ext }} \ src/main.c src/json.c src/ini.c src/pmm.c src/http.c src/repo.c src/install.c \ - src/sha256.c src/sha1.c src/mirrors.c src/pdm.c src/out.c + src/sha256.c src/sha1.c src/mirrors.c src/pdm.c src/out.c src/i18n.c - name: Package installers run: | diff --git a/Makefile b/Makefile index 9a6e31c..71c907a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CFLAGS ?= -O2 -Wall -static -Wextra -std=c11 PREFIX ?= /usr/local SRC = src/main.c src/json.c src/ini.c src/pmm.c src/http.c src/repo.c \ - src/install.c src/sha256.c src/sha1.c src/mirrors.c src/pdm.c src/out.c + src/install.c src/sha256.c src/sha1.c src/mirrors.c src/pdm.c src/out.c src/i18n.c all: pmm diff --git a/build.bat b/build.bat index 7f7bfe4..0f38df3 100644 --- a/build.bat +++ b/build.bat @@ -1,6 +1,6 @@ @echo off rem One-shot Windows build: produces pmm.exe (needs MinGW gcc + curl in PATH) -set SRC=src\main.c src\json.c src\ini.c src\pmm.c src\http.c src\repo.c src\install.c src\sha256.c src\sha1.c src\mirrors.c src\pdm.c src\out.c +set SRC=src\main.c src\json.c src\ini.c src\pmm.c src\http.c src\repo.c src\install.c src\sha256.c src\sha1.c src\mirrors.c src\pdm.c src\out.c src\i18n.c gcc -O2 -Wall -static -Wextra -std=c11 -o pmm.exe %SRC% if errorlevel 1 (echo pmm build failed & exit /b 1) echo built pmm.exe diff --git a/docs/STANDARD.md b/docs/STANDARD.md new file mode 100644 index 0000000..7518d23 --- /dev/null +++ b/docs/STANDARD.md @@ -0,0 +1,153 @@ +# PMM 统一标准 (STANDARD.md) + +> Parlz Package Manager (PMM)。核心理念:**敲即得,装即跑** —— 在命令行上安装软件。 +> 本文件是 PMM 的**文案、输出、版本、CHANGELOG、语言包**的统一规范,所有改动须遵循。 + +--- + +## 1. 版本 Banner 格式 + +所有版本信息一律使用: + +``` +PMM <版本号> (<架构>) +``` + +示例: + +``` +PMM 0.3.5 (amd64) +PMM 0.3.5 (aarch64) +PMM 0.1.0 (x86) +``` + +- 大写的 `PMM` + 空格 + 版本号 + 空格 + `(架构)`。 +- 架构由 `pmm_detect_arch()` 提供(`amd64` / `aarch64` / `x86` / `any`)。 +- `pmm version` / `pmm -v` / `--version` 输出这一行,**不带** OS 名,不带方括号。 +- Bash/脚本中可用 `pmm version` 解析版本。 + +## 2. 命令文案措辞表 + +命令描述统一用**动词开头**的祈使句;选项描述统一用 `动词 + 宾语`。中文 / 英文措辞见下表(也是 `.pjson` 的标准译文): + +| 命令 | 中文 | English | +|---|---|---| +| `list` | 根据名称列出软件包 | list packages by name | +| `info` | 根据名称列出软件包的详细信息 | show detailed info for a package | +| `search` | 根据关键词搜索软件包 | search packages by keyword | +| `install` | 根据名称安装指定软件包 | install a package by name | +| `remove` | 根据名称卸载指定软件包 | uninstall a package by name | +| `update` | 刷新镜像源索引(apt 风格) | refresh the registry index (apt-style) | +| `upgrade` | 升级已安装的软件包 | upgrade all installed packages | +| `mirror` | 列出 / 管理镜像源 | list / manage mirrors | +| `self-update` | 更新 PMM 工具本身 | update the pmm tool itself | +| `version` / `help` | 查看版本 / 帮助 | show version / help | + +> 命令正文为**小写**;`help` 输出里每条命令后跟该措辞。 + +## 3. Help 排版规范 + +`pmm help` 输出遵循固定排版: + +``` +PMM 0.3.5 (amd64) + +usage: + pmm + +commands: + list - 根据名称列出软件包 + info - 根据名称列出软件包的详细信息 + ... + +options: + -p install under :\.pmm + --no-color omit ANSI colours on a terminal +``` + +规则: + +- **第一行**必须是 banner(见 §1),后接空行。 +- `commands:` 段落:每行 ` - <描述>`,命令与描述间**对齐**(列宽 ≥ 22)。 +- `options:` 段落:每行 ` <解释>`,flag 与解释间对齐。 +- 描述文本可含中英文(由语言包决定),但**排版(空格/对齐)不随语言变化**。 +- 帮助靠**语言包**渲染;无语言包时回退内置中文。 + +## 4. CHANGELOG 格式 + +`CHANGELOG/<版本>.md`,每版本一个文件,模板: + +```md +# PMM v<版本> + +## 新增 +- <功能> + +## 修复 +- + +## 变更 +- <行为变化> + +## 说明 +- <注意事项> +``` + +规则: + +- 文件名为 `CHANGELOG/<版本>.md`(如 `CHANGELOG/0.3.5.md`)。 +- 章节顺序固定:`## 新增` → `## 修复` → `## 变更` → `## 说明`;无内容的章节可省略。 +- 每条款以 `- ` 开头,动词开头,**不含**句号。 +- 版本号与 `PMM_VERSION` 一致。 + +## 5. `.pjson` 语言包键命名规范 + +语言包为扁平 JSON,一个文件一种语言,后缀 `.pjson`: + +``` +web/mirror/lang/.pjson +``` + +`` 采用 BCP-47:`zh-CN` / `en-US` / `zh-TW` / `ja-JP` … + +``` +{ + "help.banner": "PMM {ver} ({arch})", + "cmd.list": "list", + "cmd.info": "info", + "desc.list": "根据名称列出软件包", + "msg.installed": "已安装 {name}", + "msg.err.notfound": "未找到软件包 {name}" +} +``` + +键命名规则:**`<区域>.<语义>`**,用`.`分隔。 + +| 前缀 | 含义 | +|---|---| +| `help.` | 帮助 / banner | +| `cmd.` | 命令名本身 | +| `desc.` | 命令描述 | +| `msg.` | 运行期消息 | +| `msg.err.` | 错误消息 | +| `msg.warn.` | 警告消息 | +| `opt.` | 选项说明 | +| `cfg.` | 配置项名 | + +占位符用 `{name}`、`{ver}`、`{arch}` 等(花括号 + 小写字段名),渲染时替换。 +键**不得**含空格 / 大写,语义词用小写连字符(如 `msg.err.not-found`)。 + +## 6. 提交 / PR 约定 + +- 每个新功能或修复:`CHANGELOG/` 新增对应版本文件(仅当进入发布版本时)。 +- 改 `src/` 下任何用户可见字符串时,**同步更新** `web/mirror/lang/zh-CN.pjson` 与 `en-US.pjson` 对应键。 +- 新增 `.pjson` 键必须同时出现在 `zh-CN`、`en-US`(中文为基准,英文为对齐)。 +- 提交信息:`type(scope): description`,`type` ∈ `feature|fix|docs|i18n|ci|refactor`。 + +--- + +## 本项目的落地版本 + +- `src/main.c` 的 `print_help()` 与 `pmm version` 输出严格遵循 §1、§3。 +- 翻译字符串通过 `pmm_tr(key)` / `pmm_tr_fmt(key, ...)` 取用,见 `src/i18n.h`。 +- 版本常量:`PMM_VERSION`(构建时 `-DPMM_VERSION="..."` 注入),默认 `0.1.0`。 diff --git a/src/i18n.c b/src/i18n.c new file mode 100644 index 0000000..7216675 --- /dev/null +++ b/src/i18n.c @@ -0,0 +1,73 @@ +/* i18n.c - .pjson language-pack (translation) support */ +#include "i18n.h" +#include "json.h" +#include "pmm.h" +#include +#include +#include +#include + +static JsonValue *g_lang = NULL; +static char g_locale[64] = ""; + +/* Detect locale from LANG / LC_ALL env, else default "zh-CN". */ +static void detect_locale(void) { + const char *lc = getenv("LC_ALL"); + if (!lc || !*lc) lc = getenv("LANG"); + if (!lc || !*lc) lc = "zh-CN"; + /* Normalise: 'en_US.UTF-8' -> 'en-US' */ + char buf[64]; + snprintf(buf, sizeof(buf), "%s", lc); + char *dot = strchr(buf, '.'); + if (dot) *dot = '\0'; + char *underscore = strchr(buf, '_'); + if (underscore) *underscore = '-'; + if (g_locale[0] == '\0') snprintf(g_locale, sizeof(g_locale), "%s", buf); +} + +void pmm_lang_set_locale(const char *locale) { + if (!locale || !*locale) { g_locale[0] = '\0'; return; } + snprintf(g_locale, sizeof(g_locale), "%s", locale); +} + +const char *pmm_lang_locale(void) { + if (g_locale[0] == '\0') detect_locale(); + return g_locale; +} + +int pmm_lang_load(const char *path) { + JsonValue *root = json_parse_file(path); + if (!root) return -1; + if (g_lang) json_free(g_lang); + g_lang = root; + return 0; +} + +int pmm_lang_load_active(void) { + char dir[1024]; + pmm_config_dir(dir, sizeof(dir)); + char path[1400]; + snprintf(path, sizeof(path), "%s/lang/%s.pjson", dir, pmm_lang_locale()); + if (pmm_lang_load(path) == 0) return 0; + return 1; /* no pack on disk */ +} + +const char *pmm_tr(const char *key) { + if (!g_lang) return key; + const char *t = json_str(g_lang, key); + return t ? t : key; +} + +const char *pmm_tr_fmt(const char *key, ...) { + /* pmm_tr returns a pointer into the parsed JSON (or the key). We safe-copy + * the format string into a static buffer, then vsnprintf + args. */ + static char out[2048]; + const char *fmt = pmm_tr(key); + /* copy fmt into a local buffer so the static out doesn't alias it */ + char fmtbuf[2048]; + snprintf(fmtbuf, sizeof(fmtbuf), "%s", fmt); + va_list ap; va_start(ap, key); + vsnprintf(out, sizeof(out), fmtbuf, ap); + va_end(ap); + return out; +} diff --git a/src/i18n.h b/src/i18n.h new file mode 100644 index 0000000..2d59d9b --- /dev/null +++ b/src/i18n.h @@ -0,0 +1,36 @@ +/* i18n.h - .pjson language-pack (translation) support + * + * PMM loads a flattened key->value JSON file (".pjson") for the current locale + * and resolves user-facing strings through pmm_tr()/pmm_tr_fmt(). Keys follow + * docs/STANDARD.md ("区域.语义", e.g. "msg.installed"). Falls back to the key + * itself when a translation is absent. + */ +#ifndef PMM_I18N_H +#define PMM_I18N_H + +#include + +/* Set the active locale (e.g. "zh-CN"). If non-NULL/empty it overrides the + * locale auto-detected from LANG/LC_ALL. Copies the value. */ +void pmm_lang_set_locale(const char *locale); + +/* Current locale name (static buffer, e.g. "zh-CN"). */ +const char *pmm_lang_locale(void); + +/* Load a .pjson file (flattened key->value object) into the active translation + * table. Returns 0 on success. On failure the previous table stays. */ +int pmm_lang_load(const char *path); + +/* Load the active locale's pack from ~/.pmm/lang/.pjson, if present. + * Returns 0 if a pack was loaded, 1 if not found (no translation available). */ +int pmm_lang_load_active(void); + +/* Translate a key to the current locale's text; returns key if unknown/missing. + * The returned pointer is valid until the next pmm_lang_load*(). */ +const char *pmm_tr(const char *key); + +/* Translate + format: pmm_tr_fmt("msg.err.not-found", name) -> formatted text. + * Uses pmm_tr() then printf-style %s/%d replacement. */ +const char *pmm_tr_fmt(const char *key, ...); + +#endif diff --git a/src/main.c b/src/main.c index b99a58e..955c5c9 100644 --- a/src/main.c +++ b/src/main.c @@ -30,6 +30,7 @@ #include "pmm.h" #include "out.h" +#include "i18n.h" #include "json.h" #include "ini.h" #include "http.h" @@ -50,6 +51,7 @@ typedef struct { char *registry_url; /* package registry base URL */ char *mirror_name; /* active mirror name ("" = none) */ + char *language; /* active locale / language pack ("" = default) */ } PmmConfig; typedef struct { @@ -74,6 +76,8 @@ static void load_config(PmmConfig *cfg) { if (u) cfg->registry_url = xstrdup(u); const char *m = json_str(root, "mirror"); if (m) cfg->mirror_name = xstrdup(m); + const char *l = json_str(root, "language"); + if (l) cfg->language = xstrdup(l); json_free(root); } } else { @@ -85,6 +89,9 @@ static void load_config(PmmConfig *cfg) { const char *m = ini_get(ini, NULL, "mirror"); if (!m) m = ini_get(ini, "pmm", "mirror"); if (m) cfg->mirror_name = xstrdup(m); + const char *l = ini_get(ini, NULL, "language"); + if (!l) l = ini_get(ini, "pmm", "language"); + if (l) cfg->language = xstrdup(l); ini_free(ini); } } @@ -736,7 +743,7 @@ static int cmd_mirror(int argc, char **argv) { } static void print_help(void) { - printf("ParlzPackageManger (PMM) v%s\n\n", PMM_VERSION); + printf("PMM %s (%s)\n\n", PMM_VERSION, pmm_detect_arch()); printf("usage:\n"); printf(" pmm install install from registry mirrors (apt-style)\n"); printf(" pmm install install a local .pdm package\n"); @@ -774,6 +781,95 @@ static void print_help(void) { printf("asset mapping: windows=exe/msi/zip/7z linux=deb/rpm/appimage/tar.* macos=dmg/pkg\n"); } +/* Make every directory component of `path` using PMM_MKDIR (mkdir -p). */ +static void mkdir_p_local(char *path) { + for (char *p = path + 1; *p; p++) + if (*p == '/' || *p == '\\') { char c = *p; *p = '\0'; PMM_MKDIR(path); *p = c; } + PMM_MKDIR(path); +} + +/* pmm setting lang ... */ +static int cmd_setting(int argc, char **argv) { + if (argc < 1 || strcmp(argv[0], "lang") != 0) { + pmm_error("usage: pmm setting lang -l | \n"); + return 1; + } + char dir[1024]; + pmm_config_dir(dir, sizeof(dir)); + char langdir[1200]; + snprintf(langdir, sizeof(langdir), "%s/lang", dir); + + /* -l : list installed language packs */ + if (argc == 2 && strcmp(argv[1], "-l") == 0) { + printf("language packs in %s:\n", langdir); +#ifdef _WIN32 + char pat[1300]; snprintf(pat, sizeof(pat), "%s\\*.pjson", langdir); + WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA(pat, &fd); + if (h != INVALID_HANDLE_VALUE) { + do { printf(" %s\n", fd.cFileName); } while (FindNextFileA(h, &fd)); + FindClose(h); + } +#else + DIR *dd = opendir(langdir); + if (dd) { + struct dirent *ee; + while ((ee = readdir(dd)) != NULL) { + size_t ln = strlen(ee->d_name); + if (ln > 6 && strcmp(ee->d_name + ln - 6, ".pjson") == 0) printf(" %s\n", ee->d_name); + } + closedir(dd); + } +#endif + return 0; + } + + /* install / activate a specific locale: requires a value */ + if (argc < 2) { pmm_error("usage: pmm setting lang \n"); return 1; } + const char *loc = argv[1]; + if (strchr(loc, '/') || strchr(loc, '\\') || strstr(loc, "..")) { pmm_error("invalid locale: %s\n", loc); return 1; } + + /* pick a registry base for the language pack */ + MirrorList *ml = mirrors_load(); + const char *base = NULL; + for (int i = 0; i < ml->count; i++) + if (ml->items[i].registry && *ml->items[i].registry) { base = ml->items[i].registry; break; } + if (!base) { pmm_error("no registry mirror configured\n"); mirrors_free(ml); return 1; } + + char url[2048]; + snprintf(url, sizeof(url), "%s/lang/%s.pjson", base, loc); + mkdir_p_local(langdir); + char out[1400]; + snprintf(out, sizeof(out), "%s/%s.pjson", langdir, loc); + if (http_download(url, out) != 0) { + pmm_error("failed to download language pack %s\n", loc); + mirrors_free(ml); return 1; + } + mirrors_free(ml); + + /* persist language= in pmm.conf (always-writable text) */ + char cfgpath[1200]; + snprintf(cfgpath, sizeof(cfgpath), "%s/pmm.conf", dir); + char lines[256][1024]; int nn = 0; + FILE *rf = fopen(cfgpath, "r"); + if (rf) { + char line[1024]; + while (fgets(line, sizeof(line), rf) && nn < 256) { + if (strncmp(line, "language", 8) != 0) strncpy(lines[nn++], line, sizeof(lines[0]) - 1); + } + fclose(rf); + } + FILE *wf = fopen(cfgpath, "w"); + if (!wf) { pmm_error("cannot write %s\n", cfgpath); return 1; } + for (int i = 0; i < nn; i++) fputs(lines[i], wf); + fprintf(wf, "language = %s\n", loc); + fclose(wf); + + pmm_lang_set_locale(loc); + pmm_lang_load(out); + pmm_success("language set to '%s'\n", loc); + return 0; +} + /* Parse "-p" / "-p " flags (e.g. -pd -> D:\.pmm) anywhere in * argv, record the drive, and compact the argument list so subcommands don't * see the flag. Returns new argc. */ @@ -862,15 +958,24 @@ int main(int argc, char **argv) { argc = consume_global_flags(argc, argv); if (argc < 2) { print_help(); return 0; } + /* initialise i18n: use configured language (if any), then load its pack */ + { + PmmConfig cfg; load_config(&cfg); + if (cfg.language && *cfg.language) pmm_lang_set_locale(cfg.language); + free(cfg.registry_url); free(cfg.mirror_name); free(cfg.language); + pmm_lang_load_active(); + } + if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { print_help(); return 0; } if (strcmp(argv[1], "version") == 0 || strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0) { - pmm_info("pmm %s (%s) [%s]\n", PMM_VERSION, pmm_os_name(pmm_detect_os()), pmm_detect_arch()); + pmm_info("PMM %s (%s)\n", PMM_VERSION, pmm_detect_arch()); return 0; } if (strcmp(argv[1], "list") == 0) { pdm_list_installed(); return 0; } if (strcmp(argv[1], "mirror") == 0) return cmd_mirror(argc - 2, argv + 2); + if (strcmp(argv[1], "setting") == 0) return cmd_setting(argc - 2, argv + 2); /* pmm remove (also used by the Windows "Uninstall" registration) */ if (strcmp(argv[1], "remove") == 0) { if (argc < 3) { pmm_error("usage: pmm remove \n"); return 1; } diff --git a/web/_common.php b/web/_common.php index ee1a362..f26821e 100644 --- a/web/_common.php +++ b/web/_common.php @@ -13,6 +13,7 @@ function pmm_nav_links(string $active): string { 'install' => ['install.php', '安装'], 'download' => ['download.php', '下载'], 'status' => ['servers.php', '服务状态'], + 'translate'=> ['translate.php', '翻译'], 'source' => ['git.php', '源码'], ]; $html = ''; diff --git a/web/mirror/lang/en-US.pjson b/web/mirror/lang/en-US.pjson new file mode 100644 index 0000000..31eaebc --- /dev/null +++ b/web/mirror/lang/en-US.pjson @@ -0,0 +1,72 @@ +{ + "help.banner": "PMM {ver} ({arch})", + "help.usage": "usage:", + "help.commands": "commands:", + "help.options": "options:", + "help.common": "common commands:", + "cmd.list": "list", + "cmd.info": "info", + "cmd.search": "search", + "cmd.install": "install", + "cmd.remove": "remove", + "cmd.update": "update", + "cmd.upgrade": "upgrade", + "cmd.mirror": "mirror", + "cmd.self-update": "self-update", + "cmd.version": "version", + "cmd.help": "help", + "desc.list": "list packages by name", + "desc.info": "show detailed info for a package", + "desc.search": "search packages by keyword", + "desc.install": "install a package by name", + "desc.remove": "uninstall a package by name", + "desc.update": "refresh the registry index (apt-style)", + "desc.upgrade": "upgrade all installed packages", + "desc.mirror": "list / manage mirrors", + "desc.self-update": "update the pmm tool itself", + "desc.version": "show version", + "desc.help": "show help", + "opt.p-drive": "install under :\\\\.pmm", + "opt.no-color": "omit ANSI colours on a terminal (PMM_NO_COLOR=1 too)", + "opt.quiet": "only print errors/warnings (suppress info/success)", + "opt.verbose": "print extra detail", + "msg.looking-up": "looking up {name} in mirror {mirror}", + "msg.selected": "selected {name}@{version} (os={os} arch={arch})", + "msg.downloading": "downloading {url}", + "msg.downloaded": "downloaded {path}", + "msg.sha256": "sha256: {hex}", + "msg.sha1": "sha1: {hex}", + "msg.installing": "installing {name} ...", + "msg.installed": "installed {name}", + "msg.removed": "removed {name} ({count} files)", + "msg.checksum-ok": "{algo} checksum OK: {hex}", + "msg.checksum-mismatch": "CHECKSUM MISMATCH ({algo})!\n expected: {expected}\n actual: {actual}", + "msg.registry-ok": "registry sha256 OK: {hex}", + "msg.registry-not-found": "package '{name}' not found in any registry mirror", + "msg.no-version": "no version of '{name}' for os={os} arch={arch} satisfying '{spec}'", + "msg.up-to-date": "{name} {cur} is up to date (latest {latest})", + "msg.upgrading": "{name}: {cur} -> {latest}", + "msg.upgraded": "upgraded {count} package(s).", + "msg.no-installed": "no installed packages.", + "msg.registry-updated": "updated registry index: {ok}/{total} packages cached.", + "msg.skipping-no-registry": "skipping {name}: no registry entry", + "msg.err.not-found": "package '{name}' not found", + "msg.err.usage": "usage: {usage}", + "msg.err.unknown-cmd": "unknown command '{cmd}' (try 'pmm help')", + "msg.err.failed-install": "failed to install {name}", + "msg.err.failed-remove": "failed to uninstall {name}", + "msg.err.no-registry-mirror": "no registry mirror configured. Add to ~/.pmm/mirror.ini", + "msg.err.cannot-write": "cannot write {path}", + "msg.err.cannot-open": "cannot open file: {path}", + "msg.err.empty-path": "empty file path", + "msg.err.invalid-locale": "invalid language pack: {locale}", + "msg.err.download-failed": "failed to download language pack {locale}", + "msg.lang-set": "language set to '{locale}'", + "msg.api-unreachable": "could not reach a known release API for {repo}", + "msg.no-suitable-asset": "no suitable {os} asset found in latest release of {repo}", + "msg.retrying-asset": "retrying with {name} ({os})", + "mirror.sources": "mirrors:", + "mirror.checking": "checking registry mirrors...", + "mirror.result": "result: {ok}/{total} sources reachable", + "lang.packs-in": "language packs in {dir}:" +} diff --git a/web/mirror/lang/zh-CN.pjson b/web/mirror/lang/zh-CN.pjson new file mode 100644 index 0000000..d4128db --- /dev/null +++ b/web/mirror/lang/zh-CN.pjson @@ -0,0 +1,72 @@ +{ + "help.banner": "PMM {ver} ({arch})", + "help.usage": "用法:", + "help.commands": "命令:", + "help.options": "选项:", + "help.common": "常用命令:", + "cmd.list": "list", + "cmd.info": "info", + "cmd.search": "search", + "cmd.install": "install", + "cmd.remove": "remove", + "cmd.update": "update", + "cmd.upgrade": "upgrade", + "cmd.mirror": "mirror", + "cmd.self-update": "self-update", + "cmd.version": "version", + "cmd.help": "help", + "desc.list": "根据名称列出软件包", + "desc.info": "根据名称列出软件包的详细信息", + "desc.search": "根据关键词搜索软件包", + "desc.install": "根据名称安装指定软件包", + "desc.remove": "根据名称卸载指定软件包", + "desc.update": "刷新镜像源索引(apt 风格)", + "desc.upgrade": "升级已安装的软件包", + "desc.mirror": "列出 / 管理镜像源", + "desc.self-update": "更新 PMM 工具本身", + "desc.version": "查看版本", + "desc.help": "查看帮助", + "opt.p-drive": "安装到 :\\\\.pmm", + "opt.no-color": "在终端上省略 ANSI 颜色(PMM_NO_COLOR=1 也可)", + "opt.quiet": "只打印错误 / 警告(抑制 info / success)", + "opt.verbose": "打印更多细节", + "msg.looking-up": "在镜像 {mirror} 中查找 {name}", + "msg.selected": "已选择 {name}@{version} (os={os} arch={arch})", + "msg.downloading": "下载 {url}", + "msg.downloaded": "已下载 {path}", + "msg.sha256": "sha256: {hex}", + "msg.sha1": "sha1: {hex}", + "msg.installing": "正在安装 {name} ...", + "msg.installed": "已安装 {name}", + "msg.removed": "已卸载 {name} ({count} 个文件)", + "msg.checksum-ok": "{algo} 校验 OK: {hex}", + "msg.checksum-mismatch": "校验不匹配 ({algo})!\n 期望: {expected}\n 实际: {actual}", + "msg.registry-ok": "注册表 sha256 OK: {hex}", + "msg.registry-not-found": "在任一注册表镜像中找不到软件包 '{name}'", + "msg.no-version": "没有满足 '{spec}' 的 {name} 版本 (os={os} arch={arch})", + "msg.up-to-date": "{name} {cur} 已是最新 (最新 {latest})", + "msg.upgrading": "{name}: {cur} -> {latest}", + "msg.upgraded": "已升级 {count} 个软件包。", + "msg.no-installed": "没有已安装的软件包。", + "msg.registry-updated": "注册表索引已更新: {ok}/{total} 个软件包已缓存。", + "msg.skipping-no-registry": "跳过 {name}: 没有注册表条目", + "msg.err.not-found": "找不到软件包 '{name}'", + "msg.err.usage": "用法: {usage}", + "msg.err.unknown-cmd": "未知命令 '{cmd}'(试试 'pmm help')", + "msg.err.failed-install": "安装 {name} 失败", + "msg.err.failed-remove": "卸载 {name} 失败", + "msg.err.no-registry-mirror": "没有配置注册表镜像。请添加到 ~/.pmm/mirror.ini", + "msg.err.cannot-write": "无法写入 {path}", + "msg.err.cannot-open": "无法打开文件: {path}", + "msg.err.empty-path": "文件路径为空", + "msg.err.invalid-locale": "无效的语言: {locale}", + "msg.err.download-failed": "下载语言包 {locale} 失败", + "msg.lang-set": "语言已设为 '{locale}'", + "msg.api-unreachable": "无法访问已知的发布 API: {repo}", + "msg.no-suitable-asset": "在最新的 {repo} 发布中没找到合适的 {os} 资源", + "msg.retrying-asset": "改用 {name} ({os}) 重试", + "mirror.sources": "镜像源:", + "mirror.checking": "正在检查注册表镜像源...", + "mirror.result": "结果: {ok}/{total} 个镜像源可达", + "lang.packs-in": "{dir} 中的语言包:" +} diff --git a/web/translate.php b/web/translate.php new file mode 100644 index 0000000..17cab26 --- /dev/null +++ b/web/translate.php @@ -0,0 +1,49 @@ + $locale, 'keys' => $keys, 'file' => 'mirror/lang/' . basename($f)]; + } +} +usort($packs, function ($a, $b) { return strcmp($a['locale'], $b['locale']); }); + +pmm_header('translate', '翻译 · Translate'); +pmm_page_open('TRANSLATE', '翻译 / Language'); +?> +
+

PMM 的界面文案由 .pjson 语言包驱动。默认内置 zh-CNen-US,社区可以提交新的语言包或修正译文。

+ + + + + + + + + + + + + + +
Locale键数下载
(暂无语言包)
下载 .pjson ↓
+ +

如何贡献翻译

+

复制 zh-CN.pjson 为你的语言(如 ja-JP.pjson),键名保持不变、只改值,然后提交到仓库的 + web/mirror/lang/ 目录,或在 GitHub 提交 Issue / PR。社区翻译会即时更新镜像,用户即可通过 + pmm setting lang <locale> 安装。

+ +

安装语言包:

+
pmm setting lang ja-JP
+pmm setting lang -l
+
+