flea-common使用之本地国际化实现

本文涉及的产品
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
简介: 本篇 Huazie 介绍 Flea框架下的本地国际化实现 — Flea I18N

flea-logo.png

引言

百度百科针对 国际化 的解释:

internationalization.png

本地国际化,就是指应用程序根据所处语言环境的不同【如 Java 中可用 国际化标识类 java.util.Locale 区分不同语言环境】,自动匹配应用内置的相应的语言环境下的资源配置【如 Java 中可用 资源包类 java.util.ResourceBundle 来匹配】,从而获取并对外展示相应的语言环境下的资源信息。

话不多说,直接上干货:

1. 依赖

    <!-- FLEA COMMON-->
    <dependency>
        <groupId>com.huazie.fleaframework</groupId>
        <artifactId>flea-common</artifactId>
        <version>2.0.0</version>
    </dependency>

2. 实现

上面提到了 Java 中 的 国际化标识类 java.util.Locale资源包类 java.util.ResourceBundle,这两者就是本地国际化实现的关键所在。

2.1 定义国际化资源相关配置

flea-config.xml 用于特殊配置国际化资源的路径和文件前缀。

<flea-config>
    <!-- flea-common -->
    <config-items key="flea-i18n-config" desc="Flea国际化相关配置">
        <config-item key="error" desc="error国际化资源特殊配置,指定路径和文件前缀,逗号分隔">flea/i18n,flea_i18n</config-item>
    </config-items>
</flea-config>

2.2 定义Flea I18N 配置类

在使用 FleaI18nConfig 之前,我们先了解下Flea国际化资源文件的组成,主要有如下 5 部分:

flea-i18n-config.png

上述国际化资源也可以配置默认资源文件,即文件名中不需要包含国际化标识 。例如: flea/i18n/flea_i18n_error.properties

注意: 国际化资源文件扩展名必须为 properties

好了,基础的认知有了,我们开始了解 FleaI18nConfig,如下贴出了实现:

/**
 * Flea I18N 配置类,用于获取指定语言环境下的指定资源对应的国际化数据。
 *
 * <p> 它默认读取资源路径为 flea/i18n,资源文件前缀为 flea_i18n,当然
 * 也可以在 flea-config.xml 中为指定资源文件配置路径和前缀,从而可以
 * 实现读取任意位置的资源数据。
 *
 * @author huazie
 * @version 2.0.0
 * @since 1.0.0
 */
public class FleaI18nConfig {
   

    private static final FleaLogger LOGGER = FleaLoggerProxy.getProxyInstance(FleaI18nConfig.class);

    private static volatile FleaI18nConfig config;

    private ConcurrentMap<String, String> resFilePath = new ConcurrentHashMap<>(); // 资源文件路径集

    private ConcurrentMap<String, ResourceBundle> resources = new ConcurrentHashMap<>(); // 资源集

    /**
     * 只允许通过 getConfig() 获取 Flea I18N 配置类实例
     */
    private FleaI18nConfig() {
   
        init(); // 初始化资源文件相关配置
    }

    /**
     * 获取 Flea I18N 配置类实例
     *
     * @return Flea I18N 配置类实例
     * @since 1.0.0
     */
    public static FleaI18nConfig getConfig() {
   
        if (ObjectUtils.isEmpty(config)) {
   
            synchronized (FleaI18nConfig.class) {
   
                if (ObjectUtils.isEmpty(config)) {
   
                    config = new FleaI18nConfig();
                }
            }
        }
        return config;
    }

    /**
     * 初始化资源名和资源文件相关属性的映射关系
     *
     * @since 1.0.0
     */
    private void init() {
   
        ConfigItems fleaI18nItems = FleaConfigManager.getConfigItems(CommonConstants.FleaI18NConstants.FLEA_I18N_CONFIG_ITEMS_KEY);
        if (ObjectUtils.isNotEmpty(fleaI18nItems) && CollectionUtils.isNotEmpty(fleaI18nItems.getConfigItemList())) {
   
            for (ConfigItem configItem : fleaI18nItems.getConfigItemList()) {
   
                if (ObjectUtils.isNotEmpty(configItem) && StringUtils.isNotBlank(configItem.getKey()) && StringUtils.isNotBlank(configItem.getValue())) {
   
                    String[] valueArr = StringUtils.split(configItem.getValue(), CommonConstants.SymbolConstants.COMMA);
                    if (ArrayUtils.isNotEmpty(valueArr) && CommonConstants.NumeralConstants.INT_TWO == valueArr.length) {
   
                        // 获取资源文件路径
                        String filePath = StringUtils.trim(valueArr[0]);
                        // 获取资源文件前缀
                        String fileNamePrefix = StringUtils.trim(valueArr[1]);
                        if (StringUtils.isNotBlank(filePath) && StringUtils.isNotBlank(fileNamePrefix)) {
   
                            String configResFilePath;
                            // 如果资源文件路径最后没有 "/",自动添加
                            if (CommonConstants.SymbolConstants.SLASH.equals(StringUtils.subStrLast(filePath, 1))) {
   
                                configResFilePath = filePath + fileNamePrefix;
                            } else {
   
                                configResFilePath = filePath + CommonConstants.SymbolConstants.SLASH + fileNamePrefix;
                            }
                            resFilePath.put(configItem.getKey(), configResFilePath);
                        }
                    }
                }
            }
        }
        // 添加默认资源文件路径
        String defaultResFilePath = CommonConstants.FleaI18NConstants.FLEA_I18N_FILE_PATH +
                CommonConstants.FleaI18NConstants.FLEA_I18N_FILE_NAME_PREFIX; // 默认资源文件路径(仅包含公共的部分)
        resFilePath.put(CommonConstants.SymbolConstants.ASTERISK, defaultResFilePath);
    }

    /**
     * 通过国际化数据的key,获取当前系统指定资源的国际化资源;
     * 其中国际化资源中使用 {} 标记的,需要values中的数据替换。
     *
     * @param key     国际化资源KEY
     * @param values  待替换字符串数组
     * @param resName 资源名
     * @param locale  国际化标识
     * @return 国际化资源数据
     * @since 2.0.0
     */
    public FleaI18nData getI18NData(String key, String[] values, String resName, Locale locale) {
   
        return new FleaI18nData(key, this.getI18NDataValue(key, values, resName, locale));
    }

    /**
     * 通过国际化数据的key,获取当前系统指定资源的国际化资源
     *
     * @param key     国际化资源KEY
     * @param resName 资源名
     * @param locale  国际化标识
     * @return 国际化资源数据
     * @since 1.0.0
     */
    public FleaI18nData getI18NData(String key, String resName, Locale locale) {
   
        return new FleaI18nData(key, this.getI18NDataValue(key, resName, locale));
    }

    /**
     * <p> 通过国际化数据的key,获取当前系统指定资源的国际化资源数据 </p>
     *
     * @param key     国际化资源KEY
     * @param values  国际化资源数据替换内容
     * @param resName 资源名
     * @param locale  国际化标识
     * @return 国际化资源数据
     * @since 1.0.0
     */
    public String getI18NDataValue(String key, String[] values, String resName, Locale locale) {
   
        String value = getI18NDataValue(key, resName, locale);
        if (ArrayUtils.isNotEmpty(values)) {
   
            StringBuilder builder = new StringBuilder(value);
            for (int i = 0; i < values.length; i++) {
   
                StringUtils.replace(builder, CommonConstants.SymbolConstants.LEFT_CURLY_BRACE + i + CommonConstants.SymbolConstants.RIGHT_CURLY_BRACE, values[i]);
            }
            value = builder.toString();
        }
        return value;
    }

    /**
     * <p> 通过国际化数据的key,获取当前系统指定资源的国际化资源数据 </p>
     *
     * @param key     国际化资源KEY
     * @param resName 资源名
     * @param locale  国际化标识
     * @return 国际化资源数据
     * @since 1.0.0
     */
    public String getI18NDataValue(String key, String resName, Locale locale) {
   
        Object obj = null;
        if (LOGGER.isDebugEnabled()) {
   
            obj = new Object() {
   
            };
            LOGGER.debug1(obj, "Find the key     : {}", key);
            LOGGER.debug1(obj, "Find the resName : {}", resName);
            LOGGER.debug1(obj, "Find the locale  : {} , {}", locale == null ? Locale.getDefault() : locale, locale == null ? Locale.getDefault().getDisplayLanguage() : locale.getDisplayLanguage());
        }
        ResourceBundle resource = getResourceBundle(resName, locale);

        String value = null;
        if (ObjectUtils.isNotEmpty(resource)) {
   
            value = resource.getString(key);
            if (StringUtils.isBlank(value)) {
    // 如果取不到数据,则使用key返回
                value = key;
            }
        }

        if (LOGGER.isDebugEnabled()) {
   
            LOGGER.debug1(obj, "Find the value   : {} ", value);
        }
        return value;
    }

    /**
     * <p> 根据资源名和国际化标识获取指定国际化配置ResourceBundle对象 </p>
     *
     * @param resName 资源名
     * @param locale  国际化标识
     * @return 国际化配置ResourceBundle对象
     * @since 1.0.0
     */
    private ResourceBundle getResourceBundle(String resName, Locale locale) {
   

        String key = generateKey(resName, locale);

        Object obj = null;
        if (LOGGER.isDebugEnabled()) {
   
            obj = new Object() {
   
            };
            LOGGER.debug1(obj, "Find the resKey  : {}", key);
        }

        ResourceBundle resource = resources.get(key);

        // 获取资源文件名
        StringBuilder fileName = new StringBuilder(getResFilePath(resName));
        if (StringUtils.isNotBlank(resName)) {
   
            fileName.append(CommonConstants.SymbolConstants.UNDERLINE).append(resName);
        }

        if (LOGGER.isDebugEnabled()) {
   
            if (ObjectUtils.isEmpty(locale)) {
   
                LOGGER.debug1(obj, "Find the expected fileName: {}.properties", fileName);
            } else {
   
                LOGGER.debug1(obj, "Find the expected fileName: {}_{}.properties", fileName, locale);
            }
        }

        // 获取资源文件
        if (ObjectUtils.isEmpty(resource)) {
   
            if (ObjectUtils.isEmpty(locale)) {
   
                resource = ResourceBundle.getBundle(fileName.toString());
            } else {
   
                resource = ResourceBundle.getBundle(fileName.toString(), locale);
            }
            resources.put(key, resource);
        }

        if (LOGGER.isDebugEnabled()) {
   
            Locale realLocale = resource.getLocale();
            if (ObjectUtils.isEmpty(locale) || StringUtils.isBlank(realLocale.toString())) {
   
                LOGGER.debug1(obj, "Find the real fileName: {}.properties", fileName);
            } else {
   
                LOGGER.debug1(obj, "Find the real fileName: {}_{}.properties", fileName, realLocale);
            }
        }

        return resource;
    }

    /**
     * <p> 获取国际化资源文件KEY </p>
     * <p> 如果资源名不为空,则资源名作为key,同时如果国际化标识不为空,则取资源名+下划线+国际化语言作为key;
     *
     * @param resName 资源名
     * @param locale  国际化标识
     * @return 国际化资源文件KEY
     * @since 1.0.0
     */
    private String generateKey(String resName, Locale locale) {
   
        String key = "";
        if (StringUtils.isNotBlank(resName)) {
   
            key = resName;
            if (ObjectUtils.isNotEmpty(locale)) {
   
                key += CommonConstants.SymbolConstants.UNDERLINE + locale;
            }
        }
        return key;
    }

    /**
     * <p> 根据资源名,获取资源文件路径 </p>
     *
     * @param resName 资源名
     * @return 资源文件路径
     * @since 1.0.0
     */
    private String getResFilePath(String resName) {
   
        // 首先根据资源名,从 资源文件路径集中获取
        String resFilePathStr = resFilePath.get(resName);
        if (ObjectUtils.isEmpty(resFilePathStr)) {
   
            // 取默认资源文件路径
            resFilePathStr = resFilePath.get(CommonConstants.SymbolConstants.ASTERISK);
        }
        return resFilePathStr;
    }

}

2.3 定义Flea I18N 工具类

FleaI18nHelper 封装了 I18N 资源数据获取的静态方法,主要包含如下4种:

    public static String i18n(String key, String resName, Locale locale) {
   
        return FleaI18nConfig.getConfig().getI18NDataValue(key, resName, locale);
    }

    public static String i18n(String key, String[] values, String resName, Locale locale) {
   
        return FleaI18nConfig.getConfig().getI18NDataValue(key, values, resName, locale);
    }

    // 实际在调用该方法之前,可以通过 FleaFrameManager.getManager().setLocale(Locale) 设置当前线程的国际化标识。
    public static String i18n(String key, String resName) {
   
        return i18n(key, resName, FleaFrameManager.getManager().getLocale());
    }

    // 实际在调用该方法之前,可以通过 FleaFrameManager.getManager().setLocale(Locale) 设置当前线程的国际化标识。
    public static String i18n(String key, String[] values, String resName) {
   
        return i18n(key, values, resName, FleaFrameManager.getManager().getLocale());
    }

    // 其他是对具体资源的封装,如错误码资源error、授权资源auth 和 公共信息资源common

2.4 定义Flea I18N资源枚举

FleaI18nResEnum 定义了 Flea I18N 的资源文件类型

/**
 * Flea I18N 资源枚举
 *
 * @author huazie
 * @version 1.0.0
 * @since 1.0.0
 */
public enum FleaI18nResEnum {
   

    ERROR("error", "异常信息国际码资源文件类型"),
    ERROR_CORE("error_core", "FLEA CORE异常信息国际码资源文件类型"),
    ERROR_DB("error_db", "FLEA DB异常信息国际码资源文件类型"),
    ERROR_JERSEY("error_jersey", "FLEA JERSEY异常信息国际码资源文件类型"),
    ERROR_AUTH("error_auth", "FLEA AUTH异常信息国际码资源文件类型"),
    AUTH("auth", "FLEA AUTH 国际码资源文件类型"),
    COMMON("common", "公共信息国际码资源文件类型");

    private String resName;
    private String resDesc;

    /**
     * <p> 资源文件类型枚举构造方法 </p>
     *
     * @param resName 资源名
     * @param resDesc 资源描述
     * @since 1.0.0
     */
    FleaI18nResEnum(String resName, String resDesc) {
   
        this.resName = resName;
        this.resDesc = resDesc;
    }

    public String getResName() {
   
        return resName;
    }

    public String getResDesc() {
   
        return resDesc;
    }

}

简单的介绍之后,初步了解了本地国际化的实现,下面就需要来实际测试一下了。

话不多说,开始操刀:

3. 自测

首先,我们先添加几个国际化配置文件,如下:

flea-i18n-config-file.png

资源文件 国际化标识(语言环境)
flea/i18n/flea_i18n_error.properties 默认
flea/i18n/flea_i18n_error_zh_CN.properties 中文(简体)
flea/i18n/flea_i18n_error_en_US.properties 英文(美式)

注意: 笔者电脑的本地语言环境为 中文(简体)

3.1 匹配指定语言

    @Test
    public void fleaI18nHelperTest1() {
   
        String value = FleaI18nHelper.i18n("ERROR0000000001", "error", Locale.US);
        LOGGER.debug("Value = {}", value);
    }

测试结果:

result1.png

3.2 匹配本地语言

    @Test
    public void fleaI18nHelperTest() {
   
        String value = FleaI18nHelper.i18n("ERROR0000000001", "error", Locale.FRANCE);
        LOGGER.debug("Value = {}", value);
    }

测试结果:

result2.png

3.3 匹配默认资源

首先,我们将本地语言的资源文件删除,如下:

flea-i18n-config-file1.png

    @Test
    public void fleaI18nHelperTest() {
   
        String value = FleaI18nHelper.i18n("ERROR0000000001", "error", Locale.FRANCE);
        LOGGER.debug("Value = {}", value);
    }

测试结果:

result3.png

3.4 无资源匹配

首先,我们将本地语言默认 的 资源文件删除,如下:

flea-i18n-config-file2.png

    @Test
    public void fleaI18nHelperTest() {
   
        String value = FleaI18nHelper.i18n("ERROR0000000001", "error", Locale.FRANCE);
        LOGGER.debug("Value = {}", value);
    }

测试结果:

result4.png

4. 接入

上面演示了 如何通过 FleaI18nHelper 获取本地国际化的资源数据,下面我们来看看在异常类中接入错误码国际化资源。

4.1 定义通用异常类

CommonException 定义了 Flea I18N 下的通用异常,由子类传入具体的国际化资源枚举类型

/**
 * Flea I18N 通用异常,由子类传入具体的国际化资源枚举类型
 *
 * @author huazie
 * @version 1.0.0
 * @since 1.0.0
 */
public abstract class CommonException extends Exception {
   

    private static final long serialVersionUID = 1746312829236028651L;

    private String key;                     // 国际化资源数据关键字

    private Locale locale;                  // 国际化区域标识

    private FleaI18nResEnum i18nResEnum;    // 国际化资源类型

    public CommonException(String mKey, FleaI18nResEnum mI18nResEnum) {
   
        // 使用服务器当前默认的国际化区域设置
        this(mKey, mI18nResEnum, FleaFrameManager.getManager().getLocale());
    }

    public CommonException(String mKey, FleaI18nResEnum mI18nResEnum, String... mValues) {
   
        // 使用服务器当前默认的国际化区域设置
        this(mKey, mI18nResEnum, FleaFrameManager.getManager().getLocale(), mValues);
    }

    public CommonException(String mKey, FleaI18nResEnum mI18nResEnum, Locale mLocale) {
   
        // 使用指定的国际化区域设置
        this(mKey, mI18nResEnum, mLocale, new String[]{
   });
    }

    public CommonException(String mKey, FleaI18nResEnum mI18nResEnum, Locale mLocale, String... mValues) {
   
        // 使用指定的国际化区域设置
        super(convert(mKey, mValues, mI18nResEnum, mLocale));
        key = mKey;
        locale = mLocale;
        i18nResEnum = mI18nResEnum;
    }

    public CommonException(String mKey, FleaI18nResEnum mI18nResEnum, Throwable cause) {
   
        // 使用服务器当前默认的国际化区域设置
        this(mKey, mI18nResEnum, FleaFrameManager.getManager().getLocale(), cause);
    }

    public CommonException(String mKey, FleaI18nResEnum mI18nResEnum, Throwable cause, String... mValues) {
   
        // 使用服务器当前默认的国际化区域设置
        this(mKey, mI18nResEnum, FleaFrameManager.getManager().getLocale(), cause, mValues);
    }

    public CommonException(String mKey, FleaI18nResEnum mI18nResEnum, Locale mLocale, Throwable cause) {
   
        // 使用指定的国际化区域设置
        this(mKey, mI18nResEnum, mLocale, cause, new String[]{
   });
    }

    public CommonException(String mKey, FleaI18nResEnum mI18nResEnum, Locale mLocale, Throwable cause, String... mValues) {
   
        // 使用指定的国际化区域设置
        super(convert(mKey, mValues, mI18nResEnum, mLocale), cause);
        key = mKey;
        locale = mLocale;
        i18nResEnum = mI18nResEnum;
    }

    private static String convert(String key, String[] values, FleaI18nResEnum i18nResEnum, Locale locale) {
   
        if (ObjectUtils.isEmpty(locale)) {
   
            locale = FleaFrameManager.getManager().getLocale(); // 使用当前线程默认的国际化区域设置
        }
        if (ObjectUtils.isEmpty(i18nResEnum)) {
   
            i18nResEnum = FleaI18nResEnum.ERROR; // 默认使用 国际化资源名为 error
        }
        if (ArrayUtils.isNotEmpty(values)) {
   
            return FleaI18nHelper.i18n(key, values, i18nResEnum.getResName(), locale);
        } else {
   
            return FleaI18nHelper.i18n(key, i18nResEnum.getResName(), locale);
        }
    }

    public String getKey() {
   
        return key;
    }

    public Locale getLocale() {
   
        return locale;
    }

    public FleaI18nResEnum getI18nResEnum() {
   
        return i18nResEnum;
    }
}

4.2 定义业务逻辑层异常类

ServiceException 定义了业务逻辑层抛出的异常,对应的国际化资源名为【error】

/**
 * 业务逻辑层异常类,定义了业务逻辑层抛出的异常,
 * 其对应的国际化资源名为【error】
 *
 * @author huazie
 * @version 1.0.0
 * @since 1.0.0
 */
public class ServiceException extends CommonException {
   

    public ServiceException(String key) {
   
        super(key, FleaI18nResEnum.ERROR);
    }

    public ServiceException(String key, String... values) {
   
        super(key, FleaI18nResEnum.ERROR, values);
    }

    public ServiceException(String key, Throwable cause) {
   
        super(key, FleaI18nResEnum.ERROR, cause);
    }

    public ServiceException(String key, Throwable cause, String... values) {
   
        super(key, FleaI18nResEnum.ERROR, cause, values);
    }

}

总结

好了,Flea框架下的本地国际化实现已经介绍完毕,欢迎大家使用!

相关实践学习
DataV Board用户界面概览
本实验带领用户熟悉DataV Board这款可视化产品的用户界面
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
目录
相关文章
|
7月前
|
XML JSON 数据格式
flea-jersey使用之Flea RESTful接口客户端接入
本篇介绍 Flea框架下的 flea-jersey-client模块,并提供客户端依赖管理及接入步骤
62 1
flea-jersey使用之Flea RESTful接口客户端接入
|
XML 存储 数据库连接
Entity Framework学习笔记——edmx文件
上文简单介绍了一下Entity FrameWork,这里说一下EF的核心——edmx文件。 在VisualStudio中建立edmx文件(此例环境为VisualStudio2012)
Entity Framework学习笔记——edmx文件
|
7月前
|
XML JSON 网络架构
flea-jersey使用之Flea RESTful接口介绍
本篇介绍 Flea框架下 flea-jersey 模块下的 Flea RESTful接口
60 1
flea-jersey使用之Flea RESTful接口介绍
|
7月前
|
设计模式 算法 测试技术
flea-common使用之通用策略模式实现
跟着 Huazie 一起了解通用策略模式实现【Flea Strategy】
132 1
flea-common使用之通用策略模式实现
|
7月前
|
XML Java 数据格式
Java中的Properties类详解Properties配置文件
Java中的Properties类详解Properties配置文件
120 0
|
XML Java 数据格式
一起学SF框架系列5.2-模块Beans-bean的元数据配置
一起学SF框架系列5.2-模块Beans-bean的元数据配置
105 0
|
API
The bean ‘api‘, defined in class path resource [com/common/swagger/SwaggerAutoConf
The bean ‘api‘, defined in class path resource [com/common/swagger/SwaggerAutoConf
175 0
The bean ‘api‘, defined in class path resource [com/common/swagger/SwaggerAutoConf
|
存储 开发框架 .NET
Entity Framework基础01
Entity Framework基础01
207 0
Entity Framework基础01
|
存储 API 数据库
Entity Framework 约定
Entity Framework 约定
143 0
|
XML Java 数据库连接
【珍藏】Java Bean Validation详解及国际化集成
【珍藏】Java Bean Validation详解及国际化集成
972 0