Fedora 44 上 LANG 被 /etc/profile.d/lang.sh 的 CJK fallback 逻辑强制改为 en_US.UTF-8,并通过 niri-session 的 import-environment 泄漏进 systemd user 环境。本文记录根因分析与 systemd oneshot 覆盖修复方案。

LANG 环境变量被强制改为 en_US.UTF-8 问题排查与修复

主机:Fedora 44 (qbh-x220) · 用户:qibohan · 修复日期:2026-09-09


一、问题现象

桌面环境(niri + systemd user 环境)中 LANG 显示为 en_US.UTF-8,而不是预期的 zh_CN.UTF-8

导致中文环境变量丢失,影响:

  • 应用程序界面语言(切换为英文)
  • 中文 locale 相关的排序、日期、分词行为

二、根因分析

1. 罪魁祸首:/etc/profile.d/lang.sh 的 CJK fallback 逻辑

Fedora 的 /etc/profile.d/lang.sh 中包含专门针对虚拟终端(VT / console)的 LANG 修正逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
# The ${LANG} manipulation is necessary only in virtual terminal (a.k.a. console - /dev/tty*):
if [ -n "${LANG}" ] && [ "${TERM}" = 'linux' ] && /usr/bin/tty | /usr/bin/grep --quiet -e '/dev/tty'; then
if /usr/bin/grep --quiet -E -i -e '^.+\.utf-?8$' <<< "${LANG}"; then
case ${LANG} in
ja*) LANG=en_US.UTF-8 ;;
ko*) LANG=en_US.UTF-8 ;;
si*) LANG=en_US.UTF-8 ;;
zh*) LANG=en_US.UTF-8 ;; # ← 中文被强制改成 en_US
ar*) LANG=en_US.UTF-8 ;;
fa*) LANG=en_US.UTF-8 ;;
he*) LANG=en_US.UTF-8 ;;
...

触发条件(三个同时满足才生效):

  1. LANG 变量非空(且是 .utf-8 格式)
  2. TERM = linux(虚拟终端专用 TERM)
  3. 当前 tty 是 /dev/tty*(true VT / console,不是 ssh 的 pty)

这是 Fedora 的设计行为:CJK 字符集在 Linux 控制台(无图形环境)下无法正常渲染,所以强制回退到 en_US.UTF-8 以保证 console 可用。

2. 泄漏链路:为什么桌面环境也被污染

正常情况下,登录进图形会话(Wayland/niri)后,桌面进程的 LANG 应由 display manager / session 启动器 注入,而不是走 lang.sh

但当时的链路是:

1
2
niri-session 启动 → systemctl --user import-environment → 
把 niri 进程的环境导入 systemd user 环境

如果 niri 启动时(或其上游)恰好经过了一个 TERM=linux + /dev/tty* 的登录路径,lang.sh 就把 LANG 改成了 en_US.UTF-8,并随之导入 systemd user 环境 → 所有由 systemd user 启动的桌面应用都继承到错误的 LANG。


三、排查过程摘要

步骤 检查项 结论
1 echo $LANG(交互 shell) 表现为 en_US.UTF-8
2 /etc/profile.d/lang.sh 源码审查 找到 zh* → en_US.UTF-8 fallback
3 确认触发条件 TERM=linux + /dev/tty* 同时成立
4 ~/.config/environment.d/ 检查 存在但优先级低于 import-environment
5 systemd user 环境变量(systemctl --user show-environment LANG 已被污染为 en_US.UTF-8

关键发现: niri-sessionimport-environment 注入优先级 高于 environment.d,所以光靠 environment.d/xx-lang.conf 无法覆盖,必须在 systemd user 层面修正。


四、解决方案

创建 systemd user 服务,在 niri 启动前 显式把 LANG 覆盖回 zh_CN.UTF-8

文件:~/.config/systemd/user/fix-lang.service

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=Override LANG to zh_CN.UTF-8 in systemd user environment
# 在 niri 启动前执行(import-environment 之后), 覆盖 lang.sh fallback 泄漏的 en_US
Before=niri.service
PartOf=niri.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/systemctl --user set-environment LANG=zh_CN.UTF-8

[Install]
WantedBy=graphical-session.target

启用命令

1
2
systemctl --user daemon-reload
systemctl --user enable --now fix-lang.service

五、验证结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 服务状态
systemctl --user status fix-lang.service
# → Active: active (exited) · ExecStart 状态 SUCCESS

# 2. systemd user 环境
systemctl --user show-environment | grep LANG
# → LANG=zh_CN.UTF-8

# 3. systemd-run 启动的进程也继承正确值
systemd-run --user --scope bash -c 'echo $LANG'
# → zh_CN.UTF-8

# 4. 当前 shell
echo $LANG
# → zh_CN.UTF-8

均验证通过,桌面应用恢复中文环境。


六、要点总结

  1. **不要改 /etc/profile.d/lang.sh**:那是 Fedora 对 Linux console 的正确保护(CJK 在 VT 下无法渲染),改它会影响 console 可用性,且会被系统更新覆盖。
  2. **不要只靠 ~/.config/environment.d/**:在该机器上,niri-sessionimport-environment 优先级更高,会覆盖 environment.d 的注入。
  3. 正确做法:在 systemd user 环境层创建一个 oneshot service,在图形会话启动链中(早于 niri)用 systemctl --user set-environment 强制覆盖——这样所有由 systemd user 环境派生的进程(桌面应用、托盘、后台服务)都拿到正确的 LANG。
  4. 该方案对 systemd user 环境改对环境变量后无需重启,下次 niri-session 启动即自动生效;当前会话也已即时生效。
⬆︎TOP