我们公司现在在做面向巴西市场的APP,需要有葡萄牙语、西班牙语、英语、汉语四种语言今天把我们做的多语言部分独立出来做个demo分享出来
目录
1.git地址及使用说明:
git地址
https://gitee.com/ayezs/spring-internationalization-demo
整个项目非常简单,只有一个springboot,还集成了swagger
只要有JAVA1.8+和maven环境就能跑起来
swagger访问地址:
http://localhost:8080/doc.html
2.Demo基础逻辑
2.1 首先,所有的正常的请求都有前台的APP管理
2.2 我们写了一个基础的异常类,异常类中有一个StatusCode,StatusCode是一个enum,存放了多语言返回值的key
public abstract class BaseException extends RuntimeException {
protected StatusCode statusCode;
public BaseException() {
}
public BaseException(Throwable ex) {
super(ex);
}
public BaseException(String message) {
super(message);
}
public BaseException(StatusCode statusCode) {
this.statusCode = statusCode;
}
public BaseException(StatusCode statusCode, String message) {
super(message);
this.statusCode = statusCode;
}
public BaseException(String message, Throwable ex) {
super(message, ex);
}
public Meta handler() {
Meta meta = new Meta();
meta.setSuccess(false);
meta.setCode(getStatusCode().value());
if (!StringUtils.isEmpty(getMessage())) {
meta.setMsg(getMessage()); // 取系统的错误消息
}else {
meta.setMsg(getStatusCode().msg());
}
return meta;
}
protected abstract StatusCode getStatusCode();
}
精简后的StatusCode ,注意msg()方法,这个方法会根据key获取国际化信息
Resources类在3.3中
/**
* 状态码,主要参考Http状态码,但并不完全对应
* @author amigo
*
*/
public enum StatusCode {
/** 200请求成功 */
OK(200),
/** 400请求参数出错 */
BAD_REQUEST(400),
/** 401没有登录 */
UNAUTHORIZED(401),
/** 500服务器出错 */
INTERNAL_SERVER_ERROR(500),
/** 2000用户异常 */
USER_EXCEPTION(2000),
;
private final Integer value;
StatusCode(Integer value) {
this.value = value;
}
public Integer value() {
return this.value;
}
public String msg() {
return Resources.getMessage("STATUSCODE_" + this.value);
}
public Meta handler() {
Meta meta = new Meta();
if(this.value==200){
meta.setSuccess(true);
}else {
meta.setSuccess(false);
}
meta.setCode(value);
meta.setMsg(msg());
return meta;
}
@Override
public String toString() {
return this.value.toString();
}
}
2.3 所有的业务异常都继承了这个基础的异常类,如:
@SuppressWarnings("serial")
public class DemoException extends BaseException {
public DemoException() {
super();
}
public DemoException(String message) {
super(message);
}
public DemoException(StatusCode statusCode) {
super(statusCode);
}
public DemoException(StatusCode statusCode, String message) {
super(statusCode, message);
}
public DemoException(String message, Throwable ex) {
super(message, ex);
}
@Override
protected StatusCode getStatusCode() {
return super.statusCode != null ? super.statusCode : StatusCode.USER_EXCEPTION;
}
}
2.4 如果有异常用自己的业务异常封装一下,将StatusCode传进去然后抛出来,如:
@GetMapping("/demo")
public Result demo(){
throw new DemoException(StatusCode.USER_EXCEPTION);
}
2.5 配置一个全局异常处理程序
@ControllerAdvice
@RestController
@RequestMapping( value = "/error")
@Slf4j
public class ControllerException {
@RequestMapping(value = "/401" ,produces = {"application/json;charset=UTF-8"})
public Result forbidden401(HttpServletResponse response) {
response.setStatus(200);
System.out.println("401");
return Result.error(StatusCode.UNAUTHORIZED);
}
@ExceptionHandler(Exception.class)
@ResponseBody
public Result defaultException(Exception e) {
log.error("系统内部错误", e);
return Result.error(StatusCode.INTERNAL_SERVER_ERROR);
}
/**
* 业务异常.
* @param ex
* @return
*/
@ExceptionHandler(BaseException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result businessException(BaseException ex) {
Meta meta = ex.handler();
log.error("业务异常 => {}", meta.getMsg());
return Result.error(meta);
}
}
3.最重要的多语言部分
3.1首先在resources目录下加入i18n文件夹,文件夹下存放多语言配置文件,如图:
为避免误会在贴一张win10下的目录()
3.2文件内容示例:
个人感觉这里的code做的不是太好,到时候可以自己根据需要将key的规则自行改一下
3.3根据语言和返回值的key获取返回值的message
/**
* 状态码国际化
* @author pangdonghao
* @csdn https://blog.csdn.net/pangdongh
* @version 1.0
* @createDate 2019/08/19 13:52
*/
@PropertySource(value = { "classpath:i18n/messages*.properties" })
public class Resources {
/** 将国际化信息存放在一个map中 */
private static final Map<String, ResourceBundle> MESSAGES = new HashMap<String, ResourceBundle>();
/** 获取国际化信息 */
public static String getMessage(String key, Object... params) {
//获取语言,这个语言是从header中的Accept-Language中获取的,
//会根据Accept-Language的值生成符合规则的locale,如zh、pt、en等
Locale locale = LocaleContextHolder.getLocale();
ResourceBundle message = MESSAGES.get(locale.getLanguage());
if (message == null) {
synchronized (MESSAGES) {
//在这里读取配置信息
message = MESSAGES.get(locale.getLanguage());
if (message == null) {
//注1
message = ResourceBundle.getBundle("i18n/messages", locale);
MESSAGES.put(locale.getLanguage(), message);
}
}
}
//此处获取并返回message
if (params != null) {
return String.format(message.getString(key), params);
}
return message.getString(key);
}
/** 清除国际化信息 */
public static void flushMessage() {
MESSAGES.clear();
}
}
注1:
此处要注意ResourceBundle.getBundle方法,此处如果没有匹配到合适的语言会直接使用系统的语言
源码中是这样写的
for (Locale targetLocale = locale;//这是我们传过去的locale
targetLocale != null;
//如果我们传过去的locale没有用,就会获取候选环境
//
targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
//此处省略获取资源包(ResourceBundle)的代码
}
}
getFallbackLocale方法:
public Locale getFallbackLocale(String baseName, Locale locale) {
if (baseName == null) {
throw new NullPointerException();
}
Locale defaultLocale = Locale.getDefault();
return locale.equals(defaultLocale) ? null : defaultLocale;
}
这个方法主要是调用了Locale.getDefault();
这个方法会根据当前系统的信息返回一个Locale
结语
暂时就这样吧,看不明白的话建议把代码下到本地跑一下,很简单的
如果有问题可以发邮件到 pangdonghao@foxmail.com 联系我