我相信有很多象我一样时常工作在VC环境中的程序员来说,当代码移植到Linux 下时都会遇到这一问题。今天我就来谈一下自己是如何解决的吧!在网上你一搜索,多数是说 setlocale() 、gettxt() 这类函数可以来解决乱码问题。的确,这类函数是可以搞定的。如下例子:
// prog.cpp
#include <stdio.h>
#include <locale.h>
#include <libintl.h>
#define _(STRING) gettext(STRING)
#define PACKAGE "prog"
main()
{
setlocale(LC_MESSAGES, "");
textdomain(PACKAGE);
/* 这就是指定用
/usr/share/locale/$LOC/LC_MESSAGES/prog.mo
作为讯息档。其中 $LOC 是在 setlocale 中设定的 */
printf(_("This is a test string.\n"));
/* 使用 gettext 来抓出讯息,再交给 printf 来印 */
}
接下来要做的工作:
1、使用 xgettext 产生 .pot 档:
xgettext -a -o prog.pot prog.cpp
2、生成 中文翻译的po 文件
msginit --locale=zh_CN.utf-8 --output=zh_CN.po --input=prog.pot
|
3、用vi修改 po 文件 将 msgstr "" 处修改为想要的中文,保存即可。
4 、将 po 文件编译成 mo 文件
msgfmt zh_CN.po --output=zh_CN.mo
5、将文件编译运行就可以看见了.
下面给出比较给力的文章:
Gettext 在 C 中的用法
1、原始文件
这里使用一个简单的 C 程序作为例子,注意这个文件是采用 utf-8 编码的,我故意选择了一个同时包含中 文和英文字符串的代码。?
1
2
3
4
5
6
7
8
9
10
|
#include <stdio.h>
char *gs = "Global string\n"
int main ( void )
{
printf ( "Hello, world!\n" );
printf ( "中文字符串\n" );
return 0;
}
|
2、必要的修改
为了让这个程序能够用 gettext 进行翻译,需要作一些修改(红字部分)。
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
|
#include <stdio.h>
#include <locale.h>
#include <libintl.h>
#define _(STR) gettext(STR)
#define N_(STR) STR
#define LOCALEDIR "/usr/share/locale"
#define PACKAGE "fool"
char *gs = N_( "Global string\n" );
int main ( void )
{
setlocale (LC_ALL, "" );
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
printf (_( "Hello wrold!\n" ));
printf (_( "中文字符串\n" ));
printf (_(gs));
return 0;
}
|
LOCALEEDIR 指定程序要到什么地方查找 mo 文件。通常是/usr/share/local/, 这个目录下面包含
着诸如:en_US、zh_CN、zh_TW 的目录。这些目录以 LL_CC 的形式命名,其中 LL 是语言名,CC 是国家名。我们需要把 mo 文件放入 LL_CC 下的 LC_MESSAGES 目录下面。 PACKAGE 指定了程 序的包名,假如这个程序叫做 fool。当程序在某种语言(LANG=en_US)下运行时,就会在LOCALEDIR/LL_CC/LC_MESSAGES 下查找 fool.mo。如果找到了,程序的结果就会按照这个 mo
的内容输出,否则按照源代码中的字符串输出。定义_( )宏是一种惯常用法,减少了打字。
另外一个值得注意的地方是N_()这个宏,它仅仅是扩展为原来的字符串。为什么要定义这个宏,因为gs是一个全局的字符串,需要在编译期初始化,如果同样的用_()宏包括这个字符串,程序甚至不能通过编译(至少C语言是这样,C++或许有点不同,不过我不太了解)。这里N_()宏的作用仅仅是为了做个标记,告诉gettext,这个字符串需要翻译。它与_()宏的区别是,后者既是一个标记,又是一个函数调用。所有静态字符串和全局字符串,都可以用N_()宏翻译,而实际显示这个字符串时,还要再加上_()宏来调用gettext。
3、生成 po 文件的模板
我们在源代码中把 gettext() 定义为 _(),因此这里要手动指定关键字,也就是一个下划线(–keyword=_)。在默认情况下,xgettext 会假设源文件的编码为 ASCII,但是我们的文件中包含了一个中 文字符串,并且以 utf-8 编码,这时候需要手动指定文件的编码()。–output=fool.pot 指定了输出文件名 为 fool.pot,在默认的情况下是 message.po,采用 pot 的后缀表示这个一个 po 的 template。–package-name=fool 和–package-version=1.0 分别指定包名和版本,它们填充 fool.pot 中的相应字段。这条命令在当前目录下生成了 fool.pot 文件。
PS. xgettext 后面可以同时写上多个文件。或者是在一个单独的文件中列举所有的文件名,并用– files-from=FILE 选项指定该文件。
4、生成语言的 po 文件
源文件中既包含英文,又包含中文,我们可以同时生成两种语言的翻译。
4.1 英文翻译
a) 生成 po 文件
msginit --locale=en_US --output=en_US.po --input=fool.pot
|
命令执行时会提示你输入自己的邮件地址,最后生成 en_US.po 文件,你可以对比一下 en_US.po 跟 fool.pot 有什么异同。en_US.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
|
# English translations for PACKAGE package.
# Copyright (C) 2011 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# lyre <lyre@poetpalace.org>, 2011.
#
msgid ""
msgstr ""
"Project-Id-Version: fool 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-01-19 13:11+0800\n"
"PO-Revision-Date: 2011-01-19 13:11+0800\n"
"Last-Translator: lyre <lyre@poetpalace.org>\n"
"Language-Team: English\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: fool.c:12
msgid "Global string\n"
msgstr "Global string\n"
#: fool.c:20
#, c-format
msgid "Hello wrold!\n"
msgstr "Hello wrold!\n"
#: fool.c:21
#, c-format
msgid "中文字符串\n"
msgstr "中文字符串\n"
|
留意最后一部分,其中每一行 msgstr 是我们需要翻译的地方,原始文件是中英文混杂的,而我 们目前做的是英文翻译,注意到第一个字符串本身就是英文,我们可以简单的清空对应的 msgstr,让程 序使用默认的字符串。第二个字符串是需要翻译的。修改后的结果为:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#: fool.c:12
msgid "Global string\n"
msgstr ""
#: fool.c:20
#, c-format
msgid "Hello wrold!\n"
msgstr ""
#: fool.c:21
#, c-format
msgid "中文字符串\n"
msgstr "chinese string\n"
|
PS. 有专门的程序可以编辑 po 文件,如 gtranslator
b) 把 po 编译成二进制的 mo 文件
msgfmt en_US.po --output=en_US.mo
|
c)把 mo 复制到指定位置
sudo
cp
en_US.mo
/usr/share/locale/en_US/LC_MESSAGES/fool
.mo
注意路径和文件名。
4.2 中文翻译
与英文是类似的
a) 生成 po 文件
msginit --locale=zh_CN.utf-8 --output=zh_CN.po --input=fool.pot
|
注意–locale=zh_CN.utf-8,中文除了要指定语言国家以外,还要指定编码。修改这个 po :
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#: fool.c:12
msgid "Global string\n"
msgstr "全局字符串"
#: fool.c:20
#, c-format
msgid "Hello wrold!\n"
msgstr "你好世界"
#: fool.c:21
#, c-format
msgid "中文字符串\n"
msgstr ""
|
b) 把 po 编译成二进制的 mo 文件
msgfmt zh_CN.po –output=zh_CN.mo
c)把 mo 复制到指定位置
sudo cp zh_CN.mo /usr/share/locale/zh_CN/LC_MESSAGES/fool .mo
|
注意路径和文件名。
5.编译程序并测试
1
|
gcc fool.c -Wall -o fool
|
lyre@linux-4179e1:~/Desktop/sample> LANG=en_US ./fool
Hello, world!
chinese string
#注意,这个地方指定了 utf-8 的编码
lyre@linux-4179e1:~/Desktop/sample> LANG=zh_CN.utf-8 ./fool
你好,世界!
中文字符串
#我们没有翻译法语,所以是按照原样输出的。
lyre@linux-4179e1:~/Desktop/sample> LANG=fr_FR ./fool
Hello, world!
中文字符串
6、更新 po
有时候,我们会修改源文件中的字符串,这个时候需要更新 po 和 mo。 比如,我们把源代码中的”Hello, world!”修改为”We’ve repalaced this string\n””。
重新生成 po 模板:
xgettext --keyword=_ --from-code=utf-8 --output=fool.pot --package-name=fool
--package-version=1.0 fool.c
|
1
2
|
msgmerge --update en_US.po fool.pot
msgmerge --update zh_CN.po fool.pot
|
剩下的内容就是重新翻译,生成 mo 并安装。
7、现实使用
gettext 一般是配合 autotool 一起使用的,使用起来没有那么繁琐。但是学习 autotool 需要一定的时
间,目前网上很多的教程都是过时的,而 autotool 的 manual 并不适合用于学习。我能找到的最好的 autotool 的教程来自 http://www.lrde.epita.fr/~adl/autotools.html,介绍autoconf、automake、libtool 以及 gettext 的使用,其内容比较新,上一次的更新时间在 2010 年 5 月,
这一点很重要,保证了按照里面的例子可以得到正确的结果。
看完之后,是不是觉得很不错。不过,这种方法只适合固定的文字翻译,对于我们游戏中类似玩家名字这种变化的文字,这是不可取的。后来我又做了修改环境变量的尝试:
汉字编码:
ASCII:
American Standard Code for Information Interchange,美国信息交换标准码。 目前计算机中用得最广泛的字符集及其编码,由美国国家标准局(ANSI)制定。 它已被国际标准化组织(ISO)定为国际标准,称为ISO 646标准。 ASCII字符集由控制字符和图形字符组成。 在计算机的存储单元中,一个ASCII码值占一个字节(8个二进制位),其最高位(b7)用作奇偶校验位。
UTF:
Unicode 的实现方式不同于编码方式。 一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。 Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。
* UTF-8: 8bit变长编码,对于大多数常用字符集(ASCII中0~127字符)它只使用单字节,而对其它常用字符(特别是朝鲜和汉语会意文字),它使用3字节。
* UTF-16: 16bit编码,是变长码,大致相当于20位编码,值在0到0x10FFFF之间,基本上就是unicode编码的实现,与CPU字序有关。
注意:兼容性最好的编码就是UTF-8! 毕竟GBK/GB2312是国内的标准,当我们大量使用国外的开源软件时,UTF-8才是编码界最通用的语言。
具体解决办法如下:
1、console终端乱码
在/etc/profile文件的最后一行添加如下内容:
export LC_ALL="zh_CN.GB18030"
2、xwindow终端乱码
在/etc/sysconfig/i18n文件的最后一行添加如下内容:
export LC_ALL="zh_CN.GB18030"
3. 修改i18n文件
vi /etc/sysconfig/i18n
将内容改为
LANG="zh_CN.GB18030"
LANGUAGE="zh_CN.GB18030:zh_CN.GB2312:zh_CN"
SUPPORTED="zh_CN.GB18030:zh_CN:zh:en_US.UTF-8:en_US:en"
SYSFONT="lat0-sun16"
之后重启机器,这样中文在SSH,telnet终端就可以正常显示了。
做完这些后,感觉都不是自己理想的效果,最后,我想Linux既然是 UTF-8 编码的,而 Windows 是 GB2312或者说是 GBK编码。这就是问题的关键了。所以我干脆将文件转成 UTF-8 试试,没料真的成功了。呵呵,后来我直接将 虚拟终端的编码设置成 GB18030 就行了。当然这是一种偷懒的方法,若想让程序成UTF-8 可以用 iconv命令将文件转为 UTF-8 或 将指定的地方用 iconv 函数转即可。