Java 国际化与文本格式化

简介: 国际化标准实现Java 中的字符使用 Unicode 编码,因此支持各个国家的语言。如果开发的软件仅在中国使用,那么我们直接使用中文即可。

国际化标准实现


Java 中的字符使用 Unicode 编码,因此支持各个国家的语言。如果开发的软件仅在中国使用,那么我们直接使用中文即可。如果开发的软件仅在美国使用,我们可以只使用英文。那如果我们开发的软件需要同时支持不同国家的语言呢?能否做到在不同的国家和地区使用我们开发的软件时展示相对应的语言?答案是肯定的,Java 已经进行了支持。为了支持国际化软件的开发,Java 提供了主要的两个类,分别是 Locale 和 ResourceBundle,下面加以介绍。


Java 国际化之 Locale


Java 提供了 java.util.Locale 类,表示特定的地理、政治或者文化区域。


有如下方式可以获取 Locale 的实例。


// 1. 获取系统默认 Locale,如 zh_CN,分别表示语言为 zh ,国家为 CN ;设置默认地区可调用方法 Locale#setDefault(Locale)
Locale locale = Locale.getDefault();
// 2. 通过构造方法创建 Locale,language 表示语言,country 表示国家或者地区
public Locale(String language)
public Locale(String language, String country)
// 3. Locale 内置了一些常见的实例,部分示例如下
Locale.SIMPLIFIED_CHINESE
Locale.ENGLISH
Locale.JAPANESE
Locale.KOREAN


Locale 作为地区信息通常不会直接使用,而是配合 Format 或者 ResourceBundle。Format 用于对文本进行格式化,而 ResourceBundle 用于根据地区信息和某一个 code 获取对应的文本值。


如果想打印出当前地区下的时间,我们可以使用如下的代码。


public class App {
    public static void main(String[] args) throws IOException {
        Locale locale = Locale.getDefault();
        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
        // 2020年11月21日 下午02时46分46秒
        System.out.println(dateFormat.format(new Date()));
    }
}


Java 国际化之 ResourceBundle


ResourceBundle 设计特性


ResourceBundle 作为一个抽象类,默认情况下读取类路径下用于国际化的 properties 资源文件。其设计具有如下特性。


基于 key-value 设计,可以根据 key 获取对应的 value。

层次性设计,当前 ResourceBundle 获取不到对应的 value 时将从父 ResourceBundle 查找。

缓存特性,其内部会对 key-value 进行缓存,以便下次直接获取。

提供 ResourceBundle.Control 自定义获取 ResourceBundle 实现。

通过 SPI 机制获取 ResourceBundleControlProvider 实现,用于获取 ResourceBundle.Control。

下面先看使用 ResourceBundle 直观的示例。


1. classpath 下新建目录 META-INF/
2. META-INF/ 目录下新建文件 messages_en_US.properties ,内容如下:
name = zzuhkp
3. META-INF/ 目录下新建文件 messages_zh_CN.properties ,内容如下:
name = \u5927\u9e4f
4. 为了获取中文环境下 name 对应的值,示例如下。
public class App {
    public static void main(String[] args) throws IOException {
        ResourceBundle resourceBundle = ResourceBundle.getBundle("META-INF.messages", Locale.SIMPLIFIED_CHINESE);
        String name = resourceBundle.getString("name");
        // name:大鹏
        System.out.println("name:" + name);
    }
}


这里需要注意的是获取 ResourceBundle 实例时提供的资源文件名的格式,目录和文件名使用.分隔,文件名不包含表示地区的部分以及文件类型。


如何获取 ResourceBundle 实例


ResourceBundle 是一个抽象类,同时其提供了工厂方法用于获取实现,具体如下。


public static final ResourceBundle getBundle(String baseName)
public static final ResourceBundle getBundle(String baseName,Control control)
public static final ResourceBundle getBundle(String baseName,Locale locale)
public static final ResourceBundle getBundle(String baseName, Locale targetLocale,Control control)
public static ResourceBundle getBundle(String baseName, Locale locale,ClassLoader loader)
public static ResourceBundle getBundle(String baseName, Locale targetLocale,ClassLoader loader, Control control)


上述中的工厂方法getBundle,参数少的方法将提供默认值直接调用参数最多的方法。如下所示。


    public static final ResourceBundle getBundle(String baseName)
    {
        return getBundleImpl(baseName, Locale.getDefault(),
                             getLoader(Reflection.getCallerClass()),
                             getDefaultControl(baseName));
    }


ResourceBundle 中的 Control


JDK 6 新增了一个类 ResourceBundle.Control 可用于自定义 ResourceBundle 的获取,如果使用了不带 Control 参数的工厂方法,将尝试进行查找,源码如下。


    private static final List<ResourceBundleControlProvider> providers;
    static {
        List<ResourceBundleControlProvider> list = null;
        ServiceLoader<ResourceBundleControlProvider> serviceLoaders
                = ServiceLoader.loadInstalled(ResourceBundleControlProvider.class);
        for (ResourceBundleControlProvider provider : serviceLoaders) {
            if (list == null) {
                list = new ArrayList<>();
            }
            list.add(provider);
        }
        providers = list;
    }
    // 获取默认的 Control 
    private static Control getDefaultControl(String baseName) {
        if (providers != null) {
            for (ResourceBundleControlProvider provider : providers) {
                Control control = provider.getControl(baseName);
                if (control != null) {
                    return control;
                }
            }
        }
        return Control.INSTANCE;
    }


可以看到,默认通过 SPI 机制获取的 ResourceBundleControlProvider 实例来获取 Control 实例,如果获取不到则会使用默认的 Control 实例。默认的 Control 实例支持两种策略来获取 ResourceBundle。


根据 baseName 获取 ResourceBundle 类名通过反射实例化获取 ResourceBundle 实例。

根据 baseName 获取对应的国际化资源文件,然后实例化 PropertyResourceBundle 类,前面中的示例就属于这种情况。


自定义获取 Control 获取 ResourceBundle


在前面的示例中我们看到 properties 文件中,我们使用了十六进制表示中文,这是因为默认情况下 properties 文件是使用 ISO-8859-1 编码进行读取,为了使用 UTF-8 读取 properties 文件,我们可以对前面的示例进行如下改造。


1. 修改 META-INF/messages_zh_CN.properties 内容如下:
name = 中文名称:大鹏
2. 自定义 Control
/**
 * @author zzuhkp
 * @date 2020-11-21 17:39
 * @since 1.0
 */
public class UTF8Control extends ResourceBundle.Control {
    @Override
    public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
        throws IllegalAccessException, InstantiationException, IOException {
        String bundleName = this.toBundleName(baseName, locale);
        String resourceName = this.toResourceName(bundleName, "properties");
        InputStream inputStream = loader.getResourceAsStream(resourceName);
        return new ResourceBundle() {
            private Map<String, Object> lookup;
            {
                Properties properties = new Properties();
                properties.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
                lookup = new HashMap(properties);
            }
            @Override
            protected Object handleGetObject(String key) {
                return lookup.get(key);
            }
            @Override
            public Enumeration<String> getKeys() {
                ResourceBundle parent = this.parent;
                return new ResourceBundleEnumeration(lookup.keySet(),
                    (parent != null) ? parent.getKeys() : null);
            }
        };
    }
}
3. 修改示例代码如下。
public class App {
    public static void main(String[] args) throws IOException {
        ResourceBundle resourceBundle = ResourceBundle.getBundle("META-INF.messages", Locale.SIMPLIFIED_CHINESE,new UTF8Control());
        String name = resourceBundle.getString("name");
        System.out.println("name:" + name);
    }
}


上述示例中,我们在获取 ResourceBundle 实例时提供了自定义的 Control 实例作为参数,打印结果如下。


name:中文名称:大鹏


Java 文本格式化


Java 文本格式化的核心类是 MessageFormat,它和国际化息息相关,因此这里也做提及。先通过一个样例学习其基本使用方式。


public class App {
    public static void main(String[] args) throws IOException {
        String message = "My name is {0},and my age is {1,number},and today is {2,date,full}";
        MessageFormat messageFormat = new MessageFormat(message, Locale.getDefault());
        String format = messageFormat.format(new Object[]{"zzuhkp", 26,new Date()});
        // My name is zzuhkp,and my age is 26,and today is 2020年11月21日 星期六
        System.out.println(format);
    }
}


文本格式化即文本中存在占位符,在格式化时通过提供的参数及占位符指定的位置以及格式对其进行替换。


文本格式化的占位符格式为:{ArgumentIndex,[ArgumentType,[ArgumentStyle]]},其中[]内的为可选部分。


ArgumentIndex 表示参数的位置,从0开始。

ArgumentType 表示参数的格式类型,为可选项,可选值为"",、"number"、"date"、"time"、"choice"。

ArgumentStyle 表示参数的样式,为可选项,根据不同的样式格式化时会展示不同的内容,可选值为 ""、"short"、"medium"、"long"、"full"、"currency"、 "percent"、"integer"。

如果 MessageFormat 已经实例化,我们则可以对其进行重置相关信息,具体如下。


重置消息模式:java.text.MessageFormat#applyPattern

重置消息的区域信息:java.text.MessageFormat#setLocale

重置消息索引位置的格式:java.text.MessageFormat#setFormat


总结

本篇总结了 Java 对国际化的支持,包括 Locale、ResourceBundle 以及用于文本格式化的 MessageFormat,可以看到国际化文案的获取以及对文案的格式化并未整合到一起,下篇将分析 Spring 如何对这两者进行整合。


目录
相关文章
|
6月前
|
JavaScript 前端开发 Java
java调用js实现富文本过滤
java调用js实现富文本过滤
|
21天前
|
Java API Apache
Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
【10月更文挑战第29天】Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
88 5
|
3月前
|
IDE Java 开发工具
Java系统中的错误码设计问题之为Java项目中的错误消息提供国际化支持如何解决
Java系统中的错误码设计问题之为Java项目中的错误消息提供国际化支持如何解决
51 0
|
6月前
|
前端开发 Java
基于Java爬取微博数据(二) 正文长文本+导出数据Excel
【5月更文挑战第12天】基于Java爬取微博数据,正文长文本+导出数据Excel
|
4月前
|
自然语言处理 Java API
如何在Java中实现多语言国际化支持
如何在Java中实现多语言国际化支持
|
4月前
|
存储 自然语言处理 Java
如何在Java中实现国际化与本地化
如何在Java中实现国际化与本地化
|
5月前
|
XML Java 数据格式
Java用xpdf库获取pdf文件的指定范围文本内容
Java用xpdf库获取pdf文件的指定范围文本内容
92 1
|
4月前
|
存储 搜索推荐 算法
Java中的文本搜索与全文检索引擎
Java中的文本搜索与全文检索引擎
|
4月前
|
存储 Java 文件存储
Java中的国际化与本地化处理
Java中的国际化与本地化处理
|
4月前
|
存储 自然语言处理 Java
实现Java中的国际化和本地化
实现Java中的国际化和本地化
下一篇
无影云桌面