Chinaunix首页 | 论坛 | 博客
  • 博客访问: 819809
  • 博文数量: 117
  • 博客积分: 2583
  • 博客等级: 少校
  • 技术积分: 1953
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-06 22:58
个人简介

Coder

文章分类
文章存档

2013年(1)

2012年(10)

2011年(12)

2010年(77)

2009年(13)

2008年(4)

分类: C/C++

2012-05-21 21:42:27


李荣 施
 (), 南京大学硕士

简介: 一个国际化软件的本地化成功与否,通常决定于是否能比较容易的修改影响用户的数据。如何处理这些和用户语言或者习惯相关的数据是软件国际化的关键,开发人员需要一种能够将程序代码和国际化数据分离,程序员只关注代码本身,翻译人员不需要了解程序的机制。Resource Bundle 就是这样的一种技术,在本篇文章,将继续深入介绍 ICU4C,介绍 Resource Bundle 技术的相关应用。

在本系列的第一篇文章中,我们已经介绍了 Resource Bundle 的概念以及在 ICU4C 中的实现,在本章中将更具体地描述 ICU4C 的 Resource Bundle 机制及应用。

Resource Bundle 是国际化中重要的数据查询机制,它的实现原理是将 Locale 相关的数据和程序代码分离,将数据和数据的表现采用 "键-翻译"形式映射。 最简单的 Resource Bundle 是字符串的"键-翻译"对,表1 是字符串 Resource Bundle 的例子,通过向代码告知关键字,以及对应的 Locale 类型就能得知关键字对应的具体翻译。



表1 ,Resource Bundle 的例子 

一般来说,对应于某一种 Locale 需要一个 Resource Bundle 文件,这个文件中包含了很多字符串类型的 Resource Bundle 项,每一个项都是关键字和翻译后数据的对应。关于 Resource Bundle 的类型在下小节中介绍。

总结一下 Resource Bundle 的作用,它有如下好处:

(1) 程序代码中不会出现 和 Locale 相关的数据,程序员不需要了解或者使用目标 Locale相关的语言数据。

(2) 需要翻译的数据可以直接拿给了解目标平台语言的专业人员翻译,而不需要提供代码。

(3) 数据统一集中,使得数据的管理更加方便。

(4) 使得添加对新语言支持比较容易。

ICU4C 提供了强大的 Resource Bundle 支持,ICU4C 对支持类型进行了扩充,除了支持最基本的字符串,还支持了其它丰富的资源类型,ICU4C 的 Resource Bundle 还支持自动回调功能,保证在没有指定 Locale 数据资源的情况下找到最恰当的数据。

ICU4C 的 Resource Bundle 支持了丰富的类型,ICU4C 中的 Resource Bundle 可以是字符串,也可以是二进制文件(图片,文件等),数值,数组(Array),表(Table),甚至指向其他资源的链接。其中,字符串,二进制文件(图片,文件等),数值被称为简单数据类型;表(Table)和数组(Array)都是数据的集合,表(Table)储存具有名字的数据,一个 Resource Bundle 文件实际上就是一个表(Table), 数组(Array)储存没有名字的数据,Resource Bundle 类型的具体描述见表2。

其中,表(Table)可以通过键(key),索引(index)和迭代 (iteration) 方式访问;数组(Array)可以通过索引 (index)或者迭代 (iteration) 方式来访问;字符串,二进制文件及数值这几种简单资源类型则可以通过键(key) 来访问。



表2 

经常会出现这样的一种情况,查找的 Resource Bundle 资源数据不存在,遇到这样的情况时候 ICU4C 怎么处理?ICU4C 的资源管理框架提供了 Resource Bundle 回调机制,这个机制保证了当查询的数据资源不存在的情况下,ICU4C 能自动找到相对最恰当的数据。回调的基本原则是从 Locale 的下级目录往上级目录查找,存在两种找不到资源数据的情况:

(1)某个 Locale 的资源文件被请求的时候,如果这个 Locale 相对的目标资源文件不存在,ICU4C 的资源管理框架会自动回调,按照一定顺序查找更加适合的资源文件。例如,当请求 Locale "zh_CN" 相对应的资源文件时候,没有找到这样的资源文件,ICU4C 会自动回调查找 Locale"zh" 对应的资源文件,如果这个资源文件存在,将会返回这个资源文件;如果 "zh" 对应的资源文件还不存在,ICU4C 将进一步回调。

(2)某个资源文件内部的数据找不到的时候,ICU4C 会在相对通用的资源文件中查找。 例如,某一次数据查询,某一个资源数据没有能在 Locale "en_IE_EURO" 的资源文件中找到,继而 ICU4C 会去 "en_IE" 相对的资源文件中去查找相应的数据。

现在我们介绍如何创建 ICU4C 的 Resource Bundle 文件,创建资源文件的步骤如下:

1 创建 root 的资源源文件 root.txt,这个文件需要包含所有我们需要在 Resource Bundle 中转换的数据。源文件并不是真正被使用的资源文件,目前支持的源文件格式为 txt, 源文件需要被编译成最终的资源文件,在 ICU4C 中,最终的资源文件后缀为 res,最简单的 root文件如清单1。



root { Good{ "Good" } Fatal Error{"Fatal Error"} }

2 创建对应于我们需要的 Locale 的资源源文件,在与 root.txt 文件中对应的项中填入本地化后的数据,在往文件中填写翻译数据的时候需要注意,翻译的数据可以是使用 UTF-8 或者 UTF-16 编码的 Unicode 字符串,也可以是不变字符(不变字符是指英文字母等这些在不同编码集中一直保持不变编码的字符或者符号),特别注意的是 ICU4C 的资源文件支持\uXXXX 的形式,XXXX 是指 Unicode 编码值,使用 \uXXXX 的方式可以另源文件编辑者客可以清楚地看到填写的数据对应什么字符,而不会碰到编辑源文件时候看到一堆乱码,还需要额外的编码转换才能看到字符的情况。例如,我们需要创建对应于zh_CN 的资源源文件 zh.txt, 文件内容见清单 2。



zh_CN{ Good{ "好" } Fatal Error{"致命错误"} }

3 决定Resource Bundle文件的包名,将来将通过这个包名来访问创建的Resource Bundle文件。

4 编译源文件,编译的过程实际上是将资源源文件优化成 ICU4C 实际使用的 res 格式文件。 ICU4C 提供了 genrb 程序用于 Resource Bundle 源文件的编译,这个工具可以在 ICU 目录的 bin 子目录中找到。Genrb 命令的格式为:genrb [options] list-of-input-files,常用的参数包括 -help 帮助,-p 指定包名, -d 目标目录,-s 源目录。

如果编译正确完成,目标文件夹中会生成 packagename_localename.res 的资源文件。例如,如果编译 root.txt,en.txt, zh.txt,并且使用命令 genrb -p mypackage root.txt en.txt zh.txt -s . -d .

编译完成以后当前目录下将生成 3 个文件 mypackage_root.tes, mypackage_en.res, mypackage_zh.res 于资源源文件对应。此后就可以使用ures_open("mypackage","zh_CN",ErrorCode) 来访问 Resource Bundle。

头文件 ures.h 包含了 Resource Bundle 相关的所有 API,其中最重要的类型是结构体UResourceBundle,UResourceBundle 代表了对应于某一指定 Locale 的资源信息集合(一个Resource Bundle)。Resource Bundle 相关的重要 API 见表 3。



表 3  Resource Bundle  重要的 API 

通常使用 UResourceBundle* ures_open(const char* package, const char* locale, UErrorCode* status) 打开一个资源。

第一个参数用来传递包名或者完整的"路径名/包名",例如要打开当前目录下的 mypackage包的资源文件,第一个参数需要是"./mypackage"。但如果想要直接使用包名作为第一个参数,则需要设置 ICU4C 数据目录。第二个参数传递指定用户需要打开哪种 Locale 下的Resource Bundle,如果是 NULL 或者空字符串表示查找的是 root Locale;第三个参数是错误码,需要在只用之前预先设置为 U_ZERO_ERROR。

除了返回其它普通的错误,错误码还可能返回 U_USING_FALLBACK_WARNING 和U_USING_DEFAULT_WARNING 这两个信息码。其中,U_USING_FALLBACK_WARNING是指没有找到请求的资源,而返回了一个最为接近的资源,当这个信息码在打开 Resource Bundle 的时候出现就意味着并不能保证得到的结果对于指定 Locale 一定正确;而U_USING_DEFAULT_WARNING 表示没有找到较为接近的 Resource Bundle,并且默认Locale 的 Resource Bundle 被返回。打开 Resource Bundle 的例子见清单 3。



UErrorCode status = U_ZERO_ERROR; UResourceBundle* icuRoot = ures_open(NULL, "root", &status); if(U_SUCCESS(status)) { //打开成功 }

通过前面的介绍,我们已经可以创建我们自己的资源文件,也可以打开访问这些资源文件了,在本小节中,我们要介绍在前面提到过的 ICU4C 数据与数据目录,我们可以使用加入显示目录的方式让 ures_open 函数找到资源;通过设置 ICU4C 的数据目录,无需显示的指定路径,基于 ICU4C 的应用程序也能够正确找到所需的资源文件。

首先了解一下什么是 ICU4C 的数据,在 ICU 中,有很大数量的数据表支持了国际化服务类。这些数据包括 转换映射表,字符串处理规则,断字断句的规则,以及其他的 Locale 使用的数据等。同时,用户也可以提供定制 ICU 的数据等。ICU4C 的数据可以分成两种:默认 ICU 数据和应用程序数据。

默认 ICU 数据(Default ICU Data) ,也可以称为 ICU 内部数据,ICU 内部用于 ICU 中转换器,Locale 等国际化转换需要的数据被称为默认 ICU 数据。默认 ICU 数据通常已经内置入 ICU 的共享库中,开发者只需要使用这些库就可以使用默认的 ICU 数据。

应用程序数据(Application Data), 开发者可以在基于ICU4C开发的应用程序中加入定制的国际化数据,比如本地化的字符串,定制转换表等等,每一个数据项文件都会有一个包名作为前缀。Resource Bundle的资源文件就是属于应用程序数据的ICU4C数据。

什么是ICU数据目录?ICU数据目录是存放ICU数据的默认目录,所有对没有显示指定搜索路径的数据查询的时候都会到ICU数据目录中查询。可见,当没有指定Resource Bundle的资源文件路径的时候,在ICU4C数据目录中指定也能达到同样的效果。

ICU4C 提供了 API 函数 u_setDataDirectory() 设置数据目录。对于 Resource Bundle 的应用,我们使用这个函数设置资源文件所在地址既可以让基于 ICU4C 的应用程序正确找到资源文件。

介绍了这么多理论知识,现在我们以一个具体的实例来介绍如何使用 ICU4C 的 Resource Bundle 开发。我们这个实例的目标是通过一个表(Table)类型的 Resource Bundle,程序打印出指定的键在目标 Locale 下的翻译数据。

我们的目标是给定城市和语言的代码,程序能够给出对应于目标 Locale 的翻译值,其中城市代码 CITY1001 对应纽约,CITY1002 对应上海,语言代码 Lan_en_US 对应英语, Lan_zh_CN 对应简体中文。

从前面的介绍已经知道,ICU4C 的 Resource Bundle 支持非常多的类型,其中,最常用的是表(Table)和字符串(String), 这里我们将使用表和字符串来组织 Resource Bundle。设计的内容见清单4,最外层是表类型的 Resource Bundle,包括两个项,分别是 city 和 language;city 和 language 又是两个独立的表,CITY1001 和 CITY1002 是 city 的表项;Lan_en_US 和 Lan_zh_CN 则是 language 的表项。

对应于 root, en_US, zh_CN 这三个 Locale 我们创建 3 个文件,分别是 root.txt, en.txt, zh.txt, zh.txt 的文件内容同清单 4,其它文件具体内容可以参见附带的源代码,需要注意的是,文件名不代表 Locale,文件内的起始字段才代表 Resource Bundle 的 Locale。



zh_CN { city{ CITY1001{ "纽约" } CITY1002{ "上海" } } language{ Lan_en_US {"英语"} Lan_zh_CN {"简体中文"} } }

现在需要编译 Resource Bundle 的资源源文件,在 MS-DOS 界面下进入源文件所在目录,由于我们希望生成的资源文件仍然存放在当前目录下,在命令行下输入以下命令:


genrb root.txt zh.txt en.txt -s. -d. -p mypackage

这条命令指定了生成文件的存放路径位于当前目录,资源的包名是 mypackage,在之后的程序中,我们都将使用 mypackage 作为包名来访问这些资源。如果在当前目录下发现mypackage_en_US.res,mypackage_root.res,mypackage_zh_CN.res 三个文件被创建,说明命令执行成功,这三个就是我们需要的资源文件。

我们将 ures_open 函数封装成函数 getRBByLocale,由这个函数来完成打开 Resource Bundle 的功能,输入参数分别是"路径名+包名" ,以及需要查询的 LocaleID,参数的取值都是不变字符,因此类型 const char* 即可。局部变量 status 是错误码,作为参数传递入 ures_open() 函数返回错误信息,函数 check 检测错误码,如果出现错误则打印出出错信息,然后程序退出。GetRBByLocale 函数如果调用成功,返回打开的 UResourceBundle。



UResourceBundle* getRBByLocale(const char* packagename, const char* localeid){ UResourceBundle *bundle = NULL; UErrorCode status = U_ZERO_ERROR;//清错误码 //根据包名和LocaleID 打开对应的Resource Bundle bundle = ures_open(packagename,localeid, &status); //检测在打开过程中是否出现错误,如果出现错误,退出程序 check(status,"open resource bundle failed"); return bundle; }

由于我们设计的 Resource Bundle 的类型是 2 级的表(Table),对 Resource Bundle 的操作实际上是对表的操作。对表类型的 Resource Bundle 操作通常使用 ures_getByKey 和ures_getStringByKey 函数,这两个函数也只能被用于类型是表的 Resource Bundle, 通过给定的键值获取对应的表项。ures_getByKey 函数被用于获取子表,ures_getStringByKey 被用于从使用 ures_getByKey 获取的子表内获取键对应的字符串型数据。

使用 getStringFromTable 将对这个 Resource Bundle 的操作封装,函数的第一个参数是目标Resource Bundle,第二个参数是需要查询的字符串键,第三个参数是查询字符串所在的表名。 通过这样的封装可以实现直接从最外层的 Resource Bundle 获取请求的字符串。函数代码见清单6。



const UChar* getStringFromTable(UResourceBundle* resource, const char* stringid, const char* tablename) { int32_t len=0; const UChar* returnstring = NULL; UErrorCode status = U_ZERO_ERROR; //从给定的Resource Bundle获取子级的Resource Bundle, 这里的Resource bundle 是表类型的 UResourceBundle *tablebundle = ures_getByKey(resource, tablename, NULL, &status); check(status,"get table by key error"); //从表类型的Resource Bundle中获取字符串翻译值 returnstring = ures_getStringByKey(tablebundle,stringid, &len, &status); check(status,"get string from key error"); return returnstring; }

现在可以看到,基于 Table 类型的函数都只能访问表项,如果表项是简单类型(如字符串)就可以直接获取对应翻译值,但如果表项是复杂类型,则只能返回对应这个表项的 Resource Bundle 类型指针。这导致了访问多层复杂类型资源需要进行层次性的操作,像个树型结构,查找一项数据就需要从树的最顶端一层一层的往下查。直接从最外层的 Resource Bundle 通过键访问里层的某个数据是不允许的。

程序的主函数是查询对应于 "zh_CN" Locale 的四个字符串的值,具体代码见清单 7,如果程序运行正确,程序将输出结果见清单 8。



UResourceBundle* resource = NULL; const UChar* city1001= NULL; const UChar* city1002 = NULL; const UChar* language_US = NULL; const UChar* language_ZH = NULL; resource= getRBByLocale("./mypackage","zh_CN");// 打开对应于中文Locale 的Resource Bundle. city1001 = getStringFromTable(resource,"CITY1001","city"); city1002 = getStringFromTable(resource,"CITY1002","city"); language_US = getStringFromTable(resource,"Lan_en_US","language"); language_ZH = getStringFromTable(resource,"Lan_zh_CN","language"); uprintfall(); //将所有结果打印到屏幕上



纽约 上海 英文 简体中文 Press any key to continue

本篇文章介绍了什么是 Resource Bundle, 详细介绍了 ICU4C 的 Resource Bundle 技术,最后使用了一个具体的实例介绍如何使用 Resource Bundle 开发。


施李荣,南京大学硕士,对国际化有着特别的兴趣,目前在开发一项C++平台的国际化项目,你可以通过 联系到他。

阅读(4668) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~