多国语言在线翻译

更新时间:2022-12-30 00:04:05 阅读: 评论:0


2022年12月30日发(作者:bbc南太平洋)

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 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图