并非世界上每一个人都讲一种语言。也并非世界上每一个人都会以相同的方式格式化他(她)的数字、日期和货币。
您可能会疑惑为何这现在才成为了问题。网站已经出现多年,且各个国家已经存在了上万年。问题之所以越来越尖锐是因为我们越来越多地看到网站在客户端完成更多的工作,而不是依赖于服务器或依赖于 Ajax 客户端调用来完成工作。对于每项工作,开发人员现在都想要一种纯客户端的解决方案。
让我们看一个简单的例子来证明我的观点。假设,您是一个名为 “Double It!” 的 Web 应用程序的所有者。当一个用户在一个文本字段中输入一个数字时,在其下的一个 SPAN 内,用户只要单击一个按钮,这个数字就会加倍。这看上去非常简单,但是当美国的一个用户来到这个网站并向文本字段键入 “1,250.25” 时,您的应用程序就会给出响应,即 “2,500.5”。您可以编写一些客户端 JavaScript 来轻松实现这一目的。
$("#reponseSpan").text(2 * new Number($("#inputTextField").val()));
现在,有一个德国用户来到这个网站。他们书写数字的方式与美国用户不同。德国人输入的是 “1.250,25”。如果您仍使用与为美国客户编写的相同的 JavaScript,就会在它试图创建这个 Number 对象时得到一个 JavaScript 错误。那么为何会如此呢?因为内置的 JavaScript 函数预期的数字的格式是美国格式,而您的德国访客不会按这种方式键入。
您虽然可以指导您网站的用户输入所有数字时都采取美国格式,但 这真的是最佳答案吗?您是想要强迫用户按您的方式做事,还是想要接受用户自行选择的做事方式并让用户体验尽量的完美。
表 1 显示了数字的一些可能格式,也例证了 Globalize 插件为何是最佳解决方案。
国家 | 数字格式 |
---|---|
美国 | 1,250.25 |
德国 | 1.250,25 |
法国 | 1 250,25 |
瑞士 | 1'250,25 |
Globalize 插件是如何解决前述这些问题的呢?他们从文化的角度来构建这个解决方案。一种文化不应被视为是一种语言,因为世界上的很多国家都讲的是相同的语言,比如西班牙语。同样地,一种文化也不应被视为是一个国家,因为有一些国家具有多种官方语言,比如瑞士。相反,文化的概念可以被视作是国家和 语言的惟一组合。因此,虽然西班牙语不是惟一的,西班牙也不是惟一的,但是二者的组合西班牙语-西班牙就是惟一的,因此可被视为是一种文化(以区别于西班牙语-墨西哥和加泰罗尼亚语-西班牙)。
当您将世界上的所有这些惟一的语言和国家进行排列组合时,会发现有近 350 种文化需要受到支持。(这也是为什么您甚至都不会指望能独自快速找出解决方案。) Globalize 插件在构建文化时,它使用了两个分别由两个字母组成的代码。第一个代码是两个字母小写语言代码,称为 ISO 639 码,第二个代码是两个字母大写国家代码,称为 ISO 3166 码。例如,“en-US” 表示的是英语和美国文化,“fr-FR” 表示的是法语和法国文化。
同样重要的是文化的 “中立”。实际上,Globalize 插件有这样一个原则,即 “如果您无法获得具体的语言和国家信息,那么您可以绕过语言,我们会对如何进行正确的格式化做出最佳的猜测”。这无疑是一个很大的帮助,本文稍候会详述。浏览 Web 的人易于标识他们所讲的语言,但确定其国家代码有时会很困难。
了解了问题的背景以及 Globalize 插件如何使用 Culture 对象来提取底层的细节后,让我们来看看该如何使用 JavaScript 设置文化。
第一步是下载 Globalize 插件。还要注意,该 jQuery 不是 使用 Globalize 插件所必需的。 原来曾是,现在在最新版本中不这么要求了。Globalize 插件被设计成 “主” 插件位于根文件夹,名为 “globalize.js”。 这个文件很小(只有 44 KB),但它只 处理的是美国英语。这是因为它只包含了所有功能的默认值,并未包含任何实际的文化代码。打开 “cultures” 文件夹,内有 353 个文件,可增加问题的复杂性。现在标识这个特殊的文件 “globalize.cultures.js”,这个文件包含了所有可能的文化,所以您无需加载您所要使用的那些文化了。当然,包含所有的文化也有其缺点,即它会创建一个 828 KB 的大文件。在任何生产环境中,多少有些过度。不过,对于我们在这里编写一些示例代码已经足够好了。
// Remember, you should only load the globalize.cultures.js when testing and in example
code like this
// Later sections will show how to dynamically load each Culture as needed
<script type="text/javascript" src="globalize.js"></script>
<script type="text/javascript" src="cultures/globalize.cultures.js"></script>
Setting the Culture within JavaScript is, like most things, very easy. Here are a few examples of how to set them.
// Remember, this only works if you include the globalize.js file!
// And, you HAVE to include the globalize.cultures.js file OR
// each individual culture's JS file
// You can set the culture directly by referencing its name
Globalize.culture = Globalize.cultures.de;
// You can set the culture directly by referencing it from
// the Cultures array
Globalize.culture = Globalize.cultures["de-DE"];
在处理国际化时,格式化和解析是两个最为基础的功能。毕竟,您想要让您的用户能够按他们习惯的格式键入数字和日期。但是,您需要做的是让他们输入的内容成为您能处理的(解析)格式,或者将您的数据显示成用户习惯的格式(格式化)。
格式化一个数字就是拿一个数字(一个可作为 JavaScript 内的 Number 对象存储的实际数字)并以用户习惯的方式显示给用户。比如在之前的那个示例中,美国人键入的是 “1,250.30”,而德国人则会键入 “1.250,30”。
在大多数语言中,格式化数字都会涉及到处理四种类型的格式:整数位(小数点之前的数位)、小数位(小数点之后的数位)、百分数(数字乘以 100 并显示 "%" 符号)以及货币(显示该文化的货币符号,比如 $ 或欧元符号)。每一种格式都会用到一个数值实参来告诉格式化程序如何适当地应用模式。
// Assume that the correct Culture JS file is always added here
// make the Culture German
Globalize.culture = Globalize.cultures.de;
// test the "n" command
Globalize.format(1839.560, "n1"); // outputs 1.839,6
Globalize.format(1839.560, "n0"); // outputs 1.840
Globalize.format(1839.560, "n6"); // outputs 1.839,560000
// test the "c" command
Globalize.format(1839.560, "c2"); // outputs 1.839,56 €
Globalize.format(1839.560, "c3"); // outputs 1.839,560 €
Globalize.format(1839.560, "c6"); // outputs 1.839,560000 €
// now make it English and run the same code
Globalize.culture = Globalize.cultures.en;
// test the "n" command
Globalize.format(1839.560, "n1"); // outputs 1,839.6
Globalize.format(1839.560, "n0"); // outputs 1,840
Globalize.format(1839.560, "n6"); // outputs 1,839.560000
// test the "c" command
Globalize.format(1839.560, "c2"); // outputs $1,839.56
Globalize.format(1839.560, "c3"); // outputs $1,839.560
Globalize.format(1839.560, "c6"); // outputs $1,839.560000
对于这里使用的这些格式化函数,有几个地方我不太喜欢。首先,货币格式化灵活性不佳,不允许我指定在我想要的位置放置货币符号。与 Java™ 使用的格式化程序作一比较,其中编码者可以指定货币符号是放在前面还是后面,带空格还是不带空格。虽然这对于 Globalize 插件也不是不可能的,但需要编辑 "de" Culture 对象(不建议)或创建一个定制的对象。此外,Java 格式化还允许向开头和结尾添加符号作为进一步的定制。例如,拿新工作的签约奖金来说,可以将希望的签约奖金写作 “$24k”,其中 “k” 代表的是 “千”。这种缩写在美国很常见,即 “k” 代表千,“M” 代表百万,“B” 代表十亿。遗憾的是,这个功能在 Globalize 插件目前的格式化中,如若不编写自己的 Culture 对象,尚不能实现。
基本上,我认为这里的格式化功能没有服务器端的 Java 那么强大,但是在几乎每一个您需要它的用例中,这仍然是一个解决方案。
用户键入一个数字、货币或百分比时,您都需要获得该信息,而且需要它的格式是能用来创建 Number 对象的格式。换言之,就是需要它以代码能处理的方式存在。此外,如果还要将其传递到服务器,也要确保它的格式正确。在本例中,需要解析数字,并将其从一个字符串转变成一个数字。
// Assume that the correct Culture JS file is always added here
// make the Culture German
Globalize.culture = Globalize.cultures.de;
// Call parseInt() on it, and get an object back we can work with
var num = Globalize.parseInt("1.839,56"); // will create an object with value 1840
// now make it English and run the same code
Globalize.culture = Globalize.cultures.en;
// Call parseInt() on it, and get an object back we can work with
var num = Globalize.parseInt("1,839.56"); // will create an object with value 1840
// Likewise, there's a parseFloat() that will preserve the decimal points
var num = Globalize.parseFloat("1,839.56"); // will create an object with value 1839.56
Globalize 插件的格式化并不仅限于数字。日期也可以被格式化。毕竟,日期 March 8, 2011,既可以被写成在美国惯用的 3/8/11,也可以写成在德国惯用的 8/3/11 。格式化日期要比格式化数字复杂和棘手得多,因此出于这个原因,我在这里不会过于详细地介绍格式化日期可用的全部 选项。那毕竟是文档的作用所在。
// Assume that the correct Culture JS file is always added here
// make the Culture German
Globalize.culture = Globalize.cultures.de;
// Create a date for March 8th, 2011
// NOTE - months are 0 indexed, but the day isn't...how dumb!
Globalize.format(new Date(2011,2,8),"d"); // outputs 08.03.2011
Globalize.format(new Date(2011,2,8),"M"); // outputs 08 März
Globalize.format(new Date(2011,2,8),"D"); // outputs Dienstag, 8. März 2011
// now make it English and run the same code
Globalize.culture = Globalize.cultures.en;
Globalize.format(new Date(2011,2,8),"d"); // outputs 3/8/2011
Globalize.format(new Date(2011,2,8),"M"); // outputs March 08
Globalize.format(new Date(2011,2,8),"D"); // outputs Tuesday, March 08, 2011
解析一个 Date 对象也不难。
// Assume that the correct Culture JS file is always added here
// make the Culture German
Globalize.culture = Globalize.cultures.de;
Globalize.parseDate("3/8/2011"); // creates a JavaScript Date object for August 3rd, 2011
Globalize.parseDate("Tuesday, March 08, 2011"); // returns null
// now make it English and run the same code
Globalize.culture = Globalize.cultures.en;
Globalize.parseDate("3/8/2011"); // creates a JavaScript Date object for March 8th, 2011
Globalize.parseDate("Tuesday, March 08, 2011"); // creates a JavaScript Date object for
March 8th, 2011
至此,我们已经在我们的示例中使用了 "globalize.cultures.js" 文件来简化要处理的代码。"globalize.cultures.js" 文件包含了全部 350 个文化,结果,文件达 828 KB 之大。这对于本文中的这些例子没有任何问题,但是在生产环境中,它将不 能工作。为什么要无端地在每个页面加载都要传递 349 个不需要的文化呢?大多数用户都只需要在其页面上加载一两个文化就能实现此页面的完全国际化。
此外,您又如何能知道哪个文化该被加载到一个页面上呢?浏览器不会确切地告诉您某个用户来自世界的哪个地方(虽然有了基于位置的服务和 IP 地址查找,情形在发生改变)。如果我带着我的笔记本电脑来到斯德哥尔摩,我并不会希望只因为我的地理位置改变了,每个网站就都要自动地将所有东西转变成瑞典格式的。我虽然换了地方,但我阅读数字和日期的方式并没有改变。若能有一种方式可以自动识别用户在网上冲浪时偏好使用的语言和国家,岂不很棒。
真的有这样一种方式。到目前为止,最简单的方式是能访问到 JavaScript 形式的该信息。奇怪的是,虽然 JavaScript 提供了有关浏览器的很多信息,但它却没有 给出有关偏好语言的信息。不过,即便 JavaScript 没有提供该信息,您仍然可以用服务器端语言,比如 Java 或 PHP,获得该信息。
我们的目标是要确定用户偏好使用那种(些)语言。然后,基于这些语言,我们希望向页面只 加载对应的那些文化,以便减少流量和页面加载时间。最后,我们要用用户偏好的语言调用 Globalize.culture(),以便 Globalize 插件知晓该如何进行格式化,清单7和清单8 分别显示了可用来自动地国际化 Web 页面的 Java 代码和 PHP 代码。
如果您使用的是 JSP 或 PHP,且计划使用 Globalize 插件,那么您完全可以放心地将这些代码粘贴到您自己的代码内。之后就可以部署且没有任何的外部依赖项;Java 5 或更高,PHP 4 或更高。
<%
// the user's preferred language is stored in the Header, and the Accept-Language
// field
// For example, in my Firefox browser, it shows
// en-us,en;q=0.5
// This means my primary Culture is "en-us" which has a q=1.0
// My backup Culture is "en" neutral, which has a q=0.5
// q values are used to rank the cultures and are a system
// for safe fail-over
String header = request.getHeader("Accept-Language");
// Split each language into separate locales
String[] locales = header.split(",");
// load the globalize.js file, which must always
// be loaded to use the Globalize plugin
out.println("<script src=\"globalize.js\"
type=\"text/javascript\"></script>");
// loop through each locale, and load the appropriate Globalize
// plugin file.
// for example, since I have 2 locales, it will load the
// globalize.culture.en-US.js file and the
// globalize.culture.en.js file
for (int i=0; i<locales.length; i )
{
int end = (locales[i].indexOf(";") == -1) ? locales[i].length() :
locales[i].indexOf(";");
String locale = locales[i].substring(0,end);
out.println("<script src=\"cultures/globalize.culture."
locale
".js\" type=\"text/javascript\"></script>");
}
// Finally, call culture() with the Accept-Language
// The Globalize plugin accepts the String directly from
// the Header, and deals with the q values appropriately,
// even failing over safely on its own
out.println("<script>$(document).ready(function(){Globalize.culture(\""
header
"\");});</script>");
%>
// Here's the same thing in PHP
<?
$accept_language = $_SERVER["HTTP_ACCEPT_LANGUAGE"];
$languages = explode(",", $accept_language);
echo "<script src=\"globalize.js\" type=\"text/javascript\"></script>";
for each ($languages as $language) {
$locale = explode(";", $language);
echo "<script src=\"cultures/globalize.culture.".$locale[0].".js\"
type=\"text/javascript\"></script>";
}
echo '<script>$(document).ready(function(){Globalize.culture
("'.$accept_language.'");});</script>';
?>
Globalize 插件是对 jQuery 和 JavaScript 工具箱的一个很棒的补充。它是微软多年工作的结果,微软将这一工作成果捐赠给了 jQuery 社区,并惠及了我们所有的人。此插件绝对是全覆盖的,因为它包括了 350 种惟一的由语言和国家构成的文化。不夸张地说,世界上每一个访问您网站的人都是 Globalize 插件所涵盖的。这足以说服您使用 Globalize 插件而不是您曾尝试使用的其他自制插件。(具有讽刺意味的是,虽然我在之前所写的一篇 developerWorks 文章中介绍过一种流行的 jQuery 数字格式化插件,但我现在要告诉您停止使用那个插件,开始使用这个插件。)
Globalize 插件让您可以轻松格式化数字和日期,还能让您解析这些值。这个插件可用来定制一个 Web 页面显示其数字和日期的方式,并能通过采取另一个 “让您的 Web 应用程序更像桌面应用程序” 的关键步骤来更好地将您的 Web 页面连接到您的用户。在这之前,如果开发人员想要面向每个文化格式化数字和日期,他们必须进行 Ajax 客户端-服务器调用,或是直接在服务器上完成全部的处理。正如我们在所有当前的 Web 应用程序内看到的,更多的处理和逻辑现在都可以用 JavaScript 在客户端进行。
我们没有深究 Globalize 插件各方面的更多细节。例如,此插件的自适应能力足以让您轻松编写自己的文化并将其插入到系统中。不过,由于此插件包括了 350 种文化,您真的还需要其他的么?如果您真的需要,也不难,但是我在本文中没有将其作为一个必需主题。
最后,我向您展示了如何用 Java 和 PHP 代码来动态确定访客偏好在其浏览器中使用的正确文化,如何动态加载要 Globalize 插件包含的正确的文化文件,以及如何动态调用 culture() 来设置网页上的这些文化。您应该能够将上述代码直接剪切复制到您自己的代码中来实现您 Web 页面的国际化。