gettext 使用方法
2024-07-31 23:16:48

下载

Windows版下载地址:https://mlocati.github.io/articles/gettext-iconv-windows.html

翻译流程

  1. 在软件源码中使用相应的库将需要翻译的文字标记起来。比如C语言的libintl,适合C++的 Boost.locale
  2. 使用xgettext.exe工具提取源代码中被标记的语句,生成 pot(portable object template)文件。
  3. 使用msginit.exe工具将 pot 文件转化成一个特定语言版本的 po(portable object)文件。或者使用msgmerge.exe将更新了的 pot 文件与旧的 po 文件合并生成新的 po 文件。
  4. 编辑 po 文件(可使用 Poedit 等工具),开始翻译工作。
  5. 使用msgfmt.exe工具将.po文件转换成.mo(machine object)文件。

提取原文

从源码中提取原文用到的工具是xgettext

0x01 基本用法

在源码中标记需要翻译的字符串

1
std::string str1 = boost::locale::gettext("Love");

提取命令

1
xgettext.exe --from-code="UTF-8" -o my.pot main.cpp

知识点:

  1. 工具默认提取关键字gettext标记的字符串。
  2. 工具默认认为源文件是 ASCII 文件。

但既然是要做国际化支持,建议将源码文件改为 UTF-8 编码,通过参数--from-code来指示源文件的编码即可。生成的 pot 文件内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 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: 2022-04-00 00:00+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"

#: C:/Demo/main.cpp:15
msgid "Love"
msgstr ""

0x02 自定义提取关键字

xgettext.exe默认仅识别gettext关键字,但可以用--keyword参数指示额外的关键字以满足扩展需求

1
2
3
4
#define _(STRING) boost::locale::gettext(STRING)

auto str1 = _("underline");
auto str2 = boost::locale::translate("boost");

命令:

1
xgettext --from-code="UTF-8" --keyword=_ --keyword=translate -o gettext.pot main.cpp

生成 po 文件

生成 po 文件用到的工具是msginit

0x01 基本用法

1
msginit -i gettext.pot -o gettext.po

得到po文件内容大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Chinese translations for PACKAGE package
# PACKAGE 软件包的简体中文翻译.
# Copyright (C) 2022 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# <EMAIL@ADDRESS>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-01 08:57+0800\n"
"PO-Revision-Date: 2022-04-01 09:16+0800\n"
"Last-Translator: <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (simplified)\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=GBK\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#: C:/Demo/Application.cpp:45
msgid "Love"
msgstr ""

#: C:/Demo/Application.cpp:46
msgid "underline"
msgstr ""

#: C:/Demo/Application.cpp:47
msgid "boost"
msgstr ""

0x02 指示目标语言及编码

输出的 po 文件默认使用本机区域设置,比如简体中文是zh_CN.GBK
可以通过--locale参数指示 po 文件的语言和编码。格式遵循 ISO 639-1ISO 3166-1 标准。
比如我想创建一个针对简体中文的翻译文件并采用 UTF-8 编码:

1
msginit --locale=zh_CN.UTF-8 -i gettext.pot -o zh_CN.po

但是charset却依然是GBK

1
2
3
...
"Content-Type: text/plain; charset=GBK\n"
...

google找到了答案:因为 pot 文件没有编码信息

1
"Content-Type: text/plain; charset=CHARSET\n"

明明在 xgettext 参数中指定了 UTF-8,为什么 pot 文件没有信息?

1
xgettext.exe --from-code="UTF-8"

因为 xgettext 在提取原文过程中没有发现任何非 ASCII 字符,就会忽略掉 charset,也算是个BUG了。
当我手动修改 pot 文件编码后,再次生成 po 文件,charset 就变为了 UTF-8,但文件内容其实还是GBK。。。

所以,解决这个问题需要做两件事

  1. 修改 po 文件的charset为UTF-8
  2. 修改 po 文件编码为 UTF-8

每次去手动修改有点傻,我们可以在源码中标记一个UTF-8字符,但是翻译的时候忽略它,这样就能保证 pot、po文件的charset为UTF-8。

1
2
//TRANSLATOR:无需翻译
boost::locale::pgettext("IGNORE", "ø");

这样就解决了charset的问题。文件自身编码问题可以用iconv.exe来转换,它已经包含在gettext工具包中了,所以最终的命令是:

1
2
3
msginit -i test.pot -o zh_CN.po.tmp
iconv -t utf-8 zh_CN.po.tmp > zh_CN.po
del zh_CN.po.tmp

生成 mo 语言文件

1
msgfmt -o test.mo zh_CN.po

更新 po 文件

在软件升级迭代的过程中,待翻译原文也会发生变化,如增加、删除等,这就需要将 pot 文件中变动的内容更新至 po 文件,而不是重新生成一个新的 po 文件。这就需要用到msgmerge

1
msgmerge -U zh_CN.po test.pot

-U表示更新现有 po 文件,按位置看,第一个文件就是被更新的目标文件。
当然也可以用-o参数创建一个新的 po 文件:

1
msgmerge -o new.po zh_CN.po test.pot

知识点:

  1. 新文件中词条如果有译文,任何情况下都不会被覆盖。
  2. 通过compendium参数指定翻译文件对空词条进行填充。
1
msgmerge --compendium dict.po -U target.po new.pot

注意,引用的文件可以是 pot 文件,也可以是 po 文件。

合并多个 po 文件

一个软件项目可能含有多个模块,每个模块都有自己的翻译文件,有大量重叠的内容,完全可以将多个 po 文件合并为一个。

1
msgcat -o all.po 1.po 2.po

默认合并规则:

  • 对于相同的原文,如果仅有一个被翻译了,则采用它。
  • 对于相同的原文,如果有不同的翻译,则会合并所有的翻译,需要人工处理
    1
    2
    3
    4
    5
    6
    7
    8
    #: main.cpp:21 main.cpp:25
    #, fuzzy
    msgid "Love"
    msgstr ""
    "#-#-#-#-# 1.po #-#-#-#-#\n"
    "爱情\n"
    "#-#-#-#-# 2.po #-#-#-#-#\n"
    "爱"

    如果想避免重复的翻译内容被合并,可以使用use-first参数,表示采用找到的第一个可用的翻译。
    1
    msgcat --use-first -o all.po 1.po 2.po

参考

msginit
GNU gettext工具简介
gettext使用
Generate UTF-8 dictionaries using gettext