springboot国际化

springboot对国际化的支持还是很好的,要实现国际化还简单。主要流程是通过配置springboot的LocaleResolver解析器,当请求打到springboot的时候对请求的所需要的语言进解析,并保存在LocaleContextHolder中。之后就是根据当前的locale获取message。
springboot中关于国际化消息处理的顶层接口是MessageSource,它有两个开箱即可用的实现

1。新建国际化文件
这里只是中英文,右键可加入其它的语种
多语种情况下,不用打开每个语种的文件一个一个去修改。直接在message.properties编辑即可
messages.properties
9527=bojack
come=来吧
hello=你好
testVO.flag.Max=tai大了呀
testVO.flag.Min=tai小了
messages_en_US.properties
9527=jack
come=come
hello=hello
testVO.flag.Max=too big
testVO.flag.Min=too small
messages_zh_CN.properties
9527=杰克
come=来吧
hello=你好
testVO.flag.Max=太多了呀
testVO.flag.Min=太小了呀
2。配置国际化文件的位置
application.yml
spring:
messages:
basename: i18n/messages # 多个文件用逗号分隔
3。配置localeResolver,解析当前请求的locale,LocaleResolver是个接口,它也有多种实现,这个也可以根据自己的实际情况自已去实现,我这里用的是默认的解析器 AcceptHeaderLocaleResolver,他是通过获取请求头accept-language来获取当前的locale。
    /**
* 设置默认语言
* @return
*/
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
acceptHeaderLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return acceptHeaderLocaleResolver;
}
注意:这里是zh-CN,如果写成zh_CN是解析不了的。这里还可以配置权重,具体参考https://cloud.tencent.com/developer/section/1189889
test.http
### 简体中文
GET localhost:8080/i18n/test
Accept-Language: zh-CN ### 美国英语
GET localhost:8080/i18n/test
Accept-Language: en-US
4。如何使用
package com.springmvc.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; @RestController
@RequestMapping("i18n")
public class I18nController { @Autowired
private MessageSource messageSource; @GetMapping("test")
public Object test() {
Locale locale = LocaleContextHolder.getLocale();
String displayName = locale.getDisplayName(); System.out.println("displayName = " + displayName);
String hello = messageSource.getMessage("hello", null, locale);
System.out.println("hello = " + hello); Map<String,Object> map = new HashMap<>();
map.put("hello", hello);
map.put("displayName", displayName);
map.put("locale", locale); String come= messageSource.getMessage("come", null, locale);
map.put("come", come);
String c9527= messageSource.getMessage("9527", null, locale);
map.put("9527", c9527);
return map ;
}
}

@valid 参数校验与国际化

@valid默认其实是支持国际化的,只是它感觉支持的不是很好,如果在 userId上加个@Notnull注解,当userId为空的时候,只会提示 `不能为空`,如果有多个@notnull注解,不会提示具体是哪个不能为空。这个倒还好,可以解决。但是一般给用户的提示,不可能提示 `userId 不能为空` 而是要提示成 `账号不能为空`。所以默认的validationMessage用起来还有点麻烦,还不如直接用在国际化文件里写好的message
抽象类:org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator
参数校验不通过时抛出 MethodArgumentNotValidException 异常
    /**
* The name of the default message bundle.
*/
public static final String DEFAULT_VALIDATION_MESSAGES = "org.hibernate.validator.ValidationMessages";
国际化文件:
实体类
package com.springmvc.demo.vo;

import lombok.Data;

import javax.validation.constraints.Max;//MethodArgumentNotValidException
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; @Data
public class TestVO { @Min(value = 3)//这里不用写messages了,因为要支持国际化
@Max(value = 10)
private Integer flag; @NotNull
private Integer age;
}
国际化文件中写成这样
testVO.flag.Max=too big
testVO.flag.Min=too small
注解名可以写在前也可以写在后面,可以在文件中配置,注意默认是写在前面的
spring:
messages:
basename: i18n/messages
mvc:
message-codes-resolver-format: postfix_error_code
参数校验不能过时会抛出MethodArgumentNotValidException 异常,因些可以在全局异常处理器中,捕获异常,根据locale获取相应的message
String message = messageSource.getMessage(fieldError, LocaleContextHolder.getLocale());
package com.springmvc.demo.controller;

import com.baomidou.mybatisplus.extension.api.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.HashMap;
import java.util.List;
import java.util.Map; @Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandlerResolver { @Autowired
MessageSource messageSource; @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R handleBodyValidException(MethodArgumentNotValidException exception) {
Map<String, String> errors = new HashMap<String, String>();
//得到所有的属性错误
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
//将其组成键值对的形式存入map
for (FieldError fieldError : fieldErrors) {
String[] str= fieldError.getField().split("\\.");
if(str.length>1){
errors.put(str[1], fieldError.getDefaultMessage());
}else {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
} String message = messageSource.getMessage(fieldError, LocaleContextHolder.getLocale());
return R.failed(message);
}
log.error("参数绑定异常,ex = {}", errors);
return R.failed("haha");
}
}
测试:test.http
###
GET localhost:8080/i18n/test
Accept-Language: zh ###
GET localhost:8080/i18n/test
Accept-Language: en-US ###
POST localhost:8080/test
Content-Type: application/json {
"flag": 2,
"age": 22
} ###
POST localhost:8080/test
Content-Type: application/json
Accept-Language: en-US {
"flag": 5
}
源码解析
从请求头accept-language中取locale
org.apache.catalina.connector.Request#parseLocales
    /**
* Parse request locales.
*/
protected void parseLocales() { localesParsed = true; // Store the accumulated languages that have been requested in
// a local collection, sorted by the quality value (so we can
// add Locales in descending order). The values will be ArrayLists
// containing the corresponding Locales to be added
TreeMap<Double, ArrayList<Locale>> locales = new TreeMap<>();
Enumeration<String> values = getHeaders("accept-language");
while (values.hasMoreElements()) {
String value = values.nextElement();
parseLocalesHeader(value, locales);
}
// Process the quality values in highest->lowest order (due to
// negating the Double value when creating the key)
for (ArrayList<Locale> list : locales.values()) {
for (Locale locale : list) {
addLocale(locale);
}
}
}
org.springframework.context.support.AbstractMessageSource#getMessage(org.springframework.context.MessageSourceResolvable, java.util.Locale)
    @Override
public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
String[] codes = resolvable.getCodes();
if (codes != null) {
for (String code : codes) {
String message = getMessageInternal(code, resolvable.getArguments(), locale);
if (message != null) {
return message;
}
}
}
String defaultMessage = getDefaultMessage(resolvable, locale);
if (defaultMessage != null) {
return defaultMessage;
}
throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : "", locale);
}
org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator#resolveMessage
	private String resolveMessage(String message, Locale locale) {
String resolvedMessage = message; ResourceBundle userResourceBundle = userResourceBundleLocator
.getResourceBundle( locale ); ResourceBundle constraintContributorResourceBundle = contributorResourceBundleLocator
.getResourceBundle( locale ); ResourceBundle defaultResourceBundle = defaultResourceBundleLocator
.getResourceBundle( locale ); String userBundleResolvedMessage;
boolean evaluatedDefaultBundleOnce = false;
do {
// search the user bundle recursive (step 1.1)
userBundleResolvedMessage = interpolateBundleMessage(
resolvedMessage, userResourceBundle, locale, true
); // search the constraint contributor bundle recursive (only if the user did not define a message)
if ( !hasReplacementTakenPlace( userBundleResolvedMessage, resolvedMessage ) ) {
userBundleResolvedMessage = interpolateBundleMessage(
resolvedMessage, constraintContributorResourceBundle, locale, true
);
} // exit condition - we have at least tried to validate against the default bundle and there was no
// further replacements
if ( evaluatedDefaultBundleOnce && !hasReplacementTakenPlace( userBundleResolvedMessage, resolvedMessage ) ) {
break;
} // search the default bundle non recursive (step 1.2)
resolvedMessage = interpolateBundleMessage(
userBundleResolvedMessage,
defaultResourceBundle,
locale,
false
);
evaluatedDefaultBundleOnce = true;
} while ( true ); return resolvedMessage;
}





springboot国际化与@valid国际化支持的更多相关文章

  1. SpringBoot添加对Log4j2的支持

    1.在添加对Log4j2的支持前,需要先把SpringBoot默认使用的Logback日志框架排除,修改pom.xml文件: <dependency> <groupId>org ...

  2. SpringBoot添加对Mybatis的支持

    1.修改maven配置文件pom.xml,添加对mybatis的支持: <dependency> <groupId>org.mybatis.spring.boot</gr ...

  3. 详解SpringBoot 添加对JSP的支持(附常见坑点)

    序言: SpringBoot默认不支持JSP,如果想在项目中使用,需要进行相关初始化工作.为了方便大家更好的开发,本案例可直接作为JSP开发的脚手架工程 SpringBoot+War+JSP . 常见 ...

  4. JavaWeb开发——软件国际化(文本元素国际化)

    前几天围绕着JDBC编程进行了系统的学习.现在我们对Java程序数据库操作已经是轻车熟路了.也学会了使用各种框架来帮助我们简化编程. 今天是学习计划的第七天,虽然学习热情没有前几天高涨了.但是,写博客 ...

  5. springboot 使用i18n进行国际化

    1.i18n介绍 i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称.在资讯领域,国际化(i18n)指让产品(出版物,软件,硬 ...

  6. 逐浪web无障碍与国际化以及全民族语言支持白皮书

    北京时间2019年5月10日,领先的门户网站与WEB内核服务厂商--上海Zoomla!逐浪CMS团队发布其年度重榜产品:逐浪CMS全民族语言与国际版,体验站点:http://demo2.z01.com ...

  7. springboot、Thymeleaf、国际化的简单使用

    1.项目体系结构 (1)知识体系 springboot:省去了很多繁琐的配置,如:视图解析器.前端控制器等 thymeleaf:获取controller数据逼能够进行展示 集合:用于存储数据,此练习没 ...

  8. springboot 使用i18n进行国际化乱码解决

    方式1.设置国际化的编码和你使用的编译器(IDEA之类)一致,如编译器为UTF-8则在application配置文件中添加 #i18n spring: messages: encoding: UTF- ...

  9. 国际化之Android设备支持的语种

    昨天发了关于iOS支持的语种,文章最后也补了安卓支持语种列表.但最后发现安卓设备支持跟它列的有出入,我重新完全手工整理了一遍. 我将对应的语种在安卓的语言列表里的显示,也全部逐一列出来了,方便大家到时 ...

随机推荐

  1. Java 树结构实际应用 二(哈夫曼树和哈夫曼编码)

     赫夫曼树 1 基本介绍 1) 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为 最优二叉树,也称为哈夫曼树(Huffman Tree), ...

  2. get和post的区别主要有以下几方面

    1.url可见性: get,参数url可见: post,url参数不可见 2.数据传输上: get,通过拼接url进行传递参数: post,通过body体传输参数 3.缓存性: get请求是可以缓存的 ...

  3. Django之Auth认证模块

    一.Auth模块是什么 Auth模块是Django自带的用户认证模块: 我们在开发网站的时候,无可避免的需要设计实现网站的用户系统,此时我们需要实现包括用户注册,用户登陆,用户认证,注销修改密码等功能 ...

  4. 19. 一文搞懂 Go Modules 前世今生及入门使用

    Hi,大家好. 我是明哥,在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号 <Go编程时光>,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适 ...

  5. FTT简单入门板子

    DFT : 1 #include <cstdio> 2 #include <iostream> 3 #include <cmath> 4 #include < ...

  6. [换根DP]luogu P3647 [APIO2014]连珠线

    题面 https://www.luogu.com.cn/problem/P3647 不重复地取树中相邻的两条边,每次得分为两条边权和,问最大得分 分析 容易想到状态 f[i][0/1] 分别表示 i ...

  7. WordPress的SEO优化技巧

    世界上大约有30%的网站都是由Wordpress搭建的,因为Wordpress自身构架清晰,代码规范,且网页评论直接书写在整个页面里,能够被搜索引擎检索到,因此对搜索引擎很友好.但有时候还是会出现只被 ...

  8. [Fundamental of Power Electronics]-PART II-7. 交流等效电路建模-7.4 规范电路模型

    7.4 规范电路模型 在讨论了推导开关变换器交流等效电路模型的几种方法后,让我们先停下来,说明下这些结果.所有的在 CCM下以PWM工作的DC-DC变换器都具有相似的基本功能.首先,他们在理想情况下, ...

  9. OO电梯系列优化分享

    目录 前言 HW5 HW6 第二次作业uml协作图 HW7 第三次作业uml协作图 前言 本单元作业在优化方面确实有一些想法值得分享,故单开一篇博客分享一下三次作业的优化以及架构. 三次作业的共同之处 ...

  10. 期末考试复习c#时总结的抽象类与接口的一些区别

    抽象类: (1)抽象类中可以定义抽象方法,属性,变量 (2)抽象类的派生类必须实现所有的抽象方法.要求所有的派生非抽象类都要用override重写实现抽象方法. (3)抽象类可以存放抽象方法,属性,也 ...