1. 多语言环境支持
为了在Linux下实现多语言支持,必须在定制安装程序时,引入与glibc和图形环境两者对应的多语言环境支持。对于glibc环境而言,它是整个 Linux中文化的基础,我们需要在/usr/share/locale目录下保存Locale信息,在/usr/share/consolefonts 目录下保存字体信息,在/usr/lib/gconv目录下保存字符转换模块的信息。
对于X Windows环境,我们需要在/usr/X11R6/lib/X11/locale目录下保存X Locale的配置,同时在/usr/X11R6/lib/X11/fonts目录下保存正常显示所需要的字体集。
在安装程序启动之后,必须正确设置LC_*变量同时调用setlocale函数。为了能显示中文,安装程序还必须加载正确的中文字体。
1.1. 国际化的基本概念
国际化(Internationalization,简写为I18N)是指软件在设计结构和机制上支持多语言的扩展特性,其功能和代码设计不针对某一特定语言和地域。Locale是ANSI C语言中最基本的支持国际化的标志,对中文Linux来说,支持中文Locale是最基本的要求。
1.1.1. Locale环境
Locale的命名规则:<语言>_<地区名>.<字符编码名称>
对于zh_CN.GB2312而言,zh表示中文,CN表示大陆地区,GB2312表示使用的字符集为GB2312。
Locale使用一组分类,用户可以独立的操纵每一组分类。用户既能用设置环境变量的方法,也能使用setlocale设置它们。这些分类都保存在/usr/share/locale下。它们包含了:
LC_COLLATE
用于比较和排序。
LC_CTYPE
用于字符分类和字符串处理,控制所有字符的处理方式,包括字符编码,字符是单字节还是多字节,如何打印等。
LC_MONETORY
用于格式化货币单位。
LC_NUMERIC
用于格式化非货币的数字显示。
LC_TIME
用于格式化时间和日期。
LC_MESSAGES
用于控制程序输出时所使用的语言,主要是提示信息,错误信息,状态信息, 标题,标签, 按钮和菜单等。
LC_ALL
它不是环境变量,只是一个宏,可使用setlocale设置所有的LC_*环境变量。这个变量设置之后,可以废除LC_*和LANG的设置值,使得这些变量的设置值与LC_ALL的值一致。
LANG
它的值用于指定上面环境变量没有设置的所有变量值。如果指定了上面任何一个变量的值,则会废除对应的LANG值的缺省设置。
还可以包括其他的环境变量LC_ADDRESS,LC_IDENTIFICATION,LC_PAPER,LC_NAME,LC_TELEPHONE,LC_MEASUREMENT。
标准Locale:
"C"
这是标准的C Locale。它所指定的属性和行为由ISO C标准所指定。它是程序启动时缺省使用Locale。
"POSIX"
这是标准的POSIX Locale。它是标准的C Locale的别名。
""
空名字是让程序选择当前环境设置值。
设置一个中文环境需要正确的设置上述Locale变量,举例来说,在使用zh_CN.GB2312环境时,使用locale命令,所见到的系统环境为:
LANG="zh_CN.GB2312"
LC_CTYPE="zh_CN.GB2312"
LC_NUMERIC="zh_CN.GB2312"
LC_TIME="zh_CN.GB2312"
LC_COLLATE="zh_CN.GB2312"
LC_MONETARY="zh_CN.GB2312"
LC_MESSAGES="zh_CN.GB2312"
LC_PAPER="zh_CN.GB2312"
LC_NAME="zh_CN.GB2312"
LC_ADDRESS="zh_CN.GB2312"
LC_TELEPHONE="zh_CN.GB2312"
LC_MEASUREMENT="zh_CN.GB2312"
LC_IDENTIFICATION="zh_CN.GB2312"
LC_ALL=
1.1.2. 创建Locale环境
为了建立locale环境,我们必须具备下面的描述文件:
locale-data
这个文件定义了Locale环境(LC_*)的所有细节,包括字符的分类与转换,字符排序,区域显示时间,货币显示格式等等。通常是保存在系统的/usr/share/i18n/locales目录下。
charmap
这个文件定义了Locale中所有字符与内码的对应关系。通常是保存在系统的/usr/share/i18n/charmaps目录下。
这两个文件都是纯文本文件,可以使用文本编辑器直接察看和修改。通过这两个文件就可以生成对应的locale环境。缺省条件下,生成的locale环境是以二进制的形式保存在/usr/share/locale目录下。把这两个文本文件生成locale环境的工作是由localedef程序实现的。举例来说,生成zh_CN.GB2312的locale环境:
mkdir /home/usr/share/locale/zh_CN.gb2312
localedef -I zh_CN -f GB2312 zh_CN.GB2312 --prefix=/home
cd /home/usr/share/locale/
mv zh_CN.gb2312 zh_CN.GB2312
这几条命令在/home目录下,生成Locale环境zh_CN.GB2312。因为按照POSIX标准,一个Locale的编码名称是大小写无关的。虽然我们指定的是大写的GB2312,但是glibc为了统一起见,它会生成一个小写的编码名称。但是由于很多程序依赖于zh_CN.GB2312,因此对这个文件进行了改名。
除了Locale环境之外,您还需要gconv文件。这一组文件是用来定义glibc的gconv系统在遇到GB2312编码的字符时,应使用哪一个模块来处理。gconv-modules文件描述了字符编码和处理模块文件对应关系。例如,在/usr/lib/gconv/gonv-modules文件中,需要包含下列行:
alias GB2312// EUC-CN//
1.1.3. X Window系统的多语言环境
X Window系统的多语言环境是在系统底层libc的Locale架构的基础上建立起来的。X函数库需要利用libc提供的函数来进行字符之间的转换。因此,为了使X Window应用程序的Locale正确工作,您必须首先设置一个正确的libc Locale环境,同时正确设置LC_CTYPE这个类别。
指定了编码方式并且将字符的辨识和转换用libc的函数处理之后,X Window系统的多语言处理的主要问题就变为图形显示和输入了。在X系统下,多语言环境必须能做到多语言字符图形化输出和字符输入。字符的图形化输出还要处理字型,这又与字型的设定方式有关。
与libc一样,在X Window系统下也有关于Locale的设置部分,称为X-Locale。XFree86系统都把它保留在 /usr/X11R6/lib/X11/locale目录下。在这个目录下的每个Locale都有一个目录,存放各自的X-locale,一般这个文件的名字是XLC_LOCALE。这个文件里面包含了跟该区域编码相关的设定,而文件中以#字号开头的是注释。以安装程序上简体中文XLC_LOCALE的内容为例:
XLC_FONTSET
# fs0 class (7 bit ASCII)
fs0 {
charset {
name ISO8859-1:GL
}
font {
primary ISO8859-1:GL
vertical_rotate all
}
}
# fs1 class
fs1 {
charset {
name GB2312.1980-0:GL
}
font {
primary GB2312.1980-0:GL
}
}
END XLC_FONTSET
以上内容定义的是显示Locale时使用的字符集。在多语言环境中,为了同时显示中英文,系统往往需要多种字体。例如,上例中表示:在显示简体中文时要使用两种不同编码的字体,其中一个是使用了GB2312编码的中文字体,其字体名称以GB2312.1980-0结束,另一个是ISO8859-1编码的英文字体,其字体名称以ISO8859-1结束。
XLC_LOCALE的下一部分定义了Locale中的字体在X系统中的处理方式:
XLC_XLOCALE
encoding_name zh.euc
mb_cur_max 2
state_depend_encoding False
wc_encoding_mask \x30000000
wc_shift_bits 7
cs0 {
side GL:Default
length 1
wc_encoding \x00000000
ct_encoding ISO8859-1:GL
}
cs1 {
side GR:Default
length 2
wc_encoding \x30000000
ct_encoding GB2312.1980-0:GL; GB2312.1980-0:GR
}
END XLC_XLOCALE
cs0定义的部分为iso8859-1编码的英文字符,每个字符占用一个字节。cs1定义的部分为GB2312编码的中文字符,每个字符占用两个字节,这两个字节都必须用GB2312.1980-0的字体来表示。注意,对于上面的X设置,GB2312.1980-1类型的字体是无法正常显示的,除非用户修改X Locale。
除此之外,/usr/X11R6/lib/X11/locale还使用locale.dir和locale.alias文件定义可用X-Locale的名称和位置。其中,locale.dir定义了每个X-Locale文件的位置以及实际的Locale名称。locale.alias文件则定义了每个 Locale可能的别名。
X Window 的字体存放在/usr/X11R6/lib/X11/fonts目录下,一般使用PCF的字体。在将新的字体文件拷贝到此目录之后,运行
mkfontdir
由这条命令更新改目录下的fonts.dir的内容。例如,
fzs14.pcf.Z fzs14
helvR12_iso01.pcf.gz -adobe-helvetica-medium-r-normal--12-120-75-75-p-67-iso8859-1
上面两行的信息是从字体文件中抽取出来的,比如,helvR12_iso01.pcf.gz文件中就包含iso8859-1编码的文件。但是,字体文件 fzs14.pcf.Z就不包含类似的字体说明信息,因此我们还需要使用fonts.alias文件建立字体别名。
-hlc-song-medium-r-normal--14-140-75-75-c-140-gb2312.1980-0 fzs14
这样在安装程序使用下面的资源时,X Window系统就知道要加载fzs14字体了。
style "font" {
fontset="-*-helvetica-*-r-normal-*-14-*-*-*-*-*-iso8859-1, -*-medium-*-14-*-*-*-*-*-*-gb2312.1980-0"
}
1.2. 安装程序的国际化实现
自动生成po文件
在安装程序中,定义了两个函数_()和__(),前者是对传入的串进行翻译,后者是直接返回传入的串,凡是要进行多语言支持的串必须使用这两个函数。它们两者在安装程序中的用法是不同的。前者是用于一些可以进行重建的界面控件,并且它们的值一般作为局部变量出现;而后者一般用于全局变量,为了让这个串显示多种语言,需要使用translate()函数进行处理。使用这两个函数的主要目的是可以很方便的使用xgettext程序自动生成po文件。
xgettext只对c语言的源程序文件生效,因此在使用它之前必须先将perl源程序转换为c程序,然后再运行xgettext自动提出需要进行多语言处理的文本串。下面的程序段是po目录下Makefile文件的一部分,它可以自动生成空的po文件。
PMSFILES = $(wildcard *.pm)
PMSCFILES = $(PMSFILES:%=%_.c)
POFILES = $(shell ls *.po)
all: $(PMSCFILES) DrakX.pot
clean:
rm -f empty.po messages $(POFILES:%=%t) $(PMSCFILES)
verif:
perl -ne '/^\s*#/ or $$i += my @l = /\b__?\(/g; END { print "$$i\n" }' $(PMSFILES)
perl -ne '$$i += my @l = /\.c:/g; END { print "$$i\n" }' DrakX.pot
DrakX.pot: $(PMSFILES)
xgettext -F -n --keyword=_ --keyword=__ -o $@ $(PMSCFILES)
rm $(PMSCFILES)
$(PMSCFILES): %_.c: %
perl -pe 's|^(__?\()| $$1|; s|#([^+].*)|/*\1*\/|; s|$$|\\n\\|' $< > $@
处理po文件
在安装程序中po文件并不进行编译。因为perl语言处理文本文件的能力非常强,所以它直接作为文本文件形式存在。在调入po文件时,perl根据msgid和msgstr的内容生成一个hash结构,然后由此结构进行文本串的处理。
#- 此函数加载指定语言的po文件
sub load_po {
my ($lang) = @_;
my ($s, $from, $to, $state, $fuzzy);
#- 将$s中的内容作为一个perl文件存在,在这个文件中生成一个巨大的hash结构
#- 当进行多语言处理时,根据这个hash结构获得翻译的文本串。
$s .= "package po::I18N;\n";
$s .= "no strict;\n";
$s .= "\%{'$lang'} = (";
$f = "po/$lang.po";
local *F;
my $pid;
unless ($f && -e $f) {
… …
#- 如果文件不存在,则从压缩包中取出此文件,并打开
}
local $_;
while (
/^msgstr/ and $state = 1;
/^msgid/ && !$fuzzy and $state = 2;
s/@/\\@/g;
if (/^(#|$)/ && $state != 3) {
$state = 3;
if (my @l = $to =~ /%(\d+)\$/g) {
$to =~ s/%(\d+)\$/%/g;
$to = qq([ "$to", ] . join(",", map { $_ - 1 } @l) . " ),";
} else {
$to = qq("$to");
}
#- 形成hash结构,msgid的内容保存在$from中,msgstr的内容保存在$to中
$s .= qq("$from" => $to,\n) if $from;
$from = $to = '';
}
$to .= (/"(.*)"/)[0] if $state == 1;
$from .= (/"(.*)"/)[0] if $state == 2;
$fuzzy = /^#, fuzzy/;
}
$s .= ");";
no strict "vars";
eval $s;
$pid and waitpid $pid, 0;
!$@;
}
文本串翻译
#- 文本串的翻译过程主要是访问读入po文件所形成的hash结构,由它的值返回翻译后的文本串。
sub translate {
my ($s) = @_;
#- 如果Locale环境未设置,则设置环境为英语
my ($lang) = $ENV{LANGUAGE} || $ENV{LC_MESSAGES} || $ENV{LC_ALL} || $ENV{LANG} || 'en';
require lang;
foreach (split ':', $lang) {
#- 如果hash结构未定义,则调入语言对应的po文件
lang::load_po($_) unless defined $po::I18N::{$_};
#- 如果未能正确加载po文件或者待翻译的串不存在,则返回源串$s。
if (%{$po::I18N::{$_}}) {
return if $s eq '_I18N_';
return ${$po::I18N::{$_}}{$s} || $s
}
}
$s;
}
l 设置Locale环境
设置Locale环境主要就是设置Locale变量的值。
#- [ 'English|United States', 'iso-8859-1', 'en', 'en_US:en' ]这个数组表示
#- 第一个元素是语言环境的文本描述。
#- 通过第二个元素的值查询%charset结构,可以获得对应的字体。
#- 第三个元素是LANG变量的设置值。
#- 第四个元素是LANGUAGE变量的设置值。
my %languages = (
'en_US' => [ 'English|United States', 'iso-8859-1', 'en', 'en_US:en' ],
'zh_CN.GB2312' => [ 'Chinese|Simplified', 'gb2312', 'zh_CN.GB2312', 'zh_CN.GB2312:zh_CN.gb2312:zh_CN:zh' ]
);
my %charsets = (
"iso-8859-1" => [ "lat0-sun16", undef, "iso15",
"iso8859-1", "850", sub { std("iso8859-1", @_) } ],
"gb2312" => [ undef, undef, undef,
"gb2312", "936", "-*-*-*-*-*-*-*-*-*-*-*-*-gb2312.1980-1" ],
);
unless (-e "$ENV{SHARE_PATH}/locale/$languages{$lang}[2]") {
@ENV{qw(LANG LC_ALL LANGUAGE LINGUAS)} = ();
eval { commands::rm("-r", "$ENV{SHARE_PATH}/locale") };
eval {
#- 取出Locale环境
require packdrake;
my $packer = new packdrake("$ENV{SHARE_PATH}/locale.cz2");
$packer->extract_archive("$ENV{SHARE_PATH}/locale", "UTF-8", $languages{$lang}[2]);
};
}
#- 明确设置Locale的环境变量
$ENV{LC_NUMERIC} = "C";
$ENV{LC_TIME} = "C";
$ENV{LC_COLLATE} = "C";
$ENV{LC_MONETARY} = "C";
$ENV{LC_MESSAGES} = "C";
$ENV{LC_PAPER} = "C";
$ENV{LC_NAME} = "C";
$ENV{LC_ADDRESS} = "C";
$ENV{LC_TELEPHONE} = "C";
$ENV{LC_MEASUREMENT} = "C";
$ENV{LC_IDENTIFICATION} = "C";
$ENV{LC_CTYPE} = $lang;
$ENV{LANG} = $languages{$lang}[2];
$ENV{LANGUAGE} = $languages{$lang}[3];
设置Locale变量之后,调用Gtk::Rc->parse_string()装入所需要的字体资源,然后调用Gtk->set_locale()使Locale设置生效就可以了。
2. 启动图形环境
启动图形环境,实际上就是在系统中运行X服务程序。为了使一个X服务器正常运行,必须生成正确的XFree86配置文件。在安装程序的 install_gtk.pm的createXconf函数中创建XFree86配置文件。关于XFree86的详细配置过程您也可以参看《如何在Linux下实现设备的配置》。
#- 这是安装程序的createXconf函数,它的主要功能是生成XFree86的配置文件。为了使下面
#- 的代码段更清晰,删除了一些根据芯片类型和鼠标类型进行配置的代码。因此,下面的代码和真
#- 实的安装程序代码略有不同而且只支持x86体系结构下的配置文件。
#- 建立真实鼠标设备的别名链接文件/dev/mouse。
symlinkf($mouse_dev, "/dev/mouse");
#- 建立imlib库配置文件和调色板文件的链接文件
symlink("/tmp/stage2/etc/imrc", "/etc/imrc");
symlink("/tmp/stage2/etc/im_palette.pal", "etc/im_palette.pal");
#- 生成XFree86配置文件$file
local *F;
open F, ">$file" or die "can't create X configuration file $file";
print F <
#- 下面的文本行,实际上是XFree86的配置文件。这个配置文件包含字体路径设置、键盘设置、
#- 鼠标设备设置、监视器设置和屏幕设置部分。
Section "Files"
FontPath "/usr/X11R6/lib/X11/fonts:unscaled"
EndSection
#- 键盘设置部分,设置当前键盘为标准键盘。
Section "Keyboard"
Protocol "Standard"
AutoRepeat 0 0
LeftAlt Meta
RightAlt Meta
ScrollLock Compose
RightCtl Control
XkbDisable
END
EndSection
#- 设置鼠标配置,包括设置鼠标的通讯协议类型以及鼠标设备对应的设备文件。
Section "Pointer"
Protocol "$mouse_type"
Device "/dev/mouse"
ZAxisMapping 4 5
EndSection
#- 设置监视器配置。由于在定制安装环境时,只使用了3.3.6的X服务器XF86_VGA16和
#- XF86_FBDev,这两种服务器的X配置文件都是无法指定显示分辨率的。对于XF86_VGA16,
#- 它只能支持640x480的16色显示方式。对于XF86_FBDev,它支持的显示方式是由当时内核
#- Framebuffer的工作模式决定的。比如,内核以0x311方式启动,那么对应的分辨率是
#- 640x480的16位色,那么在使用XF86_FBDev作为X服务器时,X的对应配置也必须与此完#- 全一致。
Section "Monitor"
Identifier "My Monitor"
VendorName "Unknown"
ModelName "Unknown"
HorizSync 31.5-35.5
VertRefresh 50-70
Modeline "640x480" 25.175 640 664 760 800 480 491 493 525
Modeline "640x480" 28.3 640 664 760 800 480 491 493 525
ModeLine "800x600" 36 800 824 896 1024 600 601 603 625
EndSection
#- 使用XF86_VGA16作为X服务器时,对应的X配置节。
Section "Screen"
Driver "vga16"
Device "Generic VGA"
Monitor "My Monitor"
Subsection "Display"
Modes "640x480"
ViewPort 0 0
EndSubsection
EndSection
#- 使用XF86_FBDev作为X服务器时,对应的X配置节。设置Modes为"default",表示选择
#- 的模式与内核启动时设置的模式相同。
Section "Screen"
Driver "fbdev"
Device "Generic VGA"
Monitor "My Monitor"
Subsection "Display"
Depth 16
Modes "default"
ViewPort 0 0
EndSubsection
EndSection
END
将上述程序段运行之后,X配置脚本就生成了,这时就可以使用这个配置脚本文件启动X服务程序了。下面这段启动X的程序是来自于文件install_steps_gtk.pm。
devices::make("/dev/kbd");
local (*T1, *T2);
open T1, ">/dev/tty5";
open T2, ">/dev/tty6";
#- 执行X服务器程序,返回1表示运行成功。
my $launchX = sub {
my $ok = 1;
#- 接管信号SIGCHLD的处理程序
#- 在进程终止时,SIGCHLD信号被发送给它的父进程。此程序在子进程结束时调用。
local $SIG{CHLD} = sub { $ok = 0 if waitpid(-1, c::WNOHANG()) > 0 };
#- 子进程执行
#- XF86_FBDev -kb -dpms -s 240 -allowMouseOpenFail -xf86config $f
#- allowMouseOpenFail允许鼠标打开失败
unless (fork) {
exec $_[0], "-kb", "-dpms","-s" ,"240",
"-allowMouseOpenFail", "-xf86config", $f or exit 1;
}
#- 测试X服务器是否成功激活
foreach (1..60) {
sleep 1;
log::l("Server died"), return 0 if !$ok;
return 1 if c::Xtest($ENV{DISPLAY});
}
log::l("Timeout!!");
0;
};
#- 准备测试的X服务器
my @servers = qw(FBDev VGA16);
foreach (@servers) {
log::l("Trying with server $_");
my $dir = "/usr/X11R6/bin";
my $prog = "XF86_$_";
#- 如果X服务程序不可执行
unless (-x "$dir/$prog") {
#- 删除/usr/X11R6/bin/下的所有X服务程序
unlink $_ foreach glob_("$dir/X*");
#- 从安装盘上拷贝对应的X服务程序
install_any::getAndSaveFile("HappyLinux/happyinst$dir/$prog", "$dir/$prog") or die "failed to get server $prog: $!";
chmod 0755, "$dir/$prog";
}
#- 根据/proc/fb文件的内容检测内核是否启动了Framebuffer方式。
if (/FB/) {
!$o->{vga16} && $o->{allowFB} or next;
$o->{allowFB} = &$launchX($prog) and goto OK;
} else {
$o->{vga16} = 1 if /VGA16/;
&$launchX($prog) and goto OK;
}
}
注: (我们在此专题中介绍的是HappyLinux 3.0的安装程序,它的安装程序源码您可以在安装盘的/HappyLinux/happyinst/usr/bin/perl-install目录下找到。安装程序的源码是由Perl编制的,使用PerlGtk绘制图形化用户接口。)