1. 创建国际化文件Resource Bundle

项目结构图:

SpringBoot项目国际化 - 图1

springboot项目工程详细结构

国际化文件结构图:

SpringBoot项目国际化 - 图2

springboot国际化文件

在IntelliJ IDEA中创建国际化文件:

SpringBoot项目国际化 - 图3

添加en_US的英文国际化文件

SpringBoot项目国际化 - 图4

添加zh_CN的中文国际化文件

SpringBoot项目国际化 - 图5

最终国际化添加完成的界面

2. 国际化配置类InternationalConfig

SpringBoot项目国际化 - 图6

springboot国际化配置类

代码:

  1. @Configuration
  2. public class InternationalConfig {
  3. @Value(value = "${spring.messages.basename}")
  4. private String basename;
  5. @Bean(name = "messageSource")
  6. public ResourceBundleMessageSource getMessageResource() {
  7. ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
  8. messageSource.setBasename(basename);
  9. return messageSource;
  10. }
  11. }

3. application.yml配置文件中配置国际化文件路径

  1. spring:
  2. profiles:
  3. active: dev
  4. # 配置国际化文件路径
  5. messages:
  6. basename: i18n/messages
  7. ---
  8. spring:
  9. profiles: dev
  10. ---
  11. spring:
  12. profiles: test
  13. ---
  14. spring:
  15. profiles: prod

4. 国际化处理类MessageSourceHandler

SpringBoot项目国际化 - 图7

springboot国际化处理类

代码:

  1. @Component
  2. public class MessageSourceHandler {
  3. @Autowired
  4. private HttpServletRequest request;
  5. @Autowired
  6. private MessageSource messageSource;
  7. public String getMessage(String messageKey) {
  8. String message = messageSource.getMessage(messageKey, null, RequestContextUtils.getLocale(request));
  9. return message;
  10. }
  11. }

注意:

  • 如果是根据Request请求的语言来决定国际化:
  1. @Autowired
  2. private HttpServletRequest request;
  3. public String getMessage(String messageKey) {
  4. String message = messageSource.getMessage(messageKey, null, RequestContextUtils.getLocale(request));
  5. return message;
  6. }
  • 如果是根据应用部署的服务器系统来决定国际化:
  1. public String getMessage(String messageKey) {
  2. String message = messageSource.getMessage(messageKey, null, LocaleContextHolder.getLocale());
  3. return message;
  4. }

5. 国际化使用

引入MessageSourceHandler类的对象messageSourceHandler,调用其messageSourceHandler.getMessage()方法即可。

  1. @Slf4j
  2. @RestControllerAdvice
  3. public class GlobalExceptionHandler {
  4. // 引入国际化处理类
  5. @Autowired
  6. private MessageSourceHandler messageSourceHandler;
  7. private String handleException(Exception e, String code) {
  8. return handleException(e, code, null);
  9. }
  10. // 具体异常处理类
  11. private String handleException(Exception e, String code, Object body) {
  12. String msgKey = e.getMessage();
  13. String msg = msgKey;
  14. try {
  15. msg = messageSourceHandler.getMessage(msgKey);
  16. } catch (Exception ex) {
  17. log.error(ex.getMessage(), ex);
  18. }
  19. if (StringUtils.isEmpty(msg)) {
  20. if (StringUtils.isEmpty(msgKey)) {
  21. msg = messageSourceHandler.getMessage(ErrorTypeEnum.INTERNAL_SERVER_ERROR.getMessage());
  22. } else {
  23. msg = msgKey;
  24. }
  25. }
  26. log.error("Return Error Message : " + msg);
  27. return msg;
  28. }
  29. // 请求错误异常处理
  30. @ExceptionHandler(BadRequestException.class)
  31. public String handleBadRequest(BadRequestException e) {
  32. return handleException(e, e.getCode());
  33. }
  34. // 服务器内部异常处理
  35. @ExceptionHandler(InternalServerException.class)
  36. public String handleInternalServerError(InternalServerException e) {
  37. return handleException(e, e.getCode());
  38. }
  39. // 调用其他服务异常处理
  40. @ExceptionHandler(InvokeOtherServerException.class)
  41. public String handleInvokeOtherServerError(InvokeOtherServerException e) {
  42. return handleException(e, e.getCode(), e.getBody());
  43. }
  44. // DAO异常处理
  45. @ExceptionHandler(DaoException.class)
  46. public String handleDaoError(DaoException e) {
  47. return handleException(e, e.getCode());
  48. }
  49. }

FAQ:

1. 中文国际化出现乱码或\uXXXX的问题

如图:

SpringBoot项目国际化 - 图8

中文国际化出现乱码

SpringBoot项目国际化 - 图9

中文国际化出现\uXXXX

乱码问题根源:

<1> 创建国际化文件,IDEA默认工程的初始默认编码是GBK,如下图:

SpringBoot项目国际化 - 图10

IDEA默认工程初始编码为GBK

<2> 当我添加了一些国际化内容时,此时意识到编码不是UTF-8,我修改了一下默认的工程编码和系统properties编码,改为UTF-8,如下图所示。

由于我是在GBK编码下加的中文国际化内容,后又把工程编码和properties编码改为了UTF-8,两边编码不一致,导致出现乱码。

SpringBoot项目国际化 - 图11

修改了工程编码和properties编码为UTF-8

\uXXXX问题根源:

\uXXXX是Unicode的转义字符,和\n,\r同属于转义字符,看一下IntelliJ官网对此说明,如下:

IntelliJ官网的文档地址:https://www.jetbrains.com/help/idea/2017.1/editing-resource-bundle.html

  1. ## 在properties文件中格式为\uXXXX的所有转义字符,在资源编译器中被显示为未转义的Unicode字符
  2. All escaped characters in the *.properties files in the format \uXXXX, are displayed in the resource bundle editor as un-escaped unicode literals.
  3. ## 反之亦然,如果在资源编译器中输入非ASCII字符,则它将反映在底层的properties文件中作为相应的格式为\uXXXX的转义字符
  4. Vice versa, if a non-ASCII character is entered in the resource bundle editor, it is reflected in the underlying *.properties file as a corresponding escaped character in the format \uXXXX.
  5. ##下面是举了个例子
  6. For example, if the *.properties file contains a property value
  7. Was ich nicht wei\u00df, macht mich nicht hei\u00df
  8. then the resource bundle editor will show
  9. Was ich nicht weiß, macht mich nicht heiß
  10. ## 资源编译器本身不做任何转换。若要在属性文件中正确解析转义序列,请在“设置/首选项”对话框的“文件编码页”中选择“透明本机到ascii转换”复选框。
  11. Resource bundle editor itself does not perform any conversion. To have escape sequences properly resolved in properties files, select the check box Transparent native-to-ascii conversion in the File Encoding page of the Settings/Preferences dialog.
  12. ## 可以使用大写和小写十六进制符号(例如'\u00E3'与'\u00e3')对非ascii符号进行编码。大写默认使用。要使用小写,请将bin/idea.properties文件(安装IntelliJ的文件夹)中的'idea.native2ascii.lowercase'属性设置为true。
  13. It is possible to encode non-ascii symbols using both upper- and lower-case hex symbols (e.g. '\u00E3' vs '\u00e3'). Upper case is used by default. To use lower case, set 'idea.native2ascii.lowercase' property in the bin/idea.properties file to true.
  14. Refer to the section Tuning IntelliJ IDEA for details.

继续跳转Tuning IntelliJ IDEA for details,见下截图:

SpringBoot项目国际化 - 图12

IntelliJ官网截图

总结:输入汉字(非ASCII码),在IntelliJ资源编译器中显示转义的Unicode码(\uXXXX(X大写)),勾上”Transparent native-to-ascii conversion”,则在资源编译器中转换显示为汉字,其实际存储为转义的Unicode码。

解决方法:

SpringBoot项目国际化 - 图13

IntelliJ的编码设置

有关编码的文章,可参考:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

2. SpringBoot有没有自带的国际化资源配置类?

有,当然有。

SpringBoot提供了自动配置类MessageSourceAutoConfiguration,

  1. @Configuration
  2. @ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
  3. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  4. @Conditional(ResourceBundleCondition.class)
  5. @EnableConfigurationProperties
  6. public class MessageSourceAutoConfiguration {
  7. private static final Resource[] NO_RESOURCES = {};
  8. @Bean
  9. @ConfigurationProperties(prefix = "spring.messages")
  10. public MessageSourceProperties messageSourceProperties() {
  11. return new MessageSourceProperties();
  12. }
  13. // 配置了MessageSource的Bean,并装配了上面MessageSourceProperties的Bean
  14. @Bean
  15. public MessageSource messageSource(MessageSourceProperties properties) {
  16. ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
  17. if (StringUtils.hasText(properties.getBasename())) {
  18. messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
  19. StringUtils.trimAllWhitespace(properties.getBasename())));
  20. }
  21. if (properties.getEncoding() != null) {
  22. messageSource.setDefaultEncoding(properties.getEncoding().name());
  23. }
  24. messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
  25. Duration cacheDuration = properties.getCacheDuration();
  26. if (cacheDuration != null) {
  27. messageSource.setCacheMillis(cacheDuration.toMillis());
  28. }
  29. messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
  30. messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
  31. return messageSource;
  32. }
  33. ...
  34. }
  35. // 国际化资源文件配置类Properties
  36. public class MessageSourceProperties {
  37. /**
  38. * Comma-separated list of basenames (essentially a fully-qualified classpath
  39. * location), each following the ResourceBundle convention with relaxed support for
  40. * slash based locations. If it doesn't contain a package qualifier (such as
  41. * "org.mypackage"), it will be resolved from the classpath root.
  42. */
  43. // 默认国际化资源文件名为messages,默认放在类路径下,在application.yml中不需要做任何国际化路径配置
  44. private String basename = "messages";
  45. /**
  46. * Message bundles encoding.
  47. */
  48. private Charset encoding = StandardCharsets.UTF_8;
  49. /**
  50. * Loaded resource bundle files cache duration. When not set, bundles are cached
  51. * forever. If a duration suffix is not specified, seconds will be used.
  52. */
  53. @DurationUnit(ChronoUnit.SECONDS)
  54. private Duration cacheDuration;
  55. /**
  56. * Whether to fall back to the system Locale if no files for a specific Locale have
  57. * been found. if this is turned off, the only fallback will be the default file (e.g.
  58. * "messages.properties" for basename "messages").
  59. */
  60. private boolean fallbackToSystemLocale = true;
  61. /**
  62. * Whether to always apply the MessageFormat rules, parsing even messages without
  63. * arguments.
  64. */
  65. private boolean alwaysUseMessageFormat = false;
  66. /**
  67. * Whether to use the message code as the default message instead of throwing a
  68. * "NoSuchMessageException". Recommended during development only.
  69. */
  70. private boolean useCodeAsDefaultMessage = false;
  71. ...
  72. }

如果创建自定义的国际化资源(Resource Bundle)文件,例如:i18n/messages,则需要在application.yml中配置该自定义国际化文件的路径。

SpringBoot项目国际化 - 图14

自定义国际化文件配置路径

如果在resources文件夹路径下直接创建messages国际化资源文件(名字必须为messages),则不需要在applicaiton.yml中配置国际化文件路径。

SpringBoot项目国际化 - 图15

自定义与默认的messages国际化文件

国际化处理类见上面(4. 国际化处理类MessageSourceHandler),国际化使用是一样的。

扩展学习

  1. 简书上一篇“SpringBoot - Web开发国际化”的文章:https://www.jianshu.com/p/01e0c7251d72
  2. 阮老师一篇关于编码的文章:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html