C++软件开发多国语⾔解决⽅案汇总
暂时汇总出了以下⼏种⽅法
以Unicode为核⼼
采⽤ GNU gettext
基于Qt的多语⾔开发⼯具:Qt Linguist
以Unicode为核⼼
多国语⾔的存在,使程序员在编码处理上花费了⼤量时间和精⼒;然⽽各种各样的乱码问题,如 XML 格式错误、⽂本显⽰异常、解析器异常等依然层出不穷。特别的,相对于 JAVA 语⾔,C/C++ 在处理编码问题上有更⼤的困难。本⽂避免纠缠不同编码格式的具体异同,以Unicode 为核⼼,以简体中⽂为例,从⼯程应⽤⾓度分析编码问题存在的原因,不仅提出 C/C++ 标准库编程的解决⽅案,更结合项⽬经验,总结出处理多国语⾔编码问题的⼀般思路。
问题的提出
多国语⾔的存在、不同语⾔操作系统的存在,使得针对多语⾔的设计颇费周章,在编码上所付出的⼯作量也是可观的。所谓编码的问题,归结起来,就是⼆进制的编码以何种编码格式进⾏解析的问题。特别是在硬盘⽂件和内存数据的相互转化、即读写过程中,如果采⽤了错误的编码格式,就会造成乱码。JAVA 语⾔在字符串、编码等处理⽅⾯给了程序员更为直接、⽅便的接⼝,习惯使⽤ JAVA 做编码的程序员,在使⽤ C/C++ 进⾏⽂本编码相关的操作时,常会感到困惑。本⽂的⽬的在于以常⽤的 Unicode(UCS-2)、GB2312、UTF8 三种编码为例,分析不同编码在实⽤中的关系,特别是 C/C++ 中,怎样处理各种编码的问题。
编码处理常见的问题
1. 将内存中编码 A 的字符串以编码 B 格式处理成字节流写⼊⽂件
2. 将原本以 A 编码组成的⽂件以字节流形式读⼊内存、并以编码 B 解析为字符串。
第⼀种情况,可能造成数据的变化、失真。
如果使⽤ JAVA 语⾔,发⽣这种错误的情况稍少⼀些,因为在 JAVA 中没有 wstring 这种概念,在内存中的 String,使⽤的编码都是Unicode,其中的转换对于程序员来讲是透明的。只要使⽤输⼊ / 输出⽅法时注意字节流的字符集选择即可。
例如,编码为中⽂ GB2312 的“标准”字符串被读⼊内存后转存为 UTF8 的过程:
图 1. ⽂件转换编码的 JAVA 处理⽅式
⽂件转换编码的 JAVA 处理⽅式
但 C/C++ 编程,由于通常使⽤ char、string 类型的时候⽐较多,特别是进⾏⽂件读写,基本都是操作 char* 类型的数据。并且也没有像JAVA 中 getByte(String charsetname) 这种函数,不能直接根据字符集重新编码得到字符串的 byte 数组。这时候,我们使⽤的 string 其实就⼀般不是 Unicode,⽽是符合某种编码表的。这使得我们往往困惑于 string 的编码问题。假设有 utf8 的字符串“⼀”(E4 B8 80),⽽我们错误的认为它是符合 gb2312(编码 A)的,并将其转换为 utf8(编码 B),这种转换结果是破坏性的,错误的输出将永远⽆法正确识别。
依然以“标准”为例,这是⼀个正确的转换:
图 2. ⽂件转换编码的 C/C++ 处理⽅式
⽂件转换编码的 C/C++ 处理⽅式
第⼆种情况,则是更常见到的。例如:浏览器浏览⽹页时的发⽣的乱码问题;在写 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 与Wide
CharToMultiByte 函数来完成向 Unicode 的转⼊和转出。
这⾥以 MBs2WCs 函数的实现说明 GB2312 向 Unicode 的转换的主要过程:
清单 1. 多字节字符串向宽字节字符串转换
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 可以将宽字符串转化为字节流。
清单 2. 宽字节字符串向多字节字符串转换
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 参数可以从的 Encoding Class 有关信息中获得 ]。
这⾥需要特别指出的是 setlocale 的第⼆个参数,Linux 和 Windows 是不同的:
1. 笔者在 Eclipse CDT + MinGW 下使⽤ [country].[charset](如 zh_CN.gb2312 或 zh_CN.UTF8)的格式并不能通过编码转换测试,
但可以使⽤ Code Page,即可以写成 setlocale(LC_ALL, ".20936") 这样的代码。这说明,这个参数与编译器⽆关,⽽与系统定义有关,⽽不同操作系统对于已安装字符集的定义是不同的。
2. Linux 系统下可以参见 /usr/lib/locale/ 路径,系统所⽀持的 locale 都在这⾥。转换成 UTF8 时,并不需要 [country] 部分⼀定是
zh_CN,en_US.UTF8 也可以正常转换。
另外,标准 C 和 Win32 API 函数返回值是不同的,标准 C 返回的 wchar_t 数组或者是 char 数组都没有字符串结束符,需要⼿动赋值,所以Linux 部分的代码要有区别对待。
最后,还要注意应当在调⽤这两个函数后释放分配的空间。如果将 MBs2WCs 和 WCs2MBs 的返回值分别转化为 wstring 和 string,就可以在它们函数体内做 delete,这⾥为了代码简明,故⽽省略,但请读者别忘记。
第三⽅库
⽬前的第三⽅⼯具已经⽐较完善,这⾥介绍两个,本⽂侧重点不在此,不对其做太多探讨。
Linux 上存在第三⽅的 iconv 项⽬,使⽤也较为简单,其实质也是以 Unicode 作为转换的中介。可以参阅
ICU 是⼀个很完善的国际化⼯具。其中的 Code Page Conversion 功能也可以⽀持⽂本数据从任何字符集向 Unicode 的双向转换。可
以访问其
实验测试
在代码中调⽤“编码转换⽅法”⼀节⾥提到的函数,将 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 - 1 wide char: 0x04bb
L2 - 2 bytes:0xd2,0xbb,即 gb2312 编码 0xD2BB
L3 - 返回的 wchar_t 数组内容为 0x4E00,也就是 Unicode 编码
L4 - 将 Unicode 再度转换为 UTF8 编码,输出的字符长度为 3,即 0xE4,oxB8,0x80
在 L1 ⾏,执⾏结果显⽰编码为⼀个 0x04bb,其实这是⼀个转换错误,如果使⽤其他汉字,如“哈”,编译都将⽆法通过。也就是说 Linux 环境下,直接声明中⽂宽字符串是不正确的,编译器不能够正确转换。
⽽在中⽂ windows 下使⽤相同测试代码,则会在 L1 处出现区别,ws 中的 wchar_t 元素⼗六进制值是 0x4e00,这是汉字“⼀”的 Unicode 编码。
conversion翻译方法的定义处理编码问题的经验总结
⾸先,这⾥先简单说明⼀下 Unicode 和 UTF8 的关系:Unicode 的实现⽅式和它的编码⽅式并不相同,UTF8 就是其实现之⼀。⽐⽅使⽤UltraEdit 打开 UTF8 编码的中⽂⽂件,使⽤ 16 进制查看,可以发现看到的中⽂对应部分应当是 Unicode 编码,每个中⽂字长度 2 字节——UltraEdit 在这⾥已经做了转化;如果直接查看其⼆进制⽂件,可以发现是 3 字节。但两者的差别仅在于 Unicode 向 UTF8 做了数学上的转化。(更多关于 Unicode 和 UTF8 的概念,可以参见)
其次,关于第三⽅库的选择,应当综合考虑项⽬的需求。⼀般的⽂本字符转换,系统的库函数已经可以满⾜需求,实现也很简单;如果需要针对不同地区的语⾔、⽂字、习惯进⾏编程,需要更为丰富的功能,当然选择成熟的第三⽅⼯具可以事半功倍。
最后,从逻辑上保持字符串的编码正确,需要注意⼏条⼀般规律:
编码选择:多国语⾔环境的编程,以使⽤ UTF 编码为原则,减少字符集转换。
string 并不包含编码信息,但是编码确定了 string 的⼆进制内容。
读写⼀致:读⼊时使⽤的字符集要与写出时使⽤的⼀致。如果不需要改变字符串内容,仅仅是将字符串读⼊、再写出,建议不要调整任何字符集——即使程序使⽤的系统默认字符集 A 与⽂件的实际编码 B 不符合,写出的字符串依然会是正确的 B 编码。
读⼊已知:对于必须处理、解析或显⽰的字符串,从⽂件读⼊时必须知道它的编码,避免处理字符串的代码简单使⽤系统默认字符集;即便对于程序从系统中收集到的内存字符串,也应知道其符合的编码格式——⼀般为系统默认字符集。
避免直接使⽤ Unicode:这⾥是说将⾮ ASCII 编码的 16 进制或者 10 进制数值⽤ &# 与 ; 包含起来的使⽤⽅式,例如将中⽂“⼀”写成“e00;”。这种⽅法的实质是 Unicode 编码直接写⼊⽂件。这不仅会降低代码的通⽤性、输出⽂件的可读性,处理起来也很困难。
⽐如法⽂字符在其他字符集中是⼤于 80H 的单字节字符,程序同时要⽀持中⽂的时候,很有可能会将多字节的中⽂字符错误割裂。
避免陷⼊直接的字符集编程:国际化、本地化的⼯具已经⽐较成熟,⾮纯粹做编码转换的程序员没有必要⾃⼰去处理不同编码表的映射转换问题。
Unicode/UTF8 并不能解决⼀切乱码问题:Unicode 可以说是将世界语⾔统⼀起来的⼀套编码。但是这并不意味着在⼀个系统中可以正常显⽰的按照 UTF8 编码的⽂件,在另⼀个系统中也可以正常显⽰。例如,在中⽂的 UTF8 编码或者 Unicode 编码在没有东亚语⾔包⽀持的法⽂系统中,依然是不可识别的乱码——尽管 UTF8、Unicode 它们都⽀持。
采⽤ GNU gettext
gettext 是(i18n)函数库。它常被⽤于编写多语⾔程序。
开发
程序源代码需要进⾏修改以响应 GNU gettext 请求。多数均已通过字符封装的⽅式实现了对其的⽀持。为了减少输⼊量和代码量,此功能通常以标记_的形式使⽤,所以例如以下代码:
printf(gettext("My name is %s.\n"), my_name);
应当写作:
printf(_("My name is %s.\n"), my_name);
gettext使⽤其中的字符串寻对应的其他语⾔翻译,若没有可⽤翻译则返回原始内容。
除外, GNU gettext 还⽀持,,/,脚本,脚本,,GNU ,,librep,GNU ,,GNU ,(通过 wxLocale类),YCP (语⾔),,,,,以及。⽤法均与在上类似。
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 ""
翻译者需要⼿⼯或使⽤类似、或等⼯具的相应模式编辑该⽂件。翻译完成后,⽂件应为如下的样⼦:
#: src/name.c:36
msgid "My name is %s.\n"
msgstr "Je m'appelle %s.\n"
最后 .po ⽂件需要使⽤msgfmt编译为.mo⽂件以⽤作发布。
运⾏
使⽤类型操作系统的⽤户只需设置中的LC_MESSAGES,程序将⾃动从相应的.mo⽂件中读取语⾔信息。
补充:最新版 gettext-0.18.3.2可在MSVC中实现多语⾔
“通常,程序及其⽂档信息都是⽤英语语⾔写的,程序运⾏时同⽤户交互的信息也是英语。这是⼀个事实,不仅仅GNU的软件是这样,其他⼤部分私有软件或⾃由软件也是这样。⼀⽅⾯,对于来⾃所有国家的开发者、维护者和⽤户来说,相互沟通中使⽤⼀种通⽤的语⾔⾮常的⽅便。另⼀⽅⾯,相对于母
语来说⼤多数⼈并不适应使⽤英语,⽽且他们的⽇常⼯作都是尽可能的使⽤他们⾃⼰的母语。多数⼈都会喜欢他们的计算机屏幕显⽰的英语更少,显⽰的母语更多。"
" GNU 的 'gettext' 是 GNU翻译项⽬的⼀个重要步骤,我们依赖于它作很多其他的步骤。这个软件包给程序员、翻译者,或者⽤户提供了⼀套集成⼯具和⽂档。详细地说,GNU gettext 提供了⼀套⼯具,能让其他 GNU 软件创建多语⾔信息。..."
gettext的⼯作流程是这样的:⽐如我们写⼀个Visual C++(MSVC)程序,通常printf等输出信息都是English的。如果我们在程序中加⼊gettext ⽀持,在需要交互的字符串上⽤gettext函数,程序运⾏是就可以先调⽤gettext函数获取当前语⾔的字符串,替换当前的字符串了。注意是运⾏时替换。
GNU gettext-0.18.3.2 是最新版本,上可以直接下载,只是没有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_,如简体中⽂:.\locale\ZH_CN\LC_
mo⽂件是编译后的语⾔字符串⽂件,GNU⽹站上有相应的⼯具软件可以编辑与⽣成;
基于Qt的多语⾔开发⼯具:Qt Linguist
Qt Linguist 是⼀个⽤来给 Qt 编写的应⽤程序增加多语⾔⽀持的⼯具。
QT-Linguist⼯具主要⽤在项⽬的多语⾔翻译处理过程中,所有先简单介绍⼀下整个多语⾔处理过程,最后介绍Linguist的⽤法。
(⼀)QT项⽬实现多语⾔,必须做两件事:
1)确保每⼀个⽤户可见的字符串都使⽤了tr()函数。
2)在应⽤程序启动的时候,使⽤QTranslator载⼊⼀个翻译⽂件(.qm)。
tr() 的⽤法:
1caseCheckBox = new QCheckBox(tr("Match &case"));
在main()函数⾥载⼊翻译⽂件:
1 2 3 4 5 6 7int main(int argc, char*argv[])
{
QApplication app(argc, argv);
//翻译程序
QTranslator translator;
translator.load("spreadsheet_cn.qm"); app.installTranslator(&translator);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论