目 录
学习参考书籍、网站或博文:
PostgreSQL数据库之国际化语言支持学习总结
GNU概述 gettext
通常在编写程序时,源码中首选使用英文进行编写,为了实现程序的国际化,使用gettext为交互语句添加多语言支持机制。使程序在运行过程时,以不同的语言呈现给用户
GNU gettext是GNU Translation Project的重要步骤,它是我们可以构建许多其他步骤的资产。该软件包为程序员,翻译人员甚至用户提供了一套完善的工具和文档集。具体来说,GNU gettext实用程序是一组工具,提供了一个框架来帮助其他GNU软件包生成多语言消息。这些工具包括一组关于如何编写程序以支持消息目录的约定、消息目录本身的目录和文件命名组织、支持检索已翻译消息的运行时库,以及一些以各种方式处理可翻译字符串集的独立程序,或者已经翻译的字符串
Po文件简介
PO是Portable Object(可移植对象)的缩写形式,它是面向翻译人员的、提取于源代码的一种资源文件。它将给定包的每个原始可翻译字符串与特定目标语言的译文相关联。单个PO文件专用于一种目标语言。如果一个软件包支持多种语言,则每种语言都支持一个这样的PO文件,并且每个软件包都有自己的一组PO文件。这些PO文件最好由xgettext
程序创建,然后通过msgmerge
程序进行更新或刷新。程序xgettext从一组C文件中提取所有标记的消息,并使用空翻译初始化PO文件。程序msgmerge负责在相应源的发行版之间调整PO文件,注释过时的条目,初始化新条目以及更新所有源代码行引用。
MO是Machine Object(机器对象)的缩写形式,它是面向计算机的、由.po
文件通过GNU gettext
工具包编译而成的二进制文件,应用程序通过读取.mo文件使自身的界面转换成用户使用的语言。
- po文件的命名
Postgresql源码的src/backend/po
目录下是目前支持11中语言
[postgres@local99:~/src/postgresql-13.0/src/backend/po]$ ls
de.po fr.po ja.po ru.po tr.po zh_CN.po
es.po it.po ko.po sv.po uk.po
[postgres@local99:~/src/postgresql-13.0/src/backend/po]$
po文件命名为语言.po,其中语言是 ISO 639-1 双字符语言代码(小写形式),例如法语是fr.po。如果对于一种语言有需要多个翻译任务那么这些文件也可以被命名为语言_区域.po,其中region是 ISO 3166-1 双字符国家代码(大写形式)例如巴西的葡萄牙语是pt_BR.po
- po文件的格式
PO文件由许多条目组成,每个条目都包含原始未翻译的字符串与其对应的翻译之间的关系。给定PO文件中的所有条目通常都属于一个项目,并且所有翻译都以一种目标语言表示
PO 文件的文件格式示例如下:
white-space
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid “translated-string”
msgstr “anslated-string”
...
- GNU gettext工具生成时,每个条目之间由空白行分割
#
字符引入一个注释。空白紧跟着 # 字符,则是一个由翻译者维护的注释。也可能有自动注释#.
注释是xgettext从源代码中提取出的注释内容#:
注释指示该消息在源代码中被使用的确切位置#,
注释包含以某种方法描述该消息的标志。当前有两种标志:如果该消息可能由于程序源码中的改变而过时,则fuzzy被设置;
另一种标志是c-格式,它指示该消息是一个printf-风格的格式模板。这意味着翻译也应当是一个格式字符串,其中有相同个数和相同类型的占位符#|
翻译器为之前待翻译的字符串提供的翻译
注释之后显示两个字符串
msgid
“程序源码中抽取的字符串”
msgstr
“翻译之后的字符串”
简单翻译单元实例
#: ../common/config_info.c:130 ../common/config_info.c:138
#: ../common/config_info.c:146 ../common/config_info.c:154
#: ../common/config_info.c:162 ../common/config_info.c:170
#: ../common/config_info.c:178 ../common/config_info.c:186
#: ../common/config_info.c:194
msgid "not recorded"
msgstr "没有被记录"
gettext 实现国际化语言支持的步骤
本节主要介绍使用gettext工具为程序或库实现本地化语言支持的主要过程。首先来介绍几个重要的函数,在为程序实现本地化语言支持的第一步需要在源程序中导入gettext声明以及增加触发gettext操作
#include <libintl.h>
int main(int argc,char * argv [])
{
…
setlocale(LC_ALL,“”);
bindtextdomain(PACKAGE,LOCALEDIR);
textdomain(PACKAGE);
…
}
函数原型:
char* setlocale( int category, const char* locale);
setlocale
函数将指定的系统语言环境或其部分安装为新的C语言环境。修改将一直有效,并影响所有对语言环境敏感的C库函数的执行,直到下一次调用为止。如果locale为null指针,表示使用环境变量中定义的LC_ALL的值
语言环境类别标识符 | 说明 |
---|---|
LC_ALL | 选择整个C语言环境 |
LC_COLLATE | 选择C语言环境的排序规则类别 |
LC_CTYPE | 选择C语言环境的字符分类类别 |
LC_MESSAGES | 消息语言类别 |
LC_MONETARY | 选择C语言环境的货币格式类别 |
LC_NUMERIC | 选择C语言环境的数字格式类别 |
LC_TIME | 选择C语言环境的时间格式类别 |
bindtextdomain()
和 textdomain()
函数声明如下:
#include <libintl.h>
char * bindtextdomain (const char * domainname, const char * dirname);
char * textdomain (const char * domainname);
bindtextdomain
用来设置文本域目录。所谓的文本域文件就是 mo 文件,domainname
指生成的mo文件的名字,在下面的案例中,生成的MO文件命名为zh_CN.mo
,则在对应的源程序中domainname
参数的值为zh_CN
。dirname
指MO文件存放的路径,linux中一般会将该文件放在 /usr/share/locale/zh_CN/LC_MESSAGES 中,下面的案例中将该路径设置为/home/postgres/study/locale
textdomain()
函数设置需要使用的文本域 。这些文本域之前都是经过 bindtextdomain()
指定的,再经过 textdomain()
函数设置后,那么此后 gettext 库(及其中的相关函数)便能找到相应的 mo 文件并操作它们。
简单案例
#include <libintl.h>
#include <locale.h>
#include <stdio.h>
#define PACKAGE "zh_CN"
#define LOCALEDIR "/home/postgres/study/locale"
#define _(x) gettext(x)
int main( void )
{
/* triggering gettext declaration */
setlocale (LC_ALL, "zh_CN.UTF-8");
bindtextdomain( PACKAGE, LOCALEDIR );
textdomain( PACKAGE );
printf( _( "Hello world\n" ) );
printf( _( "Where there is a will, there is a way!\n" ) );
return 0;
}
下面以该案例来介绍实现本地化支持的步骤
1、提取可翻译的字符串
xgettext --add-comments --keyword=_ hello.c -o zh_CN.pot --from-code=UTF-8
- --add-comments 在输出文件中将所有注释块置于关键字行之前
- --keyword 不要使用默认关键字
_
- -o 将输出写入指定文件
- --from-code 输入文件的编码
2、创建po文件
msginit --input=zh_CN.pot -o zh_CN.po -l zh_CN.UTF-8
- --input 输入的.pot文件
- -o 将输出写入指定文件
- --local 设置目标区域设置
3、我们指定使用locale名称为zh_CN.UTF-8
,创建mo文件存放的路径
[postgres@local99:~/study]$ mkdir -p locale/zh_CN.UTF-8/LC_MESSAGES
4、修改生成的zh_CN.po
文件,增加翻译
mkdir -p locale/zh_CN.UTF-8/LC_MESSAGES
5、修改生成的zh_CN.po
文件,增加翻译
# Chinese translations for PACKAGE package
# PACKAGE 软件包的简体中文翻译.
# Copyright (C) 2021 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# <hello@163.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-15 18:47+0800\n"
"PO-Revision-Date: 2021-01-16 19:07+0800\n"
"Last-Translator: <hello@163.com>\n"
"Language-Team: Chinese (simplified)\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. say hello here
#: hello.c:16
#, c-format
msgid "Hello world\n"
msgstr "你好 世界\n"
#. say hello again
#: hello.c:19
#, c-format
msgid "Where there is a will, there is a way!\n"
msgstr "有志者事竟成\n"
6、生成zh_CN.mo
二进制文本文件
msgfmt zh_CN.po -o zh_CN.mo
7、将生成的zh_CN.mo
文件放到locale/zh_CN.UTF-8/LC_MESSAGES
目录下
[postgres@local99:~/study]$ mv zh_CN.mo locale/zh_CN.UTF-8/LC_MESSAGES/
现在看一下汉化之后源程序执行的结果
[postgres@local99:~/study]$ gcc hello.c -o hello
[postgres@local99:~/study]$ ./hello
你好 世界
有志者事竟成
PostgreSQL生成国际化文件的过程
在Postgres源码中的c.h中有如下定义,以支持gettext
/*
* gettext support
*/
#ifndef ENABLE_NLS
/* stuff we'd otherwise get from <libintl.h> */
#define gettext(x) (x)
#define dgettext(d,x) (x)
#define ngettext(s,p,n) ((n) == 1 ? (s) : (p))
#define dngettext(d,s,p,n) ((n) == 1 ? (s) : (p))
#endif
#define _(x) gettext(x)
源码src目录下的nls-global.mk
文件定义了消息目录用的Makefile文件的规则,下面仅列出一些主要的编译规则
...
FRONTEND_COMMON_GETTEXT_FILES = $(top_srcdir)/src/common/logging.c
FRONTEND_COMMON_GETTEXT_TRIGGERS = \
pg_log_fatal pg_log_error pg_log_warning pg_log_info pg_log_generic:2 pg_log_generic_v:2
FRONTEND_COMMON_GETTEXT_FLAGS =
pg_log_fatal:1:c-format pg_log_error:1:c-format pg_log_warning:1:c-format pg_log_info:1:c-format pg_log_generic:2:c-pg_log_generic_v:2:c-
all-po: $(MO_FILES)
%.mo: %.po
$(MSGFMT) $(MSGFMT_FLAGS) -o $@ $<
...
init-po: po/$(CATALOG_NAME).pot
# For performance reasons, only calculate these when the user actually
# requested update-po or a specific file
ifneq (,$(filter update-po %.po.new,$(MAKECMDGOALS)))
ALL_LANGUAGES := $(shell find $(top_srcdir) -name '*.po' -print | sed 's,^.*/\([^/]*\).po$$,\1,' | LC_ALL=C sort -u)
all_compendia := $(shell find $(top_srcdir) -name '*.po' -print | LC_ALL=C sort)
else
ALL_LANGUAGES = $(AVAIL_LANGUAGES)
all_compendia = FORCE
FORCE:
endif
ifdef WANTED_LANGUAGES
ALL_LANGUAGES := $(filter $(WANTED_LANGUAGES), $(ALL_LANGUAGES))
endif
update-po: $(ALL_LANGUAGES:%=po/%.po.new)
$(AVAIL_LANGUAGES:%=po/%.po.new): po/%.po.new: po/%.po po/$(CATALOG_NAME).pot $(all_compendia)
$(MSGMERGE) --lang=$* $(word 1, $^) $(word 2,$^) -o $@ $(addprefix --compendium=,$(filter %/$*.po,$(wordlist 3,$(words $^),$^)))
首先,必须在编译前指定 --enable-nls="语言名"
,否则不会产生多语言支持,比如 --enable-nls="zh_CN"
[postgres@local99:~/postgresql-13.0]$ ./configure --exec-prefix=/home/postgres/pg13 --enable-nls="zh_CN"
启用之后我们在 Makefile.global
中会看到:
enable_nls = yes
...
##########################################################################
#
# Native language support
ifeq ($(enable_nls), yes)
ifneq (,$(wildcard $(srcdir)/nls.mk))
include $(top_srcdir)/src/nls-global.mk
endif # nls.mk
endif # enable_nls
可以看出,只有目录下有nls.mk
这个文件才会有nls动作
新建消息目录
Postgres源码bin目录下的pgbench工具未提供国际化语言支持,接下来我们为该目录生成国际化文件zh_CN.mo
- 创建nls.mk文件
# src/bin/ux_diagnose/nls.mk
CATALOG_NAME = pgbench
AVAIL_LANGUAGES = zh_CN
GETTEXT_FILES = $(FRONTEND_COMMON_GETTEXT_FILES) \
pgbench.c exprparse.c exprscan.c
GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
GETTEXT_FLAGS = $(FRONTEND_COMMON_GETTEXT_FLAGS)
CATALOG_NAME
程序名,如textdomain()调用中所提供的AVAIL_LANGUAGES
提供/支持的语言列表GETTEXT_FILES
包含可翻译字符串的文件列表,即那些被用gettext或另一种替代方案标记的文件GETTEXT_TRIGGERS
为翻译者产生在其上工作的消息目录的工具需要知道,哪些函数调用包含可翻译的字符串。默认情况下,只有gettext()调用是已知的。如果你使用_或其他标识符,你需要在这里列出它们。GETTEXT_FLAGS
要标记的GETTEXT–flag参数的列表,包含C格式字符串的函数参数
- 创建消息目录
[postgres@local99:~/postgresql-13.0/src/bin/pgbench]$ make init-po
/bin/xgettext -ctranslator --copyright-holder='PostgreSQL Global Development Group' --msgid-bugs-address=pgsql-bugs@lists.postgresql.org --no-wrap --sort-by-file --package-name='pgbench (PostgreSQL)' --package-version='13' -D . -D . -n -kpg_log_fatal -kpg_log_error -kpg_log_warning -kpg_log_info -kpg_log_generic:2 -kpg_log_generic_v:2 -k_ --flag=pg_log_fatal:1:c-format --flag=pg_log_error:1:c-format --flag=pg_log_warning:1:c-format --flag=pg_log_info:1:c-format --flag=pg_log_generic:2:c-format --flag=pg_log_generic_v:2:c-format --flag=_:1:pass-c-format ../../../src/common/logging.c pgbench.c exprparse.c exprscan.c
sed -e '1,18 { s/SOME DESCRIPTIVE TITLE./LANGUAGE message translation file for pgbench/;s/PACKAGE/PostgreSQL/g;s/VERSION/13/g;s/YEAR/'`date +%Y`'/g; }' messages.po >po/pgbench.pot
rm messages.po
执行完成之后可以看到pgbench目录下会自动创建一个po目录,该目录下包含生成的pgbench.pot
文件
[postgres@local99:~/postgresql-13.0/src/bin/pgbench/po]$ ls
pgbench.pot
- 将
pgbench.pot
重命名为zh_CN.po
,并为之添加翻译
[postgres@local99:~/postgresql-13.0/src/bin/pgbench/po]$ mv pgbench.pot zh_CN.po
4. 生成对应的mo文件
执行make
或make all-po
执行之后可以看到po目录下会生成对应的zh_CN.mo
文件
[postgres@local99:~/postgresql-13.0/src/bin/pgbench/po]$ ls
zh_CN.mo zh_CN.po
至此为一个新工具目录增加国际化支持的步骤就结束了
更新消息目录
如果一个目录已经提供了国际化语言支持,假设我们修改了 initdb 的输出信息,如何更新国际化文件呢?
make update-po
[postgres@local99:~/postgresql-13.0/src/bin/pgbench]$ cd ../initdb/
[postgres@local99:~/postgresql-13.0/src/bin/initdb]$ ls
findtimezone.c findtimezone.o initdb initdb.c initdb.o localtime.c localtime.o Makefile nls.mk po t
[postgres@local99:~/postgresql-13.0/src/bin/initdb]$ make update-po
/bin/msgmerge --no-wrap --previous --sort-by-file --lang=zh_CN po/zh_CN.po po/initdb.pot -o po/zh_CN.po.new --compendium=../../../src/backend/po/zh_CN.po --compendium=../../../src/bin/initdb/po/zh_CN.po --compendium=../../../src/bin/pg_archivecleanup/po/zh_CN.po --compendium=../../../src/bin/pg_basebackup/po/zh_CN.po --compendium=../../../src/bin/pg_checksums/po/zh_CN.po --compendium=../../../src/bin/pg_config/po/zh_CN.po --compendium=../../../src/bin/pg_controldata/po/zh_CN.po --compendium=../../../src/bin/pg_ctl/po/zh_CN.po --compendium=../../../src/bin/pg_dump/po/zh_CN.po --compendium=../../../src/bin/pg_resetwal/po/zh_CN.po --compendium=../../../src/bin/pg_rewind/po/zh_CN.po --compendium=../../../src/bin/pg_test_fsync/po/zh_CN.po --compendium=../../../src/bin/pg_test_timing/po/zh_CN.po --compendium=../../../src/bin/pg_upgrade/po/zh_CN.po --compendium=../../../src/bin/pg_verifybackup/po/zh_CN.po --compendium=../../../src/bin/pg_waldump/po/zh_CN.po --compendium=../../../src/bin/psql/po/zh_CN.po --compendium=../../../src/bin/scripts/po/zh_CN.po --compendium=../../../src/interfaces/ecpg/ecpglib/po/zh_CN.po --compendium=../../../src/interfaces/ecpg/preproc/po/zh_CN.po --compendium=../../../src/interfaces/libpq/po/zh_CN.po --compendium=../../../src/pl/plperl/po/zh_CN.po --compendium=../../../src/pl/plpgsql/src/po/zh_CN.po --compendium=../../../src/pl/plpython/po/zh_CN.po --compendium=../../../src/pl/tcl/po/zh_CN.po
............................. done.
[postgres@local99:~/postgresql-13.0/src/bin/initdb]$
执行make update-po之后从输出信息可以看到,initdb.pot
(翻译模板)重新生成,并且只有我们需要的 zh_CN 被 merge,生成一个新文件 zh_CN.po.new
2. 编辑zh_CN.po.new
,添加对应的翻译,并将其重命名为zh_CN.po
3. 重新生成.mo
文件
make all-po
至此,新的 .mo 文件已经重新生成