web项目 crud 二 国际化
- https://blog.****.net/wangmx1993328/article/details/81190830
- 国际化主要用于应用跨国时使用,比如在中国时应用为中文,在韩国时,应用显示为韩文,在日本时,应用又显示为日文等。又或者是用户点击什么语言就可以自动切换什么语言
- 下面以项目的首页为例进行说明
准备国际化文件
- 编写国际化配置文件,抽取页面需要显示的国际化消息
- 在类路径下新建一个目录“i18n”(目录名字随意)
- 然后在“i18n”目录下新建 “index.properties”、“index_en_US.properties”、“index_zh_CN.properties” 三个属性文件
- “index.properties”:存放默认的语言,名称通常与页面名称一致
- “index_en_US.properties”:存放英文,en表示语言(英文)缩写,US表示国家(美国)缩写,请按规范格式写即可
- “index_zh_CN.properties”:存放中文,zh表示语言(中文)缩写,CN表示国家(中国)缩写,请按规范格式写即可
- 当然还可以写更多的语言文件,当它们的命名符合规范时,IDEA会自动将它们绑定到一起,这样在后面输入值的时候会很方便
index.properties
login_button=登入
login_name=用户名
login_password=密码
login_remember=记住我
login_tip=请登入
index_en_US.properties
login_button=Sign in
login_name=Username
login_password=Password
login_remember=Remember Me
login_tip=Please Sign In
index_zh_CN.properties
login_button=登入
login_name=用户名
login_password=密码
login_remember=记住我
login_tip=请登入
- 为了防止properties资源文件中文乱码,请勾选如下IDEA设置
Spring Boot 自动配置国际化原理
- 以前使用 Spring MVC 来做国际化的步骤如下:
- 编写国际化配置文件,这一步 Spring Boot 同样要做,上面已经做了
- 使用 ResourceBundleMessageSource API管理国际化资源文件,Spring Boot帮我们做了
- 在页面使用 fmt:message 取出国际化内容,Spring Boot 因为这里使用 Thymeleaf 模板引擎,所以会用 Thymeleaf 进行取值
- Spring Boot 现在自动配置好了管理国际化资源文件的组件 MessageSourceAutoConfiguration,可以看出底层仍然使用的是 ResourceBundleMessageSource;
package org.springframework.boot.autoconfigure.context;
@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, 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 = messageSourceProperties();
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;
}
......
- 如上所示在它的 MessageSourceProperties 可以看到可以配置的属性,同理可以直接在官网进行查看,可以在全局配置文件中覆盖其默认配置,官方文档查看:https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/”
# INTERNATIONALIZATION (MessageSourceProperties)
spring.messages.always-use-message-format=false # Whether to always apply the MessageFormat rules, parsing even messages without arguments.
spring.messages.basename=messages # Comma-separated list of basenames (essentially a fully-qualified classpath location), each following the ResourceBundle convention with relaxed support for slash based locations.
spring.messages.cache-duration= # Loaded resource bundle files cache duration. When not set, bundles are cached forever. If a duration suffix is not specified, seconds will be used.
spring.messages.encoding=UTF-8 # Message bundles encoding.
spring.messages.fallback-to-system-locale=true # Whether to fall back to the system Locale if no files for a specific Locale have been found.
spring.messages.use-code-as-default-message=false # Whether to use the message code as the default message instead of throwing a "NoSuchMessageException". Recommended during development only.
- 如上所示,spring.messages.basename=messages,就是配置国际化资源文件的基本名路径,默认是“message”,意思是如果我们没有在全局配置文件中覆盖此配置项的话,它默认会扫描类路径下的“messages.properties”、"messages_en_US.properties"、"messages_zh_CN.properties"等格式的文件作为国际化文件
- 因为应用会有多个页面,就会有多套资源文件,这时 spring.messages.basename 的值用逗号分隔即可
下面重新修改之前的index.html页面,为它获取国际化值
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="asserts/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{asserts/css/signin.css} " rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login_tip}">Please sign in</h1>
<label class="sr-only">Username</label>
<input type="text" class="form-control" th:placeholder="#{login_name}" required="" autofocus="" >
<label class="sr-only">Password</label>
<input type="password" class="form-control" th:placeholder="#{login_password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" th:value="#{login_remember}"> [[#{login_remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login_button}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/(l=zh_CN)}">中文</a>
<a class="btn btn-sm" th:href="@{/(l=en_US)}">English</a>
</form>
</body>
</html>
- th:text="#{xxx}":获取国际化文件中的xxx属性值,然后替换标签原来的文本默认值
- th:placeholder="#{xxx}":获取国际化文件中的xxx属性值,然后替换标签placeholder属性原来的默认值,同理可以替换任意属性的默认值
- [[#{login_remember}]]:对于 <input xxx />这种自封闭标签得用 Thymeleaf的行内表达式取值
- 效果:根据浏览器语言设置的信息切换了国际化;
指定语言切换
- 上面是应用根据跟随浏览器语音自动进行语音切换,现在需要改成用户“点击中文”则切换中文,“点击英文”则切换英文。
- 原理是需要使用到 “国际化Locale(区域信息对象)”与 “ LocaleResolver(获取区域信息对象)”两个API
切换原理
- 应用国际化默认之所以能随着浏览器的语言变化而变化,是因为浏览器Http请求时,会在请求头中携带语言信息,这是后台国际化变换的依据。
- 第一部分中的 “自动语言切换” 之所以能用,是因为在 WebMvcAutoConfiguration 配置类中自动配置好了一个 区域信息解析器组件,如下所示逻辑是如果用户没有自己提供 区域信息解析器 “LocaleResolver”,那么它默认会使用一个叫 “AcceptHeaderLocaleResolver” 进行解析
- 所以我们只需要自己提供一个 “LocaleResolver” 组件覆盖默认的即可
package org.springframework.boot.autoconfigure.web.servlet; ..... @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { ...... @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { if (this.mvcProperties .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } ....... }
index.html 页面修改
- 修改一下首页的“中文”、“英文”按钮,使用 Thymeleaf 的 @{xxx} 取值是会默认带上应用上下文的
- @{/} 的效果就是 http://localhost:8080/tiger/
- @{/(l=zh_CN)} 相当于 @{/?zh_CN},只是 Thymeleaf 不再使用 "?" 的方式为get请求路径带参数,而是使用"()"的方式
- 页面上点击“中文”后,会跳转:http://localhost:8080/tiger/?l=zh_CN
- 页面上点击“英文”后,会跳转:http://localhost:8080/tiger/?l=en_US
<div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"/> [[#{login_remember}]] </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login_button}">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2018</p> <a class="btn btn-sm" th:href="@{/(l=zh_CN)}">中文</a> <a class="btn btn-sm" th:href="@{/(l=en_US)}">English</a> </form> </body> </html>
自定义区域信息解析器
/**
* Copyright (C), 2015-2019, XXX有限公司
* FileName: MyLocaleResolve
* Author: Administrator
* Date: 2019/2/26 19:28
* Description:
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package com.xuxu.component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
* 〈一句话功能简述〉<br>
* 〈〉
*
* @author Administrator
* @create 2019/2/26
* @since 1.0.0
*/
public class MyLocaleResolve implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
/**
* 如果请求地址中含有l参数,如 http://localhost:8080/tiger/?l=en_US,则设置新的区域对象
* 否则使用默认的区域对象,默认的区域对象不会使国际化再随着浏览器语言的变化而变化的
*/
Locale locale = Locale.getDefault();
String l = request.getParameter("l");
if (!StringUtils.isEmpty(l)) {
String[] ls = l.split("_");
locale = new Locale(ls[0], ls[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
添加组件
package com.lct.config;
import com.lct.component.MyLocaleResolve;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
/**
* Created by Administrator on 2018/7/28 0028.
* 自定义配置类
*/
@Configuration
public class MyMvcConfig {
/**
* 将我们自己的 LocaleResolver 组件交于 Spring 容器管理
* 从而覆盖 Spring Boot默认的区域信息解析器
*
* @return
*/
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolve();
}
}
运行测试
- 和预期的完全一致,因为请求路径中携带了"l"参数,所以不管浏览器 语言 选择的是什么,都不会影响
- 当请求地址中不带 “l” 参数时,此时后台使用的是默认的 Locale,此时即使浏览器切换了语言,同样国际化也不会生效的