C++软件开发多国语⾔解决⽅案汇总
暂时汇总出了以下⼏种⽅法
以Unicode为核⼼
采⽤GNUgettext
基于Qt的多语⾔开发⼯具:QtLinguist
以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(Stringchartname)这种函数,不能直接根据字符集重新编码得到字符串的byte数组。这时候,我们使⽤的string其实就
⼀般不是Unicode,⽽是符合某种编码表的。这使得我们往往困惑于string的编码问题。假设有utf8的字符串“⼀”(E4B880),⽽我们错误
的认为它是符合gb2312(编码A)的,并将其转换为utf8(编码B),这种转换结果是破坏性的,错误的输出将永远⽆法正确识别。
依然以“标准”为例,这是⼀个正确的转换:
图2.⽂件转换编码的C/C++处理⽅式
⽂件转换编码的C/C++处理⽅式
第⼆种情况,则是更常见到的。例如:浏览器浏览⽹页时的发⽣的乱码问题;在写XML⽂件时,指定了<?xmlversion="1.0"
encoding="utf-8"?>然⽽⽂件中却包含GB2312的字符串——这样经常会导致XML⽂件badformatted,⽽使得解析器出错。
这种情况下,其实数据都是正确的,只要浏览器选择正确的编码,将XML⽂件中的GB2312转换为UTF8或者修改encoding,就可以解决
问题。
需要注意的是,ASCII码的字符,即单字节字符,⼀般不受编码变动影响,在所有编码表中的值是⼀样的;需要⼩⼼处理的是多字节字符,
例如中⽂语⾔。
编码转换⽅法
⼀般的编码转换,直接做映射的不太可能,需要⽐较多的⼯作量,⼤多情况下还是选择Unicode作为转换的中介。
使⽤库函数
如前⽂所说,JAVA的String对象是以Unicode编码存在的,所以JAVA程序员主要关⼼的是读⼊时判断字节流的编码,从⽽确保可以正确
的转化为Unicode编码;相⽐之下,C/C++将外部⽂件读出的数据存为字符数组、或者是string类型;⽽wstring才是符合Unicode编码的
双字节数组。⼀般常⽤的⽅法是C标准库的wcstombs、mbstowcs函数,和windowsAPI的MultiByteToWideChar与
WideCharToMultiByte函数来完成向Unicode的转⼊和转出。
这⾥以MBs2WCs函数的实现说明GB2312向Unicode的转换的主要过程:
清单1.多字节字符串向宽字节字符串转换
wchar_t*MBs2WCs(constchar*pszSrc){
wchar_t*pwcs=NULL;
intsize=0;
#ifdefined(_linux_)
tlocale(LC_ALL,"zh_2312");
size=mbstowcs(NULL,pszSrc,0);
pwcs=newwchar_t[size+1];
size=mbstowcs(pwcs,pszSrc,size+1);
pwcs[size]=0;
#el
size=MultiByteToWideChar(20936,0,pszSrc,-1,0,0);
if(size<=0)
returnNULL;
pwcs=newwchar_t[size];
MultiByteToWideChar(20936,0,pszSrc,-1,pwcs,size);
#endif
returnpwcs;
}
相应的,WCs2MBs可以将宽字符串转化为字节流。
清单2.宽字节字符串向多字节字符串转换
char*WCs2MBs(constwchar_t*wcharStr){
char*str=NULL;
intsize=0;
#ifdefined(_linux_)
tlocale(LC_ALL,"zh_8");
size=wcstombs(NULL,wcharStr,0);
str=newchar[size+1];
wcstombs(str,wcharStr,size);
str[size]='0';
#el
size=WideCharToMultiByte(CP_UTF8,0,wcharStr,-1,NULL,NULL,NULL,NULL);
str=newchar[size];
WideCharToMultiByte(CP_UTF8,0,wcharStr,-1,str,size,NULL,NULL);
#endif
returnstr;
}
Linux的tlocale的具体使⽤可以参阅有C/C++⽂档,它关系到⽂字、货币单位、时间等很多格式问题。Windows相关的代码中20936和
宏定义CP_UTF8是GB2312编码对应的的CodePage[类似的CodePage参数可以从的EncodingClass有关信息中获得]。
这⾥需要特别指出的是tlocale的第⼆个参数,Linux和Windows是不同的:
1.笔者在EclipCDT+MinGW下使⽤[country].[chart](如zh_2312或zh_8)的格式并不能通过编码转换测试,
但可以使⽤CodePage,即可以写成tlocale(LC_ALL,".20936")这样的代码。这说明,这个参数与编译器⽆关,⽽与系统定义有
关,⽽不同操作系统对于已安装字符集的定义是不同的。
系统下可以参见/usr/lib/locale/路径,系统所⽀持的locale都在这⾥。转换成UTF8时,并不需要[country]部分⼀定是
zh_CN,en_8也可以正常转换。
另外,标准C和Win32API函数返回值是不同的,标准C返回的wchar_t数组或者是char数组都没有字符串结束符,需要⼿动赋值,所以
Linux部分的代码要有区别对待。
最后,还要注意应当在调⽤这两个函数后释放分配的空间。如果将MBs2WCs和WCs2MBs的返回值分别转化为wstring和string,就可以
在它们函数体内做delete,这⾥为了代码简明,故⽽省略,但请读者别忘记。
第三⽅库
⽬前的第三⽅⼯具已经⽐较完善,这⾥介绍两个,本⽂侧重点不在此,不对其做太多探讨。
Linux上存在第三⽅的iconv项⽬,使⽤也较为简单,其实质也是以Unicode作为转换的中介。可以参阅
ICU是⼀个很完善的国际化⼯具。其中的CodePageConversion功能也可以⽀持⽂本数据从任何字符集向Unicode的双向转换。可
以访问其
实验测试
在代码中调⽤“编码转换⽅法”⼀节⾥提到的函数,将gb2312编码的字符串转换为UTF8编码,分析其编码转换的⾏为:
在英⽂Linux环境下,执⾏下列命令:
exportLC_ALL=zh_2312
然后编译并执⾏以下程序(其中汉字都是在gb2312环境中写⼊源⽂件)
L1:wstringws=L"⼀";
L2:strings_gb2312="⼀";
L3:wchar_t*wcs=MBs2WChar(s_gb2312.c_str());
L4:char*cs=WChar2MBs(wcs);
查看输出:
L1-1widechar:0x04bb
L2-2bytes: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编
码。
处理编码问题的经验总结
⾸先,这⾥先简单说明⼀下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它们都⽀持。
采⽤GNUgettext
gettext是(i18n)函数库。它常被⽤于编写多语⾔程序。
开发
程序源代码需要进⾏修改以响应GNUgettext请求。多数均已通过字符封装的⽅式实现了对其的⽀持。为了减少输⼊量和代码量,此功能通
常以标记_的形式使⽤,所以例如以下代码:
printf(gettext("Mynameis%s.n"),my_name);
应当写作:
printf(_("Mynameis%s.n"),my_name);
gettext使⽤其中的字符串寻找对应的其他语⾔翻译,若没有可⽤翻译则返回原始内容。
除外,GNUgettext还⽀持,,/,脚本,脚本,,GNU,,librep,GNU,,GNU,(通过wxLocale类),YCP(语⾔),,,,,以及。
⽤法均与在上类似。
xgettext程序从源代码⽣成.pot⽂件,作为源代码中需翻译内容的模板。⼀个典型的.pot⽂件条⽬应当是这样的:
#:src/name.c:36
msgid"Mynameis%s.n"
msgstr""
被直接放置在字符串前,⽤于帮助翻译者理解待翻译内容:
///TRANSLATORS:Plealeave%sasitis,becauitisneededbytheprogram.
///Thankyouforcontributingtothisproject.
printf(_("Mynameis%s.n"),my_name);
本例中的注释是以///开头的,其作⽤是⽤于xgettext程序⽣成.pot模板⽂件。
xgettext--add-comments=///
在.pot⽂件中的注释应为以下形式:
#.TRANSLATORS:Plealeave%sasitis,becauitisneededbytheprogram.
#.Thankyouforcontributingtothisproject.
#:src/name.c:36
msgid"Mynameis%s.n"
msgstr""
翻译
翻译者需要⼯作的对象是.po⽂件,它是由msginit程序从.pot模板⽂件⽣成的。例如使⽤msginit初始化法语翻译⽂件时,我们运⾏以下命令:
msginit--locale=fr--input=
这将会使⽤指定的在当前⽬录创建⼀个,其中的⼀个条⽬应该是以下形式的:
#:src/name.c:36
msgid"Mynameis%s.n"
msgstr""
翻译者需要⼿⼯或使⽤类似、或等⼯具的相应模式编辑该⽂件。翻译完成后,⽂件应为如下的样⼦:
#:src/name.c:36
msgid"Mynameis%s.n"
msgstr"Jem'appelle%s.n"
最后.po⽂件需要使⽤msgfmt编译为.mo⽂件以⽤作发布。
运⾏
使⽤类型操作系统的⽤户只需设置中的LC_MESSAGES,程序将⾃动从相应的.mo⽂件中读取语⾔信息。
补充:最新版gettext-0.18.3.2可在MSVC中实现多语⾔
“通常,程序及其⽂档信息都是⽤英语语⾔写的,程序运⾏时同⽤户交互的信息也是英语。这是⼀个事实,不仅仅GNU的软件是这样,其他
⼤部分私有软件或⾃由软件也是这样。⼀⽅⾯,对于来⾃所有国家的开发者、维护者和⽤户来说,相互沟通中使⽤⼀种通⽤的语⾔⾮常的⽅
便。另⼀⽅⾯,相对于母语来说⼤多数⼈并不适应使⽤英语,⽽且他们的⽇常⼯作都是尽可能的使⽤他们⾃⼰的母语。多数⼈都会喜欢他们
的计算机屏幕显⽰的英语更少,显⽰的母语更多。"
"GNU的'gettext'是GNU翻译项⽬的⼀个重要步骤,我们依赖于它作很多其他的步骤。这个软件包给程序员、翻译者,或者⽤户提供了⼀
套集成⼯具和⽂档。详细地说,GNUgettext提供了⼀套⼯具,能让其他GNU软件创建多语⾔信息。..."
gettext的⼯作流程是这样的:⽐如我们写⼀个VisualC++(MSVC)程序,通常printf等输出信息都是English的。如果我们在程序中加⼊gettext
⽀持,在需要交互的字符串上⽤gettext函数,程序运⾏是就可以先调⽤gettext函数获取当前语⾔的字符串,替换当前的字符串了。注意是运
⾏时替换。
GNUgettext-0.18.3.2是最新版本,上可以直接下载,只是没有VisualC++(MSVC)可⽤的运⾏⽀持库,只能⾃⼰动⼿编译了,编译好的运⾏
⽀持库,。
在VisualC++(MSVC)中使⽤GNUgettext实现多语⾔时,可以编写翻译函数来实现界⾯与菜单字符串的⾃动替换,程序中的字符串只能⼀个
个⼿⼯替换了,这样使⽤起来,就跟在Delphi与C++Builder中使⽤GNUgettext差不多⽅便快捷了。
简单使⽤的例⼦
⼀个简单的例⼦,
#include
#include
/*使⽤gettext通常使⽤类似下⾯的⼀个带函数的宏定义
*你完全可以不⽤,直接使⽤gettext(字符串)
*/
#define_(S)gettext(S)
/*PACKAGE是获取语⾔字符串的⽂件名字(运⾏时输⼊的命令)*/
#definePACKAGE"default"
intmain(intargc,char**argv)
{
/*下⾯三个参数都是使⽤gettext时候需要使⽤的
*tlocale
*bindtextdomain
*textdomain
*/
tlocale(LC_ALL,"");
bindtextdomain(PACKAGE,"locale");
textdomain(PACKAGE);
printf(_("Hello,GetText!n"));
return0;
}
其中语⾔字符串⽂件的结构:.locale语⾔名称LC_,如简体中⽂:.localeZH_CNLC_
mo⽂件是编译后的语⾔字符串⽂件,GNU⽹站上有相应的⼯具软件可以编辑与⽣成;
基于Qt的多语⾔开发⼯具:QtLinguist
QtLinguist是⼀个⽤来给Qt编写的应⽤程序增加多语⾔⽀持的⼯具。
QT-Linguist⼯具主要⽤在项⽬的多语⾔翻译处理过程中,所有先简单介绍⼀下整个多语⾔处理过程,最后介绍Linguist的⽤法。
(⼀)QT项⽬实现多语⾔,必须做两件事:
1)确保每⼀个⽤户可见的字符串都使⽤了tr()函数。
2)在应⽤程序启动的时候,使⽤QTranslator载⼊⼀个翻译⽂件(.qm)。
tr()的⽤法:
1caCheckBox=newQCheckBox(tr("Match&ca"));
在main()函数⾥载⼊翻译⽂件:
1
2
3
4
5
6
7
intmain(intargc,char*argv[])
{
QApplicationapp(argc,argv);
//翻译程序
QTranslatortranslator;
("spreadsheet_");
lTranslator(&translator);
8
9
……
}
注意:翻译⽂件加载的位置必须在界⾯实例化之前完成。
(⼆)⽣成.qm翻译⽂件
1、在该应⽤程序的.pro⽂件⽂件中添加TRANSLATIONS项,可分别对应于不同的语⾔,如:spreadsheet_,对应中⽂,名字可以⾃⼰
定义,后缀名.ts不可变动。<.ts是可读的翻译⽂件,使⽤简单的XML格式;⽽.qm是经过.ts转换⽽成的⼆进制机器语⾔>
2、翻译⽂件。分三步来完成:
1)运⾏lupdate,从应⽤程序的源代码中提取所有⽤户可见的字符串。
2)使⽤QtLinguist翻译该应⽤程序。
3)运⾏lrelea,⽣成⼆进制的.qm⽂件。
以上三步均需⽤到QT⾃带的命令⾏控制台,启动⽅法:开始--->所有程序--->QtbyNokiav4.6.3(OpenSource)--->Qt4.6.3Command
Prompt
启动命令⾏后,对应输⼊如下命令:
1)lupdate–//⽣成相应的.ts⽂件
2)linguist//启动Linguist语⾔翻译⼯具,可以翻译相应可见字符串
3)lrelea–//将翻译好的⽂件⽣成.qm⽂件
(三)Linguist语⾔⼯具的使⽤
1)启动:命令⾏或者开始菜单均可
2)打开:⼯具界⾯中的File--->Open,可以打开所需的.ts⽂件
3)翻译:界⾯中部的翻译栏,两⾏:第⼀⾏:SourceText第⼆⾏:…Translation,在地⼆⾏进⾏相应的翻译即可,翻译完⼀条之后点击“确
定下⼀个”按钮。
4)发布:点击File--->Relea,⽣成.qm⽂件。(与命令⾏的效果⼀样)
(四)Linguist语⾔⼯具使⽤⽅法建议
1、在代码中所有需要使⽤中⽂的地⽅都⽤⼀段英⽂暂时代替,并⽤tr()函数做标记。
2、使⽤QtLinguist对所有被tr()函数标记的字符串进⾏翻译,并发布翻译包。
3、在程序中加载翻译包。
详细做法,可以见devbean⼤神的博客:
本文发布于:2022-12-30 00:04:05,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/56250.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |