Spring resource bundle多语言,单引号format异常

简介: Spring resource bundle多语言,单引号format异常source code前言十一假期被通知出现大bug,然后发现是多语言翻译问题。法语中有很多单引号,单引号在format的时候出现无法匹配问题。

Spring resource bundle多语言,单引号format异常

source code

前言

十一假期被通知出现大bug,然后发现是多语言翻译问题。法语中有很多单引号,单引号在format的时候出现无法匹配问题。这个问题是由spring resource bundle 并调用MessageFormat引起的,根本原因是MessageFormat会转义单引号。




创建一个简单的多语言demo,重现异常

1.配置

    @Bean
    public ResourceBundleMessageSource messageSource(){
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasenames("msg");
        source.setDefaultEncoding("UTF-8");
        source.setFallbackToSystemLocale(false);

        return source;
    }

这里没有指定localeResolver, 默认会使用AcceptHeaderLocaleResolver,也就是说从request的header中获取Accept-Language来解析语言。

ResourceBundleMessageSource是多语言翻译的逻辑处理。source.setBasenames("msg")绑定一个多语言的集合。这里我创建一个叫做msg的集合:
spring-i18n.


2.创建多语言方法

在main下右键创建一个文件夹i18n,然后将其设置为resources类型。在gradle中,可以在build.gradle里添加:

sourceSets {
    main {
        resources {
            srcDir 'src/main/i18n'
        }
    }
}

然后-New-Resource Bundle. 起一个集合的名字,比如msg, 添加需要的语言包。
在里面添加内容

#msg.properties
user.name=default for en_US, I'm {0}
user.age='18'

#msg_en_US.properties
user.name=test, the user's name is {0}.

#msg_fr_FR.properties
user.name=This is french, I'm {0}

#msg_zh_CN.properties
user.name=测试 ,用户名是 '{0}'



3.编写一个controller测试

    @Autowired
    private MessageSource messageSource;
    @ResponseBody
    @RequestMapping(value = "/i18n/{name}", method = RequestMethod.GET)
    public Map resource(Locale locale,
                        @PathVariable("name") String name){
        Map map = new HashMap();

        String[] arr = {name};
        String message = messageSource.getMessage("user.name", arr, locale);
        String age = messageSource.getMessage("user.age", null, locale);
        map.put("username", message);
        map.put("age", age);

        return map;
    }
  • java中通过MessageSource来获取配置语言包中内容
  • 在controller的参数中添加Locale会自动注入LocaleResolver解析后的Locale, 当前是采用默认的AcceptHeaderLocaleResolver。当然也可以自己添加locale拦截器来自定义locale, 这个后面再去设置。
  • 本例中获取name和age。其中name需要插入参数,而age不需要参数,原样输出即可。

如果在jsp中可以使用spring标签:

<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<spring:message code="user.name" arguments="Ryan"/>
<spring:message code="user.age"/>

4.访问

通过postman来访问get请求:
img_2c8a38c06a415a980cb62c57869498b2.png

  • 可以看到age的单引号会原样输出,但name的单引号没了,不仅如此,参数也并没有传入
  • 这是因为messageSource在getMessage的时候采用了两种策略,一种是原样输出,一种是采用MessageFormat来处理参数。
  • 因此,主要原因就是MessageFormat的问题了。

5.测试MessageFormat

@Test
public void testQuote() throws  Exception{
    String message = "I'm {0}.";
    String ryan = MessageFormat.format(message, "Ryan");
    System.out.println(ryan);
    Assert.assertEquals("Im {0}.", ryan);

    message = "I''m {0}.";
    ryan = MessageFormat.format(message, "Ryan");
    System.out.println(ryan);
    Assert.assertEquals("I'm Ryan.", ryan);
}

通过测试用例可以发现,MessageFormat会转义(escape)单引号(quote)。因此,如果想要输出一个单引号就需要针对的用两个单引号来替换。

所以,解决上述问题的关键就是在语言包中涉及单引号的地方都做一下转义,即两个单引号。然而,这个步骤会比较繁琐,而且会使得语言包的内容和显示的内容不一致。因此,最好可以通过一个工具来将单引号自动转义。

6.设置单引号转义

既然已经知道问题原因所在了,那么只要在Format之前做一下转义就可以了。
追踪getMessage方法到AbstractMessageSource可以发现有参数和无参数的不同处理:

Object[] argsToUse = args;
if(!this.isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
    String commonMessages1 = this.resolveCodeWithoutArguments(code, locale);
    if(commonMessages1 != null) {
        return commonMessages1;
    }
} else {
    argsToUse = this.resolveArguments(args, locale);
    MessageFormat commonMessages = this.resolveCode(code, locale);
    if(commonMessages != null) {
        synchronized(commonMessages) {
            return commonMessages.format(argsToUse);
        }
    }
}

那么,按道理,我们只要处理有参数的情况下就好了。接下来就应该是重写resolveCode方法,将取出来的结果中的单引号替换。

要重写的就是ResourceBundleMessageSource类, 但是发现这些方法都是私有的。这是因为我当前spring的版本是4.1.1。 意外升级成4.3.2之后发现这些方法已经变成protected。

接着发现由于私有成员变量能重写的是getStringOrNull方法,但重写后也会影响无参数的获取。所以,设置ResourceBundleMessageSource

source.setAlwaysUseMessageFormat(true);

将所有的语言包获取都走传参路线,即都会经过MessageFormat处理,即单引号都要转义。如此,便可以重写getStringOrNull了。

创建ResourceFormat

public class ResourceFormat extends ResourceBundleMessageSource {

    @Override
    protected String getStringOrNull(ResourceBundle bundle, String key) {
        if(bundle.containsKey(key)) {
            try {
                String val = bundle.getString(key);
                return val.replaceAll("'","''");
            } catch (MissingResourceException var4) {
                ;
            }
        }

        return null;
    }
    
}

然后修改配置类:

@Bean
public ResourceBundleMessageSource messageSource(){
    ResourceBundleMessageSource source = new ResourceFormat();
    source.setBasenames("msg");
    source.setDefaultEncoding("UTF-8");
    source.setFallbackToSystemLocale(false);
    source.setAlwaysUseMessageFormat(true);

    return source;
}

这样,再次访问:
img_0dd9585e57d2ddb7fd337c973496857d.png
这样就正常了,单引号可以显示,并且参数可以传进去。

后记

关于locale resolver有多个实现类,通常使用SessionLocaleResolver, 这时候需要添加一个拦截器,来将locale注入进去。注入locale的方法有很多,比如header,比如url直接传参,比如cookie。通过各种手段获取浏览器的语言之后,设置到locale里就可以了。
spring自带了一个LocaleChangeInterceptor,可以将参数locale拦截并注入。
因此,只要自己在拦截器里设置:

Locale langLocale = Locale.forLanguageTag(localeString);
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
localeResolver.setLocale(request, response, langLocale);
request.setAttribute("javax.servlet.jsp.jstl.fmt.locale", langLocale);
  • localeString就是语言代码,比如en-US, zh-CN
参考




唯有不断学习方能改变! -- Ryan Miao
目录
相关文章
|
3天前
|
JSON 前端开发 Java
解决Spring MVC中No converter found for return value of type异常
在Spring MVC开发中遇到`No converter found for return value of type`异常,通常是因缺少消息转换器、返回值类型不支持或转换器优先级配置错误。解决方案包括:1) 添加对应的消息转换器,如`MappingJackson2HttpMessageConverter`;2) 自定义消息转换器并实现`HttpMessageConverter`接口,设置优先级;3) 修改返回值类型为如`ResponseEntity`的合适类型。通过这些方法可确保返回值正确转换为响应内容。
42 1
|
3天前
|
消息中间件 监控 Java
Spring Boot中的RabbitMQ死信队列魔法:从异常到延迟,一网打尽【RabbitMQ实战 一】
Spring Boot中的RabbitMQ死信队列魔法:从异常到延迟,一网打尽【RabbitMQ实战 一】
96 0
|
3天前
|
Java
springboot全局自定义异常
springboot全局自定义异常
|
3天前
|
Java 关系型数据库 MySQL
整合SpringBoot与MyBatis时报错时区异常
整合SpringBoot与MyBatis时报错时区异常
15 0
|
3天前
|
JSON 前端开发 Java
统一异常处理:让Spring Boot项目异常更优雅
统一异常处理:让Spring Boot项目异常更优雅
31 1
|
3天前
|
Java
SpringBoot全局异常代码复制粘贴能用
SpringBoot全局异常代码复制粘贴能用
10 0
|
3天前
|
Java
SpringBoot配置全局异常
SpringBoot配置全局异常
11 0
|
3天前
|
Java
SpringBoot全局异常@RestControllerAdvice全局异常
SpringBoot全局异常@RestControllerAdvice全局异常
12 0
|
3天前
|
Java
SpringBoot开启配置全局异常
SpringBoot开启配置全局异常
12 0
|
3天前
|
SQL 监控 Java
nacos常见问题之dubbo+nacos+springboot3的native打包成功后运行出现异常如何解决
Nacos是阿里云开源的服务发现和配置管理平台,用于构建动态微服务应用架构;本汇总针对Nacos在实际应用中用户常遇到的问题进行了归纳和解答,旨在帮助开发者和运维人员高效解决使用Nacos时的各类疑难杂症。
52 2