OperaMasks 2.0的神奇魔力之二:国际化

1. 前言

本教程介绍在 OperaMasks 2.0中,是如何简化国际化多语言相关工作的。在阅读本文之前,我建议你首先阅读第一篇文章:

2. 常规国际化的做法

假设我们现在希望让 Calculator 这个示例支持中英文,让我们先回顾一下常规JSF的实现手段:
首先,我们需要准备两个资源文件,分别是:
 
   
#demo.LocalStrings_en_US.properties
first.label=First:
second.label=Second:
result.label=Result:
add.label= +
subtract.label= -
multiply.label= *
divide.label = /
 
   
#demo.LocalStrings_zh_CN.properties
first.label=数值一:
second.label=数值二:
result.label=结果:
add.label= +
subtract.label= -
multiply.label= *
divide.label = /
当然,我们需要通过 Ant 将上述的LocalStrings_zh_CN.properties进行 native2ascii 的转换,这是基础知识,不是本文介绍的重点。
为了使用上述资源文件,我们的页面要改成这样:
 
< f:view xmlns ="http://www.w3.org/1999/xhtml" xmlns:f ="http://java.sun.com/jsf/core"  
   xmlns:w ="http://www.apusic.com/jsf/widget" xmlns:layout ="http://www.apusic.com/jsf/layout"  
   renderKitId ="AJAX" xmlns:h ="http://java.sun.com/jsf/html" >  
   < f:loadBundle basename ="demo.LocalStrings" var ="msgs" />  
   < w:page title ="Calculator" >  
     < w:form id ="calc" >  
       < layout:panelGrid columns ="3" >  
         < h:outputLabel for ="first" value ="#{msgs['first.label']}" />  
         < w:textField id ="first" />  
         < h:message for ="first" value ="#{msgs['second.label']}" />  
         < h:outputLabel for ="second" />  
         < w:textField id ="second" />  
         < h:message for ="second" />  
         < h:outputLabel for ="result" value ="#{msgs['result.label']}" />  
         < h:outputText id ="result" />  
       </ layout:panelGrid >  
       < br />  
       < layout:panelGrid columns ="4" >  
         < w:button id ="add" value ="#{msgs['add.label']}" />  
         < w:button id ="subtract" value ="#{msgs['subtract.label']}" />  
         < w:button id ="multiply" value ="#{msgs['multiply.label']}" />  
         < w:button id ="divide" value ="#{msgs['divide.label']}" />  
       </ layout:panelGrid >  
     </ w:form >  
   </ w:page >  
</ f:view >  
 
经过对页面进行上述修订,在中文环境下,页面运行效果如下图所示:
calc_zh_CN.jpg
 
而在英文环境中,运行效果是这样的:
calc_en_US.jpg
 
看上去好像也完成了我们的需求,但回过头来看看我们的页面,已经被我们改的面目全非。倘若这个页面是由美工设计的,请问,她知道什么叫做资源文件吗?她知道什么是 "#{msgs['add.label']}" 吗?
让我们忘掉这种丑陋的做法吧,看看 OperaMasks 会带来怎样的魔力!

3. OperaMasks 2.0 的国际化处理

首先,让我们把页面恢复成原先那整洁的基于IoVC原理的页面:
 
   
< f:view xmlns ="http://www.w3.org/1999/xhtml" xmlns:f ="http://java.sun.com/jsf/core"
   xmlns:w ="http://www.apusic.com/jsf/widget" xmlns:layout ="http://www.apusic.com/jsf/layout"
   renderKitId ="AJAX" xmlns:h ="http://java.sun.com/jsf/html" >  
   < w:page title ="Calculator" >
     < w:form id ="calc" >
       < layout:panelGrid columns ="3" >
         < h:outputLabel for ="first" />
         < w:textField id ="first" />
         < h:message for ="first" />
         < h:outputLabel for ="second" />
         < w:textField id ="second" />
         < h:message for ="second" />
         < h:outputLabel for ="result" />
         < h:outputText id ="result" />
       </ layout:panelGrid >
       < br />
       < layout:panelGrid columns ="4" >
         < w:button id ="add" />
         < w:button id ="subtract" />
         < w:button id ="multiply" />
         < w:button id ="divide" />
       </ layout:panelGrid >
     </ w:form >
   </ w:page >
</ f:view >
我们继续保留 LocalStrings_en_US.properties 以及 LocalStrings_zh_CN.properties这 两个资源文件,但为了做到多语言,我们唯一需要做的事情是,将这两个资源文件的内容修订成如下内容:
 
   
#demo.LocalStrings_en_US.properties
CalcBean.first.label=First:
CalcBean.second.label=Second:
CalcBean.result.label=Result:
CalcBean.add.label= +
CalcBean.subtract.label= -
CalcBean.multiply.label= *
CalcBean.divide.label = /
 
   
#demo.LocalStrings_zh_CN.properties
CalcBean.first.label=数值一:
CalcBean.second.label=数值二:
CalcBean.result.label=结果:
CalcBean.add.label= +
CalcBean.subtract.label= -
CalcBean.multiply.label= *
CalcBean.divide.label = /
换言之,我们只是在资源串中的key上,加了一个限定词:CalcBean,然后,让我们运行这个页面。
你会发觉:哇,页面已经是多语言的了,这就是你想要的!
但回顾你做的工作,你会发觉,这是多么的简单,多么的享受!

4. OperaMasks的扩展能力

我们现在希望对这个示例扩展一下:当鼠标移到第一个<w:textFiled>时,希望能够显示提示“请输入第一个参数”;当鼠标移到add button时,希望能够显示提示“将这些数值相加”。那么,我们只需要扩展一下这两个资源文件:
 
   
#demo.LocalStrings.properties
CalcBean.first.description=Please input the first number
CalcBean.add.description=Add these numbers
 
   
#demo.LocalStrings_zh_CN.properties
CalcBean.first.description=请输入第一个数值
CalcBean.add.description=将数值相加
然后,我们重新运行此页面,你会发觉,你预期的效果已经达到了!
calc_zh_CN_tooltip.jpg
 
calc_en_US_tooltip.jpg
 
我们还可以做的更好!
假设我们有这样一种场景:“add”这个Button,在中文环境下显示“加”,在英文环境下显示“Plus”,由于语言的差异, 导致在不同语言下,button的宽度发生变化。我们希望,在中文环境下,此按钮的宽度为 30,而在英文环境下,此按钮的宽度为 60。那么,我们该怎么办?
解决这个问题的思路,无非要把宽度作为一个多语言资源拿出来放到properties文件中,但我们要怎么做呢?难道在页面中写入: <w:button id="add" minWidth="#{msgs['add.minWidth]}"/>?这样一来,岂非很丑陋?在 AOM 2.0中,我们只需要更改一下LocalStrings_zh_CN.properties 文件:
 
   
#demo.LocalStrings_zh_CN.properties
CalcBean.add.minWidth=30
CalcBean.subtract.minWidth=30
CalcBean.multiply.minWidth=30
CalcBean.divide.minWidth=30
我们来看一下运行效果:
calc_zh_CN_minWidth.jpg
我们再更改一下LocalStrings_en_US.properties:
 
   
#demo.LocalStrings_en_US.properties
CalcBean.add.minWidth=60
CalcBean.subtract.minWidth=60
CalcBean.multiply.minWidth=60
CalcBean.divide.minWidth=60
注意,上述资源文件中设置最小宽度为60,比中文环境下的设置大了一倍,那么,在英文环境下,效果如下:
calc_en_US_minWidth.jpg
是不是非常之简单?让我们再来看一看更高级的用法。

5. 在程序中注入资源文件

假设我们现在有这样一种需求:当用户点击某一个操作时,result并不仅仅简单的显示一个结果, 我们还希望能够显示用户的操作是什么,譬如,当用户执行 1 + 2 操作时,中文环境下显示:"数据 1 加 数据 2 等于 3",英文环境下显示:"Number 1 add number 2 equals 3"。换言之,我们现在要对多语言字符串进行参数化处理。在 OperaMasks 2.0 中是怎样做的呢?
首先,我们需要把页面改一下:
 
< f:view xmlns ="http://www.w3.org/1999/xhtml" xmlns:f ="http://java.sun.com/jsf/core"
   xmlns:w ="http://www.apusic.com/jsf/widget" xmlns:layout ="http://www.apusic.com/jsf/layout"
   renderKitId ="AJAX" xmlns:h ="http://java.sun.com/jsf/html" >
   < w:page title ="Calculator" >
     < w:form id ="calc" >
       < layout:panelGrid columns ="3" >
         < h:outputLabel for ="first" />
         < w:textField id ="first" />
         < h:message for ="first" />
         < h:outputLabel for ="second" />
         < w:textField id ="second" />
         < h:message for ="second" />
         < layout:cell colspan ="3" rowspan ="1" >
           < h:outputText id ="resultLabel" />
         </ layout:cell >
       </ layout:panelGrid >
       < br />
       < layout:panelGrid columns ="4" >
         < w:button id ="add" />
         < w:button id ="subtract" />
         < w:button id ="multiply" />
         < w:button id ="divide" />
       </ layout:panelGrid >
     </ w:form >
   </ w:page >
</ f:view >
 
请注意,我们将<h:outputText>进行了修订,它占据了三列,id改为resultLabel。
然后,我们需要修订 CalcBean,修订如下:
 
   
@ManagedBean(scope = ManagedBeanScope.REQUEST)
public class CalcBean {

  @Bind
   private double first = 22.0;

  @Bind
   private double second = 7.0;

  @Bind
   private String resultLabel;

   /**
  * 注入资源文件
  */

  @LocalString
   private Map<String,String> messages;

   /**
   * 用来保存计算结果
   */

   private double result = 0;

   /**
   * 用来保存用户的操作符
   */

   private String operator = "";

  @Action
   public void add() {
    result = first + second;
   operator = "+";
  }

  @Action
   public void subtract() {
    result = first - second;
    operator = "-";
  }

  @Action
   public void multiply() {
    result = first * second;
    operator = "*";
  }

  @Action
   public void divide() {
    result = first / second;
    operator = "/";
  }
  
  @BeforeRender
   private void beforeRender( boolean isPostBack) {
    resultLabel = MessageFormat.format(messages.get( "resultLabel"), first, operator, second, result);
  }
}
请注意,在CalcBean中,我们声明了两个临时变量 result 和 operator 来保存用户的计算结果及操作符,另外,声明了一个 @BeforeRender 的方法,意思就是告诉OperaMasks,在你决定渲染前,请调用此方法。 我们在这个方法中,设置 resultLabel的具体值。那么,资源文件是怎样获取得呢?非常简单,只需要在前面声明一个private @LocalString Map<String,String> messages 即可。
再来看一下我们的资源文件:
 
   
#demo.LocalStrings_en_US.properties
CalcBean.resultLabel=Number {0} {1} number {2} equals {3}
 
   
#demo.LocalStrings_zh_CN.properties
CalcBean.resultLabel=数值 {0} {1} 数值 {2} 等于 {3}
我们在获取上述资源以后,只需要对其进行一下 format,将参数传递进去,即可获得我们期望的结果:
calc_zh_CN_msg_para.jpg
 
calc_en_US_msg_para.jpg
 
等等,这里好像有一个疑问:你只是帮我把资源文件的获取简化了,但在对资源字符串的解析上,OperaMasks好像并没有带来任何改进, 我还是需要调用 MessageFormat.format 方法,在这方面上,OperaMasks能否带来一些便利呢?要知道,我可是非常懒惰的。
OK,我只能说:OperaMasks 的 ELite 对资源文件的处理是非常简单的,简单到令人之发指,但同时,这也超出了本章所要阐述的范围,且留待下一章节分解吧。

6. 总结

本章节,我们体会了 OperaMasks 2.0 对国际化的处理,我想,我们已经不需要再去阐述 OperaMasks 和常规处理模式的差异了,再次重申:OperaMasks,确实是为“懒人”准备的。