國(guó)際化
國(guó)際化也稱作i18n队魏,其來(lái)源是英文單詞 internationalization的首末字符i和n喳魏,18為中間的字符數(shù)悲柱。由于軟件發(fā)行可能面向多個(gè)國(guó)家民晒,對(duì)于不同國(guó)家的用戶芙贫,軟件顯示不同語(yǔ)言的過(guò)程就是國(guó)際化(舉個(gè)例子搂鲫,人們玩的電子游戲,通郴瞧剑可以選擇多個(gè)語(yǔ)言版本魂仍,適應(yīng)于多個(gè)國(guó)家的玩家)。通常來(lái)講拣挪,軟件中的國(guó)際化是通過(guò)配置文件來(lái)實(shí)現(xiàn)的擦酌,假設(shè)某個(gè)軟件要支撐兩種語(yǔ)言,那么就需要兩個(gè)版本的配置文件菠劝。
Java國(guó)際化
Java自身是支持國(guó)際化的赊舶,java.util.Locale用于指定當(dāng)前用戶所屬的語(yǔ)言環(huán)境等信息,java.util.ResourceBundle用于查找綁定對(duì)應(yīng)的資源文件赶诊。
Locale包含了language信息和country信息笼平,Locale創(chuàng)建默認(rèn)locale對(duì)象時(shí)使用的靜態(tài)方法:
/**
* This method must be called only for creating the Locale.*
* constants due to making shortcuts.
*/
private static Locale createConstant(String lang, String country) {
BaseLocale base = BaseLocale.createInstance(lang, country);
return getInstance(base, null);
}
配置文件命名規(guī)則:
basename_language_country.properties
必須遵循以上的命名規(guī)則,java才會(huì)識(shí)別舔痪。其中出吹,basename是必須的,語(yǔ)言和國(guó)家是可選的辙喂。這里存在一個(gè)優(yōu)先級(jí)概念捶牢,如果同時(shí)提供了messages.properties和messages_zh_CN.propertes兩個(gè)配置文件鸠珠,如果提供的locale符合en_CN,那么優(yōu)先查找messages_en_CN.propertes配置文件秋麸,如果沒(méi)查找到渐排,再查找messages.properties配置文件。最后灸蟆,提示下驯耻,所有的配置文件必須放在classpath中,一般放在resource目錄下炒考。
舉個(gè)例子可缚,兩個(gè)配置文件內(nèi)容分別如下:
#messages.properties
test=hello1
#messages_en_CN.propertes
test=hello2
代碼:
//ResourceBundle.getBundle接受兩個(gè)參數(shù):basename,locale
System.out.println(ResourceBundle.getBundle("messages",new Locale("en","CN")).getString("test"));
打印結(jié)果:
hello2
以下命名的配置文件優(yōu)先級(jí)從低到高:
messages.properties
messages_en.properties
messages_en_CN.properties
通過(guò)配置不同的Locale相關(guān)的資源文件斋枢,我們可以通過(guò)key值取到相應(yīng)環(huán)境的value值帘靡,這樣便做到了國(guó)際化,這種方式是非侵入式的瓤帚,讓我們編寫(xiě)代碼時(shí)可以不需要考慮國(guó)際化的問(wèn)題描姚,只需要根據(jù)配置規(guī)則配置相關(guān)資源文件,在實(shí)際讀取資源配置時(shí)戈次,指定相應(yīng)的locale即可轩勘。這也是約定優(yōu)于配置的體現(xiàn)。
Spring國(guó)際化
spring使用MessageSource接口實(shí)現(xiàn)國(guó)際化怯邪。兩個(gè)實(shí)現(xiàn)類為:
ResourceBundleMessageSource:基于java的ResourceBundle實(shí)現(xiàn)了國(guó)際化绊寻,配置文件必須放在classpath下。
ReloadableResourceBundleMessageSource:直接使用讀取文件的方式實(shí)現(xiàn)國(guó)際化悬秉,規(guī)則跟java的相同澄步,支持動(dòng)態(tài)修改后刷新配置,避免在業(yè)務(wù)不能中斷的情況下重啟進(jìn)程搂捧。配置文件可以放在任意目錄下驮俗,指定目錄后,該類會(huì)去指定目錄中加載配置文件允跑。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.stereotype.Component;
import java.util.Locale;
@Component
public class MessageService {
@Autowired
private MessageSource messageSource;
private Locale currentLocale = new Locale("en");
@Bean
public static MessageSource getMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setCacheSeconds(5);
return messageSource;
}
public String getMessage(String key) {
return messageSource.getMessage(key, null, key, currentLocale);
}
}
ReloadableResourceBundleMessageSource的優(yōu)點(diǎn)在于可以不重啟進(jìn)程動(dòng)態(tài)刷新配置王凑,以及指定資源目錄,不需要強(qiáng)制放在classpath下面聋丝,如果需要放在classpath中索烹,那么只需要在basename中的資源路徑上添加"classpath:",便可以在classpath中查找配置弱睦。messageSource.setCacheSeconds用于設(shè)置配置的過(guò)期時(shí)間百姓,單位為秒,messageSource.setCacheSeconds(5)代表每5秒配置文件就會(huì)過(guò)期况木,再重新查詢時(shí)垒拢,就會(huì)重新加載配置旬迹。
spring的默認(rèn)配置文件命名規(guī)則跟java是相同的,也都遵循約定優(yōu)于配置的思路求类。
Locale信息的獲取
一般來(lái)說(shuō)奔垦,典型的B/S架構(gòu),用戶可能在任何地點(diǎn)使用任何語(yǔ)言登陸網(wǎng)站尸疆,但是網(wǎng)站后臺(tái)是部署在固定的服務(wù)器或者是云服務(wù)上的椿猎,那么如何讓后端獲取客戶端(瀏覽器端)的locale信息,從而做到針對(duì)不同的客戶端進(jìn)行國(guó)際化呢寿弱?spring使用LocaleResolver解析用戶的http請(qǐng)求來(lái)獲取對(duì)應(yīng)的locale信息犯眠。下面的代碼是典型的controller,通過(guò)解析HttpServletRequest請(qǐng)求來(lái)獲取locale信息症革。
@Controller
public class HomeController extends BaseController {
@RequestMapping(value = "", method = GET)
public String homeDefault() {
return homePageUrl();
}
@RequestMapping(value = "/locales", method = GET)
public ResponseEntity<OpenLmisResponse> getLocales(HttpServletRequest request) {
messageService.setCurrentLocale(RequestContextUtils.getLocale(request));
return response("locales", messageService.getLocales());
}
@RequestMapping(value = "/changeLocale", method = PUT, headers = ACCEPT_JSON)
public void changeLocale(HttpServletRequest request) {
messageService.setCurrentLocale(RequestContextUtils.getLocale(request));
}
}
RequestContextUtils.getLocale(request)實(shí)現(xiàn)如下筐咧。RequestContextUtils是spring提供的工具類〉鼐冢可以看出LocaleResolver從設(shè)計(jì)上支持多種策略嗜浮。
/**
* Retrieve the current locale from the given request, using the
* LocaleResolver bound to the request by the DispatcherServlet
* (if available), falling back to the request's accept-header Locale.
* <p>This method serves as a straightforward alternative to the standard
* Servlet {@link javax.servlet.http.HttpServletRequest#getLocale()} method,
* falling back to the latter if no more specific locale has been found.
* <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getLocale()}
* which will normally be populated with the same Locale.
* @param request current HTTP request
* @return the current locale for the given request, either from the
* LocaleResolver or from the plain request itself
* @see #getLocaleResolver
* @see org.springframework.context.i18n.LocaleContextHolder#getLocale()
*/
public static Locale getLocale(HttpServletRequest request) {
LocaleResolver localeResolver = getLocaleResolver(request);
return (localeResolver != null ? localeResolver.resolveLocale(request) : request.getLocale());
}
LocaleResolver包含以下四種策略實(shí)現(xiàn)羡亩。AcceptHeaderLocaleResolver摩疑,CookieLocaleResolver,F(xiàn)ixedLocaleResolver和SessionLocaleResolver畏铆。
下面的代碼是DispatcherServlet中初始化LocaleResolver的過(guò)程雷袋,可以看到LocaleResolver實(shí)際是支持用戶進(jìn)行配置的,如果沒(méi)有配置辞居,那么使用getDefaultStrategy方法獲取默認(rèn)策略楷怒。
/**
* Initialize the LocaleResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to AcceptHeaderLocaleResolver.
*/
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver + "]");
}
}
}
查看項(xiàng)目工程代碼,發(fā)現(xiàn)項(xiàng)目中配置的是CookieLocaleResolver瓦灶。
幾種LocaleResolver的用法不同鸠删,不過(guò)從名字便可以看出一二。
AcceptHeaderLocaleResolver:直接從Http請(qǐng)求的Header中通過(guò)accept-language獲取locale信息
CookieLocaleResolver:從cookie中獲取贼陶,如果獲取不到刃泡,也是通過(guò)accept-language獲取locale信息
SessionLocaleResolver:從session中獲取,如果獲取不到碉怔,也是通過(guò)accept-language獲取locale信息
FixedLocaleResolver:固定locale烘贴,基本沒(méi)啥用
至此,整個(gè)后端的國(guó)際化過(guò)程已經(jīng)比較清楚:后端通過(guò)解析客戶端(瀏覽器端)傳遞的locale信息撮胧,將locale拿出桨踪,然后再通過(guò)spring的MessageSource類傳入locale信息,取出相應(yīng)的配置文件芹啥,解析出相應(yīng)配置锻离,從而便完成了國(guó)際化铺峭。