当前位置:网站首页>國際化配置
國際化配置
2022-06-26 09:54:00 【馬可愛家的馬可愛】
1、編寫國際化資源文件
在 src/main/resources 下創建一個 i18n 的目錄,並在該目錄中按照國際化資源文件命名格式分別創建以下三個文件,
login.properties:無語言設置時生效
login_en_US.properties :英語時生效
login_zh_CN.properties:中文時生效
以上國際化資源文件創建完成後,IDEA 會自動識別它們,並轉換成如下的模式:
然後就可以使用Resource Bundle進行可視化配置,如果沒有這個插件,下載Resource Bundle插件
打開任意一個國際化資源文件,並切換為 Resource Bundle 模式,然後點擊“+”號,創建所需的國際化屬性,如下圖。
2. 使用 ResourceBundleMessageSource 管理國際化資源文件
Spring Boot 已經對 ResourceBundleMessageSource 提供了默認的自動配置。
Spring Boot 通過 MessageSourceAutoConfiguration 對 ResourceBundleMessageSource 提供了默認配置,其部分源碼如下。
/* * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package org.springframework.boot.autoconfigure.context;
import java.time.Duration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.StringUtils;
/** * {@link EnableAutoConfiguration Auto-configuration} for {@link MessageSource}. * * @author Dave Syer * @author Phillip Webb * @author Eddú Meléndez * @since 1.5.0 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, 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();
}
@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;
}
protected static class ResourceBundleCondition extends SpringBootCondition {
private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
ConditionOutcome outcome = cache.get(basename);
if (outcome == null) {
outcome = getMatchOutcomeForBasename(context, basename);
cache.put(basename, outcome);
}
return outcome;
}
private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
for (Resource resource : getResources(context.getClassLoader(), name)) {
if (resource.exists()) {
return ConditionOutcome.match(message.found("bundle").items(resource));
}
}
}
return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
}
private Resource[] getResources(ClassLoader classLoader, String name) {
String target = name.replace('.', '/');
try {
return new PathMatchingResourcePatternResolver(classLoader)
.getResources("classpath*:" + target + ".properties");
}
catch (Exception ex) {
return NO_RESOURCES;
}
}
}
}
從以上源碼可知:
Spring Boot 將 MessageSourceProperties 以組件的形式添加到容器中;
MessageSourceProperties 的屬性與配置文件中以“spring.messages”開頭的配置進行了綁定;
Spring Boot 從容器中獲取 MessageSourceProperties 組件,並從中讀取國際化資源文件的 basename(文件基本名)、encoding(編碼)等信息,將它們封裝到 ResourceBundleMessageSource 中;
Spring Boot 將 ResourceBundleMessageSource 以組件的形式添加到容器中,進而實現對國際化資源文件的管理。
查看 MessageSourceProperties 類,其代碼如下。
/* * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package org.springframework.boot.autoconfigure.context;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.convert.DurationUnit;
/** * Configuration properties for Message Source. * * @author Stephane Nicoll * @author Kedar Joshi * @since 2.0.0 */
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. */
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;
public String getBasename() {
return this.basename;
}
public void setBasename(String basename) {
this.basename = basename;
}
public Charset getEncoding() {
return this.encoding;
}
public void setEncoding(Charset encoding) {
this.encoding = encoding;
}
public Duration getCacheDuration() {
return this.cacheDuration;
}
public void setCacheDuration(Duration cacheDuration) {
this.cacheDuration = cacheDuration;
}
public boolean isFallbackToSystemLocale() {
return this.fallbackToSystemLocale;
}
public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
this.fallbackToSystemLocale = fallbackToSystemLocale;
}
public boolean isAlwaysUseMessageFormat() {
return this.alwaysUseMessageFormat;
}
public void setAlwaysUseMessageFormat(boolean alwaysUseMessageFormat) {
this.alwaysUseMessageFormat = alwaysUseMessageFormat;
}
public boolean isUseCodeAsDefaultMessage() {
return this.useCodeAsDefaultMessage;
}
public void setUseCodeAsDefaultMessage(boolean useCodeAsDefaultMessage) {
this.useCodeAsDefaultMessage = useCodeAsDefaultMessage;
}
}
通過以上代碼,我們可以得到以下 3 點信息:
MessageSourceProperties 為 basename、encoding 等屬性提供了默認值;
basename 錶示國際化資源文件的基本名,其默認取值為“message”,即 Spring Boot 默認會獲取類路徑下的 message.properties 以及 message_XXX.properties 作為國際化資源文件;
在 application.porperties/yml 等配置文件中,使用配置參數“spring.messages.basename”即可重新指定國際化資源文件的基本名。
通過以上源碼分析可知,Spring Boot 已經對國際化資源文件的管理提供了默認自動配置,我們這裏只需要在 Spring Boot 全局配置文件中,使用配置參數“spring.messages.basename”指定我們自定義的國際資源文件的基本名即可,代碼如下(當指定多個資源文件時,用逗號分隔)。
3. 獲取國際化內容
由於頁面使用的是 Tymeleaf 模板引擎,因此我們可以通過錶達式 #{…} 獲取國際化內容。
但是這裏需要注意的是,訪問路徑中的login.html是否存在,我直接再將其放在了config下的webConfig文件中進行訪問,當然也可以隨便命名
區域信息解析器自動配置
Spring MVC 進行國際化時有 2 個十分重要的對象:
Locale:區域信息對象
LocaleResolver:區域信息解析器,容器中的組件,負責獲取區域信息對象
我們可以通過以上兩個對象對區域信息的切換,以達到切換語言的目的。
Spring Boot 在 WebMvcAutoConfiguration 中為區域信息解析器(LocaleResolver)進行了自動配置,源碼如下
@Override
@Bean
@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
public LocaleResolver localeResolver() {
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.webProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.webProperties.getLocale());
return localeResolver;
}
從以上源碼可知:
該方法默認向容器中添加了一個區域信息解析器(LocaleResolver)組件,它會根據請求頭中攜帶的“Accept-Language”參數,獲取相應區域信息(Locale)對象。
該方法上使用了 @ConditionalOnMissingBean 注解,其參數 name 的取值為 localeResolver(與該方法注入到容器中的組件名稱一致),該注解的含義為:當容器中不存在名稱為 localResolver 組件時,該方法才會生效。換句話說,當我們手動向容器中添加一個名為“localeResolver”的組件時,Spring Boot 自動配置的區域信息解析器會失效,而我們定義的區域信息解析器則會生效
AcceptHeaderLocaleResolver中的源碼如下
/* * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package org.springframework.web.servlet.i18n;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
/** * {@link LocaleResolver} implementation that simply uses the primary locale * specified in the "accept-language" header of the HTTP request (that is, * the locale sent by the client browser, normally that of the client's OS). * * <p>Note: Does not support {@code setLocale}, since the accept header * can only be changed through changing the client's locale settings. * * @author Juergen Hoeller * @author Rossen Stoyanchev * @since 27.02.2003 * @see javax.servlet.http.HttpServletRequest#getLocale() */
public class AcceptHeaderLocaleResolver implements LocaleResolver {
private final List<Locale> supportedLocales = new ArrayList<>(4);
@Nullable
private Locale defaultLocale;
/** * Configure supported locales to check against the requested locales * determined via {@link HttpServletRequest#getLocales()}. If this is not * configured then {@link HttpServletRequest#getLocale()} is used instead. * @param locales the supported locales * @since 4.3 */
public void setSupportedLocales(List<Locale> locales) {
this.supportedLocales.clear();
this.supportedLocales.addAll(locales);
}
/** * Return the configured list of supported locales. * @since 4.3 */
public List<Locale> getSupportedLocales() {
return this.supportedLocales;
}
/** * Configure a fixed default locale to fall back on if the request does not * have an "Accept-Language" header. * <p>By default this is not set in which case when there is no "Accept-Language" * header, the default locale for the server is used as defined in * {@link HttpServletRequest#getLocale()}. * @param defaultLocale the default locale to use * @since 4.3 */
public void setDefaultLocale(@Nullable Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
/** * The configured default locale, if any. * <p>This method may be overridden in subclasses. * @since 4.3 */
@Nullable
public Locale getDefaultLocale() {
return this.defaultLocale;
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
@Nullable
private Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) {
Enumeration<Locale> requestLocales = request.getLocales();
Locale languageMatch = null;
while (requestLocales.hasMoreElements()) {
Locale locale = requestLocales.nextElement();
if (supportedLocales.contains(locale)) {
if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
// Full match: language + country, possibly narrowed from earlier language-only match
return locale;
}
}
else if (languageMatch == null) {
// Let's try to find a language-only match as a fallback
for (Locale candidate : supportedLocales) {
if (!StringUtils.hasLength(candidate.getCountry()) &&
candidate.getLanguage().equals(locale.getLanguage())) {
languageMatch = candidate;
break;
}
}
}
}
return languageMatch;
}
@Override
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
throw new UnsupportedOperationException(
"Cannot change HTTP accept header - use a different locale resolution strategy");
}
}
手動切換語言
(1)、修改 login.html 切換語言鏈接,在請求中攜帶國際化區域信息,代碼如下。
(2)、模仿AcceptHeaderLocaleResolve寫配置文件,命名為MyLocalResolver,實現接口LocaleResolver並重寫LocaleResolver中的方法
package com.ma.ml.config;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocalResolver implements LocaleResolver {
/** * Resolve the current locale via the given request. * Can return a default locale as fallback in any case. * * @param request the request to resolve the locale for * @return the current locale (never {@code null}) */
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l"); //獲得請求路徑中的參數
Locale locale = Locale.getDefault(); //獲取默認的請求,如果沒有我們自己配置的國際化設置,就使用默認spring默認的
if(!StringUtils.isEmpty(language)){
//若請求的鏈接中含有國家化的參數
//zh_CN
String [] strings = language.split("_");
locale = new Locale(strings[0],strings[1]); //重新修改默認值
}
return locale;
}
/** * Set the current locale to the given one. * * @param request the request to be used for locale modification * @param response the response to be used for locale modification * @param locale the new locale, or {@code null} to clear the locale * @throws UnsupportedOperationException if the LocaleResolver * implementation does not support dynamic changing of the locale */
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
(3)、將其像下面一樣使用@Bean注册到spring Mvc中
4、啟動項目即可正常切換訪問
边栏推荐
- LeetCode 0710.黑名单中的随机数 - 预处理实现O(1)取值
- SQL 函数
- 安装 新版本cmake & swig & tinyspline
- Logical English structure [key points]
- mysql学习总结
- npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead. npm ER
- Redis notes (15) - Pipeline (the client packages and sends batch commands to save network overhead)
- Redis notes (12) - single thread architecture (non blocking IO, multiplexing) and multiple asynchronous threads
- Various errors encountered by tensorflow
- Does the go compiled executable have dynamic library links?
猜你喜欢
做测试需要知道的内容——url、弱网、接口、自动化、
Deep learning (tentsorflow2. version) three good student performance problems (1)
工企专利匹配数据(数十万数据量)1998-2014年
online trajectory generation
Druid data source for background monitoring
Detailed explanation of the network security competition questions (2) of the 2021 national vocational college skills competition (secondary vocational group)
MapReduce&Yarn理论
QPM suspended window setting information
logback
力扣------从数组中移除最大值和最小值
随机推荐
Automated testing -- Introduction and use of pytest itself and third-party modules
Opencv depthframe - > pointcloud causes segmentation fault!
druid数据源实现后台监控
定制拦截器
测试须知——常见接口协议解析
Code statistics tools cloc and SCC
2021-11-12 vrep视觉传感器配置
同花顺炒股软件安全性怎样?在同花顺怎么开户
TensorFlow遇到的各种错误
jz2440---使用uboot燒錄程序
Jz2440--- using uboot burning program
2021年全国职业院校技能大赛(中职组)网络安全竞赛试题(1)详细解析教程
2021 national vocational college skills competition (secondary vocational group) network security competition questions (1) detailed analysis tutorial
Automated testing -- on the coexistence of Unitest and pytest initialization
mysql 数据库字段查询区分大小写设置
js---获取对象数组中key值相同的数据,得到一个新的数组
力扣------从数组中移除最大值和最小值
DAY 3 数组,前置后置,字符空间,关键词和地址指针
Enter the page input box to automatically obtain the focus
Test instructions - common interface protocol analysis