Java中的国际化,第二部分

来源:互联网 发布:c语言项目实战 编辑:程序博客网 时间:2024/04/30 02:35
文章工具
编者按:使你的Java程序能在本地小镇甚至飘洋过海都运行良好,这面临诸多的挑战.我们已经在<<Java Examples in a Nutshell>>第三版中关于国际化探讨的第一部分讲述了Java国际化的前两个步骤:使用Unicode字符集编码,遵循当地使用习惯.接下来的时间,我们我们将继续探讨国际化的第三个步骤:本地化用户可视信息.

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:leniz
原文:http://www.onjava.com/pub/a/onjava/excerpt/javaexIAN3_chap8/index1.html
译文:http://www.matrix.org.cn/resource/article/44/44207_Java+Internationalization.html
关键字:Java;Internationalization

本地化用户可视信息

国际化的第三个任务涉及到确保程序中用户可视信息没有被硬编码(hardcode);相反的是,应该依据当地的俗成.在例8-3,字符”Portfolio value”,”Symbol”,”Shares”,和另外一些在程序中都是用英语进行硬编码的,即使程序可能运行在遥远的法兰西国度.唯一避免这种尴尬处境的方法就是:你的程序必须支持在运行之时才实时地提取用户可视的信息,并且这些信息需被实时地翻译成各种语言.

Java通过java.util包中的ResourceBundle 类来帮助我们完成这个任务.这个类包含了众多可以通过名称来查询的资源.将你所希望等到本地化的地区的当地资源都定义到一个资源包中,运行之时Java将准确的加载本地化资源.通过加载正确的bundle,程序就可以实时的查找到对本地化支持所需要的资源(字符是比较有代表).

使用Resource Bundles来处理资源

为了定义一系列本地化的资源,你需要继承ResourceBundle类生成一个类,并提供对handleGetObject()和getKeys()等方法的定义. 将资源的名称传给handleGetObject()方法,它将返回一个本地版本的资源.getKeys()返回的是一个Enumeration类型的类,它包含了ResourceBundle中已定义的所有的本地化资源的名称.然而比直接继承ResourceBundle类更为简单的是继承ListResourceBundle类.你可以简洁地提供一个特性文件(参阅java.util.Properties),
ResourceBundle.getBundle()方法将通过此文件生成一个PropertyResourceBundle实例.

在一个程序中,为了能通过ResourceBundle类获取本地化资源,你必须首先调用,一个静态的getBundle()方法,这个方法将动态加载并实例化一个ResourceBundle,正如我们即将要涉及的一样.返回的这个ResourceBundle包含了你所定义的资源名称,并且是针对某一特定区域的(如果没有区域被显式的声明,那么将加载默认的区域).一旦通过getBundle()方法获取了你所要的ResourceBundle,你就可以通过getObject()方法以提供资源名称的方式来获取已本地化的资源.随便提一下,你可以通过getString()方法更为便捷的把想要获得的Object资源自动转型为String类型.

当调用getBundle()方法之时,指定你所希望的ResourceBundle的base name和你所期望的地点(如果你不希望使用默认的地点的话).获取一个Locale用的是一个双字的语言代码,一个可选的双字国家代码,以及一个可选的字符变量.getBundle()方法通过把地区信息加上base name的方式,来获取正确的本地ResourceBundle.查找一个合适的类的方法用的正是下面的算法:

1.查询一个具有下列名字的类
  basename_language_country_variant
  如果没有发现这个类,或者没有特别指定locale的字符变量,那么将进行下一步查询

2.查询一个具有下列名字的类
  basename_language_country
  如果没有发现这个类,或者没有特别指定locale的国家编码,那么将进行往下下一步的查询

3.查询一个具有下列名字的类
  basename_language
   如果还是没有发现这个类的话,那么将进行最后一步

4.查询一个与basename同名的类,换句话说,就是找一个与下列名字同名的类.
       basenam
这里包含的是默认的本地资源包,针对的是所有没有明确提供本地化的区域.

在以上处理过程的每一步中,getBundle()都首先用给定的名字检测一个类文件.如果没有找到这个类文件,那么它将借助ClassLoader的getResourceAsStream()方法,通过给定的名字和.Properties后缀名去查找一个具有上述组合名称的Properties文件.如果找到这样的Properties文件的话,那么将通过这个文件提供的内容,实例化一个Properties Object,并通过getBundle()初始化,返回一个PropertyResourceBundle类,这个类借助ResourceBundle API导入Properties文件中的所有特性.

如果上述的四种查询的任何一个查询,针对某特定区域的类或Properties文件都没有被找到,那么它将重复上述的查询,但查找的是针对默认区域的类或Properties文件. 默认区域的查询仍然无法获取合适的ResourceBundle,getBundle()方法将抛出一个MissingResourceException异常.

任何一个ResourceBundle Object 都可以有一个特别针对自己的parent ResourceBundle. 当你在ResourceBundle中查找某个资源时,getObejct()首先在一个特定的bundle中查找,如果这个资源没有在这个bundle中被定义,那么查找将回溯到父bundle中.因此,每一个ResourceBundle都是继承了父类的资源,可能 覆写了某些资源,有时甚至覆盖了全部的(需要注意的是这里提到的术语继承(inherit)和覆写(override)区别与通常我们在讨论class时所提到的针对父类的继承和覆写).上述的意思就是说,我们定义的ResourceBundle不一定非要定义你的应用程序所需要的每一个资源.比如你可能定义了一个专门针对法语用户的信息ResourceBundle.而此时你希望有个专门针对加拿大的使用法语的用户的ResourceBundle,你要做的就是覆写一些信息来定义一个更为小型的而且更具有针对性的信息ResourceBundle.

你的程序不需要去查找和建立它所使用的ResourceBundle的父对象.事实上getBundle()方法可以为了你自动的做这些工作.当getBundle()方法查找到一个符合上述要求的类或properties文件时,它却不是立即返回它已查找到的ResourceBundle.相反的是,它会按照原先的步骤继续寻找更为普遍性的类或Properties文件,而ResourceBundle可以通过这些来继承资源.一旦getBundle()方法找到这个更具普遍性的资源bundle,它就会把这些资源组装成最后的bundle的祖先.一旦确定了返回的最初的ResourceBundle的所有可能,它将创建之.

为了继续不久之前开始的那个例子,那个例子实际运行在魁北克,getBundle()可能首先查找一个不那么特殊的ResourceBundle类,这个类只包含了有限的特别针对魁北克的资源.接着,它查找更具一般的ResourceBundle类,这个类可能包含的是法国的信息.那么它把这个bundle作为刚刚提及的魁北克的最初的bundle的parent bundle. getBundle()最后查找的(可能发现的)是这样一个类,它定义了一套默认的资源,有可能用的是英语(假设英语是开始的那些编程者的母语).这些bundle作为法语bundle的parent bundle(同时也就是魁北克bundle的爷字辈的bundle).当运用程序查找一个命名的资源之时,魁北克的bundle首先被查找到.如果这个命名资源没有在此被定义,那么将查找法语的bundle,如果还是没能找到所需要的资源,那么最后的可能就落在默认的bundle上了.

ResourceBundle 举例
调试一些代码有助于我们把上面讨论的东西,理解的更加清晰,透彻.例8-4是一个生成Swing菜单的常规程序.面对给定一系列菜单的细目,它在资源bundle中查找针对这些细目的标签和快捷键的资源,并创建一个本地化的菜单.下面的例子最后附有简单的测试.

图8-3,是一个分别运行在美国,英格兰和法国时创建的菜单栏程序.如果没有那些提供菜单标签本地化的resource bundle的存在,我们是不可能实现这个程序的.


图 8-3 本地化菜单panes

例8-4 SimpleMenu.java
package je3.i18n;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.MissingResourceException;

/** A convenience class to automatically create localized menu panes */
public class SimpleMenu {
  /** The convenience method that creates menu panes */
  public static JMenu create(ResourceBundle bundle,
                String menuname, String[] itemnames,
                ActionListener listener)
  {
    // Get the menu title from the bundle. Use name as default label.
    String menulabel;
    try { menulabel = bundle.getString(menuname + ".label"); }
    catch(MissingResourceException e) { menulabel = menuname; }

    // Create the menu pane.
    JMenu menu = new JMenu(menulabel);

    // For each named item in the menu.
    for(int i = 0; i < itemnames.length; i++) {
      // Look up the label for the item, using name as default.
      String itemlabel;
      try {
        itemlabel =
          bundle.getString(menuname+"."+itemnames[i]+".label");
      }
      catch (MissingResourceException e) { itemlabel = itemnames[i]; }

      JMenuItem item = new JMenuItem(itemlabel);

      // Look up an accelerator for the menu item
      try {
        String acceleratorText =
          bundle.getString(menuname+"."+itemnames[i]+".accelerator”); //绿色为新加
bundle.getString(menuname+"."+itemnames[i]+".accelerator");//这一行好像有问题.
                                                 //应该是删除此行,将上一行补充完整.
        item.setAccelerator(KeyStroke.getKeyStroke(acceleratorText));
      }
      catch (MissingResourceException e) {}

      // Register an action listener and command for the item.
      if (listener != null) {
        item.addActionListener(listener);
        item.setActionCommand(itemnames[i]);
      }

      // Add the item to the menu.
      menu.add(item);
    }

    // Return the automatically created localized menu.
    return menu;
  }

  /** A simple test program for the above code */
  public static void main(String[] args) {
    // Get the locale: default, or specified on command-line
    Locale locale;
    if (args.length == 2) locale = new Locale(args[0], args[1]);
    else locale = Locale.getDefault( );

    // Get the resource bundle for that Locale. This will throw an
    // (unchecked) MissingResourceException if no bundle is found.
    ResourceBundle bundle =
      ResourceBundle.getBundle("com.davidflanagan.examples.i18n.Menus",
                   locale); //注一

    // Create a simple GUI window to display the menu with
    final JFrame f = new JFrame("SimpleMenu: " +  // Window title
                locale.getDisplayName(Locale.getDefault( )));
    JMenuBar menubar = new JMenuBar( );     // Create a menubar.
    f.setJMenuBar(menubar);          // Add menubar to window

    // Define an action listener that our menu will use.
    ActionListener listener = new ActionListener( ) {
        public void actionPerformed(ActionEvent e) {
          String s = e.getActionCommand( );
          Component c = f.getContentPane( );
          if (s.equals("red")) c.setBackground(Color.red);
          else if (s.equals("green")) c.setBackground(Color.green);
          else if (s.equals("blue")) c.setBackground(Color.blue);
        }
      };

    // Now create a menu using our convenience routine with the resource
    // bundle and action listener we've created
    JMenu menu = SimpleMenu.create(bundle, "colors",
                    new String[] {"red", "green", "blue"},
                    listener);

    // Finally add the menu to the GUI, and pop it up
    menubar.add(menu);      // Add the menu to the menubar
    f.setSize(300, 150);     // Set the window size.
    f.setVisible(true);     // Pop the window up.
  }

}

这个程序不是独立的.它依赖资源bundle来做本地化菜单的工作.接下来,罗列了三份properties文件,这些文件是为例子中的资源bundle提供的.注意下面的这个列表包含了三个独立的文件主体.(注二)

# The file Menus.properties is the default "Menus" resource bundle.
# As an American programmer, I made my own locale the default.
colors.label=Colors
colors.red.label=Red
colors.red.accelerator=alt R
colors.green.label=Green
colors.green.accelerator=alt G
colors.blue.label=Blue
colors.blue.accelerator=alt B


# This is the file Menus_en_GB.properties.  It is the resource bundle for
# British English.  Note that it overrides only a single resource definition
# and simply inherits the rest from the default (American) bundle.
colors.label=Colours


# This is the file Menus_fr.properties.  It is the resource bundle for all
# French-speaking locales.  It overrides most, but not all, of the resources
# in the default bundle.
colors.label=Couleurs
colors.red.label=Rouge
colors.green.label=Vert
colors.green.accelerator=control shift V
colors.blue.label=Bleu


格式化信息

我们已经看到,为了实现程序的国际化,必须把所有用户可见的信息都放到资源bundle中.当这些文本的本地化只是包含一些象按键和菜单栏文本的简单标签,那么一切都显得很直观.然而,面对的是一部分静态内容和一部分动态内容的组合体,情况就变得微妙了.比如编译器可能要显示一条这样的信息,”Error at line 5 of file “Hello.java””,在这种情况下,行数和文件名是动态的,而且是区域独立的,其余的信息都是静态的即需要被简单地本地化操作的.

java.text 包中的MessageFormat类,可以帮助我们有效的解决这个难题.为了使用它们,我们只把那些静态的文字部分储存在ResourceBundle中,对于那些动态的信息部分中出现的特殊字符也必须被安置好.比如,某个资源bundle包含下面的一条信息”Error at line{0} of file {1}”.另一个资源bundle可能包含的是一个已被翻译的”Erreur:{1}:{0}”的信息.

为了使用对付这样的本地化信息问题,我们从静态信息中创建一个MessageFormat,然后调用它的format()方法.对队列中的值进行替换.在这种情况下,这个队列中包含一个用于行数的Integer 对象和一个用于文件名的String对象MessageFormat对象可以和java.text包中的其它Format类进行通信.它创建和使用NumberFormat对象来格式化数字并使用DateFormat对象格式化日期和时间.另外,可以设计信息,用这些信息创建一个ChoiceFormat对象,以此实现从数字形式到字符形式的转变.当操作计数值类型时,这些显得尤其有用,例如对应月份名称的数字,或当你需要根据数字值的多少来决定某个词的单数或复数.

例8-5 演示了这种MessageFormat的使用.这是一个很方便的类,有一个单独的静态方法用于本地化显示异常信息或错误信息.但调用的时候,代码试图加载一个名为”Errors”的ResourceBundle.当找到了这个类,那么它将查询使用了传递过来的异常对象名字的信息资源.如果这个信息资源也被找到,它将显示这个信息.拥有五个值的队列被传递给format()方法.本地化了的错误提示信息可以包括任何一个或是全部这些参数.

例子中定义的LocalizedError.display()方法,曾经在本章开始的例8-2中使用过.与此例配合使用的默认Errors.Properties资源bundle, 在代码后面也有相应提供.应用程序的错误信息提示在这里得到了很好的国际化.将一个应用程序的错误信息提示应用到一个新的区域中,所要做的无法只是转化(本地化)Errors.Properties文件.

Example 8-5. LocalizedError.java
package je3.i18n;
import java.text.*;
import java.io.*;
import java.util.*;

/**
* A convenience class that can display a localized exception message
* depending on the class of the exception.  It uses a MessageFormat,
* and passes five arguments that the localized message may include:
*   {0}: the message included in the exception or error.
*   {1}: the full class name of the exception or error.
*   {2}: the file the exception occurred in
*   {3}: a line number in that file.
*   {4}: the current date and time.
* Messages are looked up in a ResourceBundle with the basename
* "Errors", using a the full class name of the exception object as
* the resource name.  If no resource is found for a given exception
* class, the superclasses are checked.
**/
public class LocalizedError {
    public static void display(Throwable error) {
        ResourceBundle bundle;
        // Try to get the resource bundle.
        // If none, print the error in a nonlocalized way.
        try {
            String bundleName = "com.davidflanagan.examples.i18n.Errors";
            bundle = ResourceBundle.getBundle(bundleName);
        }
        catch (MissingResourceException e) {
            error.printStackTrace(System.err);
            return;
        }
        
        // Look up a localized message resource in that bundle, using the
        // classname of the error (or its superclasses) as the resource name.
        // If no resource was found, display the error without localization.
        String message = null;
        Class c = error.getClass( );
        while((message == null) && (c != Object.class)) {
            try { message = bundle.getString(c.getName( )); }
            catch (MissingResourceException e) { c = c.getSuperclass( ); }
        }
        if (message == null) { error.printStackTrace(System.err);  return; }
        
        // Get the filename and linenumber for the exception
        // In Java 1.4, this is easy, but in prior releases, we had to try
        // parsing the output Throwable.printStackTrace( );
        StackTraceElement frame = error.getStackTrace( )[0];  // Java 1.4
        String filename = frame.getFileName( );
        int linenum = frame.getLineNumber( );

        // Set up an array of arguments to use with the message
        String errmsg = error.getMessage( );
        Object[  ] args = {
            ((errmsg!= null)?errmsg:""), error.getClass( ).getName( ),
            filename, new Integer(linenum), new Date( )
        };

        // Finally, display the localized error message, using
        // MessageFormat.format( ) to substitute the arguments into the message.
        System.err.println(MessageFormat.format(message, args));
    }

    /**
     * This is a simple test program that demonstrates the display( ) method.
     * You can use it to generate and display a FileNotFoundException or an
     * ArrayIndexOutOfBoundsException
     **/
    public static void main(String[  ] args) {
        try { FileReader in = new FileReader(args[0]); }
        catch(Exception e) { LocalizedError.display(e); }
    }
}


下面罗列的是资源bundle需要的properties文件,这些文件用于本地化由例8-2的ConvertEncoding所产生的错误信息.
#
# This is the file Errors.properties
# One property for each class of exceptions that our program might
# report.  Note the use of backslashes to continue long lines onto the
# next.  Also note the use of /n and /t for newlines and tabs
#
java.io.FileNotFoundException: /
Error: File "{0}" not found/n/t/
Error occurred at line {3} of file "{2}"/n/tat {4}

java.io.UnsupportedEncodingException: /
Error: Specified encoding not supported/n/t/
Error occurred at line {3} of file "{2}"/n/tat {4,time} on {4,date}

java.io.CharConversionException:/
Error: Character conversion failure.  Input data is not in specified format.

# A generic resource.  Display a message for any error or exception that
# is not handled by a more specific resource.
java.lang.Throwable:/
Error: {1}: {0}/n/t/
Error occurred at line {3} of file "{2}"/n/t{4,time,long} {4,date,long}



当我们拥有这样一个resource bundle后, ConvertEncoding 将会产生类似下面的 error messages:
Error: File "myfile (No such file or directory)" not found
        Error occurred at line 64 of file "FileInputStream.java"
        at 7/9/00 9:28 PM


或者,如果是在法国的话:
Error: File "myfile (Aucun fichier ou repertoire de ce type)" not found
        Error occurred at line 64 of file "FileInputStream.java"
        at 09/07/00 21:28


练习 8-1
一些用于国际化的类,例如NumberFormat,DateFormat等,都有一个静态的方法getAvailableLocales(),这个方法返回一系列他们所支持的本地化对象.你可以在一个给定的本地化对象中,通过getDisplayCountry()方法来获取这个给定区域的国家名称.这个方法有两种方式.一种是不带参数的,返回显示的是默认地的国家名称.另一种是需要提供一个Locale作为参数,返回的是特定区域语言下的国家名称.

写一个程序,这个程序通过NumberFormat.getAvaileLocales()方法可以显示所有区域的国家名称.使用Locale定义的静态locale常量,用英语,法语,德语,意大利语分别显示每一个国家的名称.

附录:
注一:这里的com.davidflanagan.examples.i18n是放置了几个properties文件的package名,如果你将附带的properties放到其它的package中,请做相应的变动.Menus是默认的名称
注二:分别保存到三个独立的文本文件中,用注释中的名字进行命名,以此为
针对中国用户的菜单可以是:
文件四: Menus_zh.properties
colors.label=颜色设置
colors.red.label=红色
colors.red.accelerator=alt R
colors.green.label=绿色
colors.green.accelerator=alt G
colors.blue.label=蓝色
colors.blue.accelerator=alt B


如果出现中文字符乱码问题,请将加入下列方法,对菜单字符进行转换,所以这里也要多考虑一下字符集的问题
/**
* @todo传入一个以UTF-8编码的字符,输出UTF-16编码的中文
* @ param inputStr String  需要转换的字串
*/
public static String getChineseOut(String inputStr){
    try{
      String temp_s = inputStr;
      byte[] temp_b = null;
             temp_b = temp_s.getBytes("ISO8859-1");
      String result = new String(temp_b);
      return result;
    }
    catch(Exception  e){
      e.printStackTrace();
      return inputStr;
    }
原创粉丝点击