Spring resource bundle多语言,单引号format异常
Spring resource bundle多语言,单引号format异常
前言
十一假期被通知出现大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的集合:
.
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请求:

- 可以看到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;
}
这样,再次访问:

这样就正常了,单引号可以显示,并且参数可以传进去。
后记
关于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
参考
- http://www.cnblogs.com/woshimrf/p/5030642.html
- https://www.mkyong.com/spring-mvc/spring-mvc-internationalization-example/
Spring resource bundle多语言,单引号format异常的更多相关文章
- 浅谈SQL中的单引号
单引号:对很对计算机语言包括(SQL)是做字符串引用的:这个是大家通常知道的作用:但是对SQL语言来说:还有另外一个作用是作引号的转义 总结下:对oracle(sql)的作用. 做字符串引用:例如'a ...
- php 单引号,双引号,反引号区别
PHP中单引号,双引号,反引号具有不同的含义,最大的几项区别如下: 一.单引号中,任何变量($var).特殊转义字符(如"\t \r \n"等)不会被解析,因此PHP的解析速度更快 ...
- python中的单引号,双引号,三引号
转载自: http://blog.csdn.net/wanghai__/article/details/6285310 先说1双引号与3个双引号的区别,双引号所表示的字符串通常要写成一行 如: s1 ...
- Flex编译程序出现 Could not find compiled resource bundle 'SharedResources' for locale 'en_US'.
Flex编译程序出现 Could not find compiled resource bundle 'SharedResources' for locale 'en_US'. 而且静态类居然为nul ...
- [译]JavaScript:将字符串两边的双引号转换成单引号
原文:http://ariya.ofilabs.com/2012/02/from-double-quotes-to-single-quotes.html 代码的不一致性总是让人发狂,如果每位开发者都能 ...
- SQL中插入单引号,新增修改删除
1.插入单引号如果不转化的话,字符串插入到数据库中错误的,只要在字符串中有单引号的地方在加一个单引号即可. 例如:在数据库插入'井下设备' : insert into Static_Bel ...
- Linux 中的grep命令单引号,不加任何参数以及双引号的作用
单引号: 可以说是所见即所得:即将单引号内的内容原样输出,或者描述为单引号里面看到的是什么就会输出什么.单引号''是全引用,被单引号括起的内容不管是常量还是变量者不会发生替换. 双引号: 把双引号内的 ...
- jackson json转实体 允许特殊字符和转义字符 单引号
//允许出现特殊字符和转义符 mapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) ; //允许出现单引号 mapper.confi ...
- sql 去除列中内容的单引号
update [dbo].[历史数据补全$] set bankCardNo=replace(bankCardNo,'''','') 总共4个单引号,外边2个是字符串固有的2个,里边两个就表示是一 ...
随机推荐
- Python高手之路【六】python基础之字符串格式化
Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[PEP-3101] This ...
- 【小程序分享篇 二 】web在线踢人小程序,维持用户只能在一个台电脑持登录状态
最近离职了, 突然记起来还一个小功能没做, 想想也挺简单,留下代码和思路给同事做个参考. 换工作心里挺忐忑, 对未来也充满了憧憬与担忧.(虽然已是老人, 换了N次工作了,但每次心里都和忐忑). 写写代 ...
- Azkaban源码学习笔记
1. ConnectorParams (interface): 定义了各种常量参数,没有声明任何方法. 2. ExecutorServlet.java类 2.1 继承类HttpServlet和接口 ...
- 【探索】机器指令翻译成 JavaScript
前言 前些时候研究脚本混淆时,打算先学一些「程序流程」相关的概念.为了不因太枯燥而放弃,决定想一个有趣的案例,可以边探索边学. 于是想了一个话题:尝试将机器指令 1:1 翻译 成 JavaScript ...
- ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
- 我为什么要写LeetCode的博客?
# 增强学习成果 有一个研究成果,在学习中传授他人知识和讨论是最高效的做法,而看书则是最低效的做法(具体研究成果没找到地址).我写LeetCode博客主要目的是增强学习成果.当然,我也想出名,然而不知 ...
- 让 windows 下的命令行程序 cmd.exe 用起来更顺手
在 Windows 下使用 Larave 框架做开发,从 Composer 到 artisan 总是避免不了和 cmd.exe 打交道,系统默认的命令行界面却是不怎么好看,且每行显示的字符数是做了限制 ...
- Hawk 6. 编译和扩展开发
Hawk是开源项目,因此任何人都可以为其贡献代码.作者也非常欢迎使用者能够扩展出更有用的插件. 编译 编译需要Visual Stuido,版本建议使用2015, 2010及以上没有经过测试,但应该可以 ...
- UE4新手引导之下载和安装虚幻4游戏引擎
1) 进入虚幻4的官方主页(https://www.unrealengine.com/) 这里你可以获得关于虚幻4的最新资讯,包括版本更新.博客更新.新闻和商城等.自2015年起,该引擎已经提供免费下 ...
- Spring之初体验
Spring之初体验 Spring是一个轻量级的Java Web开发框架,以IoC(Inverse of Control 控制反转)和 ...