SpringBoot项目配置国际化

该文章已生成可运行项目,

目录

1. 引言

2. 步骤

2.1 新建国际化文件夹(i18n)到resources目录下,分别添加对应的国际化文件配置

2.1.1 messages.properties

2.1.2 message_en_US.properties

2.1.3 yml配置文件新增

2.2 将前面博客中的自定义响应体、响应码枚举类、统一异常码中的message替换成引用格式, 而非具体的中文消息

2.2.1 国际化后的自定义响应体

2.2.2 国际化后的响应码枚举类

2.2.3 国际化后的统一异常码

2.3 实现LocaleResolver实现自定义区域解析器

2.4 实现WebMvcConfigure,把自定义区域解析器添加到容器中

2.5 通过消息工具类MessageUtils,根据前端language的参数实现消息语言切换

3. 测试

4. 更新预告--->通过自定义注解根据请求头注入用户会话信息

4.1 Controller

4.2 ServiceImpl

4.3 控制台


1. 引言

前面我们讲到了

SpringBoot项目自定义统一响应体设计_附上完整测试代码-CSDN博客

SpringBoot项目企业级全局异常处理-CSDN博客

今天我们来讲讲国际化。

Spring Boot 项目里谈“国际化”(Internationalization,简称 i18n),并不是再写一堆 if-else 判断语言,而是用 Spring 自带的 MessageSource + LocaleResolver 把“可变文案”全部抽离到外部资源文件,运行时根据请求自动切换语言。下面从“是什么、怎么做、好在哪”三个维度做一个系统的介绍。

先来示例:

根据前一篇文章中代码示例, 当订单id为空的时候会抛出异常:
中文显示为

请求头设置语言为英文时, 抛出异常为对应的英文:

2. 步骤

注:其余代码在引言提到的前两篇博客么么哒!

2.1 新建国际化文件夹(i18n)到resources目录下,分别添加对应的国际化文件配置

注意文件夹名不要错误必须是i18n,这个文件夹可以自动被springboot识别的,里面文件是K=V形式,点击Resource Bundle进行添加三语言消息配置,如果没有这个选项File -> Settings -> Plugins 搜索 Resource Bundle Editor安装如图所示:

分别为默认配置、英文配置、中文配置

2.1.1 messages.properties

message.success=成功
message.fail=失败
message.request.bad=请求错误
message.token.unauthorized=未授权
message.method.not.allow=请求方法不允许
message.enum.constant.systemError=系统错误
message.enum.invalidRequestMethod=无效的请求方法
message.enum.accessDenied=访问未授权
message.enum.userSessionNotFound=用户会话信息未找到
message.enum.common.error.code.order.id.is.empty = 订单id为空

2.1.2 message_en_US.properties

message.success=Success
message.fail=Fail
message.request.bad=The request is bad
message.token.unauthorized=Unauthorized
message.method.not.allow= The request method is not allowed
message.enum.constant.systemError=systemError
message.enum.invalidRequestMethod=invalidRequestMethod
message.enum.accessDenied=accessDenied
message.enum.userSessionNotFound=userSessionNotFound
message.enum.common.error.code.order.id.is.empty = The orderId is empty

2.1.3 yml配置文件新增

spring:
  messages:
    # 国际化资源文件前缀
    basename: i18n.messages

2.2 将前面博客中的自定义响应体、响应码枚举类、统一异常码中的message替换成引用格式, 而非具体的中文消息

注:里面有新增的消息工具类用于解析消息, 见后面章节:

2.2.1 国际化后的自定义响应体

package com.rehse.common;

import com.rehse.util.SpringBeanUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;

import java.util.Locale;

/**
 * @author: Rehse
 * @description: Result
 * @date: 2025/8/14
 */
@Data
@Builder
@AllArgsConstructor
public class Result<T> {
    private String code;
    private String message;
    private T data;

    public Result(){
        this.code = ResultCodeEnum.SUCCESS.getCode();
        this.message = getMessage(ResultCodeEnum.SUCCESS);
    }
    public Result(ResultCodeEnum codeEnum) {
        this.code = codeEnum.getCode();
        this.message = getMessage(codeEnum);
    }

    public Result(ResultCodeEnum codeEnum, T data) {
        this.code = codeEnum.getCode();
        this.message = getMessage(codeEnum);
        this.data = data;
    }

    public static <T> Result<T> success() {
        Result<T> result = new Result<>();
        result.setCode(ResultCodeEnum.SUCCESS.getCode());
        result.setMessage(getMessage(ResultCodeEnum.SUCCESS));
        return result;
    }

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(ResultCodeEnum.SUCCESS.getCode());
        result.setMessage(getMessage(ResultCodeEnum.SUCCESS));
        result.setData(data);
        return result;
    }

    public static <T> Result<T> fail(String msg) {
        Result<T> result = new Result<>();
        result.setCode(ResultCodeEnum.FAIL.getCode());
        result.setMessage(msg);
        return result;
    }

    public static <T> Result<T> fail(String code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
    // 重点
    public static String getMessage(ResultCodeEnum code) {
        // 获取bean
        MessageSource messageSource = SpringBeanUtils.getBean(MessageSource.class);
        String messageHolder = code.getMessage();
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(messageHolder, null, locale);
    }

}

2.2.2 国际化后的响应码枚举类

package com.rehse.common;
import java.util.Objects;
import java.util.stream.Stream;
/**
 * @author: Rehse
 * @description: ResultCodeEnum
 * @date: 2025/8/13
 */
public enum ResultCodeEnum {
    SUCCESS("10000", "message.success"),
    FAIL("100001", "message.fail"),
    BAD_REQUEST("400", "message.request.bad"),
    UNAUTHORIZED("401", "message.token.unauthorized"),
    METHOD_NOT_ALLOWED("405", "message.method.not.allow");


    private String code;            // 状态码
    private String message;          // 消息

    ResultCodeEnum(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getCode() {
        return this.code;
    }

    public String getMessage() {
        return this.message;
    }

    public static ResultCodeEnum valueof(String code) {
        return Stream.of(values())
                .filter(option -> Objects.equals(option.code, code))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("No matching enum value for [" + code + "]"));
    }

}

2.2.3 国际化后的统一异常码

package com.rehse.exception;

import com.rehse.util.MessageUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;


@Getter
@AllArgsConstructor
public enum CommonErrorCode implements ErrorCode {


    SUCCESS("A10001", "message.success"),

    ORDER_ID_IS_EMPTY("A20001", "message.enum.common.error.code.order.id.is.empty");

    private final String code;
    private final String message;

    // 重点
    public String getMessage() {
        return MessageUtils.getMessage(this.message);
    }
}

2.3 实现LocaleResolver实现自定义区域解析器

继承LocaleResolver实现自定义区域解析器,如下代码:

package com.rehse.config;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * @author: Rehse
 * @description: 自定义区域信息解析器
 * @date: 2025/8/13
 */
@Slf4j
public class MyLocalResolver implements LocaleResolver {

    //进行配置区域解析器
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        //从请求头取出当前的语言
        String language = request.getHeader("lang");
        //获取默认的区域语言
        Locale locale = Locale.getDefault();
        if (StrUtil.isNotEmpty(language)) {
            String[] str = language.split("_");
            locale = new Locale(str[0], str[1]);
        }
        log.info("当前的系统语言是:" + language);
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }

}

2.4 实现WebMvcConfigure,把自定义区域解析器添加到容器中

package com.rehse.config;

import com.rehse.interceptor.BusinessOperatorArgumentResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

/**
 * @author: Rehse
 * @description: WebMvcConfig
 * @date: 2025/8/13
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    /**
     * 解析请求中的区域设置:从HTTP请求中提取用户的区域设置信息。
     * 设置响应中的区域设置:在响应中设置适当的区域设置,以便返回用户偏好语言的内容。
     * @return LocaleResolver
     */
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocalResolver();
    }
}

2.5 通过消息工具类MessageUtils,根据前端language的参数实现消息语言切换

其中的SpringBeanUtils工具类在下方

package com.rehse.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;

import java.util.Locale;

/**
 * @author: Rehse
 * @description: MessageUtils
 * @date: 2025/8/13
 */
@Slf4j
public class MessageUtils {

    /**
     * 获取国际化数据
     *
     * @param holder 占位符key
     * @return 返回国际化数据
     */
    public static String getMessage(String holder) {
        return getMessage(holder, null);
    }

    /**
     * 根据key获取国际化数据
     *
     * @param messageHolder 占位符key
     * @param defaultMsg    默认
     * @return 返回国际化数据
     */
    public static String getMessage(String messageHolder, String defaultMsg) {
        MessageSource messageSource = SpringBeanUtils.getBean(MessageSource.class);
        if (null == messageSource) {
            log.info("MessageSource is empty, please config confirm i18n configuration.");
            return defaultMsg;
        }
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(messageHolder, null, locale);
    }

}

其中根据bean的类型获取消息源对象的工具类代码为:

package com.rehse.util;


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;


/**
 * @author: Rehse
 * @description: SpringBeanUtils
 * @date: 2025/8/13
 */
@Component
public class SpringBeanUtils implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {

    private static ApplicationContext applicationContext;

    /**
     * @param applicationContext the ApplicationContext object to be used by this object
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringBeanUtils.applicationContext = applicationContext;
    }

    /**
     * 刷新SpringContext上下文
     *
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        SpringBeanUtils.applicationContext = context;
    }

    /**
     * 获取Bean
     *
     * @param clazz 类类型
     * @param <T>
     * @return Bean对象
     * @throws BeansException
     */
    public static <T> T getBean(Class<T> clazz) throws BeansException {
        T bean = applicationContext.getBean(clazz);
        return bean;
    }


    /**
     * 获取Bean
     *
     * @param name Bean名称
     * @return Bean对象
     * @throws BeansException
     */
    public static Object getBean(String name) throws BeansException {
        return applicationContext.getBean(name);
    }

}

3. 测试

根据请求头传入的语言为中文还是英文, 返回不同的响应消息。

示例:

设置请求头中的语言为英文, 当传入订单id为空时, 返回异常为相应的英文。

当响应成功时:

4. 更新预告--->通过自定义注解根据请求头注入用户会话信息

感谢宝宝们看完这篇博客, 感觉有帮助的话一键三连么么哒!

25届Java小登已入职, 入职后也要继续提升哇,猪咪将会不定期分享学习笔记!

重铸Java荣光, 我辈义不容辞!

强烈建议这几篇博客一起看, 因为我是一天写完的, 

为了保持CSDN活跃度, 我打算一天一发, 下一篇将会于2025年8月20日08:30:00自动更新。

接下来将会更新自动根据请求头注入用户会话信息, 会用上建造者设计模式, 请关注跟进吧么么哒!

在某个用户在平台上面进行操作的时候, 会存在很多用户会话信息:包括用户id, 用户姓名, 用户邮箱等。

后续将会更新通过一个注解@UserSession, 一步注入用户会话信息。

示例:

4.1 Controller

在接口里面添加了注解@UserSession

  @PostMapping("/saveOrder")
    public Result<Boolean> saveOrder(@UserSession UserSessionInfo userSessionInfo, @RequestBody Order order){
        return Result.success(orderService.saveOrder(order));
    }

4.2 ServiceImpl

@Override
    public Boolean saveOrder(Order order) {
        UserSessionInfo userSessionInfo = SessionThreadLocalUtil.getCurrentUserInfo();
        System.out.println("当前的UserSessionInfo为 : " + userSessionInfo);
        Order orderNew = new Order();
        BeanUtils.copyProperties(order, orderNew);
        orderNew.setUserId(String.valueOf(userSessionInfo.getUserId()));
        orderNew.setOrderId(String.valueOf(UUID.randomUUID()).replace("-", ""));
        orderNew.setCreateTime(new Date());
        orderNew.setUpdateTime(new Date());
        orderNew.setStatus(0);
        return save(orderNew);
    }

4.3 控制台

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值