首页 > 代码库 > 笔记:国际化

笔记:国际化

国际化英文单词为:Internationalization,又称I18N,I为因为单词的第一个字母,18为这个单词的长度,而N代表这个单词的最后一个字母。国际化又称本地化(Localization,L10N)。

Java国际化主要通过如下3个类完成

  • java.util.ResourceBundle:用于加载一个资源包
  • java.util.Locale:对应一个特定的国家/区域、语言环境。
  • java.text.MessageFormat:用于将消息格式化

为实现程序的国际化,必须提供程序所需要的资源文件。资源文件的内容由key-value对组成,资源文件的命名可以有3种格式:

  • basename_language_country.properties
  • basename_language.properties
  • basename_properties

若资源文件包含非西方字符,则需要用JDK自带的工具来处理:native2ascii,这个工具的语法格式如下:

native2ascii 资源文件名 目标资源文件名

如:

native2ascii mess_zh_XXX.proerties mess_zh_CN.proerties

Locale类可获取各国区域环境(如:Locale.ENGLISH、Locale.CHINESE,这些常量返回一个Locale实例),也可以获取当前系统所使用的区域语言环境,也可以使用语言和区域来创建Locale对象,Java的本地语言使用的时国际化标准组织(ISO)所定义的编码,本地语言由小写的两个字母的代码表示,遵循ISO-639-1;国家代码由大写的两个字母的代码组成,遵循ISO-3166-1,下表为常用的代码:

语言

代码

国家

代码

Chinese

zh

China

CN

English

en

United States

US

Japanese

ja

Japan

JP

可以使用Locale的静态方法 getAvailableLocales 来获取所支持的语言和国家,该方法返回一个Locale数组,该数组里包含了java所支持的语言和国家,代码如下:

Locale[] availableLocales = Locale.getAvailableLocales();

????????for (Locale l : availableLocales) {

??????????????System.out.println("Locale DisplayName=" + l.getDisplayName() + " Country=" + l.getCountry() + " Language="

+ l.getLanguage());

}

  1. 数字格式

    数字和货币的格式时高度依赖与Locale的,Java类库提供了一个格式器(formatter)对象的集合,可以对java.text包中的数字值进行格式化和解析,可以通过下面的步骤来对特定的Locale的数字进行格式化:

  • 获取Locale对象
  • 使用工厂方法获取格式器对象,工厂方法时 NumberFormat 类的静态方法,接受一个Locale 类型的参数,有三个工厂方法:getNumberInstance(数字)、getCurrencyInstance(货币) getPercentInstance(百分比)
  • 使用这个格式器对象来完成格式化和解析工作

格式化示例代码:

Locale deLocale = new Locale("de", "DE");

NumberFormat currFmt = NumberFormat.getCurrencyInstance(deLocale);

double amt = 123878.34;

String formatResult = currFmt.format(amt);

System.out.println("amt=" + amt + " Format=" + formatResult);

如果想要读取一个按照某个Locale的惯用法而输入或存储的数字,那边就需要使用 parse 方法。

解析示例代码:

Localede Locale=newLocale("de","DE");

NumberFormat currFmt=NumberFormat.getCurrencyInstance(deLocale);

Number input=currFmt.parse(formatResult);

double parseAmt=input.doubleValue();

System.out.println("FormatAmt="+formatResult+"ParseAmt="+parseAmt);

  1. 日期和时间

    当格式化日期和时间时,需要考虑4个与Locale相关的问题,例如:月份和星期应该用本地语言来表示;年月日的顺序要符合本地习惯;公历可能不是本地首选的日期表示方法;必须要考虑本地时区。Java 使用 DateFormat 类来处理这些问题,和 NumberFormat 类很类似,调用 DateFormat 类的静态方法,并传入 Locale 来实例化,还需要设置日期或时间的格式化值,DateFormat 有如下三个工厂方法:

    DateFormat.getDateInstance(dateStyle,loc);

    DateFormat.getTimeInstance(timeStyle,loc);

    DateFormat.getDateTimeInstance(dateStyle,timeStyle,loc);

    其日期和时间的风格使用 DateFormat 的静态常量来表示,常用的静态常量如下:

    DateFormat.DEFAULT

    DateFormat.FULL

    DateFormat.LONG

    DateFormat.MEDIUM

    DateFormat.SHORT

    示例代码如下:

    String fmt = "";

    Date nowDate = new Date();

    DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, zhLocale);

    fmt = dateFormat.format(nowDate);

    System.out.println("Short style date string " + fmt);

    ??

    dateFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM, zhLocale);

    fmt = dateFormat.format(nowDate);

    System.out.println("MEDIUM style time string " + fmt);

    ??

    dateFormat = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, zhLocale);

    fmt = dateFormat.format(nowDate);

    System.out.println("FULL style date FULL style time string " + fmt);

    如果要解析一个用户输入的日期,可以使用 DateFormat 类的 parse 方法,但其输入的格式必须和创建 DateFormat 对象设置的风格一致,如果不一致则会抛出 IllegalArgumentException 异常,默认支持宽松的转换,比如日期 2017年2月30日,会被解析为 2017年3月2日,如果希望关闭宽松的转换,需要设置 lenient 标识,示例代码如下:

    dateFormat.setLenient(false);

    Date convertDate = dateFormat.parse(fmt);

    System.out.println("parse string " + fmt + " convertDate " + convertDate);

  2. 字符串排序

    Java 中的排序时用Unicode字符来决定顺序的,比如小写字母的Unicode值比大写的大,有重音符的字母的值甚至更大,这样将使结果失去意义,如果需要定义排序的强度,可以使用Locale对象来创建 Collator 类,该类继承了 Comparator接口,因此可以将该对象传递给 Collections.soft 方法来进行排序,示例代码如下:

    List<String> list = new LinkedList<>();

    list.add("America");

    list.add("Zulu");

    list.add("able");

    list.add("zebra");

    ??

    Collator collator = Collator.getInstance(zhLocale);

    // 设置排序强度

    collator.setStrength(Collator.PRIMARY);

    Collections.sort(list, collator);

    可以设置排序强度来选择不同的排序行为,字符间的差别可以被分为首要的(primary)、其次的(secndary)和再次的(tertiary)。比如,在英语中,A 和 Z 之间的差异被归类为首要的;A 和 ? (重音符)之间的差异是其次的;A 和 a 之间是再次的,可以使用方法 setStrength 来设置排序强度,Collator.PRIMARY 表示首要的、Collator.SECONDARY 表示其次的、 Collator.TERTIARY 表示再次的,而Collator.IDENTICAL 则表示不允许有任何差异。

    偶尔我们会碰到一个字符或字符序列在描述成Unicode时,可以有多种方式,例如,字母序列"ffi"可以用代码U+FB03描述成单个字符"拉丁小连字ffi",Unicode 标准对字符串定义了四种范式形式:D、KD、C和KC,其中 D 和 KD 时用于排序的,在范化形式 D 重,重音字符被分解为基字符和组合重音符;范化形式 KD 更进一步将兼容性字符也进行了分解,例如 ffi 连字符或商标符号TM,我们可以选择排序器所使用的范化程度:Collator.NO_DECOMPOSITION表示不对字符串做任何范化;Collator.CANONICAL_DECOMPOSITION 使用范化形式 D;Collator.FULL_DECOMPOSITION 使用范化形式 KD。可以使用方法 setDecomposition 来设置分解模式

  3. 消息格式化

    Java 类库中有一个 MessageFormat 类,用来格式化带变量的文本,就像这样:"今天 {0} 是个好日子,{1} 公司给我发了工资 {2} 元",括号中的数字是一个占位符,可以用实际的名字和值来替换他,使用静态的 MessageFormat.format 方法,该方法使用当前系统的 Locale 对值进行格式化,如果需要用指定的 Locale 来进行格式化,需要使用 MessageFormat 实例的 format 方法,示例代码如下:

    String mfString = "今天 {0} 是个好日子,{1} 公司给我发了工资 {2} 元";

    String formatString = MessageFormat.format(mfString, new Date(), "汇元1", 50000);

    System.out.println(formatString);

    ??

    MessageFormat mf = new MessageFormat(mfString, zhLocale);

    formatString = mf.format(new Object[]{new Date(), "汇元2", 50000});

    System.out.println(formatString);

    如果还想在指定占位符的同时设置类型和样式,可以按照如下格式:

    String mfString = "今天 {0,date,long} 是个好日子,{1} 公司给我发了工资 {2,number,currency} 元";

    mf = new MessageFormat(mfString, zhLocale);

    formatString = mf.format(new Object[]{new Date(), "汇元3", 50000});

    System.out.println(formatString);

    输出内容

    今天 2017年5月26日 是个好日子,汇元3 公司给我发了工资 ¥50,000.00 元

    占位符索引后面可以跟一个类型和样式,之间用逗号隔开,类型可以是number、time、date、choice,如果类型是 number 则样式有 integer、currency、percent;如果类型是 time 或 date,那么样式有 short、medium、long、full 或者是一个日期模式(yyyy-MM-dd);choice 表示希望消息跟随占位符的值而变化,选项格式是由一个序列对构成的,每个序列对包含一个下限和一个格式化字符串,下限和字符串使用#号分隔,对于对之间用 | 分隔,示例如下:

    mfString = "今天 {0,date,yyyy-MM-dd} 是个好日子,{1,choice,0#汇元|1#汇元1|2#汇元2} 公司给我发了工资 {2,number,currency} 元";

    mf.applyPattern(mfString);

    formatString = mf.format(new Object[]{new Date(), 1, 50000});

    System.out.println(formatString);

    可以使用 < 符号或 符号 来替换 # ,则表示值小于或者小于等于下限值,示例代码如下:

    // choice说明: 这个表示 小于 1000 并且 1001-5000 的使用"可怜" ,5001-50000 的使用"不够",50001 以上为"正好"

    mfString = "今天 {0,date,yyyy-MM-dd} 是个好日子,{1} 公司给我发了工资 {2,number,currency} 元,{2,choice,1000<可怜|5000<不够|50000<正好}";

    mf.applyPattern(mfString);

    formatString = mf.format(new Object[]{new Date(), "汇元4", 60000});

    System.out.println(formatString

    输入内容

    今天 2017-05-26 是个好日子,汇元4 公司给我发了工资 ¥60,000.00 元,正好

  4. 资源包

    当本地化一个应用时,可能会有大量的消息字符串、按钮标签和其他的东西需要被翻译,为了能灵活的完成这项任务,你会希望外部定义消息字符串,通常称之为资源(resource),翻译人员不需要接触程序源代码就可以很容易的编辑资源文件,在Java 中使用属性文件来设定字符串资源,并未其他类型的资源实现相应的类。

    当本地化一个应用时,会制造出很多资源包(resource bundle),每一个包都是一个属性文件或者一个描述了玉locale相关的项的类,对于每一个包,都要为所有你想要支持的locale提供相应的版本,并需要对这些包使用一种统一的命名规则,例如,为中国定义的资源放在一个名为"包名_zh_CN"的文件中,而为所有使用中文简体的国家所共享的资源则放在名为"包名_zh"的文件中,一般来说,使用

    包名_语言_国家

    来命名所有和国家相关的资源,使用

    包名_语言

    来命名所有和语言相关的资源,最后,作为后备,可以把默认资源放到一个没有后缀的文件中,可以使用下面的代码加载一个包:

    ResourceBundle resourceBundle = ResourceBundle.getBundle(bundleName, locale);

    getBundle 方法加载包的顺序如下:

    1. 包名_当前locale的语言_当前locale的国家_当前locale的变量
    2. 包名_当前locale的语言_当前locale的国家
    3. 包名_当前locale的语言
    4. 包名_默认locale的语言_默认locale的国家_默认locale的变量
    5. 包名_默认locale的语言_默认locale的国家
    6. 包名_默认locale的语言
    7. 包名
    8. 抛出 MissingResourceException 异常

    一旦getBundle 方法定位了一个包,比如,"包名_zh_CN" ,他还会继续查找"包名_zh""包名"这二个包,如果这些包也存在,他们在资源层次中就称为了"包名_zh_CN"的父包,以后查找资源的时候,如果在当前包中没有找到,就会去查找其父包。

    1. 属性文件

      属性文件是为了提供字符串资源常用的文件,每行存放一个键-值对的文本文件,比如,MyProgramStrings.properties 就是一个属性文件,存储属性文件都是ASCII文件,然后可以使用 native2ascii 工具来产生MyProgramStrings_语言_国家.properties 文件,示例如下:

      native2ascii MyProgramStrings.properties MyProgramStrings_zh_CN.properties

      要查找一个具体的字符串,可以调用:

      String resourceText= resourceBundle.getString("show.text");

      示例资源文件内容:

      show.text=\u8fd9\u4e2a\u662f\u8d44\u6e90\u6587\u4ef6\u7684\u5185\u5bb9

    2. 包类

      为了提供字符串以外的资源,需要定义类,必须继承 ResourceBundle 类(简单的方法是继承 ListResourceBundle类),应该使用标准的命名规则来命名类,比如:

      MyProgramResource.java

      MyProgramResource_zh.java

      MyProgramResource_zh_CN.java

      可以使用与加载属性文件相同的 getBundle 方法来加载这个类,其 bundleName 资源包类的完整命名(包名和类名):

      ResourceBundle resourceBundle1Class =

      ????????????????????????????????ResourceBundle.getBundle("locale.MyProgramResource", Locale.SIMPLIFIED_CHINESE);

      要查找一个字符串或其他类型资源可以调用:

      resourceText = resourceBundle1Class.getString("resourceClass.show.text");

      Object resourceObj = resourceBundle1Class.getObject("resourceClass.show.obj");

      如果一个Key同时存在属性文件和包类,则包类的优先。

      ??

笔记:国际化