跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

国际化过程中的障碍

避免软件全球化过程中的微妙障碍

Taylor Cowan (taylor_cowan@yahoo.com), Senior Developer, Travelocity
Taylor Cowan 是一名软件工程师,偶尔也是 J2EE 方面的自由作家。他拥有计算机科学的硕士学位,还拥有北德克萨斯州大学的爵士乐作曲学士学位。

简介: Java™ 语言对多语言和多国家环境的支持是很强大的,但是也并不简单。如果您不小心,三个关键领域的错误就有可能出现在您的代码中,使得代码成为以美国为中心的。本文将识别这些国际化陷阱 (gotchas),并给出一些有助于您的应用程序更加全球可用的技术。

发布日期: 2005 年 9 月 12 日
级别: 中级
访问情况 : 1162 次浏览
评论: 


不要受 JDK 中固有的本地支持蒙蔽而放松警惕。即使 Java 语言充满了本地化特性,您的应用程序仍然可能成为以美国为中心的。许多国际化问题源自开发人员对自由文本用户输入、货币显示和日期/时间解析的无效假设。本文将展示这些假设如何会绊倒您,并帮助您让自己的应用程序更加世界通用。

国际化障碍 #1

决不假设文本字段输入总是符合 US-ASCII。即使您的应用程序是严格用于某一个地方的,也应该允许用户输入较宽范围的文本。考虑这样一种情况,即某人的合法名字中包含超出应用程序的默认语言之外的字符。

错误:您的“España”输入应该只包含字母

Java 开发人员都熟悉资源绑定,但是通常会忽视把读取输入作为应用程序的一个国际化敏感的方面。要真正成为国际化的,应用程序应该能够接受各种语言和字符集的输入。决不假设文本字段输入总是符合 US-ASCII 的。

用于国际化的正则表达式

自从 JDK 1.4 以来,Java 语言已经提供了很长时间的正则表达式支持。正则表达式已经在很多常见框架(比如 Struts)中用于输入验证,现在已在 java.lang.String 中得到直接支持。但是简化模式匹配和输入验证的同一功能,却也会妨碍国际化。考虑下面这个常见的正则表达式:

/[a-zA-Z0-9 ]*/

该表达式显然是一个字母数字掩码,旨在防止特殊字符。因而这可以保护应用程序不接受非预期的输入。但是这类严格的匹配会导致无意中的后果。该正则表达式不仅会防止不需要的符号,也会防止许多包含拉丁字母之外的字符的单词。例如,它会拒绝许多原来拼写正确的名词,比如 España (Spain) 或 München (Munich)。令人惊讶的是,它甚至还会拒绝 Washington D.C. 的规划者 Pierre L'Enfant 的名字,因为它不允许撇号。国际化的应用程序需要具有宽的而不是窄的输入掩码。由于其 ASCII 限制,传统的正则表达式是有悖于国际化的,如下面这个例子所示:

正则表达式中的 Unicode 支持

Unicode Technical Standard #18 为 Unicode 正则表达式定义了一个标准(参阅 参考资料)。支持 Unicode 是一个挑战,这有两个原因。其一,它具有一个比 US-ASCII 大得多的字符集。其二,许多受支持的语言具有不同于英语的特征。自从 JDK 1.4 以来,Java 语言在级别 1 支持该规范,或者说具有基本的 Unicode 支持。

if (inputString.matches("\\w*"))

标准表达式符号 \w(单词字符)与第一个例子是一样的。在本例中,单词 实际上只表示英语单词。支持国际化的输入需要超出标准的 regexp 匹配指示符。

当我第一次遇到这个问题时,我很高兴发现这已是一个众所周知的问题,并且有人在着手解决它了。有两种方式指定较宽的匹配,即使用 Posix 字符块 (character blocks)类别 (categories)。将它们指定为 \p{block | category}。例如,\p{L} 匹配任何 Unicode 字母。在本例中,字母 具有宽得多的含义,不仅包括拉丁字符,还包括日文片假名、朝鲜语 Hangul 以及更多的字符集。表 1 展示了 Posix 正则表达式类别的一些例子。

表 1. Posix regexp 类别例子

\p{Lu}大写字母
\p{Ll}小写字母
\p{P}标点符号

类别适合于一般情况匹配,但是如果您需要更加特定的话,可以使用字符块。字符块可以让您显式地包含或拒绝 Unicode 某个区域中的字符。表 2 展示了 Posix 字符块的一些例子。

表 2. Posix 字符块例子

[\p{InKatakana}*]匹配任何片假名字符
[\p{InBasic Latin}\p{InLatin-1 Supplement}]匹配基本的和补充的拉丁字符

必须用正确的块名指定字符块。不幸的是,JDK 没有定义任何方便的常量,javadoc 也没有详细列出所有的可能性。块名取自 Unicode 标准,并且列出在 Unicode 站点的一个文件中(参阅 参考资料)。

开始使用 Unicode 正则表达式的最好方式是体验不同语言中的简单匹配。下面的示例代码用几种语言中的文本来测试标准正则表达式和 Unicode 正则表达式。若想要运行这个例子,必须将 VM 默认编码设置为 UTF-8 (-Dfile.encoding=UTF-8)。

public static void main(String[] args)
  {
    //category examples
    doMatch("ü", "\\p{Ll}"); // Lowercase Unicode letter
    doMatch("ü", "\\p{Lu}"); // uppercase Unicode letter
    
    //character block examples
    doMatch("한글", "\\p{InHangul Syllables}*"); // Korean
    doMatch("カタカナ", "\\p{InKatakana}*"); // Japanese
    
    // German spelling for Munich
    // only matches the last two expressions
    String s[] = {"Munich", "München"};
    for (int i=0 ; i<s.length ; i++) {
      doMatch(s[i], "[a-zA-Z0-9]*"); //explicit 
      doMatch(s[i], "\\w*"); // word character
      doMatch(s[i], "\\p{Alpha}*"); // alphabetic character
      doMatch(s[i], "[\\p{InBasic Latin}\\p{InLatin-1 Supplement}]*");
      doMatch(s[i], "\\p{L}*"); // Unicode letter
    }
  }
  
  public static void doMatch(String s, String regexp) {
    if (s.matches(regexp))
      System.out.println(s + " matches " + regexp);
    else
      System.out.println(s + " doesn't match " + regexp); 
  }		 


显示货币

货币显示似乎微不足道,但通常是国际化过程中一个被忽略的领域。换算、小数格式、货币符号位置和消除二义性都是影响货币正确显示的因素。

十进制

一个错误的货币假设是,所有的金额都应该表示为带有两个小数位。$1.25 约等于 1,314.92 韩元 (Korean won),但是您永远也兑换不到这一金额。原因很简单,不可能给某人 0.92 韩元,因为韩国货币的最小面额是元。韩元 (KRW) 和日元 (JPY) 通常以不带任何小数位显示。JDK 在这一方面很有帮助,因为它有一个 java.util.Currency 类。要确定一种货币符合常规的小数位数,可以使用 getDefaultFractionDigits() 方法:

Currency c = Currency.getInstance("KRW");
int i = c.getDefaultFractionDigits();

分隔符

另一个错误是使用句点 (.) 作为小数指示符和逗号 (,) 作为分组符号。与分数不一样,小数格式与人们看待货币金额是相关的。在有些国家,逗号用于指定小数位,空格或逗号可用作分隔符,如表 3 中的例子所示。

表 3. 货币分隔符

德国1.234.567,25
法国1 234 567,25

使用 NumberFormat 类

JDK 利用 NumberFormat 类提供小数格式规则和换算。如果小心使用,NumberFormat 可以简化货币处理(参阅 参考资料)。但是它也会引入新的问题,因为它作了一些非常宽的假设。下面这个简短的例子中就作了这样一个假设:

国际化障碍 #2

Java 的 NumberFormat 类将货币映射为本地的。该假设是有效的,原因有三。其一,本地的官方货币可以非预期地改变。其二,很多时候,“官方”货币并不是主要货币。其三,全球化的应用程序不能假设任何单一的货币,而是必须同时处理若干种货币。避免让您的 Java 代码赋一种默认的货币给应用程序。

DecimalFormat format = 
   (DecimalFormat)NumberFormat.getCurrencyInstance();
String amount = format.format(1.25);

金额将被格式化为何种货币形式?不了解代码运行所在的系统的情况,就不可能预言系统包含的 amount 变量。NumberFormat 在幕后为您作出货币决策。它基于地区假设一种货币。这一表面上方便的假设是危险的,因为地区与货币之间的关系是脆弱的。在任何给定时间,某地有可能有两种货币是有效的。并且软件应用程序可能需要在同一时间处理多种货币。为了弥补这一问题,必须对格式对象应用 Currency 实例:

DecimalFormat format = 
   (DecimalFormat)NumberFormat.getCurrencyInstance();
format.setCurrency(amountCurrency);
String amount = format.format(1.25);

通过态度鲜明地对待 Currency 类型,您将避免当应用程序重新部署到一个不同的地方或者当应用程序需要支持多种货币时遇到的问题。这也可以保护代码免受现实世界货币变化的影响,这种变化发生是非预期的,并且会使 JDK 用于映射地区到货币的最新规则失效。


那是某个地方的 5 点钟

Phileas Fogg 是 Around the World in 80 Days 的主角,他最近在关于时间的错误假设上名誉扫地。他向东旅行,并忠实地将自己的手表往前拨,以适应当地时间。当回到英格兰时,他没有考虑到为适应当地时间而采取的人为因素,并错误地认为已经过了整整 80 天。对于不完全了解时区对应用程序的含义的 Java 开发人员,也会面临类似的问题。

挂钟时间和隐含的时区

考虑一个这样的应用程序,它在租赁的汽车应该还给租赁点的两个小时前通知客户。逻辑相当简单:记录还车时间 (drop-off time),并在这个时间进入两小时窗口时通知客户。要计算通知窗口,需要两个可比较的日期 —— 还车时间和当前系统时间。假设用户通过下拉列表或自由文本输入指定期望的还车时间。不管哪种方式,数据都必须被解析为给予您一个 java.util.Date 的可比较实例。在 Java 语言中,日期通常使用 DateFormat 的实例来解析:

国际化障碍 #3

一定要注意日期/时间值何时是与物理位置相对的。如果是的,就要避免让 DateFormat 应用一个默认的时区,而应该是特定的。这可以防止当服务器重新定位时出现的意外问题。

// sDropOff formatted as hh:mm
Date dropOff = dateFormat.parse(sDropOff);

parse() 方法作一个隐藏的假设。除非显式地指定,否则 sDropOff 按系统时区进行解析。Java 语言需要时区,因为它内部存储相对于 Greenwich Mean Time (GMT) 的 Date。这意味着有 24 个不同版本的 5:00 p.m.,其中四个版本只存在于美国内陆。如果还车位置与系统位于不同的时区,计算就会有问题。DateFormat 允许一个显式的时区:

// sDropOff formatted as hh:mm
dateFormat.setTimeZone(dropOffTimeZone);
Date dropOff = dateFormat.parse(sDropOff);

Java 语言中的时区支持

时区具有两个主要的属性。第一个是它与 GMT 的偏移量,正负都有可能。第二个是它的夏令时 (DST) 规则集。这些规则指示时区是否参与 DST,如果是的话,又指示 DST 何时开始和结束。依赖于您需要往回走多远,这些规则可能很广泛,并且国家与国家之间、地区与地区之间会各不相同。为了证明 DST 规则有多复杂,可以尝试运行下面这个代码片段:

Calendar cal = Calendar.getInstance();
cal.setTimeZone(TimeZone.getTimeZone(
  "America/Chicago"));
cal.clear();
cal.set(Calendar.YEAR, 1985);
cal.set(Calendar.MONTH, Calendar.APRIL);
cal.set(Calendar.DATE, 15);
cal.set(Calendar.HOUR, 8);
System.out.println(
cal.getTime().toGMTString());
cal.set(Calendar.YEAR, 2005);
System.out.println(cal.getTime().toGMTString());

注意,在 1985 年,8:00 a.m. 显示为 14:00 GMT,但是在 2005 年,它则是 13:00 GMT。在本例中,偏差是由美国国会在 1986 年通过的 Public Law 99-359 所引起的,因为这条法律将 DST 从 4 月的最后一个星期日改成了 4 月的第一个星期日。存在许多这个类型的 DST 规则的例子,并且还有增加的趋势。好消息是,Java 语言具有这些规则的一个全面的数据库,您可以充分利用这个数据库,以便您在准备处理时区时可以了解这些时区的名称。

时区名称

Java 语言的时区规则来源是公共域时区数据库(参阅 参考资料)。(HP-UX、Solaris 和 Mac OS X 也使用这个数据库。)有效的时区通常按时区内的陆地和最大城市来命名。EST (Eastern Standard Time)、CST (Central Standard Time)、MST (Mountain Standard Time) 和 PST (Pacific Standard Time) 不是有效的时区指示符,但是为了 JDK 1.0 的向后兼容性,也是受支持的。表 4 展示了一些常见时区指示符。

常见时区指示符

United States/Chicago与具有 DST 规则的 CST 相同
United States/New York与具有 DST 规则的 EST 相同
Asia/Tokyo涵盖日本
Europe/Berlin涵盖德国

结束语

熟悉货币、时间和文本全球化将有助于避免问题,但是可用的最强大的全球化工具是测试。如果在测试应用程序时考虑到这些全球化问题,那么我们这里讨论的很多问题都可以快速排除。确保测试来自多个字符块的输入。(测试 East Asian 字符集的一个简单方式就是从 Web 站点复制并粘贴。)通过给服务器或客户机分配不同操作系统级别的日期/时间属性,用不同时区中的服务器和客户机测试应用程序。最后,测试货币金额和其他数字可以被配置为以非美国约定来显示。熟悉每一组国际化约定和字符是有帮助的,但并不是必需的。您只需要在必要时提供配置的可能性,以便在您想要为一个新的国际市场部署应用程序时可以节约时间和金钱。


参考资料

关于作者

Taylor Cowan 是一名软件工程师,偶尔也是 J2EE 方面的自由作家。他拥有计算机科学的硕士学位,还拥有北德克萨斯州大学的爵士乐作曲学士学位。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=94450
ArticleTitle=国际化过程中的障碍
publish-date=09122005
author1-email=taylor_cowan@yahoo.com
author1-email-cc=jaloi@us.ibm.com

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。