JSP 标准标签库(JSP Standard Tag Library,JSTL)中的 fmt 库通过一组定制标签为 JSP 开发人员提供了一种对 Java 平台的国际化 API 的简单访问方式。fmt 库中的定制标签支持通过资源文件(ResourceBundle)对文本内容进行本地化,该功能可以对一个特定的语言请求作出相应的响应,它使用了 J2SE 的 ResourceBundle 来保持各种翻译过的语言编码,JSTL 可以从用户的浏览器设置自动确定特定用户的语言环境,或者由用户指定特定用户的语言环境,JSTL 会根据不同的语言环境设定选择适合的 ResourceBundle;fmt 标签同时支持对数字和日期时间的显示和解析。这些标签利用在 java.util 和 java.text 包中实现的 Java 语言的国际化 API。
JSTL 中的 fmt 库封装了对国际化支持的 API,标签包含了各种定制标签(tag)来支持 JSP 中资源文件的本地化,数字、时间日期和货币的显示和解析。该标签库中的标签一共有 12 个,分成两大类,国际化核心标签和格式化标签。国际化核心标签有:<fmt:setLocale>、<fmt:bundle>、<fmt:setBundle>、<fmt:message>、<fmt:param>、<fmt:requestEncoding>;格式化标签有:<fmt:timezone>、<fmt:setTimeZone>、<fmt:formatNumber>、<fmt:parseNumber>、<fmt:formatDate>、<fmt:parseDate>。
<fmt:setLocale>用来设置地区,比如<fmt:setLocale value="es_ES"/>,这等于设定了语言和国家代码。当然还可以指定 ResourceBundle,比如:<fmt:bundle basename="ApplicationResource_fr"/> 。一旦设定了 locale(地区)或 ResourceBundle,就可以使用<fmt:message>来把原文进行相应的转化,同时还可以使用< fmt:requestEncoding/>来设定请求的字符编码 。能正确显示字符,这只是解决国际化问题的一半,还必须解决数字和日期时间的格式化解析,不同的地区有不同的显示方法。使用<fmt:formatNumber> 和<fmt:parseNumber> 可以按当地的格式显示和解析数字、货币金额、百分比,同时还可以制定模式(pattern)参数,比如<fmt:formatNumber value="12.3" pattern=".00"/>会输出"12.30" 。时间和日期的显示和解析使用格式化标签<fmt:formatDate>和 <fmt:parseDate>。详细介绍如表 1 所示。
标签 | 描述 | 使用格式 |
---|---|---|
<fmt: setLocale> | 设置全局的 Locale 信息,包括语言和国家代码 | <fmt:setLocale value="locale" variant="variant" scope="page|request|session|application"/> |
<fmt:bundle> | 用于绑定资源文件在它的标签体内 <fmt:bundle>...</fmt:bundle> | <fmt:bundle basename="basename" prefix="prefix"> body </fmt:bundle> |
<fmt:setBundle> | 允许将资源配置文件保存为一个变量。 属性 var 是其独有的属性,用于保存资源文件为一个变量 | <fmt:setBundle basename="basename" var="varname" scope=""/> |
<fmt:message> | 从指定的资源文件中把指定的键值取出来 | <fmt:message key="key" bundle="resourceBundle" var="varName" scope=""> <fmt:param> </fmt:message> |
<fmt:param> | 为格式化文本串中的占位符设置参数值,只能嵌套在<fmt:message>标签内使用 | <fmt:param value="messageParameter"/> body content </fmt:param> |
<fmt:requestEncoding> | 用于定义字符编码 | <fmt:requestEncoding value="charsetName"/> |
<fmt:timezone> | 设定时区,时区设定在标签体内起作用 | <fmt:timeZone value="timeZone"/> body content </fmt:timeZone> |
<fmt:setTimeZone> | 允许将时区设置保存为一个变量,结合属性 var 使用 | <fmt:setTimeZone value="timeZone" var="varName" scope=""/> |
<fmt:formatNumber> | 根据 Locale 格式化数字,货币和百分比 | <fmt:formatNumber value="value" type="number|currency|percent" pattern="customPattern" currencyCode="code" currencySymbol="symbol" groupingUsed="true|false" maxIntegerDigits="max" minIntegerDigits="min" maxFractionDigits="max" minFractionDigits="min" var="varName" scope=""/> </fmt:formatNumber> |
<fmt:parseNumber> | 用来将字符串类型的数字, 货币或百分比转换成数字类型 | <fmt:parseNumber type"number|currency|percent" pattern="pattern" parseLocale="locale" integerOnly="true|false" var="varName" scope=""> value </fmt:parseNumber> |
<fmt:formatData> | 用来格式化日期 | <fmt:formateDate value="value" type="date|time|both" dateStyle="default|short|medium|long|full" timeStyle="default|short|medium|long|full" pattern="pattern" timeZone="zone" var="varName" scope=""/> |
<fmt:parseDate> | 将字符串类型的时间转换为日期类型 | <fmt:parseDate value="value" type="time|date|both" dateStyle="" timeStyle="" pattern="" timeZone="" parseLocale="Locale" var="" scope=""/> </fmt:parseDate |
在 JSP 页面中要使用到格式化标签,需要引入下面的语句<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>,清单 1 是 JSTL fmt 标签的综合示例。
//myTest.jsp <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <html> <head> <title>JSTL fmt: Localized tag testing</title> </head> <body> <fmt:requestEncoding value="UTF-8" /> <fmt:setLocale value="zh_TW"/> <fmt:setTimeZone value= "GMT+8" scope="request"/> <fmt:bundle basename="src.resources.bundles.java.loginInfo "> <fmt:message key="userName"/><br/> <fmt:message key="passWord"/><br/> </fmt:bundle> <fmt:setBundle basename="src.resources.bundles.java.myResources" var="applicationBundle"/> <h3>Number Format:</h3> <c:set var="numberValue" value="123456.789" /> <p>Formatted currency: <fmt:formatNumber value="${numberValue}" type="currency"/></p> <p>Formatted Number: <fmt:formatNumber type="number" maxIntegerDigits="2" value="${numberValue}" /></p> <p>Formatted Number with pattern: <fmt:formatNumber type="number" pattern="###.###E0" value="${balance}" /></p> <p>Formatted Percent: <fmt:formatNumber type="percent" maxIntegerDigits="3" value="${numberValue}" /></p> <fmt:parseNumber var="parsedNumber" integerOnly="true" type="number" value="${ numberValue }" /> <p>Parsed Number: <c:out value="${parsedNumber}" /></p> <h3>Date Format:</h3> <c:set var="now" value="<%=new java.util.Date()%>" /> <p>Formatted time: <fmt:formatDate type="time" value="${now}" /></p> <p>Formatted Datetime: <fmt:formatDate type="both" value="${now}" /></p> <p>Formatted Medium Date and short Time: <fmt:formatDate type="both" dateStyle="medium" timeStyle="short" value="${now}" /></p> <p>Formatted Date with specified format: <fmt:formatDate pattern="yyyy-MM-dd" value="${now}" /></p> </body> </html>
影响数据本地化方式的因素主要有两个:一个是用户的语言环境(我们称作 Language Locale),主要考虑到的就是翻译,在不同的国家用他们自己的语言正确运作软件,让客户感觉这个产品是为他们而设计的;另一个是用户的文化环境(我们称作 Culture Locale),主要处理的是多元文化的支持,包括货币、日历、时间、日期、排序、界面方向性(Bi-directional) 等符合各个国家自己习惯的显示方式。
从前面的章节中我们知道,如果需要格式化一个日期时间、数字和货币,我们可以使用 JSTL 库中的 fmt 标签 fmt:formatDate 和 fmt:formatNumber,并且在 JSP 中通过 fmt:setlocal 标签提前设定好用户 Locale,然而在实际的项目中,这种 fmt 标签有时候不起作用,并不能格式化成我们想要的格式;还有一个问题就是比如日本用户想用英文界面和日文的时间、日期、货币格式浏览网页,而我们在 JSP 中使用了 fmt:setlocal 标签,这样就不能很好的区分 Language Locale 和 Culture Locale。基于项目实践,我们通过在 JSP 中使用 JSTL 自定义函数的方法解决这两个问题。另外使用 JSTL 自定义函数也可以使得 JSP 中的代码非常整洁,具有很好的封装性和重用性。
在 JSP 中使用 JSTL 自定义函数有下面四个步骤:
1. 创建一个 Java 类,编写静态方法用来处理想要的格式转换;
2. 创建一个自定义函数的描述符文件.tld,声明静态方法作为一个自定义 JSTL 标签;
3. 使 TLD 和处理程序类可访问;
4. 在 JSP 中调用自定义函数(Custom Function)来格式化日期时间,数字和货币等。
第 1 步:创建一个 Java 类,编写静态方法用来处理想要的格式转换
在清单 2 的示例中,我们主要用来处理日期时间和数字的格式化。需要注意的是必须创建静态方法实现我们想要处理的功能,我们可以将 Culture Locale 作为格式转换处理函数的参数。
import java.util.Date; import java.util.Locale; import com.ibm.icu.text.DateFormat; import com.ibm.icu.text.NumberFormat; public class G11nJSTLCustomFunctions { public static String formatNumber(Number number, String locale){ NumberFormat numberFormat = NumberFormat.getNumberInstance( convertStringToJavaLocale (locale)); return numberFormat.format(number); } public static String formatDate(Date date, String locale){ DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, convertStringToJavaLocale (locale)); return dateFormat.format(date); } public static String formatPercentNumber(Number number, String locale){ NumberFormat numberFormat = NumberFormat.getPercentInstance( convertStringToJavaLocale (locale)); numberFormat.setMinimumFractionDigits(2); return numberFormat.format(number); } public static Locale convertStringToJavaLocale(String strLocale){ Locale locale = null; String[] localeCode = strLocale.split("-"); if(localeCode.length == 1){ locale = new Locale(localeCode[0]); }else { locale = new Locale(localeCode[0],localeCode[1].toUpperCase()); } return locale; } }
第 2 步:创建一个自定义函数的描述符文件.tld,声明静态方法作为一个自定义 JSTL 标签
下一步是定义包含自定义标签与处理它的 Java 类之间的映射的库。这个库是在一个名为标签库描述符(TLD)的 XML 文档中定义的,如清单 3 的示例所示。
<!--?xml version="1.0" encoding="UTF-8" ?--> <taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" version="2.1"> <display-name>g11nCustomFunction</display-name> <tlib-version>1.0</tlib-version> <uri>http://java.sun.com/xml/ns/javaee:cf</uri> <function> <description>Format number with specified locale</description> <name>formatNumber</name> <function-class>com.silverpop.marketer.ui.G11nJSTLCustomFunctions</function-class> <function-signature>java.lang.String formatNumber( java.lang.Number, java.lang.String)</function-signature> </function> <function> <description>Format number string with specified locale</description><name>formatNumberString</name> <function-class>com.silverpop.marketer.ui.G11nJSTLCustomFunctions</function-class> <function-signature>java.lang.String formatNumberString( java.lang.String, java.lang.String)</function-signature> </function> <function> <description>Format Date with specified locale</description> <name>formatDate</name> <function-class>com.silverpop.marketer.ui.G11nJSTLCustomFunctions</function-class> <function-signature>java.lang.String formatDate(java.util.Date, java.lang.String)</function-signature> </function> <function> <description>Format Percent Number with specified locale</description> <name>formatPercentNumber</name> <function-class>com.silverpop.marketer.ui.G11nJSTLCustomFunctions</function-class> <function-signature>java.lang.String formatPercentNumber( java.lang.Number, java.lang.String)</function-signature> </function> </taglib>
所有的关键信息都包含在 function 标签中,映射了函数名和处理的 Java 程序类名。另外需要注意的是:函数中的参数名需要使用全称,如 java.lang.String,如果使用 String,在调用这个自定义函数的时候将会出错。
第 3 步:使 TLD 和处理程序类可访问
使得自定义的这个类中方法可以被 Web 应用程序访问,有两种方法:第一种方法可以将类和 TLD 打包到一个 JAR 文件中,再将这个 JAR 文件储存在 Web 应用程序的 lib 目录中;第二种方法就是将类文件分散地放到 classes 子目录中,并将 TLD 文件放到 Web 应用程序的 WEB-INF 目录下面的某一位置,比如 src/web/WEB-INF/tld/cf. tld,在我们的例子中,为了方便起见,使用了第二种方法,将 cf.tld 直接放在了 src/web/WEB-INF/tld 目录中。
第 4 步:在 JSP 中调用自定义函数(Custom Function)来格式化日期时间和数字等
在前面的三个步骤中,定义了处理程序类和静态处理方法,并且创建了 TLD 文件,同时定义了处理程序类和标签之间的映射并保证类和标签在应用程序中都是可访问的。下面就是在 JSP 中访问创建的自定义函数。首先需要导入 TLD 文件的信息,接着在 JSP 中直接调用自定义函数,调用格式如 value="${cf:formatDate(myVar.myDate,"zh_CN")}"。清单 4 是在 JSP 中调用 JSTL 自定义函数的示例。
test.jsp <%@ taglib prefix="cf" uri="/WEB-INF/tld/cf.tld" %> ... <html> <head> <title>Custom Function for G11n Formatting example</title> </head> <body> <div class="value"><c:out value="${cf:formatNumber(myVar.myNumber,"zh_CN")}"/></div> <div class="value"><c:out value="${cf:formatDate(myVar.myDate,"zh_CN")}"/></div> </body> </html>
在 JSP 中有很多地方会用到表格来显示数据,正如标签<display:table>和<display:column>所显示的一样,会将 Java bean 转换成 HTML 表格,这种情况下就不能使用 JSTL 来格式化日期和数字。解决办法就是创建一个 decorator 类来显示表格,在 decorator 类里面使用 Java 格式化 API 来事先格式化好数据,然后在 JSP 中显示,下面的步骤将详细介绍如何用 decorator 类来解决问题。清单 5 的示例中需要格式化分数(score)。
<display:table name="testTable" scope="request" width="100%" class="dataGrid" cellpadding="0" cellspacing="0" border="0" defaultsort="0"> <display:column property="score" title="Student Score" sortable="false" headerClass="sortable"/> <display:column property="name" title="Student Name" sortable="false" headerClass="sortable" /> <display:column property="description" title="description" sortable="false" headerClass="sortable" /> </display:table>
第一步:搜索 JSP 中表名
在整个 Java 工程中,搜索 table 的名字“testTable”,以确定所使用 bean 的类型和 bean 所在的类名,如清单 5 中搜索到的 bean 类名为TestTableBean
。
第二步:为表“testTable”创建 decorator 类
decorator 类可以继承已经封装好的数字格式化接口,这样可以在 decorator 中的 get properties 方法中直接调用格式化接口。如清单 6 所示。
//*****testTableWrapper.java****** public class testTableWrapper extends testLocalizedTableWrapper { /**构造函数*******/ public testTableWrapper () { super(); } /**公共方法*****/ public String getName(){ TestTableBean testTable = (TestTableBean) super.getCurrentRowObject(); return testTable.getName(); } public String getDescription(){ TestTableBean testTable = (TestTableBean) super.getCurrentRowObject(); return testTable.getDescription(); } public String getScore(){ TestTableBean testTable = (TestTableBean) super.getCurrentRowObject(); return this.formatNumber(testTable.getscore()); } } //***** testLocalizedTableWrapper.java****** public String formatNumber(Number number, Locale locale) { NumberFormat numberFormat = NumberFormat.getNumberInstance(locale); return numberFormat.format(number); }
第三步:在 JSP 中添加 decorator 类
这样就可以对 table 中的 column 值进行格式化显示了,如清单 7 所示。
<display:table decorator="com.src.i18nDemo.testTableWrapper" name="testTable" scope="request" width="100%" class="dataGrid" cellpadding="0" cellspacing="0" border="0" defaultsort="0"> <display:column property="score" title="Student Score" sortable="false" headerClass="sortable"/> <display:column property="name" title="Student Name" sortable="false" headerClass="sortable" /> <display:column property="description" title="description" sortable="false" headerClass="sortable" /> </display:table>
本文对在 JSP 中利用 JSTL 实现国际化进行了总结和概述,并对在使用 JSTL 库中的 fmt 标签 fmt:setlocale、fmt:formatDate 和 fmt:formatNumber 时存在的问题提出了解决方法。希望这篇文章能为正在开发国际化 Web 应用程序的读者提供一定的参考和帮助。