前言
第一次听到国际化这个词,都会觉得它相当的高大上。
国际化它到底是什么?
国际化的目的是使同一个项目能够在不同地区运行。说简单点,页面上的文字或提示信息能够随着语言环境而改变。
国际化如何实现?
这里分以下几步:
- 语言的保存和切换
- 获取国际化数据
- 统一处理返回结果
具体实现
语言的保存和切换
要想实现语言的保存和切换,需要实现LocaleResolver和LocaleChangeInterceptor。如下:
@Slf4j
@Configuration
public class I18nConfig implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setDefaultLocale(Locale.CHINA);
return localeResolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
return localeChangeInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
-
LocaleResolver用于存储语言,这里将语言的信息保存在Cookie中,所以使用现成的实现CookieLocaleResolver。 如果需要将语言保存在Session中,可以使用SessionLocaleResolver。
-
LocaleChangeInterceptor是拦截器,用于切换语言。若请求中有lang参数,就会根据该值切换语言。 通过实现WebMvcConfigurer的addInterceptors方法,将该拦截器放入拦截器链中。
实现后,我们测试下:
@RestController
@RequestMapping("/language")
public class LanguageController extends BaseController {
@ApiOperation("切换语言")
@PostMapping("/change")
public ApiResult<String> changeLanguage(@RequestParam(name = "lang") @ApiParam(name = "lang", value = "语言", required = true) String lang) {
// 在LocaleChangeInterceptor中已切换,此处无需处理
return getLanguage();
}
@ApiOperation("获得当前语言")
@GetMapping
public ApiResult<String> getLanguage() {
Locale locale = LocaleContextHolder.getLocale();
return ApiResult.success(locale.toString());
}
}
调用切换语言的接口:curl -X POST "http://localhost:8080/language/change?lang=en_US"
可以在Cookie中看到语言信息:
调用获得当前语言接口:curl -X GET "http://localhost:8080/language" -H "accept: */*"
可以获取返回结果:
{
"code": 200,
"data": "en_US",
"message": "成功"
}
至此,我们成功的实现了语言的保存和切换。
下面来实现根据语言获取相应的国际化数据。
获取国际化数据
要想获取国际化数据,首先需要添加国际化文件。默认的文件名为messages*.properties,也可以通过配置spring.messages.basename=messages修改文件名。存放的文件路径为:/resources/i18n/,如下:
在文件中以键值对的形式存储数据。
默认数据:messages.properties
success=API调用成功
fail=API调用失败
英文数据:messages_en_US.properties
success=success
fail=fail
中文数据:messages_en_US.properties
success=API调用成功
fail=API调用失败
国际化数据有了,语言有了,那如何获取到当前语言的数据?
这里使用ResourceBundleMessageSource来解析国际化文件,如下:
@Slf4j
@Configuration
public class I18nConfig{
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("i18n/messages");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
需要获取数据时,直接注入进行使用:
@RestController
@RequestMapping("/message")
public class MessageController {
@Autowired
private MessageSource messageSource;
@ApiOperation("成功")
@GetMapping("/success")
public ApiResult<String> success() {
// 获取当前语言
Locale locale = LocaleContextHolder.getLocale();
// 根据Code值和语言,获取国际化数据
String message = messageSource.getMessage(ResultCode.SUCCESS.getMessage(), null, locale);
return new ApiResult<>(ResultCode.SUCCESS.getCode(), null, message);
}
@ApiOperation("失败")
@GetMapping("/fail")
public ApiResult<String> failure() {
Locale locale = LocaleContextHolder.getLocale();
String message = messageSource.getMessage(ResultCode.FAIL.getMessage(), null, locale);
return new ApiResult<>(ResultCode.SUCCESS.getCode(), null, message);
}
}
- LocaleContextHolder.getLocale(); 获取当前语言
- messageSource.getMessage(); 获取国际化数据
这里用到ResultCode:
@Getter
@AllArgsConstructor
public enum ResultCode {
SUCCESS(200, "success"),
FAIL(500, "fail");
private Integer code;
private String message;
}
至此,我们能成功的获取国际化数据。
但是,每个请求都需要调用,不觉得很麻烦吗?
程序员可不是只会Ctrl+C和Ctrl+V的。一般将提示信息返回到前端时才会调用国际化,我们可以对返回结果进行统一处理来解决这个问题。
统一处理返回结果
在【统一基类、接口、返回对象设计】这一篇文章中, 约定了接口的返回对象都是ApiResult,那我们只要把该返回值拦截到,然后统一进行国际化即可。
通过注解@ControllerAdvice和实现ResponseBodyAdvice接口, 数据返回前都调用beforeBodyWrite方法,在这里我们将code值转换成国际化数据。如下:
@ControllerAdvice
public class ApiResultAdvice implements ResponseBodyAdvice<ApiResult> {
@Autowired
private MessageSource messageSource;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.getParameterType().isAssignableFrom(ApiResult.class);
}
@Override
public ApiResult beforeBodyWrite(ApiResult body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
Locale locale = LocaleContextHolder.getLocale();
String message = messageSource.getMessage(body.getMessage(), null, locale);
body.setMessage(message);
return body;
}
}
在这里统一处理之后,就不需要在Controller进行处理。
调用切换语言的接口:curl -X POST "http://localhost:8080/language/change?lang=en_US"
返回结果如下:
{
"code": 200,
"data": "en_US",
"message": "success"
}
至此,我们实现了国际化,并且不需要对以前的代码进行多大的改动。 只需要将原来的提示信息等数据换成Code值,然后将Code值在国际化文件里配置即可。
总结
首先通过LocaleResolver和LocaleChangeInterceptor,实现了对语言的保存和切换。
然后通过MessageSource,实现了国际化数据的读取。
最后通过注解@ControllerAdvice和实现ResponseBodyAdvice,接口实现对返回结果的统一处理。
至此,完成了多国际化的实现,并尽可能的使使用简单,尽可能减少对以前代码的改动。
感谢阅读,如果感觉有帮助的话,不妨随手点个赞!