当前位置:网站首页>國際化配置

國際化配置

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、啟動項目即可正常切換訪問

在這裏插入圖片描述
在這裏插入圖片描述

原网站

版权声明
本文为[馬可愛家的馬可愛]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/177/202206260915349218.html