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..434a998 100644 --- a/src/main.vala +++ b/src/main.vala @@ -134,10 +134,70 @@ 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); + 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"); +#if WINDOWS + // 跟随系统: 此 gettext 构建在没有 LANG/LC_* 变量时拿不到 Windows + // 的用户语言 (locale 落回 "C" -> 英文, 见上方注释), 因此把 Windows + // 用户 locale 翻成 Unix 风格名字 (zh-CN -> "zh_CN") 喂给 LANG。 + // + // 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 } 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}"