4.2 使用 POSIX.1 本地语言支持 (NLS) 的本地化消息

原作 Gábor Kövesdán.

  除了基本的国际化功能, 例如支持不同的输入编码, 或支持类似十进制分割符之类的国民习惯之外, 还可以对程序输出的消息进行本地化。 一种比较常见的做法是使用 POSIX.1 NLS 函数, 它是作为 FreeBSD 基本系统的一部分提供的。

4.2.1 在编录文件 (Catalog Files) 中编排本地化消息

  POSIX.1 NLS 是基于包含使用需要的编码编写的本地化消息所组成的编录文件工作的。 这些消息需要编写成以整数标识的集合。 习惯上, 编录文件应使用其编码命名, 并加上扩展名 .msg。 例如, 采用 ISO8859-2 编码的匈牙利文编录对应的文件名是 hu_HU.ISO8859-2

  这些编录文件都是带编号消息的普通文本文件。 如果希望在文件中添加注释, 可以在一行的开头加上 $ 作为标志。 每个集合的边界则是用特殊的注释加以区分, 这里使用的关键词 set 必须紧接注释符 $。 在关键词 set 之后则是集合的编号。 例如:

$set 1

  接下来是消息项, 其内容是消息编号, 接着是本地化的消息内容。 在此处可以使用为人们所熟知的 printf(3) 格式串:

15 "File not found: %s\n"

  语言编录文件必须首先编译成二进制格式才能为应用程序使用。 这个转换过程是通过 gencat(1) 工具来完成的。 它的第一个参数是输出的文件名, 随后的所有参数则都是作为输入的编录源文件。 是的, 本地化的消息也可以写到多个文件当中, 然后通过 gencat(1) 来汇集成一个文件。

4.2.2 在源代码中使用编录文件

  使用编录文件比较简单, 要使用相关的函数, 需要引用头文件 nl_types.h。 在开始使用编录文件中的内容之前, 需要首先用 catopen(3) 打开它。 这个函数有两个参数。 第一个参数是安装好并事先编译过的编录的名字, 一般来说是应用程序的名字, 例如 grep。 系统会使用这个名字来查找事先编译的编录文件。 catopen(3) 函数会在 /usr/share/nls/locale/catname 以及 /usr/local/share/nls/locale/catname 中查找编录, 其中, locale 是 locale 的名字, 而 catname 则是前面调用时使用的编录名。 第二个参数是以下两者之一:

  catopen(3) 函数会返回类型为 nl_catd 的编录描述符。 请参见联机手册以了解发生错误时返回代码的含义。

  打开编录文件之后, 可以用 catgets(3) 来提取消息。 它的第一个参数是由 catopen(3) 返回的编录描述符, 第二个是消息集合的编号, 第三个是消息的编号, 第四个是当无法从编录中获得消息时使用的替代消息。

  用完编录文件之后, 应使用 catclose(3) 将其关闭。 这个函数的参数是编录描述符。

4.2.3 实例

  下面的例子展示了如何以灵活的方式使用 NLS 编录。

  下面几行是放在一个用于在所有需要本地化消息的源代码中的公共头文件中的内容:

#ifdef WITHOUT_NLS
#define getstr(n)    nlsstr[n]
#else
#include <nl_types.h>

extern nl_catd       catalog;
#define getstr(n)    catgets(catalog, 1, n, nlsstr[n])
#endif

extern char     *nlsstr[];

  接下来, 把这几行放到主源文件中的全局声明部分:

#ifndef WITHOUT_NLS
#include <nl_types.h>
nl_catd  catalog;
#endif

/*
 * Default messages to use when NLS is disabled or no catalog
 * is found.
 */
char    *nlsstr[] = {
        "",
/* 1*/  "some random message",
/* 2*/  "some other message"
};

  下面是实际的代码片段, 这里展示了如何打开, 读取和关闭编录:

#ifndef WITHOUT_NLS
    catalog = catopen("myapp", NL_CAT_LOCALE);
#endif

...

printf(getstr(1));

...

#ifndef WITHOUT_NLS
    catclose(catalog);
#endif

4.2.3.1 减少需要本地化的字符串数量

  通过利用 libc 提供的错误信息可以减少必须本地化的字符串数量。 这种方法也能为多数程序经常遇到的错误提供更为一致的错误信息。

  下面是没有使用 libc 错误信息的一个例子:

#include <err.h>
...
if (!S_ISDIR(st.st_mode))
    err(1, "argument is not a directory");
     

  上面这个例子可以改为显示与 errno 对应的错误信息:

#include <err.h>
#include <errno.h>
...
if (!S_ISDIR(st.st_mode)) {
    errno = ENOTDIR;
    err(1, NULL);
}
     

  在这个例子中, 消除了自行撰写的字符串, 因而减轻了翻译人员的负担, 而用户则会看到更为熟悉的 “Not a directory” 信息。 请注意, 需要引入头文件 errno.h 才能直接读写 errno

  有时, 此前发生错误的函数可能已经设置了 errno, 对于这种情况就不需要另外加以赋值了:

#include <err.h>
...
if ((p = malloc(size)) == NULL)
    err(1, NULL);
     

4.2.4 使用 bsd.nls.mk

  使用编录文件需要一些重复的步骤, 例如编译编录, 并将它们安装到合适的位置。 为了进一步简化这些工作, bsd.nls.mk 提供了一些宏。 一般而言, 并不需要显式地引入 bsd.nls.mk, 因为其他公共 Makefiles, 如 bsd.prog.mkbsd.lib.mk 会直接将其包含进来。

  通常, 只要定义 NLSNAMENLS 就可以了, 前者内容是前面提到的 catopen(3) 第一个参数即编录本身的名字, 而后者则是不包含 .msg 后缀的编录源文件的名字。 下面是一个例子, 它允许使用 make(1) 变量 WITHOUT_NLS 来控制是否在联编程序时加入 NLS 支持。

.if !defined(WITHOUT_NLS)
NLS=    es_ES.ISO8859-1
NLS+=   hu_HU.ISO8859-2
NLS+=   pt_BR.ISO8859-1
.else
CFLAGS+=    -DWITHOUT_NLS
.endif

  习惯上, 编录的源文件应放在程序源文件的 nls 子目录中, 这也是 bsd.nls.mk 的默认行为。 不过, 通过 make(1) 变量 NLSSRCDIR 可以改变默认的源文件目录。 预编译的编录文件名字默认也遵循前面的规范, 与此对应的 NLSNAME 变量则可以改变它。 还有一些其他微调编录文件处理方式的选项, 但通常并不需要调整那些配置, 此处不再赘述。 如欲了解进一步的详情, 请参阅 bsd.nls.mk 文件, 它很短并且很容易理解。

本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.