作者:龙飞
自从开始研究SDL的文本显示,我就一直在思考在SDL中显示中文的问题。我们知道韦诺之战(Battle for Wesnoth)使用SDL开发的,并且支持多语言。所以,我一直相信Wesnoth的源代码里面一定有我所需要的答案。网络上是纵说纷纭啊,有些人干脆说,SDL不支持中文;有些人在困难面前回到了MFC的怀抱。而,既然我的目标是跨平台,并且我也相信一定能找到答案,所以,我坚持寻找。终于,完美解决了在SDL中显示中文,甚至多语言的问题。以下的几节,我将全面,详细的说明这些方法。
1.1:po,mo与gettext
线索从Wesnoth的发布游戏与源代码中开始,我们知道,在Wesnoth游戏中,有个名为po的文件夹,多国语言翻译都放在了这个文件夹下面。游戏程序中多为*.mo文件,源代码中多为*.po文件。通过搜索,po与mo的背景浮出水面——它们来自GNU项目gettext。
gettext项目是专门为多语言设计的。我们不需要修改源代码和程序的情况下,可以让程序支持多国语言。程序将根据系统所在的国家和区域选择相应的语言,当然,也可以在执行过程中让玩家自由的选择。既然是开放源代码的,自然也很容易的被移植到win32下。win32下的这个项目主页如下:
http://gnuwin32.sourceforge.net/packages/gettext.htm
为了方便的使用,我还是建议你下载完整的安装包(Complete package)。然后,你可以看英文说明,也可以凭着直觉去试验,找到哪些库和哪些DLL文件是编译和运行时必须的——当然,我也可以直接告诉你答案。
设置编译环境的问题就不再多说了,不清楚的请看前面的章节。反正都三部分:*.h文件,*.lib文件和*.dll文件,放到相应的文件夹下面并在编译时候指明就可以了。
我们下面将用到的文件有:
libintl.h:请在写源代码的时候#include进来;
libintl.lib:这是编译时候需要的库文件;
libintl3.dll和libiconv2.dll:这是程序运行时候需要的文件,放到*.exe文件可以找到的地方。
1.2:演示程序以及说明
#include < string >
#include < clocale >
#include " GNU/libintl.h "
int main( int argc, char * argv[])
{
setlocale(LC_ALL, "" );
bindtextdomain( " myText " , " E:/My Documents/Visual Studio 2008/po " );
textdomain( " myText " );
std:: string test = gettext( " Hello, World! " );
std::cout << test << std::endl;
return 0 ;
}
libintl.h是我们刚才加入的GNU的一部分,这意味着在Linux系统下,这个头文件是系统本身自带的。它包含了后面三个函数:bindtextdomain()将一个文件夹目录绑定到一个域名上,这个域名也是将来*.mo文件的文件名;textdomain()表明我们将使用的域名;gettext()中的字符串将是被多语言翻译替换的部分。
将这个程序编译,在没有多语言包的时候,程序也能正常的运行,显示“Hello, World!”。
1.3:为源程序制作po文件和mo文件
如果你已经安装了完整的安装包,找到相关文件夹的bin目录,这里有很多工具软件。你可以通过cmd的方式一步步的转换,也可以,偷点儿懒,因为有更加现成的工具可以用。但是,第一步,从源代码提取gettext()的文本,还得靠命令:xgettext。就跟用g++命令一样,假设我们的源文件名是main.cpp,我们把它先转换成一个模板文件a.pot:
# 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: 2008-03-30 00:24+0800\n "
" PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n "
" Last-Translator: FULL NAME <EMAIL@ADDRESS>\n "
" Language-Team: LANGUAGE <[email protected]>\n "
" MIME-Version: 1.0\n "
" Content-Type: text/plain; charset=CHARSET\n "
" Content-Transfer-Encoding: 8bit\n "
#: main.cpp: 11
msgid " Hello, World! "
msgstr ""
http://www.poedit.net/
安装运行后,选择“从POT文件更新类目”,然后打开我们刚才的a.pot,什么都不用修改(当然,你也可以把自己信息都写上去),确保“字符集”是UTF-8就可以了。然后,在英语下面也上替换的文字吧,保存的时候,相应的mo文件也就建立起来了。
msgstr ""
" Project-Id-Version: \n "
" Report-Msgid-Bugs-To: \n "
" POT-Creation-Date: 2008-03-30 00:24+0800\n "
" PO-Revision-Date: 2008-03-30 00:25+0800\n "
" Last-Translator: lf426 <[email protected]>\n "
" Language-Team: \n "
" MIME-Version: 1.0\n "
" Content-Type: text/plain; charset=UTF-8\n "
" Content-Transfer-Encoding: 8bit\n "
#: main.cpp: 11
msgid " Hello, World! "
msgstr " 浜茬埍鐨勪笘鐣岋紝鎴戞潵浜嗭紒 "
如果你用的是vim,可以通过设置环境变量解决显示乱码的问题,在_vimrc文件中添加这一句:
1.4:设置mo文件的目录
下面的工作可能就有些教条了。还记得我们绑定域名的路径吧,我用的是
E:\My Documents\Visual Studio 2008\po
(请注意在C++程序里面把斜杠反过来!)
*.mo文件并不是直接放到这个路径下,而是这个路径下的./LL/LC_MESSAGES或者./LL_CC/LC_MESSAGES。其中LL表示语种,CC表示国家或区域。具体的请参考Wesnoth。就我们的中文来说,这个例子放mo文件的路径是:
E:\My Documents\Visual Studio 2008\po\zh_CN\LC_MESSAGES
现在运行程序就可以看到文本已经被替换了。如果我们删除mo文件或修改mo文件名(与绑定域名不一致),程序会继续显示原来的英文。如果我们改变系统环境,只要不是中国中文,程序都还是显示英文。如果我们要更新替换内容,直接用poedit更新po和mo文件就可以了。
1.5:构建StringData类
我们希望字符串的数据单独的保存在一个文件里,这样既方便被gettext提取,也方便修改。而且,在程序里面,我们尽量把gettext涉及到的一些特殊的设置隐藏了。所以,我们构建StringDada类,在程序中需要用到的地方,直接调用它的对象就可以了。
#ifndef STRING_DATA_H
#define STRING_DATA_H
#include < clocale >
#include < string >
#include < vector >
#include " GNU/libintl.h "
class StringData
{
private :
std::vector < std:: string > data;
public :
StringData();
std:: string operator []( const unsigned int & n) const ;
};
#endif
StringData::StringData()
{
setlocale(LC_ALL, "" );
bindtextdomain( " StringData " , " ./po " );
textdomain( " StringData " );
// 0
data.push_back(gettext( " Up was pressed. " ));
// 1
data.push_back(gettext( " Down was pressed. " ));
// 2
data.push_back(gettext( " Left was pressed. " ));
// 3
data.push_back(gettext( " Right was pressed. " ));
// 4
data.push_back(gettext( " Other key was pressed. " ));
}
std:: string StringData:: operator []( const unsigned int & n) const
{
if ( n >= data.size() )
return 0;
return data[n];
}
1.6:做个gettext的批处理
如果你按照我全面介绍的,安装了Poedit,也安装了GnuWin32,那么,我们做个批处理文件让从string_data.cpp到StringData.mo的转换更加简单吧。(如果安装路径不一样请做相应的修改)。
xgettext -- force - po - o string_data.pot string_data.cpp
msginit - l zh_CN - o StringData.po - i string_data.pot
@set path = C:\Program Files\Poedit\bin; % PATH % ;
poedit StringData.po
del string_data.pot
del StringData.po