当前位置: 首页 > news >正文

Springboot实现国际化(MessageSource)

背景

  • 在当今全球化的数字时代,软件应用的用户群体不再局限于单一语言或地区。无论是面向全球市场的电商平台,还是提供跨国服务的企业级应用,都需要能够适应不同语言环境,以满足全球用户的个性化需求。这就引出了一个关键的技术需求——国际化(Internationalization,通常简称为 i18n)。
  • Spring Boot 作为一款强大的后端开发框架,提供了多种方式来实现国际化,而 MessageSource 是其中一种非常实用且广泛采用的实现方式。接下来,我们将以 MessageSource 为例子实现国际化。

配置文件

# 国际化配置
spring:messages:default-locale: zh-CN		# 默认中文encoding: UTF-8				# 确认国际化文件采用utf-8格式,否则乱码basename: i18n/messages		# 放在resources/i18n目录下

配置类

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;import java.util.Locale;/*** MessageSourceConfig 消息源配置** @author xxx* @since 2025/9/4*/
@Configuration
public class MessageSourceConfig {@Value("${spring.messages.default-locale:}")private String defaultLocale;@Value("${spring.messages.encoding}")private String encoding;@Value("${spring.messages.basename}")private String basename;@Beanpublic MessageSource messageSource() {ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();messageSource.setBasename(basename);messageSource.setDefaultEncoding(encoding);messageSource.setDefaultLocale(Locale.forLanguageTag(defaultLocale));return messageSource;}
}

工具类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;import java.util.Locale;/*** MessageSourceUtil 消息工具类** @author xxx* @since 2025/9/4*/
@Component
public class MessageSourceUtil {private static MessageSource messageSource;@Autowiredpublic MessageSourceUtil(MessageSource messageSource) {MessageSourceUtil.messageSource = messageSource;}public static String getMessage(String code, Object[] args) {Locale locale = LocaleContextHolder.getLocale();return messageSource.getMessage(code, args, locale);}
}

国际化文件

修改属性文件编码格式

属性文件默认编码格式

国际化文件

在 resources/i18n 目录下新建:

messages_en_US.properties

PERM_NO_PERMISSION=No permission to operate

messages_zh_CN.properties

PERM_NO_PERMISSION=没有权限执行该操作

业务错误枚举类

import cn.com.techvision.mdm.common.utils.MessageSourceUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;/*** BusinessErrorConstant 业务错误枚举值** @author xxx* @since 2025/8/16*/
@AllArgsConstructor
@Getter
public enum BusinessErrorConstant {PERM_NO_PERMISSION(403, "PERM-ERR-001"),;/*** 状态码*/private final int statusCode;/*** 业务错误码*/private final String errCode;public static String getErrMsg(BusinessErrorConstant businessErrorConstant, Object... elements) {return String.format(MessageSourceUtil.getMessage(businessErrorConstant.name(), null), elements);}
}

在响应体中使用

响应体结构

import cn.com.xxx.BusinessErrorConstant;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 响应包的基类*/
@Data
@NoArgsConstructor
public class BaseResponse<T> {private int status;private String errCode;private String msg;private T data;private BaseResponse(int status, String errCode, String msg, T data) {this.status = status;this.errCode = errCode;this.msg = msg;this.data = data;}public static <T> BaseResponse<T> error(BusinessErrorConstant businessErrorConstant, Object... args) {return new BaseResponse<>(businessErrorConstant.getStatusCode(), businessErrorConstant.getErrCode(),BusinessErrorConstant.getErrMsg(businessErrorConstant, args), null);}
}

测试

测试类

@RestController
@RequestMapping("/system/org/{orgId}/project/{projectId}/test")
@Slf4j
public class TestController {@GetMapping("/i18n-res-err")public BaseResponse<String> testI18nResErr(){return BaseResponse.error(BusinessErrorConstant.PERM_NO_PERMISSION);}
}

测试用例

Accept-Language=en-US

curl --location --request GET 'http://localhost:24433/system/org/1/project/-1/test/i18n-res-err' \
--header 'Accept-Language: en-US' \
--header 'Content-Type: application/json' \
--data-raw ''

响应结果

{"status": 403,"errCode": "PERM-ERR-001","msg": "No permission to operate","data": null
}

Accept-Language=zh-CN

curl --location --request GET 'http://localhost:24433/system/org/1/project/-1/test/i18n-res-err' \
--header 'Accept-Language: zh-CN' \
--header 'Content-Type: application/json' \
--data-raw ''

响应结果

{"status": 403,"errCode": "PERM-ERR-001","msg": "没有权限执行该操作","data": null
}

在自定义业务异常中使用

import cn.com.xxx.BusinessErrorConstant;
import cn.com.xxx.MessageSourceUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;/*** BusinessException 业务异常** @author xxx* @since 2025/9/4*/
@EqualsAndHashCode(callSuper = true)
@Data
@Getter
public class BusinessException extends RuntimeException {/*** 状态码*/private final int statusCode;/*** 业务错误码*/private final String code;/*** 错误信息*/private final String message;public BusinessException(BusinessErrorConstant constant) {super(MessageSourceUtil.getMessage(constant.name(), null));this.statusCode = constant.getStatusCode();this.code = constant.getErrCode();this.message = MessageSourceUtil.getMessage(constant.name(), null);}public BusinessException(BusinessErrorConstant constant, Object... elements) {super(String.format(MessageSourceUtil.getMessage(constant.name(), null), elements));this.statusCode = constant.getStatusCode();this.code = constant.getErrCode();this.message = String.format(MessageSourceUtil.getMessage(constant.name(), null), elements);}
}

全局异常捕获

import cn.com.xxx.BaseResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler({BusinessException.class})public BaseResponse<String> businessExceptionHandler(BusinessException businessException){log.error(businessException.getMessage(), businessException);return BaseResponse.error(businessException.getStatusCode(), businessException.getCode(),businessException.getMessage(), null);}@ExceptionHandler({Exception.class})public void globalExceptionHandler(Exception ex, HttpServletResponse response){log.error(ex.getMessage(), ex);response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);}
}

测试

测试类

@RestController
@RequestMapping("/system/org/{orgId}/project/{projectId}/test")
@Slf4j
public class TestController {@GetMapping("/i18n-exception")public BaseResponse<String> testI18nThrowException(){throw new BusinessException(BusinessErrorConstant.PERM_NO_PERMISSION);}
}

测试用例

Accept-Language=en-US

curl --location --request GET 'http://localhost:24433/system/org/1/project/-1/test/i18n-exception' \
--header 'Accept-Language: en-US' \
--header 'Content-Type: application/json' \
--data-raw ''

响应结果

{"status": 403,"errCode": "PERM-ERR-001","msg": "No permission to operate","data": null
}

Accept-Language=zh-CN

curl --location --request GET 'http://localhost:24433/system/org/1/project/-1/test/i18n-exception' \
--header 'Accept-Language: zh-CN' \
--header 'Content-Type: application/json' \
--data-raw ''

响应结果

{"status": 403,"errCode": "PERM-ERR-001","msg": "没有权限执行该操作","data": null
}

文章转载自:

http://Pde7st4O.fbpdp.cn
http://AmtUxxxx.fbpdp.cn
http://zXBmnE2S.fbpdp.cn
http://HilFuAtb.fbpdp.cn
http://7ApXD8m5.fbpdp.cn
http://mEdFjJKz.fbpdp.cn
http://VTz5rZbI.fbpdp.cn
http://0vpE0e6y.fbpdp.cn
http://RJgpK5NN.fbpdp.cn
http://GREm3Ek1.fbpdp.cn
http://HKBQVye5.fbpdp.cn
http://a5Xum4QN.fbpdp.cn
http://P5wnyIcR.fbpdp.cn
http://AFaukUlN.fbpdp.cn
http://4wPYPWLS.fbpdp.cn
http://H8Ju6wE4.fbpdp.cn
http://D2h00IEn.fbpdp.cn
http://OPigLUPH.fbpdp.cn
http://ThZ7Rum8.fbpdp.cn
http://MovJNPPt.fbpdp.cn
http://PjLytZxd.fbpdp.cn
http://x2e8ZseE.fbpdp.cn
http://fGj6Xtcw.fbpdp.cn
http://x9sL3BUg.fbpdp.cn
http://pGKY0puD.fbpdp.cn
http://cGEmYsmd.fbpdp.cn
http://Z97gYzWI.fbpdp.cn
http://oxk9zPH7.fbpdp.cn
http://BLrM45Un.fbpdp.cn
http://tnmayqGJ.fbpdp.cn
http://www.dtcms.com/a/369274.html

相关文章:

  • 告别Qt Slider!用纯C++打造更轻量的TpSlider组件
  • 数字孪生赋能:智能制造如何实现从“经验驱动”到“数据驱动”?
  • 穿越市场迷雾:如何在经济周期中保持理性与长期视角
  • Mac M4环境下基于VMware Fusion虚拟机安装Ubuntu24.04 LTS ARM版
  • Vue基础知识-脚手架开发-使用Axios发送异步请求+代理服务器解决前后端分离项目的跨域问题
  • 苍穹外卖 day03
  • 【学习笔记】解决 JWT 解析报错:Claims claims = JwtUtil.parseJWT(...) Error Code 401(token过期)
  • linux下快捷删除单词、行的命令
  • AI提示词增强丨用EARS语法进行产品原子化拆解
  • 概率论第三讲——多维随机变量及其分布
  • 重大更新Claude更新用户协议把中国列为敌对国家
  • 移植Qt4.8.7到ARM40-A5
  • C++语言编程规范-初始化和类型转换
  • Gartner发布2025年数据安全领域的先锋厂商:GenAI和量子计算时代的数据安全创造性技术、产品和服务
  • 微前端架构:解构前端巨石应用的艺术
  • uniapp开发前端静态视频界面+如何将本地视频转换成网络地址
  • EI会议:第三届大数据、计算智能与应用国际会议(BDCIA 2025)
  • 9.5C++作业
  • 数据库基础知识——聚合函数、分组查询
  • MySQL 综合练习
  • 基于cornerstone3D的dicom影像浏览器 第三章 拖拽seriesItem至displayer上显示第一张dicom
  • 用户眼中的VR自来水厂之旅
  • 数据安全成焦点:基于Hadoop+Spark的信用卡诈骗分析系统实战教程
  • 瑞芯微RV1126目标识别算法Yolov8的部署应用
  • 【深入理解Batch Normalization(1)】原理与作用
  • 【教程】快速入门golang
  • Day21_【机器学习—决策树(2)—ID3树 、C4.5树、CART树】
  • std::complex
  • 深度解读:PSPNet(Pyramid Scene Parsing Network) — 用金字塔池化把“场景理解”装进分割网络
  • 【WRF-Chem】SYNMAP 土地覆盖数据概述及处理(二进制转geotiff)