From cc244dedb35ebb4c1a47d052fee82d24b756b026 Mon Sep 17 00:00:00 2001 From: Sam-Fic <169824983+Sam-Fic@users.noreply.github.com> Date: Tue, 10 Nov 2026 23:44:00 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(i18n):=20Windows=20=E4=B8=8B=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E8=AE=BE=E7=BD=AE=E4=B8=8E=E8=B7=9F=E9=9A=8F=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E4=B8=8D=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因 (本机 4.10.0 便携包实测复现): - GNU gettext 只在当前 locale 非 "C" 时才读取 LANGUAGE; 该 Windows gettext 构建在无 LANG/LC_* 环境变量时 locale 落回 "C" (Win32 回退 无效), LANGUAGE 被整体忽略。 - 偏好设置只写 LANGUAGE=zh_CN/en -> 永远英文 (包内 zh_CN.mo 完好)。 - "跟随系统"同样受害: 中文 Windows 上 locale 也落回 "C" -> 英文。 修复 (src/main.vala, 仅 Windows): - 显式 zh/en: 同时设 LANG=zh_CN.UTF-8 / LANG=en, 让 locale 脱离 "C"。 实测: 仅 LANGUAGE=zh_CN -> 英文; LANGUAGE=zh_CN + LANG=zh_CN.UTF-8 -> 中文; 两分支 en 亦验证。 - 跟随系统: 用 g_win32_getlocale() 把 Windows 用户 locale (zh-CN) 翻成 "zh_CN" 喂给 LANG; 无条件覆盖以避免重启继承残留旧值。 打包 (tools/build-windows-package.sh): 新增 i18n 运行时冒烟, 解包后 真实运行 --help (LANG=zh_CN.UTF-8) 断言中文输出, 防止 gettext 链 再断而清单校验照过的回归。 docs/I18N.md: 补充 Windows 下 LANGUAGE 无效、需用 LANG 的说明。 --- docs/I18N.md | 2 +- src/main.vala | 27 +++++++++++++++++++++++++++ tools/build-windows-package.sh | 12 ++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/I18N.md b/docs/I18N.md index fd20405..77ab607 100644 --- a/docs/I18N.md +++ b/docs/I18N.md @@ -114,7 +114,7 @@ sudo meson install | 新增含 `_()` 的源文件 | 将路径添加到 `POTFILES` → 重新生成 `.pot` → 合并 → 翻译 | | 新增 `.blp` 文件中的字符串 | 先编译 Blueprint → 重新生成 `.pot` → 合并 → 翻译 | | 新增界面语言 | 在 `LINGUAS` 添加语言代码 → 创建 `.po` → 在 `meson.build` 的 `languages` 数组添加 → 翻译 | -| 临时切换运行语言 | 使用 `LANGUAGE=en filecollector` 环境变量,对 GUI 和 CLI 均生效 | +| 临时切换运行语言 | Linux/macOS:`LANGUAGE=en filecollector`,对 GUI 和 CLI 均生效;**Windows 下 `LANGUAGE` 会被 gettext 忽略**(无 `LANG`/`LC_*` 变量时 locale 落回 "C",这是 GNU gettext 的既定行为),须改用 `LANG=zh_CN.UTF-8 filecollector` 或 `LANG=en filecollector` | --- diff --git a/src/main.vala b/src/main.vala index f954029..efd006e 100644 --- a/src/main.vala +++ b/src/main.vala @@ -134,10 +134,37 @@ public class FileCollectorApp : Adw.Application { var lang_setting = FileCollectorWindow.load_settings_language (); if (lang_setting == "en") { GLib.Environment.set_variable ("LANGUAGE", "en", true); +#if WINDOWS + // Windows 的 gettext 只在当前 locale 非 "C" 时才读取 LANGUAGE + // (GNU gettext 的既定行为); 而此环境在无 LANG/LC_* 变量时 locale + // 落回 "C", LANGUAGE 被整体忽略 -> 语言设置永远只有英文。 + // 实测须同时设 LANG 让 locale 脱离 "C": + // 仅 LANGUAGE=zh_CN -> 英文 + // LANGUAGE=zh_CN + LANG=zh_CN.UTF-8 -> 中文 + // zh 用 "zh_CN.UTF-8" 是为了与 locale/ 下的目录名一致 (gettext + // 按 LANG 值逐级尝试 zh_CN -> zh 查找 .mo), 且 UCRT 可解析。 + GLib.Environment.set_variable ("LANG", "en", true); +#endif } else if (lang_setting == "zh") { GLib.Environment.set_variable ("LANGUAGE", "zh_CN", true); +#if WINDOWS + GLib.Environment.set_variable ("LANG", "zh_CN.UTF-8", true); +#endif } else { GLib.Environment.unset_variable ("LANGUAGE"); +#if WINDOWS + // 跟随系统: 此 gettext 构建在没有 LANG/LC_* 变量时拿不到 Windows + // 的用户语言 (locale 落回 "C" -> 英文, 见上方注释), 因此把 Windows + // 用户 locale 翻成 Unix 风格名字 (zh-CN -> "zh_CN") 喂给 LANG。 + // 无条件覆盖而非仅在缺失时补设: GUI 重启子进程会继承上一会话写入 + // 的 LANG, 若仅在缺失时补设, 从 zh/en 切回"跟随系统"会残留旧值。 + var sys_locale = GLib.Win32.getlocale (); + if (sys_locale != null && sys_locale.length > 0 && sys_locale != "C") { + GLib.Environment.set_variable ("LANG", sys_locale + ".UTF-8", true); + } else { + GLib.Environment.unset_variable ("LANG"); + } +#endif } Intl.setlocale (LocaleCategory.ALL, ""); diff --git a/tools/build-windows-package.sh b/tools/build-windows-package.sh index 7dc53ac..f336a07 100755 --- a/tools/build-windows-package.sh +++ b/tools/build-windows-package.sh @@ -147,6 +147,18 @@ BAT # 打包前用 PE 导入表做依赖闭包校验, 任何一个非系统 DLL 缺失都直接失败。 python3 tools/verify_windows_package.py "${STAGING_DIR}" +# i18n 运行时冒烟: --help 走完整 gettext 链 (bindtextdomain -> zh_CN.mo 加载)。 +# 曾出过: .mo 在包里、清单校验也过, 但 gettext 运行时根本没加载译文, 语言 +# 设置/跟随系统在用户侧永远只有英文 (Windows 下 gettext 在 locale 为 "C" 时 +# 忽略 LANGUAGE)。这里真实运行主程序验证译文可达, 而不是只看清单。 +echo "Running i18n runtime smoke test (LANG=zh_CN.UTF-8 --help -> Chinese) ..." +HELP_OUT="$(cd "${STAGING_DIR}/bin" && LANG=zh_CN.UTF-8 ./filecollector.exe --help 2>/dev/null || true)" +if ! printf '%s\n' "${HELP_OUT}" | grep -q '命令行模式'; then + echo "i18n smoke test failed: --help output is not Chinese with LANG=zh_CN.UTF-8" >&2 + printf '%s\n' "${HELP_OUT}" | head -5 >&2 + exit 1 +fi + PACKAGE_PATH="${DIST_DIR}/filecollector-windows-${VERSION}-x64.zip" ( cd "${STAGING_DIR}" From 7f36c3bca2b1d9bed969b78662931f69c34146ae Mon Sep 17 00:00:00 2001 From: Sam-Fic <169824983+Sam-Fic@users.noreply.github.com> Date: Wed, 11 Nov 2026 01:36:18 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(i18n):=20=E8=B7=9F=E9=9A=8F=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=88=86=E6=94=AF=E4=BF=AE=E6=AD=A3=20g=5Fwin32=5Fget?= =?UTF-8?q?locale=20=E7=9A=84=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E9=99=B7?= =?UTF-8?q?=E9=98=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 冒烟复检发现: g_win32_getlocale() 只要见到 LANG/LC_* 就原样返回其值, 只有全空才查 Windows (GetThreadLocale)。两处受害: - 重启链路: 上一会话写入的 LANG=zh_CN.UTF-8 被原样返回, 拼成 zh_CN.UTF-8.UTF-8 垃圾值 -> 切回"跟随系统"反而英文; - Git Bash 启动: 其自带 LANG=C.UTF-8, 同样中招 (本机实测复现)。 修正: 取系统 locale 前临时摘掉 LANG/LC_ALL/LC_MESSAGES 强制走 Win32 路径; 引入 FILECOLLECTOR_LANG_MANAGED 标记区分应用自己写的 LANG (重启链路, 无条件覆盖) 与外部真实语言设置 (尊重); "C"/"C.*"/ "POSIX" 视同未设置。 --- src/main.vala | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/main.vala b/src/main.vala index efd006e..434a998 100644 --- a/src/main.vala +++ b/src/main.vala @@ -144,11 +144,13 @@ public class FileCollectorApp : Adw.Application { // zh 用 "zh_CN.UTF-8" 是为了与 locale/ 下的目录名一致 (gettext // 按 LANG 值逐级尝试 zh_CN -> zh 查找 .mo), 且 UCRT 可解析。 GLib.Environment.set_variable ("LANG", "en", true); + GLib.Environment.set_variable ("FILECOLLECTOR_LANG_MANAGED", "1", true); #endif } else if (lang_setting == "zh") { GLib.Environment.set_variable ("LANGUAGE", "zh_CN", true); #if WINDOWS GLib.Environment.set_variable ("LANG", "zh_CN.UTF-8", true); + GLib.Environment.set_variable ("FILECOLLECTOR_LANG_MANAGED", "1", true); #endif } else { GLib.Environment.unset_variable ("LANGUAGE"); @@ -156,13 +158,44 @@ public class FileCollectorApp : Adw.Application { // 跟随系统: 此 gettext 构建在没有 LANG/LC_* 变量时拿不到 Windows // 的用户语言 (locale 落回 "C" -> 英文, 见上方注释), 因此把 Windows // 用户 locale 翻成 Unix 风格名字 (zh-CN -> "zh_CN") 喂给 LANG。 - // 无条件覆盖而非仅在缺失时补设: GUI 重启子进程会继承上一会话写入 - // 的 LANG, 若仅在缺失时补设, 从 zh/en 切回"跟随系统"会残留旧值。 - var sys_locale = GLib.Win32.getlocale (); - if (sys_locale != null && sys_locale.length > 0 && sys_locale != "C") { - GLib.Environment.set_variable ("LANG", sys_locale + ".UTF-8", true); - } else { + // + // g_win32_getlocale() 的实现是: 有 LANG/LC_* 环境变量时原样返回 + // 其值, 只有全空才查 Windows (GetThreadLocale)。重启子进程总带着 + // 上一会话写入的 LANG (拼 .UTF-8 会得到垃圾值), Git Bash 还会塞 + // LANG=C.UTF-8 —— 所以先临时摘掉这三个变量, 强制走 Win32 路径。 + // + // FILECOLLECTOR_LANG_MANAGED 标记 LANG 是否由本应用写入: + // - 有标记 (重启链路) -> 无条件覆盖, 保证 zh/en 切回"跟随系统" + // 时不会残留旧语言; + // - 无标记但 LANG 是 "C"/"C.*"/"POSIX" (Git Bash 默认, 不表达 + // 语言偏好) -> 视同未设置, 覆盖为系统 locale; + // - 无标记且 LANG 有真实语言 (用户在 shell 里故意设的) -> 尊重。 + bool lang_is_ours = GLib.Environment.get_variable ("FILECOLLECTOR_LANG_MANAGED") != null; + string inherited = GLib.Environment.get_variable ("LANG") ?? ""; + bool lang_is_c = inherited == "C" || inherited.has_prefix ("C.") || inherited == "POSIX"; + if (lang_is_ours || inherited.length == 0 || lang_is_c) { + string? saved_lc_all = GLib.Environment.get_variable ("LC_ALL"); + string? saved_lc_msg = GLib.Environment.get_variable ("LC_MESSAGES"); + string? saved_lang = GLib.Environment.get_variable ("LANG"); + GLib.Environment.unset_variable ("LC_ALL"); + GLib.Environment.unset_variable ("LC_MESSAGES"); GLib.Environment.unset_variable ("LANG"); + var sys_locale = GLib.Win32.getlocale (); + if (saved_lc_all != null) { + GLib.Environment.set_variable ("LC_ALL", saved_lc_all, true); + } + if (saved_lc_msg != null) { + GLib.Environment.set_variable ("LC_MESSAGES", saved_lc_msg, true); + } + if (saved_lang != null) { + GLib.Environment.set_variable ("LANG", saved_lang, true); + } + if (sys_locale != null && sys_locale.length > 0 && !sys_locale.has_prefix ("C")) { + GLib.Environment.set_variable ("LANG", sys_locale + ".UTF-8", true); + GLib.Environment.set_variable ("FILECOLLECTOR_LANG_MANAGED", "1", true); + } else { + GLib.Environment.unset_variable ("LANG"); + } } #endif }