《Java程序设计语言 基础》第 2 部分
第 6 课:国际化

[<<后退] [目录] [下一课>>]

越来越多大大小小的公司都为了在世界各地开展自己的业务而使用不同语言。 有效的交流会带来良好的商机,于是对应用程序实施本地化就会给人们带来利益, 使他们能够更好地交流、增加客户对他们的满意程度。

JavaTM 2 平台提供有国际化功能,这些 功能使您能够从应用程序中提取出依赖于本地文化背景的信息(国际化),并使 之适应于任何您所需要的文化地区(本地化)。

本课采用了 第 2 部分、第 5 课:集合中的两 个客户程序,然后对它们实施国际化处理、使其文字适用于法国、德国和美国。


识别依赖于文化背景的数据

第一件要做的事情是识别您应用程序中依赖于文化背景的数据。依赖于文化背 景的数据是指一种文化或国家与另一种文化或国家不同的数据。文字是依赖于文 化背景的数据的最明显和最普遍的示例,当然诸如数字格式、声音、时间和日期 等其它方面也是必须考虑的。

RMIClient1.javaRMIClient2.java 类有以下可见于终端用户的依赖于文化背景的数据:

 

  • 标题和标签(窗口标题、列标题、左列标签)
  • 按钮(Purchase(购买)、Reset(重置)、View(查看))
  • 数字(总数量和总费用的值)
  • 出错信息

尽管应用程序有一个服务器程序,但该服务器程序并没有国际化和本地化。服 务器程序中唯一可见的依赖于文化背景的数据是错误信息。

服务器程序仅在一个地方运行,假设除能理解错误信息所用语言的系统管理员 之外的其它人都不能看见它。本例中,错误信息所采用语言为英语。

RMIClient1RMIClient2 中的所有错误信息 都在 trycatch 程序块中处理,如下文中的 print 方法中所示。这样,您就可以把出错信息“No data available”翻译为其它语言的译文。


 public void print(){
    if(s!=null){
      Iterator it = s.iterator();
      while(it.hasNext()){
        try{
          String customer = (String)it.next();
          System.out.println(customer);
        }catch (java.util.NoSuchElementException e){
          System.out.println("No data available");
        }
      }
    }else{
      System.out.println("No customer IDs available");
    }
  }

这个 print 方法中的程序代码已经在其下述的 throws 子句中定义了异常事件,不过这样您就不能访问错误信息文本,它是在方法试图 访问集合中的不可用数据时所抛出的。

在此情况下,无论应用程序在何处使用该出错信息,系统为该出错信息所提供 的文字都会被发送到命令行。此时的问题在于,最好始终在任何可行位置使用 trycatch 程序块;这样,一旦需要,应用程序 就可以被国际化,而您则可以对出错信息的文字实施本地化。

  public void print() 
	throws java.util.NoSuchElementException{
    if(s!=null){
      Iterator it = s.iterator();
      while(it.hasNext()){
        String customer = (String)it.next();
        System.out.println(customer);
      }
    }else{
      System.out.println("No customer IDs available");
    }
  }

以下列出了用户可见的进而需要国际化和本地化的标题、标签、按钮、数字和 错误文本。这些都摘自RMIClient1.javaRMIClient2.java

  • 标签: Apples(苹果)、Peaches(桃)、Pears(梨)、Total Items(总数量)、Total Cost(总费用)、Credit Card(信用卡)、Customer ID(客户 ID 号)
  • 标题: Fruit $1.25 Each(水果单价 1.25 美元)、Select Items(选择订购项目)、Specify Quantity(指定数量)
  • 按钮: Reset(重置)、View(查看)、Purchase(购买)
  • 数字值: Value for total items(总数量的值)、Value for total cost(总费用的值)
  • 错误: Invalid Value(无效值)、Cannot send data to server (不能发送数据到服务器)、Cannot look up remote server object(无法查 找远程服务器对象)、No data available(无可用数据)、No customer IDs available(无可用客户 ID)、Cannot access data in server(无法访问服务器中的数据)

创建关键字/值对文件

由于所有对用户具有可视性的文字信息都将从应用程序中提取出来并进行翻译, 所以您的应用程序需要提供一种在执行期间访问已翻译文字的方式。这一点可通过 关键字/值对文件来完成,该文件具有各种语言版本。关键字是从应用程序而不是 从硬编码文本中引用的,用于从所用语言的对应文件中调入相应的文字信息。

例如,您可以把关键字 purchase 映射到德语文件中的 Kaufen、法语文件中 的 Achetex 和美国英语文件中的 Purchase。在您的应用程序中,您可以引用关 键字 purchase 并指明其所采用的语言。

关键字/值对存放在属性文件中,它保存着程序属性和特征信息。属性文件是 纯文本格式的文件,每一种需要使用的语言都需要有一个属性文件。

本例中有三个属性文件,分别对应于英语、法语和德语译文。由于本应用程序 当前使用的是硬编码文本,所以对它进行国际化最简单的方法是使用硬编码文字 来设定英语属性文件的关键字/值对。

属性文件都遵循命名约定,这样应用程序才能够在运行时定位并调入正确的文 件。命名约定中规定语言和国家代码应成为文件名一部分。之所以要同时包含语 言和国家,是因为相同语言会因国家的不同而不同。例如,美国英语和澳大利亚 英语的差别很小,瑞士德语和澳大利亚德语不仅相互不同而且都不同于德国本土 的德语。

德语、法语、美国英语译文的各自属性文件的文件名分别为:de_DEfr_FRen_US,其中 defren 分别表示德语(德国)、法语、英语;DEFRUS 分别表示德国(Deutschland)、法国和美 国:

  • MessagesBundle_de_DE.properties
  • MessagesBundle_en_US.properties
  • MessagesBundle_fr_FR.properties

以下为英语属性文件。关键字出现在等号(=)的左边,而文字值(英文文字) 则出现在右边。

MessagesBundle_en_US.properties

apples=Apples:
peaches=Peaches:
pears=Pears:
items=Total Items:
cost=Total Cost:
card=Credit Card:
customer=Customer ID:

title=Fruit 1.25 Each
1col=Select Items
2col=Specify Quantity

reset=Reset
view=View
purchase=Purchase

invalid=Invalid Value
send=Cannot send data to server
nolookup=Cannot look up remote server object

nodata=No data available
noID=No customer IDs available
noserver=Cannot access data in server

以上属性文件完成后,您就可以把它交给您的法语和德语翻译人员,让他们给 出与等号(=)右边的文字向对等的法语和德语。请为自己保留该文件的备份,因 为您也需要对您的应用程序文本中的关键字进行国际化。

带有 德语 译文的属性文件将为水果订购客户创建以下的用户界面:

带有 法语 带有 译文的属性文件则会为水果订购客户创建以下的用户界面:

国际化应用程序文本

本节讲述如何国际化RMIClient1.java 程序。RMIClient2.java 程序的代码与前者几乎相同,因此您可以把相同步骤应用到您自己的程序中。

实例变量

除了对包含国际化类的 java.util.* 包添加一个导入语句外, 本程序还需要为国际化进程声明以下实例变量:

//Initialized in main method
  static String language, country;
  Locale currentLocale;
  static ResourceBundle messages;

//Initialized in actionPerformed method
  NumberFormat numFormat;

main 方法

本程序的设计是为了让用户指定命令行上使用的语言。所以,对 main 方法进行第一个修改是增加检查命令行参数的程序代码。指定了命令行的所用语言 就表示,一旦应用程序被国际化了,您就可以无需经过重新编译而轻易地改变语言 种类。

main 方法的参数 String[] args 包含了从命令 行传递给程序的参数。当终端用户需要英语以外的其他语言时,程序代码需要使 用3 个命令行参数。第一个参数是运行程序的计算机的名称。由于这个程序是使 用远程方法调用(RMI)API 的联网程序,所以当程序启动后并且需要时,这个参 数的值就会传递给程序。

其它两个参数分别代表语言和国家代码。如果程序被调用时带有 1 个命令行 参数(仅指计算机名),国家和语言参数就分别假设为美国和英语。

以下示例将说明,带有用于指明计算机名称和德语(de DE)命令行参数的程 序是如何启动并运行的。其中的所有操作都是在同一命令行上进行的。

  java -Djava.rmi.server.codebase=
	http://kq6py/~zelda/classes/
	-Djava.security.policy=java.policy 
	RMIClient1 kq6py.eng.sun.com de DE 

以下是 main 方法的代码。currentLocale 实例 变量是通过命令行传递来的 languagecountry 信息进行初始化,而实例变量 messages 则通过 currentLocale 进行初始化。

对象 messages 提供对所用语言的翻译文本的访问。它需要两个 参数:第一个参数“"MessagesBundle"”是本应用程序所用译文文件 系列的前缀;第二个参数是 Locale 对象,用于告诉 ResourceBundle 使用哪种译文。

 


注意:这种风格的程序设计使同一用户可以用不同的语言运行程序,但是 在大多数情况下,程序都只使用一种语言而不依赖于命令行参数所设定的国家和 语言。

如果调用应用程序时带有命令行参数 de DE,该程序代码就会 创建一个 ResourceBundle 变量来访问 MessagesBundle_de_DE.properties 文件。

  public static void main(String[] args){
//Check for language and country codes
    if(args.length != 3) {
      language = new String("en");
      country = new String ("US");
      System.out.println("English");
    }else{
      language = new String(args[1]);
      country = new String(args[2]);
      System.out.println(language + country);
    }

//Create locale and resource bundle
    currentLocale = new Locale(language, country);
    messages = ResourceBundle.getBundle("MessagesBundle", 
	currentLocale);

    WindowListener l = new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    };

//Create the RMIClient1 object
    RMIClient1 frame = new RMIClient1();

    frame.addWindowListener(l);
    frame.pack();
    frame.setVisible(true);

    if(System.getSecurityManager() == null) {
      System.setSecurityManager(
               new RMISecurityManager());
    }

    try {
      String name = "//" + args[0] + "/Send";
      send = ((Send) Naming.lookup(name));
    } catch (java.rmi.NotBoundException e) {
      System.out.println(messages.getString(
                                    "nolookup"));
    } catch(java.rmi.RemoteException e){
      System.out.println(messages.getString(
                                    "nolookup"));
    } catch(java.net.MalformedURLException e) {
      System.out.println(messages.getString(
                                    "nolookup"));
    }
  }
对可用错误文本信息的访问,可通过调用 ResourceBundle 上的 getString 方法并向其传递映射到可用文本信息的关键字来实现。
     try {
       String name = "//" + args[0] + "/Send";
       send = ((Send) Naming.lookup(name));
     } catch (java.rmi.NotBoundException e) {
       System.out.println(messages.getString(
                                     "nolookup"));
     } catch(java.rmi.RemoteException e){
       System.out.println(messages.getString(
                                     "nolookup"));
     } catch(java.net.MalformedURLException e) {
       System.out.println(messages.getString(
                                     "nolookup"));
     }

构造函数

窗口标题的设定,可通过调用 ResourceBundle 上的 getString 方法并向其传递映射到标题文本上的关键字来实现。必 须准确无误地按照译文文件中的内容传递关键字,否则将会得到指示资源不可用 的运行时错误。

   RMIClient1(){ 

//Set window title
     setTitle(messages.getString("title"));
构造函数下面所要做的是,使用参数 args 查找远程服务器对象。 如果这一过程发生任何错误,catch 语句就会从 ResourceBundle 中获取可用错误文本信息,并把信息显示到命令行上。JLabelJButton 等显示文本信息的用户界面对象可用相同的方法创建:
//Create left and right column labels
     col1 = new JLabel(messages.getString("1col"));
     col2 = new JLabel(messages.getString("2col"));
...
//Create buttons and make action listeners
     purchase = new JButton(messages.getString(
                                       "purchase"));
     purchase.addActionListener(this);

     reset = new JButton(messages.getString("reset"));
     reset.addActionListener(this);

actionPerformed 方法

actionPerformed 方法可捕获 Invalid Value 错 误并对其进行翻译:

  if(order.apples.length() > 0){
//Catch invalid number error
    try{
      applesNo = Integer.valueOf(order.apples);
      order.itotal += applesNo.intValue();
    }catch(java.lang.NumberFormatException e){
      appleqnt.setText(messages.getString("invalid"));
    }
  } else {
    order.itotal += 0;
  }
方法 actionPerformed 计算订购总数和总费用,然后把它们转换 成适用于当前所用语言的正确格式并显示在用户界面上。

国际化数字

NumberFormat 对象用于把数字转换成适用于当前语言的正确格 式。为此,需要从 currentLocale 创建一个 NumberFormat 对象。currentLocale 中的信息告诉 NumberFormat 对象要使用哪一种数字格式。

NumberFormat 对象创建之后,需要完成的工作就是向它传递您 想转换的值,而您将接收到一个包含正确格式数字的 String。该值 可以采用 intIntegerdoubleDouble 等适用于数字的任何数据类型传递。不需要用于把 Integer 转换成 int 和把 int 转换 回 Integer 的任何代码。

//Create number formatter
  numFormat = NumberFormat.getNumberInstance(
	currentLocale);

//Display running total
  text = numFormat.format(order.itotal);
  this.items.setText(text);

//Calculate and display running cost
  order.icost = (order.itotal * 1.25);
  text2 = numFormat.format(order.icost);
  this.cost.setText(text2);

  try{
     send.sendOrder(order);
   } catch (java.rmi.RemoteException e) {
     System.out.println(messages.getString("send"));
   }

编译并运行应用程序

以下是示例程序编译和运行的概括说明。值得注意的是,当您启动客户程序时, 若想使用除美式英语之外的语言,就需要包含语言和国家代码。

编译

以下这些命令假设开发工作是在 zelda 主目录下进行的。

Unix:
cd /home/zelda/classes
javac Send.java
javac RemoteServer.java
javac RMIClient2.java
javac RMIClient1.java
rmic -d . RemoteServer
cp RemoteServer*.class /home/zelda/public_html/classes
cp Send.class /home/zelda/public_html/classes
cp DataOrder.class /home/zelda/public_html/classes
Win32:
cd \home\zelda\classes
javac Send.java
javac RemoteServer.java
javac RMIClient2.java
javac RMIClient1.java
rmic -d . RemoteServer
copy RemoteServer*.class 
                \home\zelda\public_html\classes
copy Send.class \home\zelda\public_html\classes
copy DataOrder.class \home\zelda\public_html\classes

启动 rmi 注册程序

Unix:

cd /home/zelda/public_html/classes
unsetenv CLASSPATH
rmiregistry &

Win32:

cd \home\zelda\public_html\classes
set CLASSPATH=
start rmiregistry 

启动服务器

Unix:

  cd /home/zelda/public_html/classes
  java -Djava.rmi.server.codebase=
        http://kq6py/~zelda/classes
  -Dtava.rmi.server.hostname=kq6py.eng.sun.com
  -Djava.security.policy=java.policy RemoteServer
Win32:
  cd \home\zelda\public_html\classes
  java -Djava.rmi.server.codebase=
	file:c:\home\zelda\public_html\classes
  -Djava.rmi.server.hostname=kq6py.eng.sun.com
  -Djava.security.policy=java.policy RemoteServer

启动德语版 RMIClient1

请注意在命令行的末尾添加表示德语语言和国家的 de DE

Unix:
  cd /home/zelda/classes

  java -Djava.rmi.server.codebase=
	http://kq6py/~zelda/classes/
  -Djava.security.policy=java.policy 
	RMIClient1 kq6py.eng.sun.com de DE
Win32:
cd \home\zelda\classes

  java -Djava.rmi.server.codebase=
	file:c:\home\zelda\classes\
  -Djava.security.policy=java.policy RMIClient1 
	kq6py.eng.sun.com de DE

启动法语版的 RMIClient2

请注意在命令行的末尾添加表示法语语言和国家的 fr FR

Unix:
  cd /home/zelda/classes

  java -Djava.rmi.server.codebase=
	http://kq6py/~zelda/classes
  -Djava.rmi.server.hostname=kq6py.eng.sun.com
  -Djava.security.policy=java.policy 
	RMIClient2 kq6py.eng.sun.com fr FR
Win32:
  cd \home\zelda\classes

  java -Djava.rmi.server.codebase=
	file:c:\home\zelda\public_html\classes
  -Djava.rmi.server.hostname=kq6py.eng.sun.com
  -Djava.security.policy=java.policy RMIClient2 
	kq6py.eng.sun.com/home/zelda/public_html fr FR

程序的改进

诸如本应用程序的订货应用程序的现实情况可能是:RMIClient1 是一个嵌入在网页中的 applet 程序。当提交订单时,订单处理人员就从它们的 本地计算机上把 RMIClient2 当作应用程序运行。

这样我们就可以做一个有趣的练习,把 RMIClient1.java 转化 成等效的 applet 程序。该 applet 程序会采用与从浏览器调入 applet 类时的 相同的目录中调入转化(translation)文件。

过点击执行相应 applet 程序的链接来选择语言。以下分别是 英语 法语 德语

以下是在网页上调用法语版 applet 程序的 HTML 程序代码。

<HTML>
<BODY>
<APPLET CODE=RMIFrenchApp.class WIDTH=300 HEIGHT=300>
</APPLET>
</BODY>
</HTML>

注意:若要在某个浏览器中运行采用 JavaTM 2 API 编写的 applet 程序,就必须使该浏览器适用于 Java 2 平台。如果您的 浏览器不适用于 Java 2 平台,您就必须使用 appletviewer 来运行 applet 程 序或安装 Java 插件。 Java 插件使您能够在 1.2 版的Java1 虚拟机(VM)下的网页上运行 applet 程序,而不使用网络浏览器的缺省 Java VM。
若使用 applet 程序浏览器,则应输入以下程序代码,使 rmiFrench.html 作为法语版 applet 程序的 HTML 文件。
  appletviewer rmiFrench.html

对程序当前状态的另一项改进是加强出错信息文本。您可以在 Java API 文档 中查找错误并使用其中的信息,通过提供更多特殊信息而使出 错信息对用户更加友好。

也许您还需要采用客户程序来捕获和处理使用不正确关键字时产生的错误。以 下是此类错误发生时系统提供的错误和栈跟踪:

Exception in thread "main" 
  java.util.MissingResourceException: 
Can't find resource
  at java.util.ResourceBundle.getObject(Compiled Code)
  at java.util.ResourceBundle.getString(Compiled Code)
  at RMIClient1.<init>(Compiled Code)
  at RMIClient1.main(Compiled Code)

更多信息

有关国际化的详细说明,请参阅Java教程中的国际化部分。

有关 applet 程序的更多信息,请参阅 Java 教程 中的 编写 Applet 程序 部分。


1当术语“Java virtual machine(Java 虚拟机 )”或“JVM”在本网站中使用时是指适用于 Java 平台的虚拟机。

[TOP]

 

 

 

 

常见问答
下载中心
产品简介
 
 
Solaris论坛
 
   
 
null