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

《开发框架-Flea》

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

引言

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

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

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

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

话不多说,直接上干货:

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-common使用之本地国际化实现

上述国际化资源也可以配置默认资源文件,即文件名中不需要包含国际化标识 。例如: 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-common使用之本地国际化实现

资源文件 国际化标识(语言环境)
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);
}

测试结果:

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

3.2 匹配本地语言

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

测试结果:

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

3.3 匹配默认资源

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

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

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

测试结果:

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

3.4 无资源匹配

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

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

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

测试结果:

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

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框架下的本地国际化实现已经介绍完毕,欢迎大家使用!

原文链接:https://juejin.cn/post/7359893210796015627 作者:Huazie

(0)
上一篇 2024年4月22日 上午9:40
下一篇 2024年4月22日 上午9:50

相关推荐

发表回复

登录后才能评论