如果应用系统是面向多种语言的,编程时就不得不设法解决国际化问题,包括操作界面的风格问题、提示和帮助语言的版本问题、界面定制个性化问题等。 |
由于Java语言具有平台无关、可移植性好等优点,并且提供了强大的类库,所以Java语言可以辅助我们解决上述问题。Java语言本身采用双字节字符编码,采用大汉字字符集,这就为解决国际化问题提供了很多方便。从设计角度来说,只要把程序中与语言和文化有关的部分分离出来,加上特殊处理,就可以部分解决国际化问题。在界面风格的定制方面,我们把可以参数化的元素,如字体、颜色等,存储在数据库里,以便为用户提供友好的界面;如果某些部分包含无法参数化的元素,那么我们可能不得不分别设计,通过有针对性的编码来解决具体问题。 |
Java类包 |
在用Java解决国际化问题的过程中,可能利用到的主要的类都是由java.util包提供的。该类包中相关的类有Locale、ResourceBundle、ListResourceBundle、PropertyResourceBundle等,其继承关系如下图所示。 |
其中各类提供的主要功能如下: |
Locale:该类包含对主要地理区域的地域化特征的封装。其特定对象表示某一特定的地理、政治或文化区域。通过设定Locale,我们可以为特定的国家或地区提供符合当地文化习惯的字体、符号、图标和表达格式。例如,我们可以通过获得特定Locale下的Calendar类的实例,显示符合特定表达格式的日期。 |
ResourceBundle:该类是一个抽象类,需要通过静态方法ResourceBundle.getBundle()指定具体实现类或属性文件的基本名称。基本名称会协同指定的或默认的Locale类,决定具体调用的类或属性文件的唯一名称。例如:指定基本类或属性文件名称为TestBundle,而指定的Locale是CHINESE,那么最适合匹配的类名称为TestBundle_zh_CN.class,而最佳匹配属性文件名称为TestBundle_zh_CN.properties。按照Java Doc和相关文档的要求,如果该类或属性文件没有找到,系统会查找近似匹配(主文件名依次为TestBundle_zh和TestBundle的类或属性文件)。该类提供的getKeys()方法用于获得所有成员的键名,并提供handleGetObject方法获得指定键的对应元素。 |
ListResourceBundle:该类继承ResourceBundle类,主要是增加了一些便于操作的成分,但还是抽象类。如果希望使用类的方式实现具体的ResourceBundle,一般情况下最好继承这个类。 |
PropertyResourceBundle:该类也继承ResourceBundle类,可以实例化。该类的行为特征如同java.util.properties类,可以从输入流中获得具体属性对。 |
如果涉及日期和时间显示等问题时,可以利用java.text包以及java.util包中的TimeZone、SimpleTimeZone和Calendar等类进行辅助处理。 |
参数化解决方法 |
在具体应用时,可以把具体国家或地区特征中可以参数化的部分放在经过特殊命名的属性文件中,在确定具体的Locale后,通过PropertyResourceBundle类读取相应的属性文件,实现国际化特征。 |
使用PropertyResourceBundle类获得当地版本的国际化信息,部分代码如下: |
…… |
public static final String BASE_PROP_FILE = |
“DISP”; |
public static final String SUFFIX = |
“.properties”; |
locale = Locale.getDefault(); |
String propFile = BASE_PROP_FILE + “_” + locale.toString()+ SUFFIX; |
ResourceBundle rb; |
try { |
File file = new File(propFile); |
if (file.exists()) { |
is = new FileInputStream(file); |
rb = new PropertyResourceBundle(is); |
if (rb == null) System.out.println(“No Resource”); |
} |
} catch (IOException ioe) { |
System.out.println(“Error open file named ” + propFile); |
} |
Enumeration e = rb.getKeys(); |
while (e.hasMoreElements()){ |
key = (String)e.nextElement(); |
value = (String)rb.handleGetObject(key); |
System.out.println(“KEY: ” + key + |
“/t/t Value: ” + value); |
} |
…… |
DISP_zh_TW.properties文件的具体内容如下: |
Key1=/u53ef/u4ee5 |
Key2=/u64a4/u9500 |
等号后面是利用native2ascii程序转化后的繁体汉字,如果不进行转化,系统可能显示乱码。 |
处理提示和帮助 |
对于提示语言和帮助文件部分,可以把语言映射放在属性文件或者ListResourceBundle类的子类中。下面程序是一个Servlet,它通过接受客户端的选择,把特定语言和字符版本的信息返回到客户端。 |
…… |
public class ProcessServlet extends HttpServlet |
{ //默认语言为中文 |
public static final String DEFAULT_LANGUAGE = “zh”; |
//默认字符集为简体中文 |
public static final String DEFAULT_COUNTRY = “CN”; |
public void service(HttpServletRequest req, |
HttpServletResponse res) throws IOException, ServletException { |
HttpSession session = req.getSession(true); |
// 从客户端收到的指定语言和字符的参数应当与Sun公司相关规定一致 |
String lang = req.getParameter |
(“language”); |
String country = req.getParameter |
(“country”); |
if (lang == null) |
{ |
//如果没有收到参数,就试图从Session里获得 |
lang = (String) session.getAttribute |
(“language”); |
country = (String) session.getAttribute |
(“country”) |
} else { |
session.setAttribute(“language”, lang); |
session.setAttribute(“country”, country); |
} |
if (lang == null) |
{ |
//如果无法从上述手段得到语言和字符信息,就使用默认值 |
lang = DEFAULT_LANGUAGE; |
country = DEFAULT_COUNTRY |
session.setAttribute(“language”, lang); |
session.setAttribute(“country”, country); |
} |
Locale locale = null; |
ResourceBundle bundle = null; |
try { |
locale = new Locale(lang, country); |
} catch (Exception e) { |
System.out.println(“No locale with” + |
country + “_” + lang); |
locale = Locale.getDefault(); |
} |
try { |
bundle = ResourceBundle.getBundle( |
“DisplayList”, locale); |
} catch( MissingResourceException e) { |
System.out.println( “No resources available for locale ” + locale); |
bundle = ResourceBundle.getBundle |
(“DisplayList”, Locale.US); |
} |
res.setContentType(“text/html”); |
PrintWriter out = res.getWriter(); |
out.println(“<html>”); |
out.println(“<head>”); |
String title = bundle.getString(“title”); |
String welcome =bundle.getString |
(“welcome”); |
String notice = bundle.getString(“notice”); |
out.println(“<title>”+ title + |
“</title>”); |
out.println(“</head>”); |
out.println(“<body bgcolor=/” |
white/“>”); |
out.println(“<h3>” + welcome + |
“</h3>”); |
out.println(“<br>”); |
out.println(“<b>” + notice + |
“</b>”); |
out.println(“</body>”); |
out.println(“</html>”); |
} |
} |
上述Servlet使用的属性文件(DisplayList_zh_CN. |
properties)内容如下: |
title=中文版 |
welcome=这是简体中文版面 |
notice=简体中文测试成功 |
注意:该文件直接采用了中文,而不是经过转化的Unicode编码,这是由于大多数Web服务器不需要上述转化。 |
在实际使用中,如果Web服务器支持Servlet 2.3规范(如jakarta-tomcate 4.0),那么上面提到的Servlet应当稍加改变,以作为其他Servlet的处理器使用。另外,如果把ResourceBundle的特定版本存放在无状态会话Bean中,就可以在一定程度上提高程序效率。 |
小 结 |
笔者在实际测试中发现了如下问题,其中部分问题得到了解决: |
1. 对于显示字符出现乱码的问题,如果是通过属性文件实现国际化解决方案,那么可能是直接在属性文件中写入了非标准ASCII文字。解决方法是利用JDK提供的工具native2ascii.exe扫描所有属性文件,用扫描结果覆盖原有文件内容。如果我们是利用类文件实现转换方案,那么需要重新编译相关类文件,并在编译时指定编码集。例如,编译使用国标码的类文件,采用的编译命令如下: |
javac -encoding GB2312 your_java_file |
2. 虽然Sun宣称,在ResourceBundle类的实例化过程中,该类会查找与指定的基础类绝对匹配和尽量与指定的Locale属性相匹配的类。例如:如果我们指定ResourceBundle基础类为TestBundle,而Locale中指定使用zh_CN(*地区简体中文),那么如果系统找不到TestBundle_zh_CN,系统应当顺次查找TestBundle_zh、TestBundle。但是笔者在系统开发过程中发现,该匹配没有产生任何实际效果。 |
笔者的测试平台是Windows 2000 Server,没有配置任何Service Pack,使用的JDK版本是1.3.0版本。笔者试图通过查看JDK目录下src.jar中附带的源码找到引起问题的原因,但是发现有关的操作被封装在sun.misc包中,而src.jar文件没有提供该包中任何类的源码。本文把这个问题提出来,希望与有关开发人员一起探讨。 |