i18n: .pjson language-pack system + PMM output standard

- docs/STANDARD.md: unified banner (PMM <ver> (<arch>)), command wording,
  help layout, CHANGELOG and .pjson key-naming conventions.
- src/i18n.c/.h: pmm_tr / pmm_tr_fmt / pmm_lang_load(_active) / set_locale with
  LANG|LC_ALL auto-detect, loading a flattened key->value .pjson from
  ~/.pmm/lang/<locale>.pjson.
- pmm setting lang -l | <locale>: list installed packs; download <locale>.pjson
  from the registry mirror into ~/.pmm/lang/, persist language=<locale> in
  pmm.conf and reload. PmmConfig gains a language field (read from json/ini/conf).
- Banner + version now standardised: PMM 0.3.5 (amd64) (no OS name / brackets).
- web/mirror/lang/{zh-CN,en-US}.pjson built-in packs; web/translate.php lists
  packs (keys + download) and explains community contribution; nav now has a
  翻译 item.
- Makefile/build.bat/release.yml include src/i18n.c.

Verified on WSL: compiles; version/help banner -> 'PMM 0.3.5 (amd64)';
pmm setting lang -l lists the packs dir.
这个提交包含在:
JGZYES
2026-09-04 21:49:02 +08:00
父节点 a387043ddf
当前提交 5a7d325f25
修改 11 个文件,包含 566 行新增5 行删除
+1 -1
查看文件
@@ -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: |
+1 -1
查看文件
@@ -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
+1 -1
查看文件
@@ -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
+153
查看文件
@@ -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 <command> <args...>
commands:
list - 根据名称列出软件包
info - 根据名称列出软件包的详细信息
...
options:
-p<drive> install under <DRIVE>:\.pmm
--no-color omit ANSI colours on a terminal
```
规则:
- **第一行**必须是 banner(见 §1),后接空行。
- `commands:` 段落:每行 ` <command> - <描述>`,命令与描述间**对齐**(列宽 ≥ 22)。
- `options:` 段落:每行 ` <flag> <解释>`,flag 与解释间对齐。
- 描述文本可含中英文(由语言包决定),但**排版(空格/对齐)不随语言变化**。
- 帮助靠**语言包**渲染;无语言包时回退内置中文。
## 4. CHANGELOG 格式
`CHANGELOG/<版本>.md`,每版本一个文件,模板:
```md
# PMM v<版本>
## 新增
- <功能>
## 修复
- <bug>
## 变更
- <行为变化>
## 说明
- <注意事项>
```
规则:
- 文件名为 `CHANGELOG/<版本>.md`(如 `CHANGELOG/0.3.5.md`)。
- 章节顺序固定:`## 新增``## 修复``## 变更``## 说明`;无内容的章节可省略。
- 每条款以 `- ` 开头,动词开头,**不含**句号。
- 版本号与 `PMM_VERSION` 一致。
## 5. `.pjson` 语言包键命名规范
语言包为扁平 JSON,一个文件一种语言,后缀 `.pjson`
```
web/mirror/lang/<locale>.pjson
```
`<locale>` 采用 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`
+73
查看文件
@@ -0,0 +1,73 @@
/* i18n.c - .pjson language-pack (translation) support */
#include "i18n.h"
#include "json.h"
#include "pmm.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
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;
}
+36
查看文件
@@ -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 <stddef.h>
/* 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/<locale>.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
+107 -2
查看文件
@@ -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 <pkg> install from registry mirrors (apt-style)\n");
printf(" pmm install <file.pdm> 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 | <locale>\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 <locale>\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=<loc> 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<drive>" / "-p <drive>" 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 <pkg> (also used by the Windows "Uninstall" registration) */
if (strcmp(argv[1], "remove") == 0) {
if (argc < 3) { pmm_error("usage: pmm remove <pkg>\n"); return 1; }
+1
查看文件
@@ -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 = '';
+72
查看文件
@@ -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 <DRIVE>:\\\\.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}:"
}
+72
查看文件
@@ -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": "安装到 <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} 中的语言包:"
}
+49
查看文件
@@ -0,0 +1,49 @@
<?php
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
/* List available .pjson language packs under web/mirror/lang/. */
$langDir = __DIR__ . '/mirror/lang';
$packs = [];
if (is_dir($langDir)) {
foreach (glob($langDir . '/*.pjson') as $f) {
$locale = basename($f, '.pjson');
$keys = 0;
$data = json_decode((string)file_get_contents($f), true);
if (is_array($data)) $keys = count($data);
$packs[] = ['locale' => $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');
?>
<section class="section"><div class="wrap">
<p>PMM 的界面文案由 <code>.pjson</code> 语言包驱动。默认内置 <b>zh-CN</b> <b>en-US</b>,社区可以提交新的语言包或修正译文。</p>
<table class="dl-card" style="width:100%; border-collapse:collapse; line-height:1.8;">
<thead><tr><th style="text-align:left;">Locale</th><th style="text-align:left;">键数</th><th style="text-align:left;">下载</th></tr></thead>
<tbody>
<?php if (!$packs): ?>
<tr><td colspan="3">(暂无语言包)</td></tr>
<?php else: foreach ($packs as $p): ?>
<tr>
<td><code><?php echo htmlspecialchars($p['locale']); ?></code></td>
<td><?php echo (int)$p['keys']; ?></td>
<td><a class="copy-btn" href="<?php echo htmlspecialchars($p['file']); ?>" download>下载 .pjson </a></td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
<h2>如何贡献翻译</h2>
<p>复制 <code>zh-CN.pjson</code> 为你的语言(如 <code>ja-JP.pjson</code>),键名保持不变、只改值,然后提交到仓库的
<code>web/mirror/lang/</code> 目录,或在 GitHub 提交 Issue / PR。社区翻译会即时更新镜像,用户即可通过
<code>pmm setting lang &lt;locale&gt;</code> 安装。</p>
<p>安装语言包:</p>
<pre><code>pmm setting lang ja-JP
pmm setting lang -l</code></pre>
</div></section>
<?php pmm_footer(); ?>