Java Spring项目国际化(i18n)详细方法与实例

网友投稿 762 2022-12-11


Java Spring项目国际化(i18n)详细方法与实例

Spring国际化概述

国际化基本规则

国际化信息”也称为“本地化信息”,一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。java通过java.util.Locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。

语言参数使用ISO标准语言代码表示,这些代码是由ISO-639标准定义的,每一种语言由两个小写字母表示。在许多网站上都可以找到这些代码的完整列表,下面的网址是提供了标准语言代码的信息:http://loc.gov/standards/iso639-2/php/English_list.php。

国家/地区参数也由标准的ISO国家/地区代码表示,这些代码是由ISO-3166标准定义的,每个国家/地区由两个大写字母表示。用户可以从以下网址查看ISO-3166的标准代码:http://iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html,部分语言和国家/地区的标准代码如下所示:

语言

简称

简体中文(中国)

zh_CN

繁体中文(中国台湾)

zh_TW

繁体中文(中国香港)

zh_HK

英语(中国香港)

en_HK

英语(美国)

en_US

英语(英国)

en_GB

英语(全球)

en_WW

英语(加拿大)

en_CA

英语(澳大利亚)

en_AU

英语(爱尔兰)

en_IE

英语(芬兰)

en_FI

芬兰语(芬兰)

fi_FI

英语(丹麦)

en_DK

丹麦语(丹麦)

da_DK

英语(以色列)

en_IL

希伯来语(以色列)

he_IL

英语(南非)

en_ZA

英语(印度)

en_IN

英语(挪威)

en_NO

英语(新加坡)

en_SG

英语(新西兰)

en_NZ

英语(印度尼西亚)

en_ID

英语(菲律宾)

en_PH

英语(泰国)

en_TH

英语(马来西亚)

en_MY

英语(阿拉伯)

en_XA

韩文(韩国)

ko_KR

日语(日本)

ja_JP

荷兰语(荷兰)

nl_NL

荷兰语(比利时)

nl_BE

葡萄牙语(葡萄牙)

pt_PT

葡萄牙语(巴西)

pt_BR

法语(法国)

fr_FR

法语(卢森堡)

fr_LU

法语(瑞士)

fr_CH

法语(比利时)

fr_BE

法语(加拿大)

fr_CA

西班牙语(拉丁美洲)

es_LA

西班牙语(西班牙)

es_ES

西班牙语(阿根廷)

es_AR

西班牙语(美国)

es_US

西班牙语(墨西哥)

es_MX

西班牙语(哥伦比亚)

es_CO

西班牙语(波多黎各)

es_PR

德语(德国)

de_DE

德语(奥地利)

de_AT

德语(瑞士)

de_CH

俄语(俄罗斯)

ru_RU

意大利语(意大利)

it_IT

希腊语(希腊)

el_GR

挪威语(挪威)

no_NO

匈牙利语(匈牙利)

hu_HU

土耳其语(土耳其)

tr_TR

捷克语(捷克共和国)

cs_CZ

斯洛文尼亚语

sl_SL

波兰语(波兰)

pl_PL

瑞典语(瑞典)

sv_SE

西班牙语(智利)

es_CL

语言类型判断

1)基于浏览器语言

根据Request Headers中的Accept-language来判断。

2)基于客户端传参

要求客户端第一次(或者每次)传递的自定义参数值来判断,如规定传locale,值为zh-cn、en-us等内容,如果只在第一次传入则local以及timeZone先关信息要存入session或者cookie中,后面的请求语言方式则直接从两者中取,其有效时间与session和cookie设置的生命周期关联。

3)基于默认配置

当获取语言类型时没有找到对应类型时,会使用默认的语言类型。

语言类型保存

基于url

该种方式需要每次都在请求的url上带上local参数,指定该次需要的语言类型,并且该方式的local解析器需要配置,如下:

中文英文

但在该配置下使用会抛Cannot change HTTP accept header - use a different locale resolution strategy异常,这是因为spring source做了限制,无法对本地的local赋值修改,解决办法如下,新建一个类MyLocaleResolver继承AcceptHeaderLocaleResolver,重写resolveLocale和setLocale方法,并将上面的localeResolver的class指向如下MyLocaleResolver类:

public class MyLocaleResolver extends AcceptHeaderLocaleResolver {

private Locale myLocal;

public Locale resolveLocale(HttpServletRequest request) {

return myLocal == null ? request.getLocale() : myLocal;

}

public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

myLocal = locale;

}

}

基于session

基于session的状态保存方式只需要在第一次请求的时候指定语言类型,localResolver会将该属性保存到session中,后面的请求直接从session中获取该语言类型,该种方式的localResolver对应的类为SessionLocaleResolver,如下配置:

基于cookie

与session的机制类似,差异在于两者的存储和周期,鉴于安全、大小以及体验等因素的影响,实际使用中使用者更倾向于前者,该种cookie保存方式的localResolver为

文案数据来源

对于语言类型的资源文件,需要开发者对文案进行搜集整理,并翻译成相应的语言确定关键字key,目前大多数情况是将这些信息置于.properties文件中,在使用的时候直接访问获取,当然也可置于数据库中,但频繁的文案获取会影响服务器性能及产品体验,可结合数据字典以及缓存工具使用。

数据库

1)spring 配置方式

resource

validation

这里定义了一个propertiesMessageSource,一个databaseMessageSourcer,和一个messageInterpolator。propertiesMessageSource用于读取properties文件databaseMessageSourcer用于读取数据库的数据配置,其中,有一个属性设置它的父MessageSource为propertiesMessageSource。意思是如果数据库找不到对应的数据,到properties文件当中查找。messageInterpolator是个拦截器。

2)数据库的POJO定义

@Entity

@SuppressWarnings("serial")

@Table(name="resource")

public class Resource implements Serializable {

@Id

@GeneratedValue(strategy=GenerationType.IDENTITY)

@Column(name="resource_id")

private long resourceId;

@Column(name="name", length=50, nullable=false)

private String name;

@Column(name="text", length=1000, nullable=false)

private String text;

@Column(name="language", length=5, nullable=false)

private String language;

public long getResourceId() {

return resourceId;

}

public void setResourceId(long resourceId) {

this.resourceId = resourceId;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getText() {

return text;

}

public void setText(String text) {

this.text = text;

}

public String getLanguage() {

return language;

}

public void setLanguage(String language) {

this.language = language;

}

}

定义了一张表[resource],字段有:[resource_id]、[name]、[text]、[language]。

3)读取数据库的MessageResource类

/**

* 取得资源数据

* @author Robin

*/

public class MessageResource extends AbstractMessageSource implements ResourceLoaderAware {

@SuppressWarnings("unused")

private ResourceLoader resourceLoader;

@Resource

private ResourceService resourceService;

/**

* Map切分字符

*/

protected final String MAP_SPLIT_CODE = "|";

protected final String DB_SPLIT_CODE = "_";

private final Map properties = new HashMap();

public MessageResource() {

reload();

}

public void reload() {

properties.clear();

properties.putAll(loadTexts());

}

protected Map loadTexts() {

Map mapResource = new HashMap();

List resources = resourceService.findAll();

for (com.obs2.service.bean.Resource item : resources) {

String code = item.getName() + MAP_SPLIT_CODE + item.getLanguage();

mapResource.put(code, item.getText());

}

return mapResource;

}

private String getText(String code, Locale locale) {

String localeCode = locale.getLanguage() + DB_SPLIT_CODE + locale.getCountry();

String key = code + MAP_SPLIT_CODE + localeCode;

String localeText = properties.get(key);

String resourceText = code;

if(localeText != null) {

resourceText = localeText;

}else {

localeCode = Locale.ENGLISH.getLanguage();

key = code + MAP_SPLIT_CODE + localeCode;

localeText = properties.get(key);

if(localeText != null) {

resourceText = localeText;

}else {

try {

if(getParentMessageSource() != null) {

resourceText = getParentMessageSource().getMessage(code, null, locale);

}

} catch (Exception e) {

logger.error("Cannot find message with code: " + code);

}

}

}

return resourceText;

}

@Override

public void setResourceLoader(ResourceLoader resourceLoader) {

this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());

}

@Override

protected MessageFormat resolveCode(String code, Locale locale) {

String msg = getText(code, locale);

MessageFormat result = createMessageFormat(msg, locale);

return result;

}

@Override

protected String resolveCodeWithoutArguments(String code, Locale locale) {

String result = getText(code, locale);

return result;

}

}

主要是重载AbstractMessageSource和ResourceLoaderAware,以实现Spring MVC的MessageSource国际化调用。类中的reload()方法,我把它写到了一个ServletListener当中,让项目启动时,自动加载数据到static的map中。

4)Listener

/**

* 系统启动监听

* @author Robin

*/

public class SystemListener implements ServletContextListener {

/**

* context初始化时激发

*/

@Override

public void contextInitialized(ServletContextEvent e) {

// 取得ServletContext

ServletContext context = e.getServletContext();

WebApplicationContext applicationContext = WebApplicationContextUtils .getWebApplicationContext(context);

// 设置国际化多语言

MessageResource messageSource = applicationContext.getBean(MessageResource.class);

messageSource.reload();

}

/**

* context删除时激发

*/

@Override

public void contextDestroyed(ServletContextEvent e) {

}

/**

* 创建一个 session时激发

* @param e

*/

public void sessionCreated(HttpSessionEvent e) {

}

/**

* 当一个 session失效时激发

* @param e

*

public void sessionDestroyed(HttpSessionEvent e) {

}

/**

* 设置 context的属性,它将激发attributeReplaced或attributeAdded方法

* @param e

*/

public void setContext(HttpSessionEvent e) {

}

/**

* 增加一个新的属性时激发

* @param e

*/

public void attributeAdded(ServletContextAttributeEvent e) {

}

/**

*删除一个新的属性时激发

* @param e

*/

public void attributeRemoved(ServletContextAttributeEvent e) {

}

/*

* 属性被替代时激发

* @param e

*/

public void attributeReplaced(ServletContextAttributeEvent e) {

}

}

该Listener需要加入到web.xml当中:

com.obs2.util.SystemListener

5)Interceptor拦截器

/**

* 拦截Annotation验证信息

* @author Robin

*

*/

public class MessageResourceInterpolator implements MessageInterpolator {

@Resource

private MessageResource messageResource;

public void setMessageResource(MessageResource messageResource) {

this.messageResource = messageResource;

}

@Override

public String interpolate(String messageTemplate, Context context) {

String messageTemp = null;

if(messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) {

messageTemp = messageTemplate.substring(1, messageTemplate.length() - 1);

}else {

return messageTemplate;

}

String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params");

MessageBuilder builder = new MessageBuilder().code(messageTemp);

if (params != null) {

for (String param : params) {

builder = builder.arg(param);

}

}

String result = builder.build().resolveMessage(messageResource, Locale.ENGLISH).getText();

return result;

}

@Override

public String interpolate(String messageTemplate, Context context, Locale locale) {

String messageTemp = null;

if(messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) {

messageTemp = messageTemplate.substring(1, messageTemplate.length() - 1);

}else {

return messageTemplate;

}

String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params");

MessageBuilder builder = new MessageBuilder().code(messageTemp);

if (params != null) {

builder = builder.args(params);

}

String result = builder.build().resolveMessage(messageResource, locale).getText();

return result

}

}

静态资源

文案获取

资源获取接口

MessageSource详解

Spring定义了访问国际化信息的MessageSource接口,并提供了几个易用的实现类。首先来了解一下该接口的几个重要方法:

1)String getMessage(String code, Object[] args, String defaultMessage, Locale locale) code

表示国际化资源中的属性名;args用于传递格式化串占位符所用的运行期参数;当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;locale表示本地化对象;

2)String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException

与上面的方法类似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException异常;

3)String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException

MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第一个接口方法相同。

MessageSource类结构

MessageSource分别被HierarchicalMessageSource和ApplicationContext接口扩展,这里我们主要看一下HierarchicalMessageSource接口的几个实现类

HierarchicalMessageSource接口添加了两个方法,建立父子层级的MessageSource结构,类似于前面我们所介绍的HierarchicalBeanFactory。该接口的setParentMessageSource (MessageSource parent)方法用于设置父MessageSource,而getParentMessageSource()方法用于返回父MessageSource。

HierarchicalMessageSource接口最重要的两个实现类是ResourceBundleMessageSource和ReloadableResourceBundleMessageSource。它们基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源。ReloadableResourceBundleMessageSource提供了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。StaticMessageSource主要用于程序测试,它允许通过编程的方式提供国际化信息。而DelegatingMessageSource是为方便操作父MessageSource而提供的代理类。

ResourceBundleMessageSource与ReloadableResourceBundleMessageSource对比

1)通过ResourceBundleMessageSource配置资源

class="org.springframework.context.support.ResourceBundleMessageSource">

com/baobaotao/i18n/fmt_resource

class="org.springframework.context.support.ResourceBundleMessageSource">

com/baobaotao/i18n/fmt_resource

2)通过ReloadableResourceBundleMessageSource配置资源

class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

com/baobaotao/i18n/fmt_resource

class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

com/baobaotao/i18n/fmt_resource

3)对比

两者都是利用资源名通过getMessage()接口就可以加载整套的国际化资源文件,唯一区别在于ReloadableResourceBundleMessageSource可以定时刷新资源文件,以便在应用程序不重启的情况下感知资源文件的变化。很多生产系统都需要长时间持续运行,系统重启会给运行带来很大的负面影响,这时通过该实现类就可以解决国际化信息更新的问题。上面的配置中cacheSeconds属性让ReloadableResourceBundleMessageSource每5秒钟刷新一次资源文件(在真实的应用中,刷新周期不能太短,否则频繁的刷新将带来性能上的负面影响,一般不建议小于30分钟)。cacheSeconds默认值为-1表示永不刷新,此时,该实现类的功能就蜕化为ResourceBundleMessageSource的功能。

页面获取文案

利用Spring标签获取

引入标签库:

<%@ taglib prefix="spring" uri="http://springframework.org/tags" %>

获取文案:

利用jsTL标签获取

引入标签库:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt"%>

获取文案:

Java代码中获取文案

利用MessageSource接口获取

1)自动注入

@Autowired

private MessageSource messageSource;

String s = messageSource.getMessage("SystemError", new Object[]{}, Locale.US);

2)手动bean获取

a. 获取容器

容器已经初始化:

WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();

容器没有初始化:

String[] configs = {"com/baobaotao/i18n/beans.xml"};

ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);

b. 获取bean跟文案

MessageSource ms = (MessageSource) wac.getBean("myResource");

Object[] params = {"John", new GregorianCalendar().getTime()};

String str1 = ms.getMessage("greeting.common",params,Locale.US);

利用Spring容器获取

在前面的MessageSource类图结构中我们发现ApplicationContext实现了MessageSource的接口,也就是说ApplicationContext的实现类本身也是一个MessageSource对象。

将ApplicationContext和MessageSource整合起来, Spring此处的设计人为:在一般情况下,国际化信息资源应该是容器级。我们一般不会将MessageSource作为一个Bean注入到其他的Bean中,相反MessageSource作为容器的基础设施向容器中所有的Bean开放。只要我们考察一下国际化信息的实际消费场所就更能理解Spring这一设计的用意了。国际化信息一般在系统输出信息时使用,如Spring MVC的页面标签,控制器Controller等,不同的模块都可能通过这些组件访问国际化信息,因此Spring就将国际化消息作为容器的公共基础设施对所有组件开放。

既然一般情况下我们不会直接通过引用MessageSource Bean使用国际信息,那如何声明容器级的国际化信息呢? Spring容器启动过程时,在初始化容器的时候通过initMessageSource()方法所执行的工作就是初始化容器中的国际化信息资源,它根据反射机制从BeanDefinitionRegistry中找出名称为“messageSource”且类型为org.springframework.context.MessageSource的Bean,将这个Bean定义的信息资源加载为容器级的国际化信息资源。请看下面的配置:

class="org.springframework.context.support.ResourceBundleMessageSource">

com/baobaotao/i18n/fmt_resource

class="org.springframework.context.support.ResourceBundleMessageSource">

com/baobaotao/i18n/fmt_resource

然后通过ApplicationContext直接访问国际化信息:

a. 获取容器

容器已经初始化:

WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();

容器没有初始化:

String[] configs = {"com/baobaotao/i18n/beans.xml"};

ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);

b. 获取bean跟文案

Object[] params = {"John", new GregorianCalendar().getTime()};

String str1 = ctx.getMessage("greeting.common",params,Locale.US);

注意事项

1)编码问题

a. 改变properties文件编码为UTF-8/GBK,然而ResourceBundleMessageSource的默认编码defaultEncoding是ISO-8859-1,需要在xml中增加一个相应属性将其改变为你需要的UTF-8/GBK之类。

b. 如果资源文件想统一使用ISO-8859-1格式,可以将原本用UTF-8写好的中文资源文件使用jdk自带的工具native2ascii将UTF-8文件和内容转为ISO-8859-1文件,其中的中文内容会使用16进制unicode编码为\u****格式:

cmd命令:

JAVA_HOME\bin\native2ascii -encoding UTF-8 messages_zh_CN.properties messages_zh_C1N.properties

本文主要讲解了Java Spring项目国际化(i18n)详细方法与实例,更多关于Java Spring项目国际化技巧请查看下面的相关链接


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring Boot2开发之Spring Boot整合Shiro两种详细方法
下一篇:Java 冻结或解除冻结Excel中的行和列的方法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~