作者:龙飞
自从开始研究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 <iostream>
#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;
}
我们先说#include进来的<clocale>,我用“<>”表示它是标准C++的一部分。它包含了函数setlocale()。这个函数在这里的两个参数——常量LC_ALL与空字符串""的意思是,在这个程序中的所有语言与区域,都设置为系统默认的语言与区域。
libintl.h是我们刚才加入的GNU的一部分,这意味着在Linux系统下,这个头文件是系统本身自带的。它包含了后面三个函数:bindtextdomain()将一个文件夹目录绑定到一个域名上,这个域名也是将来*.mo文件的文件名;textdomain()表明我们将使用的域名;gettext()中的字符串将是被多语言翻译替换的部分。
将这个程序编译,在没有多语言包的时候,程序也能正常的运行,显示“Hello, World!”。
1.3:为源程序制作po文件和mo文件
如果你已经安装了完整的安装包,找到相关文件夹的bin目录,这里有很多工具软件。你可以通过cmd的方式一步步的转换,也可以,偷点儿懒,因为有更加现成的工具可以用。但是,第一步,从源代码提取gettext()的文本,还得靠命令:xgettext。就跟用g++命令一样,假设我们的源文件名是main.cpp,我们把它先转换成一个模板文件a.pot:
xgettext -o a.pot main.cpp
你可以用vim之类的文本编辑器看看*.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: 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 <LL@li.org>\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 ""
下面,我们使用一个简单的小工具poedit。又一个跨平台的软件,主页在:
http://www.poedit.net/安装运行后,选择“从POT文件更新类目”,然后打开我们刚才的a.pot,什么都不用修改(当然,你也可以把自己信息都写上去),确保“字符集”是UTF-8就可以了。然后,在英语下面也上替换的文字吧,保存的时候,相应的mo文件也就建立起来了。
msgid ""
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 <zbln426@163.com>\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 "浜茬埍鐨勪笘鐣岋紝鎴戞潵浜嗭紒"
这是po文件。怎么是乱码?那是因为windows不是用UTF-8保存的文本文件(默认一般是GB2312)。用poedit打开时候是正确显示的。我的文本内容是:“亲爱的世界,我来了!”。
如果你用的是vim,可以通过设置环境变量解决显示乱码的问题,在_vimrc文件中添加这一句:
set fileencodings=gb2312,ucs-bom,utf-8,chinese
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类,在程序中需要用到的地方,直接调用它的对象就可以了。
//FileName: string_data.h
#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的构造函数中,将来gettext也只需要提取StringData的实现文件就可以了。
#include "string_data.h"
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的转换更加简单吧。(如果安装路径不一样请做相应的修改)。
@set path=C:\Program Files\GnuWin32\bin;%PATH%;
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
Poedit打开StringData.po的时候会报错,那是因为文件指明的编码不可用,请在“字符集”中选择UTF-8,另外,在“工程名称以及版本”中写点信息,不要使用默认值就可以了。然后翻译并保存,StringData.mo文件就生成了。
posted on 2008-03-30 02:02
lf426 阅读(4950)
评论(3) 编辑 收藏 引用 所属分类:
SDL入门教程 、
跨平台与GNU