用 Boost.Locale 使软件支持多国语言
2024-03-17 17:33:41

用过Qt的人都知道它自带有多语言支持的功能,只要在源码中将需要翻译的字符串用tr包裹起来,就可以很轻松的利用其提供的 Qt Linguist 软件进行文字翻译。
如果我们的工程并非Qt项目,那么可以考虑使用 gettext 解决方案。其实现库libintl是C语言开发的,在 linux 系统上用的比较多。
在C++中,更适合使用 Boost.Locale,用于替代libintl

基本用法

需要了解的是,Boost.Locale 只是一个运行时,它本身并不生成语言文件。我们需要使用gettext的相关工具去进行提取和翻译工作。

文本假设你已经知道了如何制作.mo等语言文件。

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <boost/locale.hpp>

void test()
{
boost::locale::generator gen;

// 指定多语言文件的根目录
gen.add_messages_path(R"(.\languages)");

// 对应语言文件名,不需要写扩展名。等同于 foo.mo
gen.add_messages_domain("foo");

// generator 会去对应的文件夹下找语言文件。这里会尝试从 zh 和 zh_CN 文件夹下寻找文件
std::locale loc = gen.generate("zh_CN.GBK");

// 设置全局locale
std::locale::global(loc);

std::string str1 = boost::locale::translate("Love").str();
std::string str2 = boost::locale::gettext("Love");
}

对于上面的例子,以下文件路径都是合法的

1
2
\languages\zh\LC_MESSAGES\foo.mo
\languages\zh_CN\LC_MESSAGES\foo.mo

generate 方法使用空字符串作为参数时将采用系统默认区域 + UTF8编码

1
2
3
4
5
6
7
8
9
10
// 重载了 () 操作符,等同于调用 generate 方法
std::locale loc = gen("");

// 验证
auto& info = std::use_facet<boost::locale::info>(loc);
std::string v1 = info.name(); // zh_CN.UTF-8
std::string v2 = info.language(); // zh
std::string v3 = info.country(); // CN
std::string v4 = info.encoding(); // utf-8
bool v5 = info.utf8(); // true

翻译字符串主要是两个静态方法translategettext
translate返回的是一个模板类basic_message,它允许原文延迟翻译,也就是只有在需要string的时候才会翻译。
gettext方法是建立在basic_message之上的,它会立即构造一个basic_message并且调用str()方法得到字符串。

1
2
3
4
5
template<typename CharType>
std::basic_string<CharType> gettext(CharType const *id, std::locale const &loc=std::locale())
{
return basic_message<CharType>(id).str(loc);
}

如果只是想立即得到译文,用gettext就够了。

gettext

  1. 获得译文,没有译文就返回原文
    1
    std::string str = boost::locale::gettext("Love");
  2. 第二个参数可以指定 locale
    1
    std::string str = boost::locale::gettext("Love", de);

dgettext

从指定的 domain(文件) 中获取译文。

1
2
std::string str1 = boost::locale::dgettext("foo2", "Love");
std::string str2 = boost::locale::dgettext("foo2", "Love", gbk);

ngettext

ngettext用于处理某些语言的复数形式

1
2
3
4
5
auto count = 1;
auto str = fmt::format(boost::locale::ngettext("1 file", "{} files", count), count); // 1 file

count = 2;
str = fmt::format(boost::locale::ngettext("1 file", "{} files", count), count); // 2 files

pgettext

根据上下文获得译文。上下文功能在某些场景下是有用的,它可以使同一个原文返回不同的译文。

1
2
std::string str1 = boost::locale::pgettext("ctx1", "Love");
std::string str2 = boost::locale::pgettext("ctx2", "Love", gbk);

总结

gettext是高级别的方法,使用简单。还有扩展的的方法如dgettextngettextpgettext等等。我们只要记住三个字母:

  1. d表示domain,支持指定域(语言文件)。
  2. n表示number,用于处理复数形式。
  3. p表示specific,表示处理特定语境下的语句。

gettext所有的方法如下,看字母就知道怎么用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
boost::locale::gettext()

// d 开头
boost::locale::dgettext()
boost::locale::dngettext()
boost::locale::dnpgettext()
boost::locale::dpgettext()

// n 开头
boost::locale::ngettext()
boost::locale::npgettext()

// p 开头
boost::locale::pgettext();

translate

前面说过,translate才是底层的API,主要用于需要延迟翻译的时候,使用方法和gettext差不多

1
2
3
4
5
6
7
8
9
10
11
std::string str = boost::locale::translate("Love").str();
// 等同于 gettext

std::string str = boost::locale::translate("Love").str("domain");
// 等同于 dgettext

std::string str = boost::locale::translate("1 files", "N files", 1).str();
// 等同于 ngettext

std::string str = boost::locale::translate("ctx", "Love").str();
// 等同于 pgettext

实践

软件首次运行时一般有三种选择

  1. 默认采用英文显示。
  2. 有安装程序的,在安装过程中让用户决定使用的语言。
  3. 跟随系统区域设置决定使用哪种语言。

对于第三种方式,我们需要获取计算机的区域设置。上面说过generator支持用空字符串来生成系统默认区域的locale,但是编码是用的UTF-8。
如果我们想获取系统的区域设置,可以用boost完成这项工作:

1
2
std::string localeStr = boost::locale::util::get_system_locale();
// 在简体中文Windows上,结果为 zh_CN.windows-936

然后将结果告诉generator,这样程序首次运行时将使用系统默认的语言显示了。

1
std::locale loc = boost::locale::generator().generate(localeStr);

参考

gettext使用
Locale Generation
Messages Formatting (Translation)