免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 948 | 回复: 0
打印 上一主题 下一主题

J2EE业务系统的国际化 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-01-08 10:51 |只看该作者 |倒序浏览

基于B/S的系统通常都要求支持国际化,常用的方法是识别HTTP头的Accept-Language确定浏览器语言类型,然后使用ResourceBundle载入对应语言类型的properties格式化页面并返回。通常需要国际化的信息都是面向用户的信息,一是页面标签,二是业务信息。
页面标签国际化
页面标签国际化指的是页面上静态的文本的国际化,可以一个页面对应一个properties文件,该文件储存页面标签对应的文本信息,如一个登陆页面有两个文字标签“用户名”和“密码”,可以使用“username”和“password”来表示,这样中文的properties文件是:
# login_zh.properties
username = 用户名:
password = 密码:
英文的properties文件是:
# login_en.properties
username = User Name:
password = Password:
通过HTTP头Accept-Language识别出语言类型后,载入对应语言的properties文件,格式化登陆页面,返回给用户。

Properties文件使用KEY – VALUE表存储数据,用K表示KEY,V表示VALUE,h表示查找函数,那么:
V = h (K)

可以一个页面对应一个properties文件,也可以多个页面对应一个properties文件,当多个页面对应一个properties文件时,需要K命名管理,否则多个页面可能有K冲突,可以采用命名空间的方式来管理,比如login.jsp页面的位置是/profile/login.jsp,那login.jsp的properties文件里面的K命名都加上login.jsp所在的路径,并且用“.”分隔,这时properties文件为:
# login_en.properties
profile.login.username = User Name:
profile.login.password = Password:
这样处理以后,任何页面的properties文件都可以根据需要分割和合并。比如下面的树状结构:
/profile
  |-- admin
  |      |-- login.jsp   ---------> login.properties
  |      |-- logout.jsp  ---------> logout.properties
  |      |-- error.jsp    --------> logout.properties
  |-- others

附带一段登陆页面login.jsp的代码,为了便于理解,将页面格式化的代码放在了login.jsp里。
!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
%@ page contentType="text/html;charset=UTF-8"%>
%
    java.util.Locale locale = request.getLocale();
    java.util.ResourceBundle res = java.util.ResourceBundle.getBundle("login.properties", locale);
%>
html>
head>
meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
title>/title>
/head>
body>
%=res.getString("username")%>input type="text" name="username"/>
%=res.getString("password")%>input type="password" name="password"/>
/body>
/html>

业务信息国际化
业务信息指的是后台业务产生的信息,通常有提示信息和异常信息两种。
提示信息是进行一个操作时,正常操作结果的信息,比如成功买入一本¥100的书;成功转帐¥100到另一个账户等。
异常信息是进行一个操作时,非正常操作结果的信息,比如买入¥100的一本书,但是账户只有¥50;或者购买书时应该输入“¥100”金额,但是错误输入了“abc”这样的文本。
提示信息和异常信息没有明显的界限,只是按照正常结果和非正常结果进行了一个大致的划分。

以买一本¥100的书为例,先看看提示信息:
成功买入该书,消费¥100.00整!
程序里面是怎么做的呢,可能是这样的:
double sum = 100.00;
String info = "成功买入该书,消费¥%.02f整!";
info = String.format(info, sum);
然后将info通过合适的方式显示出来。

再看看如果我账户只有¥50,而我要买%100的书,再看看异常信息:
购买该书需要¥100.00,你的账户余额只有¥50.00!
程序可能这样实现的:
double blance = 50.00;
double sum = 100.00;
if (blance  sum)
{
    throw new Exception(String.format("购买该书需要¥%.02f,你的账户余额只有¥%.02f!", blance, sum));
}
然后将抛出的异常通过合适的方式显示出来。

如果用户输错金额,异常信息为:
无法将“abc”转换为数字!
程序可能这样实现的:
String sum_str = "abc";
double sum = 0.0;
try
{
    sum = Double.parseDouble(sum_str);
}
catch(NumberFormatException err)
{
    throw new Exception(String.format("无法将“%s”转换为数字!", sum_str));
}
然后将抛出的异常通过合适的方式显示出来。

看看上面三个例子的特征,都是静态的格式化文本如“成功买入该书,消费¥%.02f整!”,附带一系列格式化参数如sum,最后的信息是由静态的格式化文本和格式化参数合成的。

可以用T表示静态的格式化文本,使用P1, P2, P3 … 组成的集合{P}表示格式化参数,f为格式化函数,那么格式化后的信息I为:
I = f (T, {P})
T是静态的跟语言有关的文本,{P}是由业务系统运行时决定的数据,并且是语言无关的,使用不同语言的T,就可以得到不同语言的输出信息I。

上面的三个例子中的静态文本就是T,{P}是String.format里面的静态文本后面的参数列表,f是String.format。

因为T是静态的而且跟语言相关的,所以可以用properties来保存,使用properties的查找函数h来得到T:
T = h (K)
K被硬编码到程序中,唯一并且是语言无关的,这样I:
I = f (h (K), {P})
也就是程序知道了用户的语言环境后,载入对应properties文件,使用K来查找得到T,然后用T将P格式化为最后的信息I。

在实际使用时,通常先构造一个支持国际化的接口,然后将提示信息和异常信息按业务系统分类,分类方法可以采用业务系统所在模块和包的结构,使用上面的例子构造一颗继承树:
IFormatted
  |-- FormattedInformation
  |        |-- ExchangeInformation
  |-- FormattedException
  |        |-- InvalidException
  |        |-- ExchangeException
因为{P}是不定长度的集合,可以使用数组参数,更为方便的方法是使用Java的不定参数列表,下面给出一个参考实现:
// IFormatted.java
public interface IFormatted
{
    public String getI();
}
// FormattedInformation.java
import java.util.Locale;
import java.util.ResourceBundle;
public class FormattedInformation implements IFormatted
{
    private Locale lc;
    private String K;
    private Object[] P;
    private String I;
    public FormattedInformation(Locale lc, String K, Object ... P)
    {
        this.lc = lc;
        this.K = K;
        this.P = P;
    }
    public String getI()
    {
        if (null == I)
        {
            ResourceBundle res = ResourceBundle.getBundle("information", lc);
            String T = res.getString(K);
            I = String.format(T, P);
        }
        return I;
    }
}
// ExchangeInformation.java
import java.util.Locale;
public class ExchangeInformation extends FormattedInformation
{
    public ExchangeInformation(Locale lc, String K, Object ... P)
    {
        super(lc, K, P);
    }
}
// FormattedException.java
import java.util.Locale;
import java.util.ResourceBundle;
public class FormattedException extends Exception implements IFormatted
{
    private Locale lc;
    private String K;
    private Object[] P;
    private String I;
    public FormattedException(Locale lc, String K, Object ... P)
    {
        super(K);
        this.lc = lc;
        this.K = K;
        this.P = P;
    }
    public String getI()
    {
        if (null == I)
        {
            ResourceBundle res = ResourceBundle.getBundle("exception", lc);
            String T = res.getString(K);
            I = String.format(T, P);
        }
        return I;
    }
}
// InvalidException.java
import java.util.Locale;
public class InvalidException extends FormattedException
{
    public InvalidException(Locale lc, String K, Object ... P)
    {
        super(lc, K, P);
    }
}
// ExchangeException.java
import java.util.Locale;
public class ExchangeException extends FormattedException
{
    public ExchangeException(Locale lc, String K, Object ... P)
    {
        super(lc, K, P);
    }
}

在这个参考实现中,载入properties的操作是在FormattedInformation和FormattedException基类中进行的,FormattedInformation载入information_(locale).properties,FormattedException载入exception_(locale).properties,这样可以在树较深时,避免大量重复代码,并且减少properties的文件数量;参考实现中出于直观的考虑没有进行错误处理,比如K不存在时的处理,实际使用时必须添加。

在实际的业务系统中,上面的树结构可以继续扩展,properties文件也可以根据实际需要分割和合并,只要K的命名不容易混淆和冲突。

异常信息除了上面说的面向用户的异常信息,还有系统异常信息,比如数据库连接被断开、系统内存溢出等,这种情况一般不需要国际化而只需要告诉用户系统致命错误,错误的相信信息写入日志,供系统维护人员纠正错误。

特殊情况
有一类特殊情况,如日期和货币,以日期为例,中国采用“2008年1月8日星期二”表示日期,而西欧采用“Tue 01/08/2008”表示日期,这样使用String.format作为格式化函数时比较难以拆分出T和{P},提供几种供参考的解决方法:
1. 改进格式化函数
因为主要的问题是String.format对日期、货币等特殊数据的格式化支持较差,所以可以使用改进的format函数,将T的格式化通配符进行扩展,比如用两个大括号“{}”包围一个日期格式符,那么中国的日期可以表示为“{yyyy年MM月dd日W}”,西欧日期可以表示为“{w dd/MM/yyyy}”。
       这种方法可能需要大量的算法,并且通用性不强。
2. 统一格式化
       统一格式化的基本思路是基于日期、货币等特殊数据的表示方法通常在一个业务系统的一种语言里面是一致的,比如采用“Tue 01/08/2008”的方式时,其他日期也会采用“Tue 01/08/2008”这种方式,所以可以每种语言都有统一的数据格式,这个格式可以被预先定义而不用加到{P}中,{P}里面的参数是已经被格式化后的字符串。
       以上面的FormattedException为例,如果{P}中有日期,可以先把这个日期格式化:
for (int i = 0;i  P.length;i ++)
    {
        Object obj = P;
        if (obj instanceof Date)
        {
            DateFormat f = new SimpleDateFormat(res.getString("global.date.format"), lc);
            P = f.format(obj);
        }
}
       上面的“global.date.format”是properties里面定义的lc这种语言里面日期的格式化通配符,JDK预定义了一系列格式化规范,DateFormat是其中之一,推荐这种方法,可以减少工作量,最大限度利用已有资源,并且容错性好。

总结
本文主要讲述了使用静态文本和动态参数分离的方法来实现业务系统的国际化,主要目的是能帮助开发者清理思路,减少工作量,节省时间,希望对开发者有一定的启发和帮助,里面所讲都是一些经验总结,如果有其他经验和建议,欢迎交流。



本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/52224/showart_459452.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP