SDL入门教程(十):1、多语言支持,Win32下的GetText

SDL入门教程(十):1、多语言支持,Win32下的GetText

作者:龙飞

        自从开始研究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 <[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 
""
下面,我们使用一个简单的小工具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 <[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 
" 浜茬埍鐨勪笘鐣岋紝鎴戞潵浜嗭紒 "
这是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文件就生成了。

你可能感兴趣的:(SDL入门教程(十):1、多语言支持,Win32下的GetText)