【Springboot采坑日记】Springboot+thymeleaf集成i18n的配置过程及注意事项

【Springboot采坑日记】Springboot+thymeleaf集成i18n过程及注意事项

  • Springboot+thymeleaf+i18n的配置方式
    • thymeleaf集成
    • Springboot+i18n集成
    • 需要注意的是

Springboot+thymeleaf+i18n的配置方式

thymeleaf集成

导入thymeleaf依赖:

		<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
            <version>2.3.3.RELEASEversion>
        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","

Thymeleaf,
I'm learning.

"
); 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


<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello worldtitle>
head>


<body>

    <p th:text="${welcome}">这里将显示数据,但这些文字不显示p>
    <div th:text="${welcome}">这里将显示数据,但这些文字不显示div>
    <span th:text="${welcome}">这里将显示数据,但这些文字不显示span>
    <hr>


    <p th:text="${student}" id="student">aaaap>
    <p th:text="${student.name}">vvvvp>
    <p th:text="${student.age}">ccccp>
    <hr>

    <p th:text="*{student}">aaaap>
    <p th:text="*{student.name}">vvvvp>
    <p th:text="*{student.age}">ccccp>
    <hr>
    <div th:object="${student}">
        <p th:text="*{name}">vvvvp>
        <p th:text="*{age}">ccccp>
    div>
    <hr>


    <a th:href="@{
      'http://localhost:8888/find/' + ${student.age}}">查询a>
    <a th:href="@{|http://localhost:8888/find/${student.age}|}">查询2a>
    <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>


    <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>


    <p th:each="stu, xxx : ${students}">
        
        <span th:text="${xxx.count}">span>
        
        <span th:text="${xxx.index}">span>
        
        <span th:text="${xxx.first}">span>
        
        <span th:text="${xxx.last}">span>
        
        <span th:text="${xxx.even}">span>
        
        <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}">
    
    <span th:text="${stuStat.count}">span>
    
    <span th:text="${stuStat.index}">span>
    
    <span th:text="${stuStat.first}">span>
    
    <span th:text="${stuStat.last}">span>
    
    <span th:text="${stuStat.even}">span>
    
    <span th:text="${stuStat.odd}">span>
    
    <span th:text="${stu.name}">span>
    <span th:text="${stu.age}">span>
p>
<hr>


<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>


<div th:text="${welcome1}">div>

<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>


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>

<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">对象值为nullspan><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>


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>

<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目录即可
【Springboot采坑日记】Springboot+thymeleaf集成i18n的配置过程及注意事项_第1张图片
后台有的时候需要调用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内容
如:
【Springboot采坑日记】Springboot+thymeleaf集成i18n的配置过程及注意事项_第2张图片
并且在前端引入

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中一起捕获一起甩出,这里就是采用了这种断言的方法代替传统的异常抛出。

你可能感兴趣的:(springboot采坑日记,java,html,spring,boot,前端)