SpringBoot项目国际化

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国际化配置类

代码:

@Configuration
public class InternationalConfig {

    @Value(value = "${spring.messages.basename}")
    private String basename;

    @Bean(name = "messageSource")
    public ResourceBundleMessageSource getMessageResource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename(basename);
        return messageSource;
    }
}

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

spring:
  profiles:
    active: dev
  # 配置国际化文件路径
  messages:
    basename: i18n/messages
    
---

spring:
  profiles: dev
  
---

spring:
  profiles: test
  
---

spring:
  profiles: prod
  

4. 国际化处理类MessageSourceHandler

SpringBoot项目国际化_第7张图片
springboot国际化处理类

代码:

@Component
public class MessageSourceHandler {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private MessageSource messageSource;

    public String getMessage(String messageKey) {
        String message = messageSource.getMessage(messageKey, null, RequestContextUtils.getLocale(request));
        return message;
    }
}

注意:

  • 如果是根据Request请求的语言来决定国际化:
@Autowired
private HttpServletRequest request;

public String getMessage(String messageKey) {
    String message = messageSource.getMessage(messageKey, null, RequestContextUtils.getLocale(request));
    return message;
}
  • 如果是根据应用部署的服务器系统来决定国际化:
public String getMessage(String messageKey) {
    String message = messageSource.getMessage(messageKey, null, LocaleContextHolder.getLocale());
    return message;
}

5. 国际化使用

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

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 引入国际化处理类
    @Autowired
    private MessageSourceHandler messageSourceHandler;

    private String handleException(Exception e, String code) {
        return handleException(e, code, null);
    }
    
    // 具体异常处理类 
    private String handleException(Exception e, String code, Object body) {
        String msgKey = e.getMessage();
        String msg = msgKey;
        try {
            msg = messageSourceHandler.getMessage(msgKey);
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
        if (StringUtils.isEmpty(msg)) {
            if (StringUtils.isEmpty(msgKey)) {
                msg = messageSourceHandler.getMessage(ErrorTypeEnum.INTERNAL_SERVER_ERROR.getMessage());
            } else {
                msg = msgKey;
            }
        }
        log.error("Return Error Message : " + msg);
        return msg;
    }

    // 请求错误异常处理 
    @ExceptionHandler(BadRequestException.class)
    public String handleBadRequest(BadRequestException e) {
        return handleException(e, e.getCode());
    }
    
    // 服务器内部异常处理
    @ExceptionHandler(InternalServerException.class)
    public String handleInternalServerError(InternalServerException e) {
        return handleException(e, e.getCode());
    }
    
    // 调用其他服务异常处理
    @ExceptionHandler(InvokeOtherServerException.class)
    public String handleInvokeOtherServerError(InvokeOtherServerException e) {
        return handleException(e, e.getCode(), e.getBody());
    }

    // DAO异常处理
    @ExceptionHandler(DaoException.class)
    public String handleDaoError(DaoException e) {
        return handleException(e, e.getCode());
    }
}

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

## 在properties文件中格式为\uXXXX的所有转义字符,在资源编译器中被显示为未转义的Unicode字符
All escaped characters in the *.properties files in the format \uXXXX, are displayed in the resource bundle editor as un-escaped unicode literals.

## 反之亦然,如果在资源编译器中输入非ASCII字符,则它将反映在底层的properties文件中作为相应的格式为\uXXXX的转义字符
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.

##下面是举了个例子
For example, if the *.properties file contains a property value
Was ich nicht wei\u00df, macht mich nicht hei\u00df

then the resource bundle editor will show
Was ich nicht weiß, macht mich nicht heiß

## 资源编译器本身不做任何转换。若要在属性文件中正确解析转义序列,请在“设置/首选项”对话框的“文件编码页”中选择“透明本机到ascii转换”复选框。
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.

## 可以使用大写和小写十六进制符号(例如'\u00E3'与'\u00e3')对非ascii符号进行编码。大写默认使用。要使用小写,请将bin/idea.properties文件(安装IntelliJ的文件夹)中的'idea.native2ascii.lowercase'属性设置为true。
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.

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,

@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

    private static final Resource[] NO_RESOURCES = {};

    @Bean
    @ConfigurationProperties(prefix = "spring.messages")
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    // 配置了MessageSource的Bean,并装配了上面MessageSourceProperties的Bean
    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
            messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
                    StringUtils.trimAllWhitespace(properties.getBasename())));
        }
        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }
        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }
        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }
    
    ...
}

// 国际化资源文件配置类Properties
public class MessageSourceProperties {

    /**
     * Comma-separated list of basenames (essentially a fully-qualified classpath
     * location), each following the ResourceBundle convention with relaxed support for
     * slash based locations. If it doesn't contain a package qualifier (such as
     * "org.mypackage"), it will be resolved from the classpath root.
     */
    // 默认国际化资源文件名为messages,默认放在类路径下,在application.yml中不需要做任何国际化路径配置
    private String basename = "messages";

    /**
     * Message bundles encoding.
     */
    private Charset encoding = StandardCharsets.UTF_8;

    /**
     * Loaded resource bundle files cache duration. When not set, bundles are cached
     * forever. If a duration suffix is not specified, seconds will be used.
     */
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration cacheDuration;

    /**
     * Whether to fall back to the system Locale if no files for a specific Locale have
     * been found. if this is turned off, the only fallback will be the default file (e.g.
     * "messages.properties" for basename "messages").
     */
    private boolean fallbackToSystemLocale = true;

    /**
     * Whether to always apply the MessageFormat rules, parsing even messages without
     * arguments.
     */
    private boolean alwaysUseMessageFormat = false;

    /**
     * Whether to use the message code as the default message instead of throwing a
     * "NoSuchMessageException". Recommended during development only.
     */
    private boolean useCodeAsDefaultMessage = false;
    
    ...
}

如果创建自定义的国际化资源(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

你可能感兴趣的:(SpringBoot项目国际化)