关键字
文章内容
使用m17n实现对各国语言间的代码移植和转换
 
 
修改时间:[2012/05/14 15:30]    阅读次数:[1227]    发表者:[起缘]
 
    为了让 Linux® 应用程序在全世界范围都可以使用,而不会在西方语言与世界上其他语言之间产生任何区别,我们应该发行一些本地化后的版本,它们可以输入、存储、提取或呈现任何语言,而不管这些语言是多么复杂。多语言库,或称为 m17n,为类 UNIX® 平台上的所有语言提供了一个国际化解决方案。

    在很短的时间之内 —— 总共还不到 20 年 —— 个人计算机已经成为我们工作和生活中的一种必需设备。受到半导体和处理器快速发展的推动,大量的供应商使得计算机的价格一落千丈,Internet 也已经在全球广为分布,个人计算机现在已经不再是一种奢侈品,而是一种常见的家用电器了。

    实际上,在很多富裕的国家(例如美国、日本、英国),每两个家庭就会拥有一台计算机,并且会使用宽带服务。就全世界来看,虽然家庭收入可能会有很大的不同,但是个人计算机都很容易购买了,即使在马尔代夫,我们也很容易购买到笔记本。另外,如果我们碰巧说的是 Dhivehi 方言(马尔代夫的一种方言),微软也为我们提供了一个这种版本的 Microsoft® Windows® XP 操作系统。

    就全球广泛接受的个人计算机来说,大部分现代操作系统都提供了一些编程库来促进 国际化 的发展,或者将软件调整为支持多种语言的。国际化(通常简写为 i18n,节选自 i-nternationalizatio-n)库通常都会将应用程序的文本资源(按钮标签、用户界面[UI] 提示和菜单选项)保存成多种语言的。在启动国际化后的应用程序后显示哪种语言,这要取决于用户的区域设置 —— 通常,这是一个可配置的系统或个人帐号首选项。

    理想来说 —— 至少对于独立软件供应商来说 —— 相同的可执行程序以日语或希腊语运行时都能运行得一样好。然而,构建 “本地方言” 版本的应用程序的情况远远没有这么理想。包括被广泛认可的 ISO(International Standards Organization)/IEC(International Engineering Consortium)10646 和 Unicode,没有哪种字符编码可以解决如何实现任意语言的输入和呈现问题。ISO/IEC 10646 和 Unicode 只指定了如何存储、检索和排序字符以及字符的特殊组合。例如,这些标准并没有规定统一的格式、嵌入式数据或标识来让使用泰国语书写的文档怎样才能按照泰国语的规范规则正确地呈现出它们的样子来。是的,Unicode 可以维护使用泰国语书写的文档的内容,也可以保证这种文件在所有使用 Unicode 的平台上都可以很好地进行移植,但是它并不能保证我们可以正确查看文件,也不能保证文档所呈现出来的样子与作者的意图一致。

    我们来考虑一下这种情况:尽管 Linux GNU C 库(glibc)提供了一些函数来处理 ISO 10646 兼容的 31 位字符,但是它并不能保证这些字符可以在显示设备上正确进行显示。有些 glibc 字符串函数,例如 strcat() 和 strlen(),都可以正确地处理多字节的问题,但是要正确显示阿拉伯语,需要双向(bidi)显示的功能,这种功能只有在图形用户界面(GUI)工具包和专用字符串显示库中才能找到。

    例如,GNOME 需要 GTK+ 工具包和 Pango(一个文本显示库)来实现对 i18n 的完整支持(然而,Pango 在解决自己用途不够广泛方面有一些限制。请参看侧栏 pango 的问题)。其他 GUI 工具包提供了对 i18n 的支持,但却并不总是能兼容这些标准。当然,Linux 上的图形应用程序也需要 X Window System 的基本显示库 Xlib,它提供了两种绘图(形状和线条)和字符显示原语。不幸的是,Xlib 只能显示西欧语言。

    Pango 的问题

    Pango 可以放置(布局)并显示一些复杂的手稿,但是不能对多字节的文本进行排序或搜索功能。Pango 假设底层库 —— 通常是使用 C 语言编写的,可以对使用 Unicode 标准指定的所有语言都进行操作 —— 可以执行基本的文本处理操作。
 
 
    一个库显示所有语言

    要让应用程序在全球都可以使用 —— 而不会在西方国家和世界上其他很多语言之间产生不公平的现象 —— 我们必须要能够 输入、存储、提取并 显示 任何语言,而不管这究竟会多么复杂。正如上面介绍的一样,有一些广为认可的标准为多字节存储和可移植性提供了一些便利;然而,现在还没有为输入和显示制定标准。更加糟糕的是,即使是最好的多语言文本编辑器也会被迫混合使用简单的国际化库和私有 GUI 工具包。添加一种语言可能会需要另外一种(很可能是新的)定制库。

    Multilingualization Library 或 m17n 库会尽力为类 UNIX 平台上多种语言书写的文本的输入、处理和显示提供一种单一的解决方案。另外,m17n 的目标是充分利用现有且大家都可以很好地理解的典型 UNIX 应用程序框架,而不是利用软件开发人员的其他模型。

    最后,m17n 会努力使国际化的内容更加丰富,而不仅仅是简单地从英语移植到另外一种语言上。使用 m17n,同一个二进制文件可以在一个系统上显示法语,在另外一个系统上显示蒙古语,甚至在同一个屏幕上就可以显示多种语言的文本。更好的是,m17n 可以(令人信服地)实现诸如文本数据库之类的功能,这使它可以存储并处理大量的国际化内容。

    m17n 库是在日本 Tsukuba 的 National Institute for Advanced Science and Technology 工作的 4 个日本程序员编写的。很多年以来,日本都一直走在了国际化的前端,部分原因是日语学者一直在试图为人文学科探索一种百科全书式的方法 —— 尤其对世界上各种语言更为关注。

    m17n 库是由 3 个库和一个存储单一脚本以及正确显示脚本所需要的元数据的数据库构成的:

    m17n C 库可以类似地实现 glibc (以及各种风格的 libc )的一些基本的文本处理功能。 
    m17n X 库与 Xlib 是紧密对应的。它提供了基本的绘制字符的功能,并且对呈现所做的假设很少。 
    m17n 工具包提供了一些功能,可以对复杂的脚本进行处理,使它们可以准备好在屏幕上进行显示。例如,泰国语在显示之前,必须进行排序、合成和重新排序。

    最后,m17n 数据库存储了每种语言所特有的一些数据。例如,某种特有语言可能会需要自己的字体、一种特定的编码以及一些特殊的机制来输入自己的数据。m17n 库是与语言无关的;m17n 数据库中保存了所有与语言有关的信息。


    图 1 给出了 m17n 的 4 个部分,以及这些库是如何与现有的系统组件对应的。m17n 组件和传统 UNIX 库之间存在惊人的类似之处并不意外:m17n 的创建者希望能够让多语言的应用程序的编写尽量简单。我们只要使用一个等效的多语言库来替换相同语义的函数即可。

    (从侧面来看,m17n C 和 X 库就预示着 X 服务器可以提供国际化功能。不过,m17n 对底层操作系统和呈现机制的假设较少,因此我们可以将 m17n 移植到其他窗口系统上。实际上,将 m17n 集成到跨平台的 GUI 工具包(例如类 UNIX 系统上使用的 Qt)正是当前的工作重点,m17n 团队正在将自己的代码加入 GTK 的修正版本中。)


    字符一瞥

    添加新拼字法也非常简单:不需要改编 m17n 库来显示新脚本。相反,只需要创建一个新的 m17n M-text,并将 M-text 添加到 m17n 数据库上即可。

    可以将 M-text 当作一个泛化的 C 字符串,因为这就可以将任意属性添加到通常与 C 字符串有关的字符代码中。一个属性可以指定语言要显示的字符,而另外一个属性则可以指定特定的字体。Bidi 信息也是使用 M-text 表示来进行编码的,并且基本的字形信息都可以出现。

    例如,图 2(已经得到 m17n 开发人员的许可进行复制)展示了这些属性如何用来修改文本字符串的外观。这个字符串非常简单,内容是 “This is sample text to show the property”。然而,每个字符串都有一个 face 属性 —— 或多个 face 属性 —— 它决定了要使用哪些字体来显示字符。该图中所显示的 face 属性都故意进行了简化,但是我们可以看到这种特性所提供的灵活性,这对于世界上很多手写语言来说都是必要的。

    有很多脚本都需要复杂的过程来重新进行排序,或重新放置各个要显示的复杂合成图形。诸如泰米尔语、缅甸语和泰国语之类的脚本在进行显示之前都需要这种重新排序过程。作为更为具体的一个例子,图 3(也已经获得作者许可进行复制)展示了单词 Hindi 是如何进行处理来使用 Devanagri 脚本正确显示的。这需要两个阶段。第一个阶段是将字符序列从字节顺序(字符在内存中如何存储)转换成正确的手写顺序(字符如何在纸面上显示)。第二个阶段负责扫描特有的字形和读音序列(如果存在)并将这个序列替换成 “复合” 字形(英语有很多这种转换来增强文本的可读性。根据所使用的字体的不同, f 和 i 序列通常都会使用一个 fi 字形代替,这要取决于我们选择的字体)。

    这个重新排序过程的通用名字是 Complex Font Layout(CFL)。通常,CFL 信息都包含在字体中,在某些情况中,已经写死到显示库中了。在 m17n 中,CFL 信息可以在 FLT(Font Layout Table)中找到。有些拼字法需要少量的 FLT 数据;另外一些字符则需要很多信息来捕获复杂的规则。

    例如, Sino-Japanese orthography 就没有前后规则可以影响单个字形组合的复合。然而,泰国语的确有一些有趣的规则可以影响 orthography 的变化,但是它对于泰国口语不会产生任何影响。泰国语的拼字法 对于周围的文本来说非常敏感,但是对于口语来说则并非如此。印度脚本中特定的组合规则也相当复杂,必须使用 FLT 来显示。

    最后,诸如字体、双向显示、Unicode 和语言之类的数据都会将文本的显示呈现在屏幕上。下一个棘手的问题 —— 也可能是现在正出现在您脑海中的问题 —— 是我们如何以非 ASCII 字体的形式来输入文本。

    使用有 500 个键的键盘

    对于英语和很多欧洲语言来说,一个字符映射为一个键(或两个键)就足够了。大写键可以直接打印,键盘驱动程序可以对很多更为特殊的情况进行编码,但是其模型是相同的:按下一个键就代表输入某个特定的字符。

    那么,如果一种拼字法中有数百个字符,或者更特殊一点,它们之间有很多组合,情况如何呢?那么我们就不能简单地使用一次击键来实现了,而是需要使用 击键序列 ,或者快速连续输入多个键。有一种特别的软件叫 输入法,它可以将每个键盘序列转换成一个字符或一系列字符。

    当然,有些击键序列可能就是一次击键。另外,我们可以创建一种输入法将标准的拉丁字母键盘 转换 成其他语系的拼字法。例如,老式的日语键盘就将拉丁字母转换成平假名和片假名。然而,试图使用 26 个字母(A 到 Z 的拉丁字母)表示大约 46 个平假名有些困难。

    键盘映射、击键序列以及音译输入法都可以使用 m17n 数据库来表示。这种方法很大的一个优点是可以将拼字法 的规则与应用程序代码清楚地区分开来。应用程序代码最适合由程序员来开发;如何显示正确的文本则是语言学家的工作。

    获取 m17n 库

    正如上面介绍的一样,m17n 包括 3 个库和 m17n 数据库。现在,我们可以使用一个 m17n libc,另外还可以使用一个 Xlib 的 m17n 版本进行编码。开发团队正在努力编写第 3 层的库即 m17n X 工具包,它将成为 GTK+ 的一部分。m17n 开发人员也从事语言绑定的工作,这样诸如 Perl 和 Ruby 之类的编程语言就都可以使用 m17n 了(这个工具包和绑定何时可用,尚没有进度表)。m17n 库也已经被接纳为 Linux Standard Base(LSB)的一个部分,它可能会成为 Linux 国际化标准实现的一个很好的部分。

    m17n 库的最新版本是 1.3.3,这是在 2006 年 2 月 22 日发布的。我们可以按照下面的方式来获取 m17n 库:

    下载 m17n 源代码。这个下载页面中还提供了使用英语和日语编写的程序员文档包。
如果喜欢使用 CVS(Concurrent Versions System),也可以使用下面的 CVS 命令来下载代码:
$ cvs -d :pserver:[email protected]:/cvs/m17n login
$ cvs -d :pserver:[email protected]:/cvs/m17n co m17n-lib
$ cvs -d :pserver:[email protected]:/cvs/m17n co m17n-db
 

    从源代码开始编译程序也非常简单:m17n 库使用了典型的配置脚本来配置系统,并为编译和安装创建合适的 Makefile(详细内容请参看 m17n 软件包中的 README 文件)。

    如果碰巧使用的是 Debian 的发行版本,就可以使用非常方便的 APT 安装工具来安装 m17n 库和它所依赖的库。例如,要寻找 Debian 系统上所有可用的 m17n 包,就需要使用 apt-cache,例如 apt-cache search m17n。
根据 APT 所指向的 Debian 储存库的不同,可能会看到如清单 1 所示的输出内容。

    清单 1. apt-cache search m17n 命令的输出结果
 
libm17n-0 - a multilingual text processing library - runtime
libm17n-dev - a multilingual text processing library - development
m17n-db - a multilingual text processing library - database
m17n-docs - a multilingual text processing library - documents
m17n-env - set up multilingual X environment
m17n-lib-bin - a multilingual text processing library - utilities
mlterm-im-m17nlib - MultiLingual TERMinal, m17nlib input method plugin
 

    在找到包名之后,就可以运行 apt-get install 来自动下载并安装 m17n 包了。根据 m17n 开发人员的说法,为 Fedora Core、Mandrake、SUSE Linux 和 Gentoo Linux 提供的包也都可以使用。

    m17n 库依赖于几个其他库,这几个库在您的系统上可能有,也可能没有。请阅读前提条件中最新的列表。

    m17n 简介

    从内部来说,m17n 库会被组织成几个应用程序接口(API):

Core:这个 API 提供了处理 M-texts 的功能。Core API 并不需要 m17n 数据库。
Shell:Shell API 增加了 m17n 的数据库查找和搜索功能。Shell 包括了这个 API 的所有功能和特性。
GUI:GUI API 提供了在图形显示设备上输入并显示文本的功能。GUI 隐式地包含了 Shell 和 Core API 的所有特性。
Miscellaneous:这个 API 定义了几个函数来帮助对 m17n 库进行调试和跟踪。
m17n 库的使用与其他 Linux 或 UNIX 的库的使用相同。如果要使用这个库的所有特性,就需要在程序中包含 m17n.h 头文件,然后在链接选项中加上 -lm17n 选项,这可以在 Makefile 中实现。 如果只想使用 m17n 的一部分功能,Core、Shell、GUI 以及 Miscellaneous API 每个都有单独的包含文件。不幸的是,m17n 并没有很多样例代码,很多明显引用它们的程序,例如可以识别 m17n 的应用程序,也只有两年的时间。然而,m17n 的软件开发包(SDK)确实包含了一个简单的程序,它可以使用各种编码来显示文件。我们可以查看一下所下载的 m17n 工具包中的 example 目录。在这个目录中,打开 mview.c 文件。这个文件的一部分如清单 2 所示。


    清单 2. m17n 示例文件
 
...
325  M17N_INIT ();
326  if (merror_code != MERROR_NONE)
327    FATAL_ERROR ("%s\n", "Fail to initialize the m17n library.");
328 
329  /* Decide how to decode the input stream.  */
330  if (coding_name)
331    {
332      coding = mconv_resolve_coding (msymbol (coding_name));
333      if (coding == Mnil)
334        FATAL_ERROR ("Invalid coding: %s\n", coding_name);
335    }
336  else
337    coding = Mcoding_utf_8;
338 
339  mt = mconv_decode_stream (coding, fp);
340  fclose (fp);
341  if (! mt)
342    FATAL_ERROR ("%s\n", "Fail to decode the input file or stream!");
343 
344  {
345    MPlist *param = mplist ();
346    MFace *face = mface ();
347 
348    if (fontsize)
349      mface_put_prop (face, Msize, (void *) fontsize);
350    mplist_put (param, Mwidget, shell);
351    mplist_put (param, Mface, face);
352    frame = mframe (param);
353    m17n_object_unref (param);
354    m17n_object_unref (face);
355  }
356 
357  /* Create this widget hierarchy.
358     Shell - form -+- quit
359                   |
360                   +- viewport - text  */
361 
362  form = XtCreateManagedWidget ("form", formWidgetClass, shell, NULL, 0);
363  XtSetArg (arg[0], XtNleft, XawChainLeft);
364  XtSetArg (arg[1], XtNright, XawChainLeft);
365  XtSetArg (arg[2], XtNtop, XawChainTop);
366  XtSetArg (arg[3], XtNbottom, XawChainTop);
367  XtSetArg (arg[4], XtNaccelerators, XtParseAcceleratorTable (quit_action));
368  quit = XtCreateManagedWidget ("quit", commandWidgetClass, form, arg, 5);
369  XtAddCallback (quit, XtNcallback, QuitProc, NULL);
370 
371  viewport_width = (int) mframe_get_prop (frame, Mfont_width) * 80;
372  viewport_height
373    = ((int) mframe_get_prop (frame, Mfont_ascent)
374       + (int) mframe_get_prop (frame, Mfont_descent)) * 24;
375  XtSetArg (arg[0], XtNallowVert, True);
376  XtSetArg (arg[1], XtNforceBars, False);
377  XtSetArg (arg[2], XtNfromVert, quit);
378  XtSetArg (arg[3], XtNtop, XawChainTop);
379  XtSetArg (arg[4], XtNbottom, XawChainBottom);
380  XtSetArg (arg[5], XtNright, XawChainRight);
381  XtSetArg (arg[6], XtNwidth, viewport_width);
382  XtSetArg (arg[7], XtNheight, viewport_height);
383  viewport = XtCreateManagedWidget ("viewport", viewportWidgetClass, form,
384                                    arg, 8);
385 
386  /* Before creating the text widget, we must calculate the height of
387     the M-text to draw.  */
388  control.two_dimensional = 1;
389  control.enable_bidi = 1;
390  control.disable_caching = 1;
391  control.max_line_width = viewport_width;
392  mdraw_text_extents (frame, mt, 0, mtext_len (mt), &control,
393                      NULL, NULL, &metric);
...
 


    下面对这些代码详细介绍一下:

第 325 行负责对 m17n 库进行初始化。
第 330 行的 coding_name 变量源自于一个指定输入文件编码的命令行参数;如果没有提供这种命令行,就使用 UTF-8 编码。
第 339 行读取到达的数据,并根据编码类型对其进行解码,现在都反应在 coding 中。
第 345 到 354 行设置要画的文本框架。第 345 行从手头的 M-text 中提取出一些属性,而 346 行则提取出给定文本使用的适当字体。第 348 行设置要显示使用的字体大小(fontsize 是另外一个命令行参数),第 350 和 351 行设置该框架中要画的其他属性,包括要画到哪个小部件上(前面的 shell = XtOpenApplication (&context, "M17NView", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL, 0) 和最后的类型定义。
第 362 到 383 行是典型的 X 工具包调用,用来设置应用程序的主窗口。第 371 到 372 行负责计算对于本地拼字法的一个 80 x 24 的窗口来说,视口应该多大。
最后,在为 M-text 呈现设置一些参数之后,在 392 行就会开始显示 m17n 文本。
总而言之,上面对这段代码片段的简短分析就说明了在标准的 X 应用程序中通常要执行哪些操作。在很多情况中,创建一个多语言的应用程序只需要很少的额外代码就可以实现,这需要采用 m17n 的函数,而不是传统的 X 调用。

    展望

    如果没有可以构建 m17n 代码的系统,也不要烦恼。您仍然可以通过在线 m17n 呈现演示来体验这个库的作用(参看 参考资料 中的链接)。

    据开发人员说,他们正在继续在 GTK+ 中集成 m17n —— 这是扩宽 m17n 的认可程度以及影响力的下一个必不可少的步骤。现在,m17n 项目缺少样例代码供参考和扩展。建立更好的文档也是另外一个需要做的工作,这与为主流平台提供二进制文件一样重要。然而,m17n 确实承诺会对各个省的方言也能够实现 WYSIEYG 的编辑。这对任何语言来说都是个好消息。

    个人计算机已经不再是什么新奇的东西了。实际上,在不到 20 年的时间内,计算机已经成为了家庭的日常用品 —— 只不过它不是什么衣服之类的东西,而是用来管理信息的工具。然而,有些国家计算机的获得和使用还并不普遍。为了平衡这种不平等,需要让这些国家能够获得负担得起的各种计算机硬件和软件。另外,还要保证本土居民能够以本地方言来使用计算机。

    m17n 库构建在 Unicode 和其他标准之上,用来根据手写语言的规则画出任意复杂的拼字法。它将代码与字符的格式区分开来,因此相同的代码可以反复使用,甚至是在相同的应用程序中呈现不同的拼字法都可以。随着这些工作的不断进展,m17n 正在逐渐让计算机语言变成一种全球的方言。