背景:因为后台中在进行国际化配置时考虑到,需要国际化的除了一些提示性的语言外,还有所有界面的查询条件也需要。且查询界面上的查询条件是需要在数据库中进行维护的。
实现:基于上面的背景,再加上前端使用React,因查询界面上的所有查询条件是在后台组装好直接返回前端进行渲染的。所以设计为 数据库添加国际化配置和 在配置文件中 添加。
数据库配置:主要存储界面要显示的查询条件
配置文件中设置:主要存储界面上的提示语和一个错误性提示。
前端React中:因为前端使用的是Antd提供的procompents组件,一些组件内自带的或者模块内的国际化配置在前端中实现。
一. 前期准备
1.1 国际化信息(后台在配置文件中存储国际化信息)
- 我们的 message 文件是直接创建在 resources 目录下的,IDEA 在展示的时候,会多出一个 Resource Bundle,这个大家不用管,千万别手动去创建这个目录。
- messages.properties 这个是默认的配置,其他的则是不同语言环境下的配置,en_US 是英语(美国),zh_CN 是中文简体,zh_TW 是中文繁体(文末附录里边有一个完整的语言简称表格)。
- 首先我们在项目的 resources 目录下创建一个文件夹i18n,新建语言文件,language_en_us.properties 和 language_zh-cn.properties,如下图:
修改上面的文件,可以打开messages.properties文件,选择Resource Bundle,即可在右侧添加所有语言的配置
二. 国际化相关配置
2.1 指定i18n国际化文件路径
spring:
messages:
basename: i18n/messages
encoding: UTF-8
2.2 自定义MessageSource类整合国际化消息
要点
ResourceBundle.getBundle() 读取国际化文件消息
Collectors.groupingBy() 分组
Collectors.toMap() 收集为Map
LocaleContextHolder.getLocale() 获取设置的当前地区Locale
package com.averydennison.searchConfig.business.service.impl;
import com.averydennison.searchConfig.business.entity.I18Message;
import com.averydennison.searchConfig.business.mapper.paxarprofile.ConfigI18nMapper;
import com.google.common.collect.ImmutableMap;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
@Component("messageSource")
//: 也可以在此处指明bean的名称为 messageSource
public class CustomMessageSource extends AbstractMessageSource implements InitializingBean {
// 这个是用来缓存数据库中获取到的配置的 数据库配置更改的时候可以调用reload方法重新加载
private static final Map<String, Map<String, String>> LOCAL_CACHE = new ConcurrentHashMap<>();
// 注入查询接口对象
@Resource
private ConfigI18nMapper i18nMessageMapper;
// 程序启动之后,会自动加载
@Override
public void afterPropertiesSet() {
this.reload();
}
// 重新加载消息到该类的Map缓存中
public void reload() {
// 清除该类的缓存
LOCAL_CACHE.clear();
// 加载所有的国际化资源
Map<String, Map<String, String>> localeMsgMap = this.loadAllMessageResources();
LOCAL_CACHE.putAll(localeMsgMap);
}
// 加载所有的国际化消息资源
public Map<String, Map<String, String>> loadAllMessageResources() {
// 从数据库中查询所有的国际化资源
List<I18Message> allLocaleMessage = i18nMessageMapper.getAllLocaleMessage();
if (ObjectUtils.isEmpty(allLocaleMessage)) {
allLocaleMessage = new ArrayList<>();
}
// 将查询到的国际化资源转换为 Map<地区码, Map<code, 信息>> 的数据格式
Map<String, Map<String, String>> localeMsgMap = allLocaleMessage
// stream流
.stream()
// 分组
.collect(Collectors.groupingBy(
// 根据国家地区分组
I18Message::getLocale,
// 收集为Map,key为code,value为信息
Collectors.toMap(
I18Message::getKey
, I18Message::getItem
)
));
// 获取国家地区List
//List<Locale> localeList = localeMsgMap.keySet().stream().map( Locale::new).collect(Collectors.toList());
List<Locale> localeList = localeMsgMap.entrySet().stream()
.map(entry -> {
Map<String, String> newValue = ImmutableMap.copyOf(entry.getValue());
return new Locale(entry.getKey().replace("-", "_"));
})
.collect(Collectors.toList());
for (Locale locale : localeList) {
// 按照国家地区来读取本地的国际化资源文件,我们的国际化资源文件放在i18n文件夹之下
ResourceBundle resourceBundle = ResourceBundle.getBundle("i18n/messages", locale);
// 获取国际化资源文件中的key和value
Set<String> keySet = resourceBundle.keySet();
// 将 code=信息 格式的数据收集为 Map<code,信息> 的格式
Map<String, String> msgFromFileMap = keySet.stream()
.collect(
Collectors.toMap(
Function.identity(),
resourceBundle::getString
)
);
// 将本地的国际化信息和数据库中的国际化信息合并
Map<String, String> localeFileMsgMap = localeMsgMap.get(locale.getLanguage().replaceAll("_","-"));
localeFileMsgMap.putAll(msgFromFileMap);
localeMsgMap.put(locale.getLanguage().replaceAll("_","-"), localeFileMsgMap);
}
return localeMsgMap;
}
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
String msg = this.getSourceFromCacheMap(code, locale);
return new MessageFormat(msg, locale);
}
@Override
public String resolveCodeWithoutArguments(String code, Locale locale) {
return this.getSourceFromCacheMap(code, locale);
}
// 缓存Map中加载国际化资源
public String getSourceFromCacheMap(String code, Locale locale) {
String language = ObjectUtils.isEmpty(locale)
? LocaleContextHolder.getLocale().getLanguage() : locale.getLanguage();
// 获取缓存中对应语言的所有数据项
Map<String, String> propMap = LOCAL_CACHE.get(language);
if (!ObjectUtils.isEmpty(propMap) && propMap.containsKey(code)) {
// 如果对应语言中能匹配到数据项,那么直接返回
return propMap.get(code);
}
// 如果找不到国际化消息,就直接返回code
return code;
}
public String getLengthValidMessage(int length,String code, Locale locale) {
String message = getMessage(code, new Object[]{length}, locale);
return message;
}
}
要点
- 自定义MessageSource类要交给Spring管理,bean名称一定要叫
messageSource
2.3校验Controller
// 注入自定义MessageSource
@Resource
private CustomMessageSource customMessageSource;
// 重新加载国际化消息
@PostMapping("/reloadMessage")
@ResponseBody
public void reloadMessage() {
customMessageSource.reload();
}
@RequestMapping(value = "/v1.0/initVipsPage",
produces = {"application/json"},
consumes = {"application/json"},
method = RequestMethod.POST)
public ResponseEntity<ApiResponseMessage>initVipsPage(
@RequestHeader("retailer") String retailer
, @RequestBody ManualOrder manualOrder
) {
List<SearchColumnItem> searchColumnItemList=new ArrayList<>();
Locale locale=new Locale("zh-CN");
//这个map是获取到db 和配置文件中所有的国际化配置的map集合
Map<String, Map<String, String>>i18nMap= customMessageSource.loadAllMessageResources();
//这个方法可以直接获取到每个国际化配置的key对应语言的转换
customMessageSource.resolveCodeWithoutArguments("Retailer",locale);
//使用占位符来实现添加动态的值在一条信息中。
customMessageSource.getLengthValidMessage(5,"min_vaild_message",locale)
}
2.3.1实现添加动态的值在一条信息中
1. 在国际化配置文件中,添加需要的信息和占位符。例如,假设我们要添加一条动态信息,内容为“你好,{0}”,则可以在配置文件中添加以下内容:
dynamic.message=你好,{0}
其中,`{0}`表示占位符,后面在使用时需要传入动态的值。
2. 在Java代码中,使用`MessageSource`类获取国际化信息,并传入动态的值。例如:
@Autowired
private MessageSource messageSource;
public String getLengthValidMessage(int length,String code, Locale locale) {
String message = getMessage(code, new Object[]{length}, locale);
return message;
}}
其中,`messageSource.getMessage`方法的第二个参数`new Object[]{name}`就是动态的值,这里传入了一个名字。
3. 在Spring配置文件中,配置国际化资源的位置和默认语言。例如:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
其中,`basename`指定了国际化配置文件的路径和文件名,`defaultEncoding`指定了文件的字符集编码。