美文网首页Spring全家桶
SpringBoot项目国际化

SpringBoot项目国际化

作者: 程序猿蛋蛋哥 | 来源:发表于2019-01-28 20:58 被阅读6次

    1. 创建国际化文件Resource Bundle

    项目结构图:


    springboot项目工程详细结构

    国际化文件结构图:


    springboot国际化文件

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


    添加en_US的英文国际化文件 添加zh_CN的中文国际化文件 最终国际化添加完成的界面

    2. 国际化配置类InternationalConfig

    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国际化处理类

    代码:

    @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的问题

    如图:


    中文国际化出现乱码 中文国际化出现\uXXXX

    乱码问题根源:

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


    IDEA默认工程初始编码为GBK

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

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

    修改了工程编码和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,见下截图:


    IntelliJ官网截图

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

    解决方法:

    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中配置该自定义国际化文件的路径。

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

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

    自定义与默认的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项目国际化

        本文链接:https://www.haomeiwen.com/subject/tytzjqtx.html