前言
Docker 容器多用于部署、运行程序或服务,像开发的 Web 应用之类的程序的本地化可以交给容器中运行的程序完成,毕竟用户不会直接操作容器中的 Linux 系统。从这点看,将 Docker 容器本地化似乎是多此一举,但 Linux 中的许多程序可能使用 Linux 的本地化库实现多语言功能,这时程序就需要使用 Linux 的本地化设置。如果想让这些程序在容器中显示正确的语言文字,就必须配置好容器中的 Linux。
本文将给出简单方便的解决方案,不需要安装 locales/locales-all 之类的包、甚至不用改动任何文件。
Linux 的本地化
Arch Linux 安装时,我们会修改 /etc/locale.gen 文件,取消掉想要安装的本地化语言的注释,然后使用 locale-gen 进行安装。最后在 /etc/locale.conf 中设置 LANG 和 LC_ALL 等环境变量,就完成了本地化配置。
local-gen
下面是 locale-gen 命令的关键代码:
echo "Generating locales..." while read locale charset; do \ case $locale in \#*) continue;; "") continue;; esac; \ is_entry_ok || continue echo -n " `echo $locale | sed 's/\([^.\@]*\).*/\1/'`"; \ echo -n ".$charset"; \ echo -n `echo $locale | sed 's/\([^\@]*\)\(\@.*\)*/\2/'`; \ echo -n '...'; \ if [ -f $LOCALES/$locale ]; then input=$locale; else \ input=`echo $locale | sed 's/\([^.]*\)[^@]*\(.*\)/\1\2/'`; fi; \ localedef -i $input -c -f $charset -A /usr/share/locale/locale.alias $locale; \ echo ' done'; \ done < $LOCALEGEN echo "Generation complete."
locale-gen 逐行读取 /etc/locale.gen 文件,将每行的空格分割的两个值赋值到 locale 和 charset 变量,其中 # 开头的行将被跳过。最后使用了 localedef 命令编译安装相应的本地化语言。
LANG 和 LC_ALL 环境变量
LANG 、LC_ALL 和其他 LC_ 开头的环境变量用于指明当前系统使用的本地化语言。LC_ALL 和 LANG 是其他 LC_ 开头的环境变量的默认值,并且 LC_ALL 要优先于 LANG 。即,如果同时设置 LC_ALL 和 LANG ,有效的将是 LC_ALL 。将它们写入到 /etc/locale.conf 可以在系统启动时被加载。
Docker 容器本地化的方法
理论上,我们只需对容器中的 Linux 进行相同操作即可。一些 Docker 基镜像并不会提供 locale-gen 命令和 /etc/locale.gen 配置文件。不过,前文我们已经得知 locale-gen 实际上使用 localedef 命令安装本地化语言,因此我们可以直接用 localedef 替代 locale-gen 命令。
但事与愿违的是,一些镜像不会包含本地化必要的文件。也就是说,即便使用 localedef 命令替代 locale-gen 命令,在一些容器中也无法成功。
localedef 编译安装本地化语言需要使用 /usr/share/i18n/locales/ 和 /usr/share/i18n/charmaps/ 中的文件,上图的镜像中没有,所以无法安装。对于 Ubuntu 系统这些文件在 locales 包中(题外话,安装 locales-all 包就是安装所有本地化语言),使用 apt-get 安装这个包固然没问题,但不要忘了我们的宿主机或许有些什么可以拿来用用。
最终方案
看到这里,你可能会想到下面两个问题:
- localedef 到底创建/修改了什么文件使得系统语言改变?
- 既然 /usr/share/i18n/ 目录挂载到容器可以正常的被 localedef 使用;那么如果将 localedef 创建/修改的这些文件挂载到容器相应目录,容器是不是可以直接支持宿主机的本地化语言?
回到 locale-gen 命令,上文贴出的 while 循环的代码前面有几行令人在意。
# Remove all old locale dir and locale-archive before generating new # locale data. rm -rf /usr/lib/locale/* || true
从注释可以看出,locale-gen 的运行似乎会在 /usr/lib/locale/ 生成文件。而 locale-gen 基本上只调用了 localedef 命令。事实上, /usr/lib/locale/ 确实是存放编译好的本地化数据文件, localedef 编译的本地化文件就放在该目录。
因此我们最终的、也是最简单方便的解决方法如下:
- 在容器中挂载宿主机的 /usr/lib/locale/ 目录
- 将宿主机的 LANG / LC_ALL 环境变量设置到容器上
当然,前提是你的宿主机配置好了中文本地化语言。
图形界面
通过一些参数可以在 Docker 容器中运行 X Window 客户端。使用上文的本地化方法可能还不能使 GUI 中的文字显示正常。你可能会看到许多方块,这一般是缺少中文字体文件造成的。同样,我们可以将宿主机的 /usr/share/fonts/ 目录挂载到容器中。
输入法
目前为止,运行在容器中的 GUI 程序可以正常显示中文了。但如果你的宿主机装有 fcitx 之类的输入法,你应该无法正常切换成中文输入。此时,在容器中设置 XMODIFIERS、QT_IM_MODULE 和 GTK_IM_MODULE 环境变量即可正常使用输入法。同样,如果宿主的这些环境变量设置正常,可以直接使用宿主的环境变量的值。
sudo docker run -it --rm -v $XAUTHORITY:/root/.Xauthority:ro \ -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:ro \ -v /usr/lib/locale:/usr/lib/locale:ro -e LANG \ -v /usr/share/fonts:/usr/share/fonts:ro \ -e XMODIFIERS -e QT_IM_MODULE -e GTK_IM_MODULE \ scottyhardy/docker-wine notepad
结果如下图,中文输入法可以正常切换出来。不过文字输入到记事本后显示为方块。在“字体”设置中,发现默认的字体是“System”。换成文泉驿字体后就如下图正常显示了。
其他问题
localedef 等命令及机制,都是建立在 glibc 库基础上的。由于 Docker 的 Alpine 镜像使用的 musl 库而非 glibc,因此本文的方法不能在 Alpine 镜像中直接使用。在 Alpine 中,许多依赖于 glibc 库的程序需要安装好 glibc 库才能正常使用。
如有谬误,望加指正。转载注明出处即可。
近期评论