i18n: built-in zh fallback + fix setting lang URL + localize bare help

- pmm_tr now falls back to a built-in zh-CN string table when no .pjson is
  installed, so output is readable instead of printing raw keys (msg.xxx).
- pmm_lang_load_active() loads the built-in zh if the on-disk pack is absent.
- main(): initialise i18n BEFORE the bare-arg print_help, so 'pmm' help is
  localized too (previously it ran before the language pack was loaded).
- pmm setting lang <loc>: the download URL now points to {host}/mirror/lang/
  (from the registry base, swapping '/packages' for '/lang'), fixing the
  'download-failed' for language packs.
这个提交包含在:
JGZYES
2026-09-05 14:09:18 +08:00
父节点 fed6ecb8b5
当前提交 fcec803c16
修改 2 个文件,包含 50 行新增8 行删除
+27 -1
查看文件
@@ -43,13 +43,39 @@ int pmm_lang_load(const char *path) {
return 0; return 0;
} }
/* Built-in fallback so PMM is usable even before any .pjson is installed. */
static const char *kBuiltinZh =
"{\"help.usage\":\"用法:\",\"help.commands\":\"命令:\",\"help.options\":\"选项:\","
"\"cmd.install\":\"install\",\"cmd.remove\":\"remove\",\"cmd.update\":\"update\","
"\"cmd.upgrade\":\"upgrade\",\"cmd.mirror\":\"mirror\",\"cmd.search\":\"search\","
"\"msg.looking-up\":\"在镜像 %s 中查找 %s\",\"msg.selected\":\"已选择 %s@%s (os=%s arch=%s)\","
"\"msg.downloading\":\"下载 %s\",\"msg.downloaded\":\"已下载 %s\","
"\"msg.hash-sha256\":\"sha256: %s\",\"msg.hash-sha1\":\"sha1: %s\","
"\"msg.installing\":\"正在安装 %s ...\",\"msg.installed\":\"已安装 %s\","
"\"msg.checksum-ok\":\"%s 校验 OK: %s\",\"msg.checksum-mismatch\":\"校验不匹配 (%s)!\","
"\"msg.resolving-dep\":\"正在解析依赖 %s\",\"msg.registry-ok\":\"注册表 sha256 OK: %s\","
"\"msg.path-exists\":\"PATH 已包含 PMM 目录\",\"msg.err.registry-not-found\":\"找不到软件包 '%s'\","
"\"msg.err.usage\":\"用法\",\"msg.err.unknown-cmd\":\"未知命令 '%s' (试试 'pmm help')\","
"\"msg.err.download-failed\":\"下载语言包 %s 失败\",\"msg.err.invalid-locale\":\"无效语言 %s\","
"\"msg.err.not-found\":\"找不到软件包 '%s'\",\"msg.err.failed-install\":\"安装 %s 失败\","
"\"msg.err.no-registry-mirror\":\"没有配置注册表镜像\",\"msg.err.cannot-write\":\"无法写入 %s\","
"\"desc.install\":\"安装\",\"desc.remove\":\"卸载\",\"desc.update\":\"刷新索引\","
"\"desc.upgrade\":\"升级\",\"desc.mirror\":\"镜像源\",\"desc.search\":\"搜索\","
"\"desc.info\":\"详情\",\"desc.self-update\":\"更新自身\",\"desc.help\":\"帮助\","
"\"opt.p-drive\":\"安装盘符\",\"opt.no-color\":\"禁用颜色\",\"opt.quiet\":\"静默\","
"\"opt.verbose\":\"详情\"}";
int pmm_lang_load_active(void) { int pmm_lang_load_active(void) {
char dir[1024]; char dir[1024];
pmm_config_dir(dir, sizeof(dir)); pmm_config_dir(dir, sizeof(dir));
char path[1400]; char path[1400];
snprintf(path, sizeof(path), "%s/lang/%s.pjson", dir, pmm_lang_locale()); snprintf(path, sizeof(path), "%s/lang/%s.pjson", dir, pmm_lang_locale());
if (pmm_lang_load(path) == 0) return 0; if (pmm_lang_load(path) == 0) return 0;
return 1; /* no pack on disk */ /* No pack on disk: fall back to the built-in zh-CN so output is readable
* instead of printing raw keys (msg.xxx). */
JsonValue *root = json_parse(kBuiltinZh);
if (root) { if (g_lang) json_free(g_lang); g_lang = root; return 0; }
return 1;
} }
const char *pmm_tr(const char *key) { const char *pmm_tr(const char *key) {
+23 -7
查看文件
@@ -934,8 +934,23 @@ static int cmd_setting(int argc, char **argv) {
if (ml->items[i].registry && *ml->items[i].registry) { base = ml->items[i].registry; break; } if (ml->items[i].registry && *ml->items[i].registry) { base = ml->items[i].registry; break; }
if (!base) { pmm_error("%s", pmm_tr("msg.err.no-registry-mirror")); mirrors_free(ml); return 1; } if (!base) { pmm_error("%s", pmm_tr("msg.err.no-registry-mirror")); mirrors_free(ml); return 1; }
/* Language packs live under {host}/mirror/lang/, NOT under the registry
* base {host}/mirror/packages. Derive it by swapping the trailing
* "/packages" (if present) for "/lang". */
char base2[2048];
snprintf(base2, sizeof(base2), "%s", base);
{
size_t bl = strlen(base2);
static const char *pkgsuf = "/packages";
size_t pl = strlen(pkgsuf);
if (bl >= pl && strcmp(base2 + bl - pl, pkgsuf) == 0) {
strcpy(base2 + bl - pl, "/lang"); /* .../mirror/packages -> .../mirror/lang */
} else {
snprintf(base2 + bl, sizeof(base2) - bl, "/lang");
}
}
char url[2048]; char url[2048];
snprintf(url, sizeof(url), "%s/lang/%s.pjson", base, loc); snprintf(url, sizeof(url), "%s/%s.pjson", base2, loc);
mkdir_p_local(langdir); mkdir_p_local(langdir);
char out[1400]; char out[1400];
snprintf(out, sizeof(out), "%s/%s.pjson", langdir, loc); snprintf(out, sizeof(out), "%s/%s.pjson", langdir, loc);
@@ -1049,19 +1064,20 @@ int main(int argc, char **argv) {
} }
} }
#endif #endif
if (argc < 2) { print_help(); return 0; }
pmm_set_self_path(argv[0]);
argc = consume_drive_flag(argc, 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 */ /* initialise i18n BEFORE any output (so `pmm` bare help is localized too).
* Use the configured language (if any), then load its pack (or built-in). */
{ {
PmmConfig cfg; load_config(&cfg); PmmConfig cfg; load_config(&cfg);
if (cfg.language && *cfg.language) pmm_lang_set_locale(cfg.language); if (cfg.language && *cfg.language) pmm_lang_set_locale(cfg.language);
free(cfg.registry_url); free(cfg.mirror_name); free(cfg.language); free(cfg.registry_url); free(cfg.mirror_name); free(cfg.language);
pmm_lang_load_active(); pmm_lang_load_active();
} }
if (argc < 2) { print_help(); return 0; }
pmm_set_self_path(argv[0]);
argc = consume_drive_flag(argc, argv);
argc = consume_global_flags(argc, argv);
if (argc < 2) { print_help(); return 0; }
if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
print_help(); return 0; print_help(); return 0;