Linux 程序中的消息国际化

作者:Pancrazio de Mauro

Linux 每天都变得越来越流行。到目前为止,典型的 Linux 用户一直是系统管理员、学生或 UNIX 黑客。GNOME、KDE 和 GNUStep 等新项目正在为不同的、技术准备较少的用户铺平道路。

对于至少具备中等计算机技能的人来说,运行英文软件通常不是问题,但最终用户需要(并且 想要)使用他们自己的语言的软件,以便提高工作效率或对系统感到舒适。此外,许多程序需要知道诸如日期或金额等本地约定,以便有用和完整。

本文是对 GNU gettext 系统的介绍,它是一组工具和库,供程序员和翻译人员使用,使他们能够生成使用指定语言的文本消息的多语言程序。我们将处理使用 ISO-8859-X 字符集之一的语言,但日语和中文除外,因为它们需要额外的注意。

定义

在讨论程序中对不同语言的支持时,经常出现两个词:国际化本地化。由于一遍又一遍地书写这些单词(没有拼写错误)既烦人又耗时,人们将它们缩写为 I18NL10N。18 和 10 表示每个单词的第一个字母和最后一个字母之间的字母数。

国际化 程序意味着采取必要的步骤使其了解不同的语言和国家标准。

本地化 过程发生在当国际化的程序被赋予正确行为所需的信息,以适应某种语言和一套文化习惯时。

首要事项

对于程序员和最终用户来说,首先要做的是配置 Linux 机器以使用区域设置。大多数用户只需要按照可从 ftp://sunsite.unc.edu/pub/Linux/docs/ 和镜像站点下载的 Locales mini-HOWTO 进行操作即可。最新的发行版(例如 Red Hat 5.0)包含支持区域设置所需的一切。

一旦系统启用了区域设置支持,您必须指定您希望使用的特定标准和语言。这是通过一组环境变量完成的。每个变量控制区域设置系统的特定方面

  • LANG 指定全局区域设置,但可以被以下变量覆盖。

  • LC_COLLATE 指定用于排序和比较的区域设置。

  • LC_CTYPE 指定正在使用的字符集,以便 isupper('<\#192>') 在意大利区域设置中返回 true

  • LC_MONETARY 提供有关在特定区域设置中表示货币的信息。

  • LC_NUMERIC 提供有关数字的信息:数字如何在组中划分和分隔,小数点是什么等等。

  • LC_TIME 指定使用哪个区域设置来表示时间:例如,AM/PM 或 24 小时制值。

  • LC_MESSAGES 指示您首选的程序文本消息的语言。

  • LC_ALL 覆盖任何先前的指示并设置全局区域设置。

全局区域设置值的示例包括

  • en_US 表示美国的英语。

  • it_IT 表示意大利的意大利语。

  • fr_CA 表示加拿大的法语。

基本上,要使用国家 CC 中语言 LL 的标准,区域设置值将为 LL_CC

列表 1。

默认情况下使用的区域设置(除非被之前的变量覆盖)称为 C(或 POSIX)区域设置。因此,通过使用 date 可以很容易地说明区域设置感知程序的行为,例如(参见列表 1)。首先,在不设置 LC_ALL 变量的情况下,响应是英文的。接下来,设置 LC_ALL 以获得意大利语响应,法语响应(指定了加拿大法语),然后是英语响应(加拿大英语)。意大利语区域设置的“No such file or directory”未被翻译,这意味着意大利语信息不可用;因此,而是使用了默认值。

在 C 程序中处理消息

让我们先看一下 GNU gettext 包。如果您的系统上没有安装它,您可以从 ftp://prep.ai.mit.edu/pub/gnu/ 或其镜像站点下载它。

当使用此包编写多语言程序时,字符串被“包装”在函数调用中,而不是直接在源代码中编码。该函数称为 gettext,它接受恰好一个字符串参数并返回一个字符串。

尽管 gettext 非常简单,但它非常有效:传递作为参数的字符串在表中查找以找到相应的翻译。如果找到翻译,则 gettext 返回它;否则,返回传递的字符串,程序将继续使用默认语言。

我们的第一个国际化的 Hello, world! 程序可能是

#include <stdio.h>
#include <libintl.h>
void main(void) {
        textdomain("hello-world");
        printf(gettext("Hello, world!\n"));
}

始终记住在每个使用 gettext 包的 C 程序中包含 <libintl.h>

函数 textdomain 应在使用 gettext 之前调用。它的目的是为程序选择正确的“消息数据库”(更合适的术语是“消息目录”)以供使用。

然后,每个可翻译的字符串都必须用作 gettext 的参数。每次都写 gettext("foobar") 可能会很烦人。这就是为什么许多程序员使用这个宏

#define _(x) gettext(x)

通过这样做,消息国际化引入的开销非常小:与其写 "foobar",不如只写 _("foobar")。每个可翻译的字符串只多三个字符,其优点是此宏完全消除了模块中的 gettext 代码。

翻译消息

一旦程序国际化,本地化过程就可以开始了。首先要做的是从源代码中提取所有需要翻译的字符串。

这个自动过程由 xgettext 执行。结果是一个可编辑的 .po(可移植对象)文件。xgettext 扫描作为参数传递的源文件,并提取程序员用 gettext 或其他标识符标记的每个可翻译的字符串。

列表 2。

在我们的例子中,我们可以这样调用 xgettext

xgettext -a -d hello-world -k_ -s
-v hello-world.c

生成的 hello-world.po 如列表 2 所示。

我建议您查看 gettext info 文档,了解其他有用的开关。我在这里使用的开关定义如下

  • -a 提取所有字符串。

  • -d 将结果输出到 hello-world.po(默认为 messages.po)。

  • -k 指示 xgettext 在搜索可翻译的字符串时查找 _ (默认值 gettextgettext_noop 仍然会被查找)。

  • -s 生成排序后的输出并删除重复项。

  • -v 告诉 xgettext 在生成消息时显示详细信息。

此时,翻译人员可以简单地用消息填充 hello-world.po,而无需了解源代码。实际上,程序可以在添加新语言之前进行国际化和编译。

可移植对象必须编译成机器对象(.mo 文件)才能有用。这是通过命令完成的

msgfmt -o hello-world.mo -v hello-world.po
Internationalizing Messages in Linux Programs

图 1

图 1. 表示从 C 源代码获取 .mo 文件所需的所有步骤的框图。最关键的部分是运行 tupdate(见下文)以合并新的、未翻译的字符串和之前的工作,而不会丢失它。

最后一步是将 hello-world.mo 复制到合适的位置,gettext 系统可以在那里找到它。在我的 Linux 机器上,默认位置是 /usr/share/locale/LL/ LC_MESSAGES/ 或 /usr/share/locale/LL_CC/LC_MESSAGES/,其中 LL 是语言,CC 是国家。例如,意大利语翻译应放置在 /usr/share/locale/it/ LC_MESSAGES/hello-world.mo。

textdomain 必须在程序开始时调用,以便系统可以根据当前的区域设置变量选择合适的 .mo 文件。按优先级顺序(优先级从高到低),它们是 LC_ALL、LC_MESSAGES 和 LANG。

如果程序员决定这样做,则 .mo 文件可以在许多程序之间共享。GNU fileutils 就是如此。

维护消息文件

如果源代码发生更改,则应更新相应的 .po 文件,而不会丢失任何以前的翻译。不幸的是,简单地再次调用 xgettext 是行不通的,因为它会覆盖旧的 .po 文件。在这种情况下,程序 tupdate 派上用场。它合并两个 .po 文件,保留已完成的翻译,只要新字符串与旧字符串匹配即可。它的语法很简单

tupdate new.po old.po > latest.po

新字符串显然在 latest.po 中仍然为空,但已经翻译的字符串将在那里,而无需重新处理。

例外

列表 3

并非总是可以“直接”使用 gettext 函数。让我们看一下列表 3 中的源代码摘录作为示例。在此代码的国际化期间必须达到两个目标。首先,每个可翻译的字符串都必须出现在 .po 文件中。其次,在运行时打印每个字符串之前,我们必须将其传递给 gettext。

字符串 "You have %d %s" 提出了一个问题。我们不能简单地将 item_names 中声明的每个字符串转换为 gettext 调用,因为数组必须使用常量值初始化。

列表 4

列表 4 中显示了一种解决方案。gettext_noop 是一个标记,用于使字符串可被 xgettext 识别(这就是默认情况下会查找它的原因)。翻译在运行时通过正常的 gettext 调用发生。

消息文件格式

.po 文件具有非常简单的文本结构,可以使用任何文本编辑器进行修改。在处理它们时,Emacs 等编辑器可以设置为特殊的 po 模式。

每个消息文件都由一系列记录组成。每个记录都具有以下结构

(blank lines)
#  optional human comments
#. optional automatic comments
#: optional source code reference
msgid original-string
msgstr translated-string

翻译人员引入的注释应在 # 字符后立即留一个空格。自动注释由 xgettext 和 tupdate 生成,以增强文件的可读性,并允许翻译人员快速浏览源代码并找到使用字符串的行。这有时对于产生正确的翻译是必要的。

字符串的格式与 C 语言中的格式相同。例如,可以合法地写成

msgid ""
"Hello     "
"world!\n"
msgstr ""
"Ciao      "
"mondo!\n"

如您所见,字符串可以跨越多行,反斜杠用于引入制表符和换行符等特殊字符。

其他消息目录系统

不存在消息目录的 POSIX 标准——委员会无法就任何内容达成一致。

GNU gettext 不是国际化程序可以使用的唯一消息目录系统。还存在另一个基于 catgets 函数调用的库。catgets 接口受 X/Open 联盟支持,而 gettext 接口最初由 Sun 使用。

catgets 的主要缺点是必须为每条消息选择一个 唯一 标识符,并在每次调用 catgets 时传递它。这使得管理大量消息非常困难,在这些消息中,条目会定期插入和删除。但是,GNU gettext 可以在支持它的系统上使用 catgets 作为底层接口。

Linux 同时支持 gettext 和 catgets 接口。我个人的看法是,对于程序员和翻译人员来说,gettext 系统都更容易使用。

本文中引用的所有列表都可以通过匿名下载在文件 ftp.linuxjournal.com/pub/lj/listings/issue59/3023.tgz 中找到

Internationalizing Messages in Linux Programs
Pancrazio de Mauro (pdemauro@datanord.it) 是一名技术作家和 Linux 顾问。他大部分时间都在倡导 Linux,并试图说服他的朋友叫他 Ezio,当然,这听起来和英语中的 Pancrazio 一样糟糕。
加载 Disqus 评论