【Springboot採坑日記】Springboot+thymeleaf整合i18n的配置過程及注意事項
【Springboot採坑日記】Springboot+thymeleaf整合i18n過程及注意事項
Springboot+thymeleaf+i18n的配置方式
thymeleaf整合
匯入thymeleaf依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
配置thymeleaf的yml檔案
spring:
thymeleaf:
cache: false # 將thymeleaf快取關閉
encoding: UTF-8
然後就可以將值傳入model中,由前端html直接展示
Controller:
@Controller
public class TestController {
@RequestMapping("test")
public String indexHandler(Model model, HttpServletRequest request, HttpSession session){
model.addAttribute("welcome","hello world");
model.addAttribute("student",new Test("zhangsan",80));
model.addAttribute("gender","male");
List<Test> students = new ArrayList<>();
students.add(new Test("張三",13));
students.add(new Test("李四",25));
students.add(new Test("趙三",60));
model.addAttribute("students",students);
Map<String,Object> map = new HashMap<>();
map.put("stu7",new Test("田七",27));
map.put("stu8",new Test("劉八",28));
map.put("stu9",new Test("鄭九",29));
model.addAttribute("map",map);
model.addAttribute("attrName","score");
model.addAttribute("attrValue",99);
model.addAttribute("welcome1","<h2>Thymeleaf,<br>I'm learning.</h2>");
model.addAttribute("photo","email.png");
model.addAttribute("elementId","reddiv");
model.addAttribute("bgColor","red");
model.addAttribute("isClose",false);
model.addAttribute("school",null);
List<String> cities = new ArrayList<>();
model.addAttribute("cities",cities);
request.setAttribute("req","reqValue");
session.setAttribute("ses","sesValue");
session.getServletContext().setAttribute("app","appValue");
int[] nums = {1,2,3,4,5,6};
model.addAttribute("nums",nums);
model.addAttribute("today",new Date());
model.addAttribute("cardId","684515202008258475");
//這裡test表示thymeleaf所對應的test.html,不用寫副檔名
return "test";
}
}
HTML
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello world</title>
</head>
<!--請求路徑:http://localhost:8888/test?name=zhangsan-->
<body>
<!--展示文字 類似jsp ${}-->
<p th:text="${welcome}">這裡將顯示資料,但這些文字不顯示</p>
<div th:text="${welcome}">這裡將顯示資料,但這些文字不顯示</div>
<span th:text="${welcome}">這裡將顯示資料,但這些文字不顯示</span>
<hr>
<!--展示類-->
<p th:text="${student}" id="student">aaaa</p>
<p th:text="${student.name}">vvvv</p>
<p th:text="${student.age}">cccc</p>
<hr>
<p th:text="*{student}">aaaa</p>
<p th:text="*{student.name}">vvvv</p>
<p th:text="*{student.age}">cccc</p>
<hr>
<div th:object="${student}">
<p th:text="*{name}">vvvv</p>
<p th:text="*{age}">cccc</p>
</div>
<hr>
<!--頁面跳轉-->
<a th:href="@{'http://localhost:8888/find/' + ${student.age}}">查詢</a>
<a th:href="@{|http://localhost:8888/find/${student.age}|}">查詢2</a>
<a th:href="@{|/find/${student.age}|}">查詢3,絕對路徑</a>
<a th:href="@{|../find/${student.age}|}">查詢4,相對路徑</a>
<a th:href="@{|/static/img/email.png|}">跳轉靜態資源</a>
<hr>
<!--分支查詢,類似jsp <c:if/>-->
<p th:if="${gender == 'male'}">
男
</p>
<p th:if="${gender != 'male'}">
女
</p>
<hr>
<div th:switch="${student.age/10}">
<p th:case="0">兒童</p>
<p th:case="1">少年</p>
<p th:case="2">青年</p>
<p th:case="3">中年</p>
<p th:case="4">青年</p>
<p th:case="*">老年</p>
</div>
<hr>
<!--迴圈查詢,遍歷list,類似jsp <c:for:each>-->
<p th:each="stu, xxx : ${students}">
<!-- 顯示當前遍歷物件是第幾個(從1開始計數)-->
<span th:text="${xxx.count}"></span>
<!-- 顯示當前遍歷物件的索引(從1開始計數)-->
<span th:text="${xxx.index}"></span>
<!-- boolean值,判斷當前遍歷物件是不是第一個-->
<span th:text="${xxx.first}"></span>
<!-- boolean值,判斷當前遍歷物件是不是最後一個-->
<span th:text="${xxx.last}"></span>
<!-- boolean值,判斷當前遍歷物件是不是偶數-->
<span th:text="${xxx.even}"></span>
<!-- boolean值,判斷當前遍歷物件是不是奇數-->
<span th:text="${xxx.odd}"></span>
<!-- 獲取屬性-->
<span th:text="${stu.name}"></span>
<span th:text="${stu.age}"></span>
</p>
<hr>
<p th:each="stu : ${students}">
<!-- 顯示當前遍歷物件是第幾個(從1開始計數)-->
<span th:text="${stuStat.count}"></span>
<!-- 顯示當前遍歷物件的索引(從1開始計數)-->
<span th:text="${stuStat.index}"></span>
<!-- boolean值,判斷當前遍歷物件是不是第一個-->
<span th:text="${stuStat.first}"></span>
<!-- boolean值,判斷當前遍歷物件是不是最後一個-->
<span th:text="${stuStat.last}"></span>
<!-- boolean值,判斷當前遍歷物件是不是偶數-->
<span th:text="${stuStat.even}"></span>
<!-- boolean值,判斷當前遍歷物件是不是奇數-->
<span th:text="${stuStat.odd}"></span>
<!-- 獲取屬性-->
<span th:text="${stu.name}"></span>
<span th:text="${stu.age}"></span>
</p>
<hr>
<!--迴圈遍歷,遍歷map-->
<div th:each="entry: ${map}">
<p th:text="${entryStat.count}"></p>
<!--?表示不為空-->
<p th:text="${entry?.key}"></p>
<p th:text="${entry.value}"></p>
<p th:text="${entry.value.name}"></p>
<p th:text="${entry.value.age}"></p>
</div>
<hr>
<!--html 相關的屬性使用-->
<div th:text="${welcome1}"></div>
<!--utext會解析動態標籤-->
<div th:utext="${welcome1}"></div>
<hr>
<input th:type="text" name="age" value="0">
<input type="text" th:name="${attrName}" th:value="${attrValue}">
<img src="/img/key.png">
<img th:src="|/img/${photo}|">
<hr>
<!--css-->
<!--內聯-->
th:inline的取值可以有四種:<br>
1)text: 標籤體中需要嵌入動態內容,預設值
2)javascript:js中需要嵌入動態內容
3)css:css中需要嵌入動態內容
4)none:不解析內嵌動態內容
<p th:inline="text">
他的姓名是:[[${student.name}]]
</p>
<hr>
<p>
她的姓名是:[[${student.name}]]
</p>
<hr>
<!--內嵌指令碼-->
<script th:inline="javascript" type="text/javascript">
alert([[${student.name}]]);
</script>
<hr>
<!--內嵌CSS-->
<div id="reddiv">
我的背景顏色為紅色
</div>
<style>
#[[${elementId}]] {
width: 500px;
height: 100px;
background: [[${bgColor}]];
}
</style>
<hr>
Thymeleaf 包含四種字面常量:文字、數字、布林值,及null<br>
<div>
我愛你,<span th:text="中國"></span>
</div>
<div>
3.14+6 = <span th:text="${3.14+6}"></span>
</div>
<div>
Thymeleaf中的boolean常量為:true,false,TRUE,FALSE,True,False(不區分大小寫)
<span th:if="${isClose == false}">
歡迎光臨
</span>
<span th:if="${isClose} == false">
歡迎光臨2
</span>
</div>
<div>
關於null值需要注意:<br>
1)若物件未定義,其值為null<br>
2)若物件定義了,但其值被指定為null,則值為null<br>
3)若集合已經被定義,不為(null),擔其長度為0,此時的集合是不為null<br>
school: <span th:if="${school} == null">物件值為null</span><br>
cities: <span th:if="${cities} != null">集合不為空</span><br>
country: <span th:if="${country} == null">物件未定義</span><br>
</div>
<div th:text="|我的姓名是:${student.name}|"></div>
<hr>
<!--API Sevlet內建物件-->
req = <div th:text="${#request.getAttribute('req')}"></div>
ses = <div th:text="${#session.getAttribute('ses')}"></div>
app = <div th:text="${#servletContext.getAttribute('app')}"></div>
contextPath = <div th:text="${#request.getContextPath()}"></div>
params = <div th:text="${#request.getParameter('name')}"></div>
<hr>
<!--thymeleaf內建物件-->
<div th:text="${#aggregates.sum(nums)}"></div>
<div th:text="${today}"></div>
<!--日期格式化-->
<div th:text="${#dates.format(today,'yyyy-MM-dd')}"></div>
<!--字串擷取-->
<div th:text="${#strings.substring(cardId,6,14)}"></div>
<!--全域性變數-->
version:<p th:text="${version}"></p>
</body>
</html>
Springboot+i18n整合
由於Springboot本身就整合了核心包core,因此可以直接通過定義解析器的形式完成i18n的配置
解析器定義方法如下:
@Configuration
@ComponentScan
public class I18nConfig extends AbstractLocaleContextResolver {
public static final String LOCALE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName()+".LOCALE";
public static final String TIME_ZONE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".TIME_ZONE";
@Value("${spring.messages.basename}")
public String[] basefilenames;
@Bean(name = "localeResolver")
public LocaleResolver localeResolverBean(){
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
//設定解析器的預設語言
sessionLocaleResolver.setDefaultLocale(CommConsts.I18N_CONFIG);
return sessionLocaleResolver;
}
@Bean(name = "messageSource")
public ResourceBundleMessageSource resourceBundleMessageSource(){
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
if(basefilenames != null ){
for (int i = 0; i < basefilenames.length; i++){
String basename = basefilenames[i];
Assert.hasText(basename,"Basename must not be empty");
this.basefilenames[i] = basename.trim();
}
source.setBasenames(basefilenames);
}
source.setDefaultEncoding("UTF-8");
source.setUseCodeAsDefaultMessage(true);
return source;
}
public void setLocale(HttpServletRequest request,HttpServletResponse response, Locale locale){
this.setLocaleContext(request,response,locale != null ? new SimpleLocaleContext(locale) : null);
}
@Override
public LocaleContext resolveLocaleContext(HttpServletRequest httpServletRequest) {
return null;
}
@Override
public void setLocaleContext(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, LocaleContext localeContext) {
Locale locale = null;
TimeZone timeZone = null;
if(localeContext != null){
locale = localeContext.getLocale();
if(localeContext instanceof TimeZoneAwareLocaleContext){
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
}
WebUtils.setSessionAttribute(httpServletRequest,LOCALE_SESSION_ATTRIBUTE_NAME,locale);
WebUtils.setSessionAttribute(httpServletRequest,TIME_ZONE_SESSION_ATTRIBUTE_NAME,timeZone);
}
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
Locale locale = CommConsts.I18N_CONFIG;
{
String temp = httpServletRequest.getParameter("language");
if(StringUtils.isNotEmpty(temp)){
locale = new Locale(temp);
return locale;
}
}
Cookie[] cookies = httpServletRequest.getCookies();
if(cookies != null){
for(Cookie cookie : cookies){
if(cookie.getName().equals("LONGi_Language")){
String temp = cookie.getValue();
if(StringUtils.isNotEmpty(temp)){
locale = new Locale(temp);
}
continue;
}
}
}
return locale;
}
}
該配置類中:
localeResolverBean:用於每次解析bean時對解析器配置語言
ResourceBundleMessageSource :用於定義本地要掃描的i18n的包
setLocaleContext:將i18n的配置資訊繫結request
resolveLocale:是介面LocaleResolver的方法的重寫,動態繫結cookie中,實現屆時切換
CommConsts.I18N_CONFIG:自己定義的locale常量,大家可以根據自己需求制定
如:
public static final Locale I18N_CONFIG = Locale.ROOT;
其中basenames的配置在yaml檔案中
spring:
#i18n
messages:
basename: i18n/comm,i18n/customer,i18n/homepage,i18n/login,i18n/meter,i18n/prepay,i18n/system
最後,把解析器作為攔截器放入攔截器配置中
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor(){
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("language");
return lci;
}
然後,再建立Resource生成相應的i18n目錄即可
後臺有的時候需要呼叫i18n,因此需要一個MessageUtils類進行獲取
package top.powersys.system.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.text.MessageFormat;
import java.util.Locale;
@Component
@Slf4j
public class MessageUtils {
private static MessageSource messageSource;
public MessageUtils(MessageSource messageSource){
MessageUtils.messageSource = messageSource;
}
/**
* 獲取單個國際化翻譯值
* @param msgkey
* @param defaultMsg
* @return
*/
public static String get(String msgkey,String defaultMsg){
try {
return messageSource.getMessage(msgkey,null, LocaleContextHolder.getLocale());
} catch (NoSuchMessageException e) {
log.error(e.getMessage(),e);
return defaultMsg;
}
}
/**
* 獲取多個引數取代翻譯
* @param msgKey
* @param defaultMsg
* @param arg
* @return
*/
public static String get(String msgKey,String defaultMsg,Object... arg){
try {
msgKey = messageSource.getMessage(msgKey, arg, LocaleContextHolder.getLocale());
return msgKey;
} catch (NoSuchMessageException e) {
log.error(e.getMessage(),e);
return MessageFormat.format(defaultMsg, arg);
}
}
/**
* 指定語言獲得單個國際化翻譯
* @param msgKey
* @param defaultMsg
* @param language
* @return
*/
public static String getByLanguage(String msgKey,String defaultMsg,String language){
try {
Locale locale = new Locale(language);
msgKey = messageSource.getMessage(msgKey,null,locale);
return msgKey;
} catch (NoSuchMessageException e) {
log.error(e.getMessage(),e);
return defaultMsg;
}
}
public static String getByLanguage(String msgKey,String defaultMsg,String language,Object... arg){
try {
Locale locale = new Locale(language);
msgKey = messageSource.getMessage(msgKey,arg,locale);
return msgKey;
} catch (NoSuchMessageException e) {
log.error(e.getMessage(),e);
return MessageFormat.format(defaultMsg,arg);
}
}
}
上述配置完成後,只需要在i18n中寫入相應的值,即可得到其對應的i18n內容
如:
並且在前端引入
i18n test:
<cite th:text="#{test}"></cite>
<p th:text="#{test}"></p>
前端展示結果為
如果是對於後臺配置,如返回碼,如:
ErrorCode碼中改為
USERNAME_OR_PASSWORD_ERROR(1001, MessageUtils.get("error_code.user_pwd_wrong",null))
即可以配置成功,至於result的封裝方法,大家可以參考網路上各位大佬的方式。
I18n的配置這裡是結合了
springboot i18n國際化後臺多種語言設定的方式
和
springboot+thymeleaf+i18n
兩位博主的配置方式後,筆者根據實際情況做了改進以後的配置結果。
需要注意的是
1、在html引用的時候,這種配置方式是在載入的時候將yml下所有的包都作為basename引入,如上述程式碼可知,伺服器一啟動載入,就會對messageSource初始化,執行resourceBundleMessageSource方法,然後迴圈對各bundle配置,所以不會區分是哪一個bundle,所以配置的時候,最好寫成xx.xx的形式。
2、在實際操作的過程中,MessageUtils的defaultMsg一般無值,不寫成null,也可以賦一個""表示空,在後端獲取全員配置的時候,也可以自己根據實際情況選擇字符集,但生產中基本用不到,因為基本都是全伺服器實現一種國際化。
3、完成配置返回的時候,要注意#{xxx}的寫法中,xxx表示的是i18n的xxx.properties具體的key,而不是檔名.key的格式,這裡即便生成目錄的時候就生成i18n/aaa/bbb/message的形式,寫html還是要寫成#{xxx}形式,而不是#{aaa.bbb.message.xxx}的形式,不管寫幾層目錄,配置的位置都只能在yml檔案中。這個是受限於messageSource的載入順序,resourceBundleMessageSource剛開始的初始化全部載入,後續使用並不會重新初始化messageSource,也不會在每次request連線來臨時再去指定指定bundle下的key,所以上述的配置不能指定指定的bundle,這點和jsp的配置不同,解決辦法有兩種:
1)每次獲取request連線的時候,再去指定bundle,要求每次連線都要指定bundle(不推薦,浪費資源,而且thymeleaf官方也沒有推薦)
2)一次性將所有的basenames載入完後,在寫bundle的時候,規範化寫,比如aaa.bbb的形式,避免key值重複
4、在用springboot做快速化開發的過程中,可以用assert代替throw new exception的形式,然後通過@ControllerAdivce中一起捕獲一起甩出,這裡就是採用了這種斷言的方法代替傳統的異常丟擲。
相關文章
- 儲存過程注意事項儲存過程
- Linux原始碼包安裝過程及注意事項Linux原始碼
- job呼叫儲存過程的注意事項儲存過程
- SVN安裝配置及安全注意事項
- 編寫儲存過程基本注意事項儲存過程
- 教程:MySQL 8安裝與配置及注意事項MySql
- JDK1.7安裝和配置及注意事項JDK
- AIX配置NFS注意事項AINFS
- 儲存過程基本語法和須注意事項儲存過程
- 伺服器配置的注意事項伺服器
- struts2 + urlrewrite 整合注意事項
- TokuDB的安裝及注意事項
- @Transactional spring 配置事務 注意事項Spring
- DUBBO安裝配置注意事項
- Linux zabbix 配置注意事項Linux
- 筆記:MMM注意事項筆記
- 搭建 nuget 私服及注意事項
- Guava HashMultimap使用及注意事項Guava
- [爬坑日誌1]寫查詢的mysql一些小注意事項MySql
- SpringBoot最佳化之——1.Thymeleaf 配置等注意事項Spring Boot
- Python eval的用法及注意事項Python
- 原始碼加密軟體採購時的注意事項!原始碼加密
- MySQL 8.0.20 MGR資料遷移過程以及注意事項MySql
- oracle rac安裝配置注意事項Oracle
- AWS雲建立EC2與使用注意事項-踩坑記錄
- Angular 14 inject 函式使用過程中的一些注意事項Angular函式
- 介面開發文件及注意事項
- mysql索引使用技巧及注意事項MySql索引
- MySQL常用語句及注意事項MySql
- oracle hash join原理及注意事項Oracle
- Spring+SpringMvc+Mybatis整合注意事項SpringMVCMyBatis
- 開發小程式過程中採坑
- SpringBoot整合任務排程框架Quartz及持久化配置Spring Boot框架quartz持久化
- 開發及上線中的注意事項
- Oracle Outline的使用及注意事項Oracle
- Tars | Win10下Docker部署TarsJava(SpringBoot)全過程及踩坑記錄Win10DockerJavaSpring Boot
- 【STARTUP】Oracle資料庫啟動過程中引數檔案的使用的順序及注意事項Oracle資料庫
- 測試過程中的評審工作及關注事項