Java以及j2me的国际化问题

Java的国际化问题(中日文编码问题)

1、常见的Encoding现象

网上常出现的 JSP/Servlet encoding 问题一般都表现在 browser 或应用程序.

隐藏在这些问题后面的是各种错误的字符转换和处理。解决类似的字符 Encoding问题,需要了解 Jsp/Servlet 的运行过程,检查可能出现问题的各个点。

2、JSP/Servlet web 编程时的 Encoding具体分析 

1JSP 编译。 

2Java 需要被编译为 .class 才能在 JVM 中执行。 

3Servlet 需要将HTML页面内容转换为 browser 可接受的 encoding 内容发送出去。

依赖于各 JAVA App Server 的实现方式,有的将查询 Browser 的 accept-charset 和 accept-language 参数或以其它猜的方式确定 encoding 值,有的则不管。因此采用固定encoding 也许是最好的解决方法。对于中文网页,可在 JSP 或 Servlet 中设置 contentType="text/html; charset=GB2312";如果页面中有GBK字符,则设置为contentType="text/html; charset=GBK",由于IE 和 Netscape对GBK的支持程度不一样,作这种设置时需要测试一下。

因为16位 JAVA char在网络传送时高8位会被丢弃,也为了确保Servlet页面中的汉字(包括内嵌的和servlet运行过程中得到的)是期望的内码,可以用 PrintWriter out = res.getWriter() 取代 ServletOutputStream out = res.getOutputStream()。PrinterWriter 将根据contentType中指定的charset作转换 (ContentType需在此之前指定!);也可以用OutputStreamWriter封装 ServletOutputStream 类并用write(String)输出汉字字符串。对于 JSP,JAVA Application Server 应当能够确保在这个阶段将嵌入的汉字正确传送出去。这是解释 URL 字符 encoding 问题。

4、如果通过 get/post 方式,servlet 将无法得到正确的值。

SUN的 J2SDK 中,HttpUtils.parseName 在解析参数时根本没有考虑 browser 的语言设置,而是将得到的值按 byte 方式解析。这是网上讨论得最多的 encoding 问题。因为这是设计缺陷,只能以 byte 方式重新解析得到的字符串;或者以修改HttpUtils 类的方式解决。不过最好将其中的中文 Encoding GB2312、 CP1381 都改为 GBK,否则遇到 GBK 汉字时,还是会有问题。
----------------------------------------------------

J2ME开发中的国际化问题 

昨天,闲来无事阅读了一下JSR 238的规范。规范是由Nokia领导完成的。规范中最重要的一点就是定义了一种二进制格式的资源文件,资源文件中可以存储文本,二进制数据例如png图片。二进制文件的优势在规范中列举了4点,大家有兴趣可以下载来看看。资源文件由四个部分构成:文件特征,头文件长度,头文件和资源数据。头文件长度标记了头文件的大小。而头文件中包含了所有资源数据的id,类型和偏移量(相对文件)。每个实体由8个字节组成,高位4个字节代表id,低4位第个字节的第一个代表类型,主要有三种类型文本,二进制和文件尾。后面三个字节是偏移量。资源数据中就是所有的数据了,比如文本和图片。

JSR 238中还定义了4个Class,最重要的就是ResourceManager。他可以管理资源文件,并且根据ID读取出需要的内容来。目前支持jsr 238的手机还比较少一些,但是有了这个规范我们大可以自己来实现ResourceManager,并配合适当的缓存机制把经常使用的内容缓存在内存中。其他的随时读取。ResourceManager一定要把Header表缓存起来,这样就不用每次查找的时候都要解析header了。

有了这个想法之后就动手写了一个ResourceManager,把wtk 2.5里面 I18NDemo里面的res文件读取了一下,果然里面定义了一些中文文本。接着又读取了一个德文的,就看不明白了。

希望这个思路对大家有帮助。



j2me国际化问题

J2me国际化是很难解决的问题,它不像j2ee的开源框架那样好解决,下面让我以自己的工作中遇到的为例谈谈自己的看法。
我是首先把要替换的字符集整理成配置文件然后再从配置文件中读取。
这说以来很简单,我是花了好几天才搞定的,我当时遇到的难点是如何把配置文件和如何使读取配置文。我们可以这样考虑,以##开头的行为模块,以◎开头。。。,之后的输入流就按这样的思路去使用。呵呵,直接贴代码吧。

--------------------------------------------------------------------

MIDlet 国际化

使用 JSR-238 Mobile Internationalization API 更快地全球化您的应用程序

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


级别: 初级

Meng Wong, 软件工程师, IBM

2007 年 1 月 25 日

国际化不仅仅是一个趋势 —— 它已经成为应用程序开发的必要需求。JSR-238 的目的是帮助 Java™ Platform,Micro Edition(Java ME)开发人员利用 Mobile Information Device Profile(MIDP)/Connected Limited Device Configuration(CLDC)将其国际化工作减至最少。通过这篇文章发现一种国际化 MIDlet 的更便捷方法。

简介

您是否曾尝试过国际化 MIDlet,却以失败告终?使用 MIDP/CLDC 本地化 MIDlet 需要完成大量的工作,Java Platform,Standard Edition(Java SE)以成熟的 API 简化国际化工作。CDC Foundation 提供了大量 API 来完成国际化和本地化工作。而如果使用 MIDP/CLDC 来完成这些工作,将是一次非常痛苦的经历;甚至没有一个可用的 ResourceBundle 类。还好有另外一个选择 —— JSR-238 提供了可直接用于资源本地化和数据格式化的类。

作为开始,看一下 Java SE 和 Java ME 对全球化支持的对比,如 图 1 所示。(本图是从 “Considerations of Globalization Solutions in J2ME” 文中转载的。参见 参考资料 中的链接。)


图 1. Java SE 和 Java ME 全球化支持的对比

图 1 显示出,编写一个支持全球化的 Java SE 应用程序是十分简单的;Java SE 提供了开发全球化应用程序所需的几乎所有东西。CDC Foundation 提供了一些用来编写全球化应用程序的 API ,但不是全部。然而建立在 MIDP/CLDC 基础上的应用程序仅提供有限的日历和时区的相关类,您需要亲自编写几乎所有的全球化类。

全球化应用程序的历史

要本地化 MIDlet,通常需要使用 MIDP 核心类编写 ResourceBundleLocale。有很多关于如何完成这一任务的文章,包括 “Globalize Your Embedded Apps”(参见 参考资料)。

为了实现国际化功能,就是说根据不同的地区显示其当地语言,您需要编写自己的 ResourceBundle 类,这对检索本地化字符串是很必要的,例如面向不同地区的 “Hello” 和 “Bonjour”。对于数据格式化,例如 Currency、Numbers 和 DateTime,您也需要实现这些程序。这将耗费大量的精力,因为您不得不收集大量的地区数据信息,还必须熟知具体的文化差异。





回页首


JSR-238 Mobile Internationalization API 简介

国际化 MIDlet 是接触更广阔市场的一个关键条件。新的 JSR-238 Mobile Internationalization API 提供了一种标准方法帮助您更快、更轻松地实现 MIDlet 国际化,还提供了用 MIDP/CLDC 实现 MIDlet 国际化的基础 API。

请注意:JSR-238 API 是一个可选的软件包。开始向其部署 JSR-238 MIDlet 之前,必须确保您的设备支持它。

JSR-238 目标声明

此声明引自 JSR-238 规范:

“Mobile Internationalization API 的目标是为运行在 CLDC 上 MIDP 中的 Java MIDlet 提供国际化服务。Java 2 Standard Edition 提供对国际化的广泛支持,JAVA 2,Micro Eition 更具局限性的设置和配置文件并不包括这种支持。”

这种 API 被设计为以简洁通用的形式提供服务。在国际化方面,它使 MIDP 应用程序达到一个与桌面应用程序类似的级别,从而扩充了 MIDP 应用程序平台。

JSR-238 包,javax.microedition.global 提供的一些重要类如下:

  • ResourceManager 类:管理应用程序资源
  • Formatter 类:创建数据项特定于地区的翻译
  • StringComparator 类:使用特定于地区的规则比较两个字符串

我将具体讨论这些类,但在此之前您必须了解,您将需要使用 Sun Java Wireless Toolkit 2.5 for CLDC Beta、Nokia JSR-238 RI 或 Eclipse 来配合 JSR-238 开发 MIDlet。本例将使用 Eclipse。

首先,您需要获得 JSR-238 库,它是随 Sun WTK 2.5 Beta、Nokia 的 JSR-238 RI 一起提供的。IBM® 在 WebSphere® Everyplace Micro Environment 移植包中提供了 JSR-238,IBM 业务合作伙伴都可以使用。

如果您现在使用的是 WTK 的老版本,其中不包括 JSR-238 库,您需要自己添加 JSR-238。您依然可以在 Eclipse 编写和编译,但由于类加载器的限制,Eclipse 是无法启动的。要配置 Java ME 项目以包含 JSR-238 支持:

  1. 启动 Eclipse。
  2. 创建一个新的 Java ME 项目,如 图 2 所示。在这个例子中,我把它命名为 “Simple Globalization Midlets”,单击 Finish

    图 2. 创建一个新的 Java ME 项目


  3. 在 Package Explorer 视图中展开 “Simple Globalization Midlet” 项目(如 图 3 所示),请注意 JSR-238 库不在这里。

    图 3. 项目树


  4. 选中 Java ME 库节点,右击并选择 Build Path > Configure Build Path...
  5. 单击 Add External Jars...,选择已下载的 JSR-238 jar 文件,然后单击 OK

    图 4. 将 JSR-238 jar 添加到构建路径中


就是这样!您已经将 JSR-238 库添加到开发环境中可,现在就可以开始编写 MIDlet 国际化程序了。





回页首


使用 ResourceManager

现在我将演示如何使用 ResourceManager 来管理不同地区的资源文件。

要用不同的语言来显示本地字符串,就需要准备不同地区的资源文件。资源文件包括要本地化的字符串,如果有图片的话,也要将其包含在资源文件里面。资源文件就像 Java SE 的属性文件,但是是二进制格式。它有以下优点:

  • 部署到设备时占用较少的存储空间。
  • 装载时需要较少内存。
  • 它可以包含嵌入的新行。

对于条件有限的设备来说,这些优点是十分重要和有效的。

现在让我们编写一个简单的 MIDlet,根据地区显示字符串和图片。图 5 显示了资源文件结构。在以下步骤中,我将使用 WebSphere Everyplace Micro Environment 6.1 和 IBM WebSphere Everyplace Micro Environment 移植包的 JSR-238 库。

编写 MIDlet 程序之前需要知道的标准。
  • 地区标识符 地区标识符必须符合 MIDP 2.0 规范定义的格式,使用短横线而不是下划线(例如,“en-US”)。
  • 基本名称 使用基本名称将相关资源分组(例如,“labels” 和 “messages”)。
  • 资源文件结构 必须将不同地区的资源文件放到 /global/{地区标识符}目录下。

图 5. 资源文件结构

步骤 1:准备资源文件

二进制的资源文件必须遵守严格的格式。(参考 JSR-238 规范)。可以使用任何一种二进制文件编辑器或者 Nokia JSR-238 RI 创建资源文件。

为演示起见,我使用 Nokia JSR-238 RI 来创建二进制资源文件,如 图 6 所示。我想为 zh-TW,de-DE 和 es-ES 这几个地区显示 “Welcome” 字符串和一幅图片。可以阅读 Nokia 238 RI 的自述文件了解相关步骤,但基本上,您需要为字符串和图片编写一个 xml 资源文件,运行 ResourceMaker 工具获得二进制文件。


图 6. Nokia JSR-238 RI 示例资源文件

步骤 2:将二进制资源文件放在正确的文件结构中

图 7 中,采用当地语言的字符串存储在一个地区文件夹中,图片的二进制文件存储在 \global\WelcomeMidlets.res 中。您不会希望图像的二进制文件存储在每一个资源文件中!


图 7. 将资源文件放到正确的结构中

步骤 3:编写 MIDlet

为了从资源文件检索字符串和图片,您需要执行下列步骤:

  1. 构造一个 ResourceManager 实例。您可以指定一个地区,也可以使用系统默认的地区。表 1 介绍了可用于构建一个 ResourceManager 实例的两种方法。第一种方法只要求基本名称,它将使用系统默认的地区。第二种方法要求基本名称和特定地区。

表 1. ResourceManager 方法
public static final ResourceManager getManager(java.lang.String baseName)
public static final ResourceManager getManager(java.lang.String baseName, java.lang.String locale)

以下代码返回具有 “WelcomeMidlet” 基本名称以及系统默认地区的 ResourceManager 实例:

res = ResourceManager.getManager("WelcomeMidlet");

                        

使用以下代码检索字符串:

//STRING_WELCOME is the resource id in the resource file; in this case 1

                        StringItem desc = new StringItem(res.getString(STRING_WELCOME),"");

                        

使用下列代码检索图片:

//IMAGE_EILEAN is the resource id in resource file, in this case 111

                        byte[] imageData = res.getData(IMAGE_EILEAN);

                        

清单 1 演示了这些步骤。


清单 1. 字符串和图片检索

                        try

                        {

                        //Get the resource manager with the base name and default systemlocale

                        res = ResourceManager.getManager("WelcomeMIDlet");

                        //Retrieve the welcome string

                        StringItem desc = new StringItem(res.getString(STRING_WELCOME),"");

                        fmMain = new Form(res.getString(STRING_WELCOME));

                        fmMain.append(desc);

                        //Retrieve the Eilean image

                        byte[] imageData = res.getData(IMAGE_EILEAN);

                        Image eilean  = Image.createImage(imageData, 0, imageData.length);

                        fmMain.append(eilean);

                        }catch(ResourceException re)

                        {

                        System.out.println("Exception when retrieving resources");

                        re.printStackTrace();

                        }

                        

  1. 运行 MIDlet。我在 Eclipse 中使用 IBM J9 仿真器运行它。

表 2 展示了在 zh-TW 和 de-DE 地区显示 “Welcome MIDlet” 的结果。


表 2. Welcome MIDlet
zh-TW 地区 de-DE 地区

就是这样!使用资源文件管理不同地区的本地化字符串,将图片的二进制文件转换为所有地区都可以使用的通用资源。ResourceManager 类将根据系统默认地区或您在构造函数中指定的地区来装载资源文件。现在您可以使用一种更快、更简单的方法来本地化菜单、按钮和消息了。





回页首


数据格式化

每一个地区都有自己的文化,都采用自己的方式显示日期、时间、数字、百分数和货币数据。Formatter 类可以满足格式化此类数据的全部需求。表 3 显示了它所提供的类。


表 3. 数据格式化方法
日期和时间 formatDateTime(java.util.Calendar dateTime, int style)
数字和百分比 formatNumber(double number)
formatNumber(double number, int decimals)
formatNumber(long number)
formatPercentage(float value, int decimals)
formatPercentage(long value)
货币值 formatCurrency(double number)
formatCurrency(double number, java.lang.String currencyCode)

现在将编写一个简单的 MIDlet 程序来显示日期、时间、数字、百分比和货币数据。

您需要了解的知识
  • 货币代码必须遵守 ISO-4217 货币代码标准。
  • MIDP 规范允许您列入一个空地区。如果您在 Formatter 构造函数中为一个地区使用了空值或空字符串,它将保留为中立格式(Neutral formatting)。参阅 JSR-238 规范以获得更多细节。

步骤 1:构造一个 Formatter 实例

您可以指定地区,也可使用系统默认地区。下面是所需要的代码:

//Using system default locale

                        Formatter fmt = new Formatter();

                        //Specify the locale

                        Formatter fmt = new Formatter("zh-TW");

                        

步骤 2:创建可编辑文本字段。

您希望创建可编辑的文本文件以使用户输入数字、百分数、货币数据等等。您需要这些文本字段的内容和对其进行格式化的 Formatter 类方法,如 清单 2 所示。


清单 2. 数据格式化

                        public String formatNumber(double number){

                        return fmt.formatNumber(number);

                        }

                        public String formatPercentage(float number, int decimals){

                        return fmt.formatPercentage(number,decimals);

                        }

                        public String formatCurrency(double number){

                        return fmt.formatCurrency(number);

                        }

                        .....

                        //Process events

                        public void commandAction(Command c, Displayable s)

                        {

                        if (c == cmExit)

                        {

                        destroyApp(false);

                        notifyDestroyed();

                        }

                        else if( c == cmFormat)

                        {

                        //Number formatting

                        double num = Double.parseDouble(tfNumeric.getString());

                        siResultNum.setText(this.formatNumber(num));

                        //Percentage formatting

                        float percentage = Float.parseFloat(tfPercentage.getString());

                        siResultPercent.setText(this.formatPercentage(percentage,2));

                        //Currency formatting

                        double curr = Double.parseDouble(tfCurrency.getString());

                        siResultCurr.setText(this.formatCurrency(curr));

                        //Display the result

                        displayResult();

                        }

                        

现在运行 MIDlet。表 4 显示了应获得的结果。(注意:我为 zh-TW 使用了一种不同的字体。)


表 4. Formatter MIDlet
zh-TW 地区 de-DE 地区

格式化日期/时间的不同风格

Formatter 类支持以下风格:

  • 仅 short date
  • 仅 long date
  • 仅 short time
  • 仅 long time
  • short date 和 time
  • short date 和 time

Formatter 类为不同的风格定义了字段。例如,对于仅 short date 这种风格定义了 Formatter.DATE_SHORT,您在 formatDate 方法里使用这些风格就可以了。

清单 3 给出了一个简单的 MIDlet,它示范了日期格式化。


清单 3. 日期格式化 MIDlet

                        public String formatDateTime(Calendar c){

                        //Short date format

                        String result = fmt.formatDateTime(c,Formatter.DATE_SHORT);

                        String result_set = "";

                        result_set= result_set+"\n"+"formatDateTime(Calendar, DATE_SHORT) returns:"+result;

                        //Long date format

                        result = fmt.formatDateTime(c,Formatter.DATE_LONG);

                        result_set = result_set+"\n"+"formatDateTime(Calendar, DATE_LONG)returns:"+result;

                        //Long date and time

                        result = fmt.formatDateTime(c,Formatter.DATETIME_LONG);

                        result_set = result_set+"\n"+"formatDateTime(Calendar, DATETIME_LONG)returns:"+result;

                        //Short date and time

                        result = fmt.formatDateTime(c,Formatter.DATETIME_SHORT);

                        result_set = result_set+"\n"+"formatDateTime(Calendar, DATETIME_SHORT) returns:"+result;

                        //Long time

                        result = fmt.formatDateTime(c,Formatter.TIME_LONG);

                        result_set = result_set+"\n"+"formatDateTime(Calendar, TIME_LONG) returns:"+result;

                        //Short time

                        result = fmt.formatDateTime(c,Formatter.TIME_SHORT);

                        result_set = result_set+"\n"+"formatDateTime(Calendar, TIME_SHORT) returns:"+result;

                        return result_set;

                        }

                        //Called by the application manager to start the MIDlet.

                        public void startApp()

                        {

                        display.setCurrent(fmMain);

                        }

                        public void pauseApp()

                        {}

                        public void destroyApp(boolean unconditional)

                        {}

                        //Process events

                        public void commandAction(Command c, Displayable s)

                        {

                        if (c == cmExit)

                        {

                        destroyApp(false);

                        notifyDestroyed();

                        }

                        else if( c == cmFormat)

                        { //Get the current system date and time

                        Date dt = new Date();

                        Calendar cal = Calendar.getInstance();

                        cal.setTime(dt);

                        siResult.setText(this.formatDatetime(cal));

                        displayResult();

                        }

                        }

                        

表 4 展示了 DateTime format MIDlet 在 zh-TW 和 de-DE 这两个地区的显示结果。


表 4. DateTime format MIDlet
zh-TW 地区 de-DE 地区




回页首


另一个有用的类

可以使用 StringComparator 执行字符串的排序和比较操作。相关的排序编程示例,请参考 JSR-238 指南。





回页首


结束语

JSR-238 提供了一种更快、更简单的方法,利用 MIDP/CLDC 来国际化您的 MIDlet。使用 javax.microedition.global 包中的 ResourceManagerFormatterStringComparator 类来进行本地化工作以及特定于地区的数据格式化。提醒一下,JSR-238 是一个可选包;务必确保您的设备支持它。现在您应该熟悉了 JSR-238,可以去更出色地完成开发工作了!


你可能感兴趣的:(java)