文章目录
- 1.简介
- 2.核心概念
- 国际化 (i18n)
- 本地化 (l10n)
- POT 文件
- PO 文件
- MO 文件
- 文本域
- 翻译函数
- 3.主要组件
- 4.使用示例
- 参考文献
1.简介
GNU gettext 是一套用于软件国际化(internationalization,i18n)和本地化(localization,l10n)的工具集,它帮助开发者创建多语言应用程序。
下面大致翻译自 GetText 主页的介绍 :
“通常,程序及其文档信息都是用英语语言写的,程序运行时同用户交 互的信息也是英语。这是一个事实,不仅仅 GNU 的软件是这样,其他大 部分私有软件或自由软件也是这样。一方面,对于来自所有国家的开 发者、维护者和用户来说,相互沟通中使用一种通用的语言非常的方便。另一方面,相对于母语来说大多数人并不适应使用英语,而且他 们的日常工作都是尽可能的使用他们自己的母语。多数人都会喜欢他 们的计算机屏幕显示的英语更少,显示的母语更多。"
GNU 的 ‘gettext’ 是 GNU 翻译项目的一个重要步骤,我们依赖于它 来作很多其他的步骤。该软件包为程序员、翻译人员甚至用户提供了一套集成良好的工具和文档。详细地说,GNU gettext 提供了一套工具, 能让其他 GNU 软件创建多语言信息。这些工具包括一组关于如何编写程序以支持消息目录的约定,消息目录本身的目录和文件命名组织,支持检索已翻译消息的运行时库,以及一些以各种方式处理可翻译字符串集或已翻译字符串集的独立程序。一个特殊的 GNU Emacs 模式也可以帮助有兴趣的各方准备这些集合,或者使它们更新。
gettext 的工作流程是这样的:比如我们写一个 C 程序,通常 printf 等输 出信息都是 English 的。如果我们在程序中加入 gettext 支持,在需要交互的字符串上用 gettext 函数,程序运行就可以先调用 gettext 函数处理字符串,替换当前的字符串。注意是运行时替换。
2.核心概念
国际化 (i18n)
国际化(Internationalization)简写为 i18n,其中 i 和 n 分别代表单词的第一个和最后一个字母,中间的"18"代表省略的18个字母。
国际化是设计和开发软件应用的过程,使其架构能够支持多种语言和地区设置,而无需进行重大修改。
国际化的关键任务包括:
- 将所有用户界面元素(如文本、图标)从代码中分离出来,使其可以独立于代码进行更改。
- 使用通用的日期、时间和数字格式,可以根据用户的地区设置进行调整。
- 支持多种字符编码,如 UTF-8,确保可以处理各种语言的输入和显示。
- 设计灵活的布局,可以适应从右到左的阅读顺序等不同的文本方向。
本地化 (l10n)
本地化(Localization)简写为 l10n,其中 l 和 n 分别代表单词的第一个和最后一个字母,中间的"10"代表省略的10个字母。
本地化是将国际化的软件适配到特定地区或语言的过程,涉及翻译文本和调整功能以符合特定文化和语言的需求。
本地化的关键任务包括:
- 翻译软件界面、帮助文档以及其他用户面向的文本。
- 调整图形元素,使其符合地区文化的期望和标准。
- 根据目标市场的习惯和法律要求调整软件功能,如货币、税率计算等。
- 适应地区特有的日期、时间、地址和电话号码格式。
POT 文件
POT 文件(Portable Object Template)是 GNU gettext 工具链中的翻译模板文件,用于软件国际化和本地化(i18n/l10n)。
它作为翻译模板,存储源代码中提取的原始字符串(通常是英文),供后续生成多语言 PO(Portable Object)文件使用。
文件头:元数据(如语言、字符集、复数形式规则等)
msgid ""
msgstr ""
"Project-Id-Version: MyApp 1.0\n" # 项目名称和版本
"POT-Creation-Date: 2025-04-28 12:00\n" # 创建时间
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" # PO文件修订时间(模板中通常留空)
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" # 最后译者(PO文件填充)
"Language-Team: LANGUAGE <LL@li.org>\n" # 翻译团队(PO文件填充)
"Language: \n" # 语言代码(PO文件填充,POT中为空)
"MIME-Version: 1.0\n" # MIME版本
"Content-Type: text/plain; charset=UTF-8\n" # 字符集(通常UTF-8)
"Content-Transfer-Encoding: 8bit\n" # 编码方式
"Plural-Forms: nplurals=2; plural=(n != 1);\n" # 复数规则(示例为英语)
不同语言的复数形式不同(如俄语有3种复数形式,阿拉伯语有6种),需根据语言调整 nplurals 和 plural 表达式。
PO 文件
PO(Portable Object)特定语言的翻译文件,基于 POT 文件生成,由翻译人员填充具体语言的译文。
示例:
msgid "Hello, world!"
msgstr "你好,世界!"
MO 文件
MO(Machine Object)文件,PO 文件的二进制编译版本,供程序运行时高效加载。
特点:
- 二进制格式,不可直接编辑。
- 文件扩展名:.mo。
- 由 msgfmt 工具从 PO 文件编译生成。
- 程序通过 gettext() 函数读取 MO 文件中的翻译。
文本域
文本域(Text Domain)是 gettext 中用来管理和隔离翻译资源的一个重要机制。
文本域允许程序将其翻译文件组织在不同的命名空间内,使同一程序可以同时加载多个独立的翻译文件(MO 文件)。
在代码中通过 textdomain() 函数设置当前文本域:
#include <libintl.h>
textdomain("myapp"); // 设置文本域为 "myapp"
在翻译文件中,MO 文件需放在特定路径:
./locale/<语言>/LC_MESSAGES/<文本域>.mo# 例如
./locale/zh_CN/LC_MESSAGES/myapp.mo
若程序需要同时使用多个文本域(如主程序和插件):
// 临时切换文本域(保存旧域)
char* old_domain = textdomain("plugin1");
printf("%s", gettext("Delete")); // 使用 plugin1 的翻译
textdomain(old_domain); // 恢复原文本域
注意,MO 文件必须与文本域同名:
文本域 "myapp" -> MO 文件名为 myapp.mo
文本域 "gtk" -> MO 文件名为 gtk.mo
标准存放路径:
/usr/share/locale/zh_CN/LC_MESSAGES/myapp.mo # 系统级
./locale/zh_CN/LC_MESSAGES/myapp.mo # 项目内
翻译函数
函数 | 作用 | 示例 |
---|---|---|
textdomain() | 设置/获取当前文本域 | textdomain(“myapp”); |
bindtextdomain() | 指定文本域的 MO 文件搜索路径 | bindtextdomain(“myapp”, “./locale”); |
gettext() | 根据当前文本域获取翻译 | gettext(“Hello”); |
dgettext() | 显式指定文本域获取翻译 | dgettext(“plugin1”, “Save”); |
ngettext() | 根据给定的数量(n)选择正确的单数或复数翻译字符串 | ngettext(“%d file”, “%d files”, n); |
3.主要组件
- 开发库
libintl:提供 gettext() 等国际化函数
支持多种编程语言:C, C++, Python, Java, Perl, PHP 等。
- 工具集
xgettext:从源代码提取可翻译字符串,生成 POT 文件(翻译模板文件)。
msginit:根据 POT 文件创建新的 PO 文件(翻译文件)。
msgmerge:合并新旧 PO 文件。
msgfmt:将 PO 文件编译为 MO 文件。
- 运行时组件
gettext:在运行时根据当前 locale 加载适当的翻译。
4.使用示例
下面以 C 语言给出使用示例。
- 标记源代码。
#include <stdio.h>
#include <locale.h>
#include <libintl.h> // gettext 头文件// 定义简化宏(约定俗成)
#define _(STRING) gettext(STRING)
#define LOCALEDIR "/usr/share/locale" // 翻译文件存放目录int main() {// 1.设置本地化环境采用系统默认设置。// 必须调用,否则 gettext 无法正确加载翻译。setlocale(LC_ALL, "");// 2.绑定文本域(指定翻译文件位置和名称)bindtextdomain("hello", LOCALEDIR);textdomain("hello"); // 设置当前文本域// 3.使用可翻译字符串printf(_("Hello, World!\n"));printf(_("This is a demo of gettext.\n"));// 4.带变量的翻译int count = 3;printf(_("You have %d new message.\n"), count);return 0;
}
- 提取字符串。
一旦您具有了使用 gettext 调用的代码,您可以使用 xgettext 从中提取消息,并将它们存储在 .pot 中
xgettext -d hello --keyword=_ -o hello.pot main.c
-d hello
指定生成的翻译模板文件(.pot)关联的文本域(text domain)为 “hello”。
文本域是 gettext 系统中用于区分不同模块或应用程序翻译的命名空间。
--keyword
查找被指定关键词包裹的字符串。
生成的翻译模板文件内容如下:
cat hello.pot# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-30 18:31+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"#: main.c:18
#, c-format
msgid "Hello, World!\n"
msgstr ""#: main.c:19
#, c-format
msgid "This is a demo of gettext.\n"
msgstr ""#: main.c:23
#, c-format
msgid "You have %d new message.\n"
msgstr ""
- 创建翻译文件。
这里创建中文简体的翻译文件 zh_CN.po。
msginit --input=hello.pot --locale=zh_CN -o hello.po
-i, --input
参数指定输入的模板文件,包含了所有待翻译的字符串。这个文件通常是通过 xgettext 命令从源代码中提取所有标记为翻译的字符串得到的。
-l, --locale
参数指定了目标语言的语言代码,这里使用 zh_CN 表示简体中文。语言代码通常由两部分组成:语言(zh)和国家/地区(CN),这里 CN 代表中国。这个参数告诉 msginit 创建一个针对中文简体的 .po 文件。
- 编辑翻译文件进行翻译。
msgid "Hello, World!"
msgstr "你好,世界!"msgid "This is a demo of gettext."
msgstr "这是一个gettext的演示。"msgid "You have %d new message.\n"
msgstr "你有 %d 条新消息。\n"
- 编译翻译文件。
使用工具 msgfmt 将 PO 文件编译为程序使用的二进制 MO 文件。
msgfmt -o hello.mo hello.po
如果出现如下错误:
zh_CN.po:22:11: invalid multibyte sequence
zh_CN.po:22:16: invalid multibyte sequence
zh_CN.po:22:17: invalid multibyte sequence
...
说明生成的 PO 文件头中的元信息 Content-Type 不对,需要修改成正确的字符编码。
# 原来 GB2312
"Content-Type: text/plain; charset=UTF-8\n"# 改成 UTF-8
"Content-Type: text/plain; charset=UTF-8\n"
为什么使用 msginit 生成的 PO 文件字符编码为 GB2312 呢?
注意: MO 文件必须与文本域同名。
因为代码中使用的文本域为 hello,所以这里生成的 MO 文件需要命名为 hello.mo。
将生成的 MO 文件拷贝至代码中使用的文本域目录:
cp hello.mo /usr/share/locale/zh_CN/LC_MESSAGES/
- 验证多语言是否生效。
编译上面的示例程序。
gcc main.c -o hello.out
执行 hello.out 输出如下结果:
LANG=zh_CN.UTF-8 ./hello.out你好,世界!
是一个gettext的演示。
你有 3 条新消息。
注意,执行程序前需要临时设置环境变量 LANG 为 zh_CN.UTF-8,表明程序的语言环境和字符编码为简体中文和 UTF-8,这样程序就可以去 locale 目录找到特定语言的文本域对应的翻译文件(MO 文件),这里是 hello.mo 文件且字符编码为 UTF-8。这样程序就可以将 gettext 包裹的内容翻译成简体中文了。
参考文献
deepseek.com
gettext - GNU Project - Free Software Foundation
使用GNU gettext 来翻译软件 - Weblate