标签:des style blog http io color os ar 使用
参考:http://www.ibm.com/developerworks/cn/linux/l-cn-ccppglb/
多国语言的存在,使程序员在编码处理上花费了大量时间和精力;然而各种各样的乱码问题,如 XML 格式错误、文本显示异常、解析器异常等依然层出不穷。特别的,相对于 JAVA 语言,C/C++ 在处理编码问题上有更大的困难。本文避免纠缠不同编码格式的具体异同,以 Unicode 为核心,以简体中文为例,从工程应用角度分析编码问题存在的原因,不仅提出 C/C++ 标准库编程的解决方案,更结合项目经验,总结出处理多国语言编码问题的一般思路。
多国语言的存在、不同语言操作系统的存在,使得针对多语言的设计颇费周章,在编码上所付出的工作量也是可观的。所谓编码的问题,归结起来,就是二进制的编码以何种编码格式进行解析的问题。特别是在硬盘文件和内存数据的相互转化、即读写过程中,如果采用了错误的编码格式,就会造成乱码。JAVA 语言在字符串、编码等处理方面给了程序员更为直接、方便的接口,习惯使用 JAVA 做编码的程序员,在使用 C/C++ 进行文本编码相关的操作时,常会感到困惑。本文的目的在于以常用的 Unicode(UCS-2)、GB2312、UTF8 三种编码为例,分析不同编码在实用中的关系,特别是 C/C++ 中,怎样处理各种编码的问题。
第一种情况,可能造成数据的变化、失真。
如果使用 JAVA 语言,发生这种错误的情况稍少一些,因为在 JAVA 中没有 wstring 这种概念,在内存中的 String,使用的编码都是 Unicode,其中的转换对于程序员来讲是透明的。只要使用输入 / 输出方法时注意字节流的字符集选择即可。
例如,编码为中文 GB2312 的“标准”字符串被读入内存后转存为 UTF8 的过程:
但 C/C++ 编程,由于通常使用 char、string 类型的时候比较多,特别是进行文件读写,基本都是操作 char* 类型的数据。并且也没有像 JAVA 中 getByte(String charsetname) 这种函数,不能直接根据字符集重新编码得到字符串的 byte 数组。这时候,我们使用的 string 其实就一般不是 Unicode,而是符合某种编码表的。这使得我们往往困惑于 string 的编码问题。假设有 utf8 的字符串“一”(E4 B8 80),而我们错误的认为它是符合 gb2312(编码 A)的,并将其转换为 utf8(编码 B),这种转换结果是破坏性的,错误的输出将永远无法正确识别。
依然以“标准”为例,这是一个正确的转换:
第二种情况,则是更常见到的。例如:浏览器浏览网页时的发生的乱码问题;在写 XML 文件时,指定了 < ?xml version="1.0" encoding="utf-8" ?> 然而文件中却包含 GB2312 的字符串——这样经常会导致 XML 文件 bad formatted,而使得解析器出错。
这种情况下,其实数据都是正确的,只要浏览器选择正确的编码,将 XML 文件中的 GB2312 转换为 UTF8 或者修改 encoding,就可以解决问题。
需要注意的是,ASCII 码的字符,即单字节字符,一般不受编码变动影响,在所有编码表中的值是一样的;需要小心处理的是多字节字符,例如中文语言。
编码转换方法
一般的编码转换,直接做映射的不太可能,需要比较多的工作量,大多情况下还是选择 Unicode 作为转换的中介。
如前文所说,JAVA 的 String 对象是以 Unicode 编码存在的,所以 JAVA 程序员主要关心的是读入时判断字节流的编码,从而确保可以正确的转化为 Unicode 编码;相比之下,C/C++ 将外部文件读出的数据存为字符数组、或者是 string 类型;而 wstring 才是符合 Unicode 编码的双字节数组。一般常用的方法是 C 标准库的 wcstombs、mbstowcs 函数,和 windows API 的 MultiByteToWideChar 与 WideCharToMultiByte 函数来完成向 Unicode 的转入和转出。
这里以 MBs2WCs 函数的实现说明 GB2312 向 Unicode 的转换的主要过程:
wchar_t * MBs2WCs(const char* pszSrc){ wchar_t* pwcs = NULL; intsize = 0; #ifdefined(_linux_) setlocale(LC_ALL, "zh_CN.GB2312"); size = mbstowcs(NULL,pszSrc,0); pwcs = new wchar_t[size+1]; size = mbstowcs(pwcs, pszSrc, size+1); pwcs[size] = 0; #else size = MultiByteToWideChar(20936, 0, pszSrc, -1, 0, 0); if(size <= 0) returnNULL; pwcs = new wchar_t[size]; MultiByteToWideChar(20936, 0, pszSrc, -1, pwcs, size); #endif returnpwcs; }
相应的,WCs2MBs 可以将宽字符串转化为字节流。
char* WCs2MBs(const wchar_t * wcharStr){ char* str = NULL; intsize = 0; #ifdefined(_linux_) setlocale(LC_ALL, "zh_CN.UTF8"); size = wcstombs( NULL, wcharStr, 0); str = new char[size + 1]; wcstombs( str, wcharStr, size); str[size] = ‘\0‘; #else size = WideCharToMultiByte( CP_UTF8, 0, wcharStr, -1, NULL, NULL, NULL, NULL ); str = new char[size]; WideCharToMultiByte( CP_UTF8, 0, wcharStr, -1, str, size, NULL, NULL ); #endif returnstr; }
Linux 的 setlocale 的具体使用可以参阅有 C/C++ 文档,它关系到文字、货币单位、时间等很多格式问题。Windows 相关的代码中 20936 和宏定义 CP_UTF8 是 GB2312 编码对应的的 Code Page[ 类似的 Code Page 参数可以从 MSDN的 Encoding Class 有关信息中获得 ]。
这里需要特别指出的是 setlocale 的第二个参数,Linux 和 Windows 是不同的:
另外,标准 C 和 Win32 API 函数返回值是不同的,标准 C 返回的 wchar_t 数组或者是 char 数组都没有字符串结束符,需要手动赋值,所以 Linux 部分的代码要有区别对待。
最后,还要注意应当在调用这两个函数后释放分配的空间。如果将 MBs2WCs 和 WCs2MBs 的返回值分别转化为 wstring 和 string,就可以在它们函数体内做 delete,这里为了代码简明,故而省略,但请读者别忘记。
目前的第三方工具已经比较完善,这里介绍两个,本文侧重点不在此,不对其做太多探讨。
实验测试
在代码中调用“编码转换方法”一节里提到的函数,将 gb2312 编码的字符串转换为 UTF8 编码,分析其编码转换的行为:
在英文 Linux 环境下,执行下列命令:
export LC_ALL=zh_CN.gb2312
然后编译并执行以下程序(其中汉字都是在 gb2312 环境中写入源文件)
L1: wstring ws = L"一"; L2: string s_gb2312 = "一"; L3: wchar_t * wcs = MBs2WChar(s_gb2312.c_str()); L4: char* cs = WChar2MBs(wcs);
查看输出:
在 L1 行,执行结果显示编码为一个 0x04bb,其实这是一个转换错误,如果使用其他汉字,如“哈”,编译都将无法通过。也就是说 Linux 环境下,直接声明中文宽字符串是不正确的,编译器不能够正确转换。
而在中文 windows 下使用相同测试代码,则会在 L1 处出现区别,ws 中的 wchar_t 元素十六进制值是 0x4e00,这是汉字“一”的 Unicode 编码。
处理编码问题的经验总结
首先,这里先简单说明一下 Unicode 和 UTF8 的关系:Unicode 的实现方式和它的编码方式并不相同,UTF8 就是其实现之一。比方使用 UltraEdit 打开 UTF8 编码的中文文件,使用 16 进制查看,可以发现看到的中文对应部分应当是 Unicode 编码,每个中文字长度 2 字节—— UltraEdit 在这里已经做了转化;如果直接查看其二进制文件,可以发现是 3 字节。但两者的差别仅在于 Unicode 向 UTF8 做了数学上的转化。(更多关于 Unicode 和 UTF8 的概念,可以参见 有关文献)
其次,关于第三方库的选择,应当综合考虑项目的需求。一般的文本字符转换,系统的库函数已经可以满足需求,实现也很简单;如果需要针对不同地区的语言、文字、习惯进行编程,需要更为丰富的功能,当然选择成熟的第三方工具可以事半功倍。
最后,从逻辑上保持字符串的编码正确,需要注意几条一般规律:
参考:http://zh.wikipedia.org/wiki/Gettext
gettext 是GNU国际化与本地化(i18n)函数库。它常被用于编写多语言程序。
程序源代码需要进行修改以响应 GNU gettext 请求。多数编程语言均已通过字符封装的方式实现了对其的支持。为了减少输入量和代码量,此功能通常以标记别名 _ 的形式使用,所以例如以下C语言代码:
printf(gettext("My name is %s.\n"), my_name);
应当写作:
printf(_("My name is %s.\n"), my_name);
gettext使用其中的字符串寻找对应的其他语言翻译,若没有可用翻译则返回原始内容。
除C语言外, GNU gettext 还支持 C++, Objective-C,Pascal/Object Pascal,sh 脚本,bash 脚本,Python,GNU CLISP,Emacs Lisp,librep,GNU Smalltalk,Java,GNU awk,wxWidgets(通过 wxLocale类),YCP (YaST2语言),Tcl,Perl,PHP,Pike,Ruby以及R。用法均与在C语言上类似。
xgettext程序从源代码生成 .pot 文件,作为源代码中需翻译内容的模板。一个典型的 .pot 文件条目应当是这样的:
#: src/name.c:36
msgid "My name is %s.\n"
msgstr ""
注释被直接放置在字符串前,用于帮助翻译者理解待翻译内容:
/// TRANSLATORS: Please leave %s as it is, because it is needed by the program.
/// Thank you for contributing to this project.
printf(_("My name is %s.\n"), my_name);
本例中的注释是以 /// 开头的,其作用是用于 xgettext 程序生成 .pot 模板文件。
xgettext --add-comments=///
在 .pot文件中的注释应为以下形式:
#. TRANSLATORS: Please leave %s as it is, because it is needed by the program.
#. Thank you for contributing to this project.
#: src/name.c:36
msgid "My name is %s.\n"
msgstr ""
翻译者需要工作的对象是 .po文件,它是由msginit程序从 .pot 模板文件生成的。例如使用msginit初始化法语翻译文件时,我们运行以下命令:
msginit --locale=fr --input=name.pot
这将会使用指定的 name.pot 在当前目录创建一个 fr.po,其中的一个条目应该是以下形式的:
#: src/name.c:36
msgid "My name is %s.\n"
msgstr ""
翻译者需要手工或使用类似 Poedit、gtranslator或Emacs等工具的相应模式编辑该文件。翻译完成后,文件应为如下的样子:
#: src/name.c:36
msgid "My name is %s.\n"
msgstr "Je m‘appelle %s.\n"
最后 .po 文件需要使用msgfmt编译为.mo文件以用作发布。
使用Unix类型操作系统的用户只需设置环境变量中的LC_MESSAGES
,程序将自动从相应的.mo
文件中读取语言信息。
参考:http://www.aslike.net/showart.asp?id=154
“通常,程序及其文档信息都是用英语语言写的,程序运行时同用户交互的信息也是英语。这是一个事实,不仅仅GNU的软件是这样,其他大部分私有软件或自由软件也是这样。一方面,对于来自所有国家的开发者、维护者和用户来说,相互沟通中使用一种通用的语言非常的方便。另一方面,相对于母语来说大多数人并不适应使用英语,而且他们的日常工作都是尽可能的使用他们自己的母语。多数人都会喜欢他们的计算机屏幕显示的英语更少,显示的母语更多。"
" GNU 的 ‘gettext‘ 是 GNU翻译项目的一个重要步骤,我们依赖于它 作很多其他的步骤。这个软件包给程序员、翻译者,或者用户提供了一套集成工具和文档。详细地说,GNU gettext 提供了一套工具, 能让其他 GNU 软件创建多语言信息。..."
gettext的工作流程是这样的:比如我们写一个Visual C++(MSVC)程序,通常printf等输出信息都是English的。如果我们在程序中加入gettext支持,在需要交互的字符串上用gettext函数,程序运行是就可以先调用gettext函数获取当前语言的字符串,替换当前的字符串了。注意是运行时替换。
GNU gettext-0.18.3.2 是最新版本,GNU官网上可以直接下载,只是没有Visual C++(MSVC)可用的运行支持库,只能自己动手编译了,编译好的运行支持库,点击这里下载。
在Visual C++(MSVC)中使用GNU gettext实现多语言时,可以编写翻译函数来实现界面与菜单字符串的自动替换,程序中的字符串只能一个个手工替换了,这样使用起来,就跟在Delphi与C++Builder中使用GNU gettext差不多方便快捷了。
简单使用的例子
一个简单的例子,
#include <stdio.h>
#include <libgnuintl.h>
/*使用gettext通常使用类似下面的一个带函数的宏定义
*你完全可以不用,直接使用 gettext(字符串)
*/
#define _(S) gettext(S)
/*PACKAGE是获取语言字符串的文件名字(运行时输入的命令)*/
#define PACKAGE "default"
int main(int argc, char **argv)
{
/* 下面三个参数都是使用gettext时候需要使用的
* setlocale
* bindtextdomain
* textdomain
*/
setlocale(LC_ALL,"");
bindtextdomain(PACKAGE, "locale");
textdomain(PACKAGE);
printf(_("Hello,GetText!\n"));
return 0;
}
其中语言字符串文件的结构: .\locale\语言名称\LC_MESSAGES\default.mo,如简体中文:.\locale\ZH_CN\LC_MESSAGES\default.mo
mo文件是编译后的语言字符串文件,GNU网站上有相应的工具软件可以编辑与生成;
点击这里下载Visual C++(MSVC)中可用的GNU gettext-0.18.3.2运行支持库
参考:http://www.oschina.net/p/qt+linguist
http://www.oschina.net/question/54100_146029
http://www.oschina.net/question/54100_146030
http://devbean.blog.51cto.com/448512/244689
http://devbean.blog.51cto.com/448512/245063
Qt Linguist 是一个用来给 Qt 编写的应用程序增加多语言支持的工具。
QT-Linguist工具主要用在项目的多语言翻译处理过程中,所有先简单介绍一下整个多语言处理过程,最后介绍Linguist的用法。
1)确保每一个用户可见的字符串都使用了tr()函数。
2)在应用程序启动的时候,使用QTranslator载入一个翻译文件(.qm)。
tr() 的用法:
1
|
caseCheckBox = new QCheckBox(tr( "Match &case" )); |
在main()函数里载入翻译文件:
1
2
3
4
5
6
7
8
9
|
int main( int argc, char *argv[]) { QApplication app(argc, argv); //翻译程序 QTranslator translator; translator.load( "spreadsheet_cn.qm" ); app.installTranslator(&translator); …… } |
注意:翻译文件加载的位置必须在界面实例化之前完成。
1、 在该应用程序的.pro文件文件中添加TRANSLATIONS项,可分别对应于不同的语言,如:spreadsheet_cn.ts, 对应中文,名字可以自己定义,后缀名.ts不可变动。<.ts是可读的翻译文件,使用简单的XML格式;而.qm是经过.ts转换而成的二进制机器 语言>
2、翻译文件。分三步来完成:
1)运行lupdate, 从应用程序的源代码中提取所有用户可见的字符串。
2)使用Qt Linguist 翻译该应用程序。
3)运行lrelease,生成二进制的.qm 文件。
以上三步均需用到QT自带的命令行控制台,启动方法:开始--->所有程序--->Qt by Nokia v4.6.3 (OpenSource)--->Qt 4.6.3 Command Prompt
启动命令行后,对应输入如下命令:
1)lupdate –verbose spreadsheet.pro //生成相应的.ts 文件
2)linguist //启动Linguist语言翻译工具,可以翻译相应可见字符串
3)lrelease –verbose spreadsheet.pro //将翻译好的文件生成.qm文件
1)启动:命令行或者开始菜单均可
2)打开:工具界面中的File--->Open,可以打开所需的 .ts 文件
3)翻译:界面中部的翻译栏,两行:第一行:Source Text 第二行:… Translation, 在地二行进行相应的翻译即可,翻译完一条之后点击“确定下一个”按钮。
4)发布:点击File--->Release, 生成 .qm 文件。(与命令行的效果一样)
1、在代码中所有需要使用中文的地方都用一段英文暂时代替,并用tr()函数做标记。
2、使用Qt Linguist对所有被tr()函数标记的字符串进行翻译,并发布翻译包。
3、在程序中加载翻译包。
详细做法,可以见devbean大神的博客:
《Qt学习之路(33): 国际化(上)》: http://devbean.blog.51cto.com/448512/244689
《Qt学习之路(34): 国际化(下) 》: http://devbean.blog.51cto.com/448512/245063
标签:des style blog http io color os ar 使用
原文地址:http://www.cnblogs.com/learnopencad/p/4066313.html