文章目录

  • 1 摘要
  • 2 核心代码
    • 2.1 多语言枚举类
    • 2.2 多语言处理工具类
    • 2.3 多语言的API返回状态码枚举类
    • 2.4 多语言 API 接口返回结果封装
    • 2.5 i18n 国际化多语言配置文件
  • 3 多语言 API 应用
    • 3.1 参数接收 java bean
    • 3.2 DAO,Service,Controller 层
  • 4 测试
  • 5 参考资料推荐

1 摘要

随着互联网的迅速普及,目前(2019)国内的越来越多网络公司都开始走出国门,开辟海外市场,然而在出海之前需要解决的重大问题之一就是多语言环境。以往只有大型的跨国公司才会在项目中使用多语言国际化方案,现在这样的国际化应用将会越来越多。本文将介绍在 Spring Boot 项目中提供 RESTful 风格 API 的多语言国际化(i18n)解决方案

2 核心代码

2.1 多语言枚举类

将需要用到的语言添加到此类中

../demo-common/src/main/java/com/ljq/demo/springboot/common/i18n/LanguageEnum.java

package com.ljq.demo.springboot.common.i18n;import lombok.Getter;
import lombok.ToString;
import org.springframework.util.StringUtils;/*** @Description: 国际化 语言枚举类* @Author: junqiang.lu* @Date: 2019/1/24*/
@Getter
@ToString
public enum LanguageEnum {/*** 美式英文*/LANGUAGE_EN_US("en_us"),/*** 简体中文*/LANGUAGE_ZH_CN("zh_cn");private String language;private LanguageEnum(String language){this.language = language;}/*** 获取指定语言类型(如果没有对应的语言类型,则返回中文)** @param language 语言类型* @return*/public static String getLanguageType(String language){if (StringUtils.isEmpty(language)) {return LANGUAGE_ZH_CN.language;}for (LanguageEnum languageEnum : LanguageEnum.values()) {if (languageEnum.language.equalsIgnoreCase(language)) {return languageEnum.language;}}return LANGUAGE_ZH_CN.language;}}

2.2 多语言处理工具类

../demo-common/src/main/java/com/ljq/demo/springboot/common/i18n/I18nMessageUtil.java

package com.ljq.demo.springboot.common.i18n;import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;import java.io.IOException;/*** @Description: 多语言国际化消息工具类* @Author: junqiang.lu* @Date: 2019/1/24*/
public class I18nMessageUtil {private static MessageSourceAccessor accessor;private static final String PATH_PARENT = "classpath:i18n/";private static final String SUFFIX = ".properties";private static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();private I18nMessageUtil(){}/*** 初始化资源文件的存储器* 加载指定语言配置文件** @param language 语言类型(文件名即为语言类型,eg: en_us 表明使用 美式英文 语言配置)*/private static void initMessageSourceAccessor(String language) throws IOException {/*** 获取配置文件名*/Resource resource = resourcePatternResolver.getResource(PATH_PARENT + language + SUFFIX);String fileName = resource.getURL().toString();int lastIndex = fileName.lastIndexOf(".");String baseName = fileName.substring(0,lastIndex);/*** 读取配置文件*/ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource();reloadableResourceBundleMessageSource.setBasename(baseName);reloadableResourceBundleMessageSource.setCacheSeconds(5);reloadableResourceBundleMessageSource.setDefaultEncoding("UTF-8");accessor = new MessageSourceAccessor(reloadableResourceBundleMessageSource);}/*** 获取一条语言配置信息** @param language 语言类型,zh_cn: 简体中文, en_us: 英文* @param message 配置信息属性名,eg: api.response.code.user.signUp* @param defaultMessage 默认信息,当无法从配置文件中读取到对应的配置信息时返回该信息* @return* @throws IOException*/public static String getMessage(String language, String message, String defaultMessage) throws IOException {initMessageSourceAccessor(language);return accessor.getMessage(message,defaultMessage,LocaleContextHolder.getLocale());}
}

2.3 多语言的API返回状态码枚举类

../demo-common/src/main/java/com/ljq/demo/springboot/common/api/ResponseCodeI18n.java

package com.ljq.demo.springboot.common.api;import lombok.Getter;
import lombok.ToString;/*** @Description: 多语言,国际化接口返回码枚举* @Author: junqiang.lu* @Date: 2019/1/24*/
@Getter
@ToString
public enum  ResponseCodeI18n {SUCCESS(1000, "api.response.code.success"),FAIL(-1, "api.response.code.fail"),// 公共参数PARAM_ERROR(1001, "api.response.code.paramError"),LANGUAGE_TYPE_ERROR(1002, "api.response.code.languageTypeError"),// 用户模块// 帐号ACCOUNT_NULL_ERROR(2001,"api.response.code.user.accountNullError"),ACCOUNT_FORMAT_ERROR(2002, "api.response.code.user.accountFormatError"),ACCOUNT_NOT_EXIST(2003,"api.response.code.user.accountNotExist"),ACCOUNT_EXIST(2004,"api.response.code.user.accountExist"),// 密码PASSWORD_NULL_ERROR(2101, "api.response.code.user.passwordNullError"),PASSWORD_FORMAT_ERROR(2102, "api.response.code.user.passwordFormatError"),PASSWORD_ERROR(2103,"api.response.code.user.passwordError"),UNKNOWN_ERROR(-1000,"api.response.code.unknownError");// 返回码private int code;// 返回信息private String msg;private ResponseCodeI18n(int code, String msg) {this.code = code;this.msg = msg;}
}

2.4 多语言 API 接口返回结果封装

../demo-common/src/main/java/com/ljq/demo/springboot/common/api/ApiResultI18n.java

package com.ljq.demo.springboot.common.api;import com.ljq.demo.springboot.common.i18n.I18nMessageUtil;
import com.ljq.demo.springboot.common.i18n.LanguageEnum;
import lombok.Data;import java.io.IOException;
import java.io.Serializable;/*** @Description: 多语言, 国际化接口返回结果封装* @Author: junqiang.lu* @Date: 2019/1/24*/
@Data
public class ApiResultI18n implements Serializable {private static final long serialVersionUID = 4518290031778225230L;/*** 返回码,1000 正常*/private int code = 1000;/*** 返回信息*/private String msg = "成功";/*** 返回数据*/private Object data;/*** api 返回结果*/private ApiResultI18n() {}/*** api 返回结果,区分多语言** @param language 语言类型,eg: en_us 表示美式英文*/public ApiResultI18n(String language){this.code = ResponseCodeI18n.SUCCESS.getCode();language = LanguageEnum.getLanguageType(language);try {this.msg = I18nMessageUtil.getMessage(language,ResponseCodeI18n.SUCCESS.getMsg(),"SUCCESS");} catch (IOException e) {this.msg = "SUCCESS";}}/*** 获取成功状态结果,区分多语言(默认简体中文)** @param language 语言类型,eg: en_us 表示美式英文* @return*/public static ApiResultI18n success(String language) {return success(null, language);}/*** 获取成功状态结果,区分多语言(默认简体中文)** @param data 返回数据* @param language 语言类型,eg: en_us 表示美式英文* @return*/public static ApiResultI18n success(Object data, String language) {ApiResultI18n result = new ApiResultI18n(language);result.setData(data);return result;}/*** 获取失败状态结果,区分多语言(默认简体中文)** @param language 语言类型,eg: en_us 表示美式英文* @return*/public static ApiResultI18n failure(String language) {return failure(ResponseCodeI18n.FAIL.getCode(), ResponseCodeI18n.FAIL.getMsg(), null, language);}/*** 获取失败结果,区分多语言(默认中文)** @param responseCodeI18n 返回码* @param language 语言类型* @return*/public static ApiResultI18n failure(ResponseCodeI18n responseCodeI18n, String language) {return failure(responseCodeI18n.getCode(), responseCodeI18n.getMsg(), null, language);}/*** 获取失败状态结果,区分多语言(默认中文)** @param code 返回状态码* @param msg 错误信息* @param language 语言类型,eg: en 表示英文* @return*/public static ApiResultI18n failure(int code, String msg, String language) {return failure(code ,msg, null, language);}/*** 获取失败返回结果,区分多语言(默认中文)** @param code 错误码* @param msg 错误信息* @param data 返回结果* @param language 语言类型,eg: en 表示英文* @return*/public static ApiResultI18n failure(int code, String msg, Object data, String language) {ApiResultI18n result = new ApiResultI18n(language);language = LanguageEnum.getLanguageType(language);try {msg = I18nMessageUtil.getMessage(language, msg, msg);} catch (IOException e) {msg = "Error";}result.setCode(code);result.setMsg(msg);result.setData(data);if (data instanceof String) {String m = (String) data;if (!m.matches("^.*error$")) {m += "error";}}return result;}
}

2.5 i18n 国际化多语言配置文件

语言的数量根据项目的需要,这里只列举类中文和英文两种语言

中文:

../demo-web/src/main/resources/i18n/zh_cn.properties

英文:

../demo-web/src/main/resources/i18n/en_us.properties

中文i18n多语言配置文件信息

# language = zh_cn(简体中文)# api response code
api.response.code.success=成功
api.response.code.fail=失败
api.response.code.paramError=参数错误
api.response.code.languageTypeError=语言类型错误# 用户模块
# 帐号
api.response.code.user.accountNullError=帐号为空
api.response.code.user.accountFormatError=账号(格式)错误
api.response.code.user.accountNotExist=账号不存在
api.response.code.user.accountExist=账号已经被注册
# 密码
api.response.code.user.passwordNullError=密码为空
api.response.code.user.passwordFormatError=密码格式错误
api.response.code.user.passwordError=密码错误# 未知异常
api.response.code.unknownError=未知异常

英文的配置和中文格式一致,这里就不再列出

3 多语言 API 应用

项目配置了多语言之后,在接收参数的java bean 中需要添加语言字段,用于区分语言类型

语言类型字段属于一个公共字段,每一个接口都需要传递,建议写在一个基础公共类中,其他的参数接收类继承该类

3.1 参数接收 java bean

参数接收基础 bean

../demo-model/src/main/java/com/ljq/demo/springboot/BaseBean.java

package com.ljq.demo.springboot;import lombok.Data;import javax.validation.constraints.Pattern;
import java.io.Serializable;/*** @Description: 基础 bean* @Author: junqiang.lu* @Date: 2018/12/24*/
@Data
public class BaseBean implements Serializable {private static final long serialVersionUID = 6877955227522370690L;/*** 语言类型*/@Pattern(regexp = "^\\w{2,10}$", message = "api.response.code.languageTypeError")private String language;}

用户注册参数接收 bean

../demo-model/src/main/java/com/ljq/demo/springboot/vo/UserSignUpBean.java

package com.ljq.demo.springboot.vo;import com.ljq.demo.springboot.BaseBean;
import lombok.Data;import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;/*** @Description: 用户注册接收参数bean* @Author: junqiang.lu* @Date: 2019/1/24*/
@Data
public class UserSignUpBean extends BaseBean {/*** 账号*/@NotNull(message = "api.response.code.user.accountNullError")@Pattern(regexp = "^\\S{5,50}$", message = "api.response.code.user.accountFormatError")private String userName;/*** 密码*/@NotNull(message = "api.response.code.user.passwordNullError")@Pattern(regexp = "^\\w{5,80}$", message = "api.response.code.user.passwordFormatError")private String userPasscode;}

3.2 DAO,Service,Controller 层

DAO 层基本不涉及到多语言,这里便不再贴出相关代码

Service 层

用户模块Service 接口

../demo-service/src/main/java/com/ljq/demo/springboot/service/UserService.java

package com.ljq.demo.springboot.service;import com.ljq.demo.springboot.common.api.ApiResult;
import com.ljq.demo.springboot.common.api.ApiResultI18n;
import com.ljq.demo.springboot.vo.UserSignUpBean;import java.util.Map;/*** @Description: 用户模块业务* @Author: junqiang.lu* @Date: 2018/10/9*/
public interface UserService {/*** 用户注册** @param userSignUpBean 用户注册信息* @return*/ApiResultI18n signUp(UserSignUpBean userSignUpBean) throws Exception;}

用户模块 Service 实现类

../demo-service/src/main/java/com/ljq/demo/springboot/service/impl/UserServiceImpl.java

package com.ljq.demo.springboot.service.impl;import com.ljq.demo.springboot.common.api.ApiResult;
import com.ljq.demo.springboot.common.api.ApiResultI18n;
import com.ljq.demo.springboot.common.api.ResponseCodeI18n;
import com.ljq.demo.springboot.dao.user.UserDao;
import com.ljq.demo.springboot.entity.UserDO;
import com.ljq.demo.springboot.service.UserService;
import com.ljq.demo.springboot.vo.UserSignUpBean;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.Map;/*** @Description: 用户业务具体实现* @Author: junqiang.lu* @Date: 2018/10/9*/
@Service("userService")
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;/*** 用户注册** @param userSignUpBean 用户注册信息* @return*/@Overridepublic ApiResultI18n signUp(UserSignUpBean userSignUpBean) throws Exception{/*** 参数校验顺序: 基本入参校验 --> 具体参数合法性校验(非数据库层校验) --> 数据库层参数校验*//*** 请求参数获取*/UserDO userParams = new UserDO();BeanUtils.copyProperties(userSignUpBean, userParams);/*** 此处省略* 参数合法性校验校验... ...*/// 注册帐号数据库层校验int userCount = userDao.signUpCheck(userParams);if (userCount > 0) {return ApiResultI18n.failure(ResponseCodeI18n.ACCOUNT_EXIST, userSignUpBean.getLanguage());}/*** 此处省略* 用户注册相关操作... ...*/return ApiResultI18n.success(userSignUpBean.getLanguage());}}

用户模块 Controllerr 层

../demo-web/src/main/java/com/ljq/demo/springboot/web/controller/UserController.java

package com.ljq.demo.springboot.web.controller;import com.ljq.demo.springboot.common.api.ApiResult;
import com.ljq.demo.springboot.common.api.ApiResultI18n;
import com.ljq.demo.springboot.common.api.ResponseCodeI18n;
import com.ljq.demo.springboot.common.exception.ParamsCheckException;
import com.ljq.demo.springboot.service.UserService;
import com.ljq.demo.springboot.vo.UserSignUpBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.Map;/*** @Description: 用户控制中心* @Author: junqiang.lu* @Date: 2018/10/9*/
@RestController
@RequestMapping("api/user")
public class UserController {private static final Logger logger = LoggerFactory.getLogger(UserController.class);@Autowiredprivate UserService userService;/*** 用户注册** @param userSignUpBean 注册信息* @return*/@RequestMapping(value = "signup", method = RequestMethod.POST)public ApiResultI18n signUp(@RequestBody UserSignUpBean userSignUpBean){ApiResultI18n apiResultI18n= null;try {apiResultI18n = userService.signUp(userSignUpBean);} catch (Exception e) {if (ParamsCheckException.class.isAssignableFrom(e.getClass())){logger.error("注册失败,参数错误");return apiResultI18n.failure(ResponseCodeI18n.PARAM_ERROR.getCode(), e.getMessage(),userSignUpBean.getLanguage());}logger.error("注册失败,未知异常",e);return apiResultI18n.failure(ResponseCodeI18n.UNKNOWN_ERROR, userSignUpBean.getLanguage());}return apiResultI18n;}}

4 测试

本地测试接口地址

http://127.0.0.1:8088/api/user/signup

中文参数

{"language" : "zh_cn","userName" : "tom","userPasscode" : "123456"
}

中文接口返回结果

{"code": 1001,"msg": "账号(格式)错误","data": null
}

中文接口请求日志

2019-01-28 11:30:46:390 [http-nio-8088-exec-3] INFO  com.ljq.demo.springboot.web.acpect.LogAspect(LogAspect.java 66) -[AOP-LOG-START]requestMark: cc866c67-5fdc-4adb-855e-a79d67d31344requestIP: 127.0.0.1contentType:application/jsonrequestUrl: http://127.0.0.1:8088/api/user/signuprequestMethod: POSTrequestParams: {"language":"zh_cn","userName":"tom","userPasscode":"123456"}targetClassAndMethod: com.ljq.demo.springboot.web.controller.UserController#signUp
2019-01-28 11:30:46:405 [http-nio-8088-exec-3] ERROR c.l.demo.springboot.web.controller.UserController(UserController.java 91) -注册失败,参数错误
2019-01-28 11:30:46:407 [http-nio-8088-exec-3] INFO  com.ljq.demo.springboot.web.acpect.LogAspect(LogAspect.java 74) -[AOP-LOG-END]ApiResultI18n(code=1001, msg=账号(格式)错误, data=null)

英文参数

{"language" : "en_us","userName" : "tom","userPasscode" : "123456"
}

英文接口返回结果

{"code": 1001,"msg": "Account format error","data": null
}

英文接口请求日志

2019-01-28 11:32:51:672 [http-nio-8088-exec-4] INFO  com.ljq.demo.springboot.web.acpect.LogAspect(LogAspect.java 66) -[AOP-LOG-START]requestMark: 756575c3-471a-4873-8f93-4850b3353038requestIP: 127.0.0.1contentType:application/jsonrequestUrl: http://127.0.0.1:8088/api/user/signuprequestMethod: POSTrequestParams: {"language":"en_us","userName":"tom","userPasscode":"123456"}targetClassAndMethod: com.ljq.demo.springboot.web.controller.UserController#signUp
2019-01-28 11:32:51:692 [http-nio-8088-exec-4] ERROR c.l.demo.springboot.web.controller.UserController(UserController.java 91) -注册失败,参数错误
2019-01-28 11:32:51:693 [http-nio-8088-exec-4] INFO  com.ljq.demo.springboot.web.acpect.LogAspect(LogAspect.java 74) -[AOP-LOG-END]ApiResultI18n(code=1001, msg=Account format error, data=null)

至此,一个 Spring boot RESTful 风格的多语言国际化 API 接口框架已经搭建完成

5 参考资料推荐

Spring 读取i18n国际化资源文件的工具类

自己动手在Spring-Boot上加强国际化功能

使rest的消息和内容国际化

Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.

SpringBoot RESTful 风格 API 多语言国际化i18n解决方案相关推荐

  1. SpringBoot RestFul风格API接口开发

    本文介绍在使用springBoot如何进行Restful Api接口的开发及相关注解已经参数传递如何处理. 一.概念: REST全称是Representational State Transfer,中 ...

  2. RESTful风格API详解

    在学习RESTful 风格接口之前,即使你不知道它是什么,但你肯定会好奇它能解决什么问题?有什么应用场景?听完下面描述我想你就会明白: 在互联网并没有完全流行的初期,移动端也没有那么盛行,页面请求和并 ...

  3. SpringBoot2.1.5(39)--- 开发restful 风格Api

    SpringBoot 实现RestFul 相关注解的介绍 如果说你会使用SpringMVC 那么下面的内容你阅读将会很轻松,我这里通过搭建一个用户管理 接口API 让你快速学会如何创建restful ...

  4. java restful接口开发实例_实战:基于Spring Boot快速开发RESTful风格API接口

    写在前面的话 这篇文章计划是在过年期间完成的,示例代码都写好了,结果亲戚来我家做客,文章没来得及写.已经很久没有更新文章了,小伙伴们,有没有想我啊.言归正传,下面开始,今天的话题. 目标 写一套符合规 ...

  5. 团队RESTful 风格API规范

    实际上就是用RESTful风格来包装HTTP协议,并用json或xml格式实现数据交互. RESTful风格: 网络资源实体化,CURD对资源进行操作. 好的规范评判标准:直观.扩展.优雅 1.数据交 ...

  6. HTTP的REST服务-RESTful风格API

    Rest关键词解释 REST概念 REST遇到的问题及示例 总结 一. Rest关键词解释 REST(Representational State Transfer):表述性状态转移 Rest是web ...

  7. 通俗易懂RESTful,如何设计RESTful风格API

    REST – REpresentational State Transfer 直译:表现层状态转移.这个中文直译经常出现在很多文章中.尼玛,谁听得懂"表现层状态转移",这是人话吗? ...

  8. 如何设计RESTful风格API

    REST -- REpresentational State Transfer 直译:表现层状态转移.这个中文直译经常出现在很多文章中.尼玛,谁听得懂"表现层状态转移",这是人话吗 ...

  9. 深入理解幂等性及Restful风格API的幂等性问题详解

    什么是幂等性 HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外).也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同. ...

最新文章

  1. VMware 虚拟化编程(12) — VixDiskLib Sample 程序使用
  2. Binary Tree Maximum Path Sum
  3. 《R和Ruby数据分析之旅》目录—导读
  4. node : 无法将“node”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
  5. 如何在面试中脱颖而出?
  6. springboot 配置文件-yaml的用法
  7. 零零散散的一些知识点(二)
  8. 微信H5框架:WEUI
  9. 运算放大器由来及虚短虚断的讨论
  10. (转)CentOS系统启动流程图文详解
  11. tableau地图城市数据_Tableau-地图
  12. 麻省理工的服务器位置,美国麻省理工大学位置在哪里?
  13. 磁卡、条码卡、IC卡、ID卡基本知识
  14. SQL数据库完美恢复 SQL数据库损坏修复
  15. latex大括号 多行公式_问题百出的MathType公式编辑器,会有替代品吗?
  16. 从后端数据库获取数据并传值到前端vue项目的echarts柱状图/折线图/饼图里
  17. C语言大数相乘(整形)
  18. C/C++浮点数格式——IEEE754标准
  19. codevs 2627 村村通
  20. 独家 | 蚂蚁金服漆远首谈刚完成的AI重大突破及紧缺人才,呼吁国内多点技术性强的大会,少点网红

热门文章

  1. aircrack-ng暴力破解WIFI密码
  2. 前端鉴权必须了解的 5 个兄弟:cookie、session、token、jwt、单点登录
  3. Scrum的基本功 - 集合中英文版本 (角色和责任篇)
  4. Java 设计模式之责任链模式实现的三种方式
  5. 0x800704cf 共享打印机_共享打印机,错误0x80070035、错误0x00000709、错误0x800704CF 的解决办法...
  6. PC连接汇川PLC方法
  7. 联通物联网卡比其他物联卡有什么优势
  8. 江苏大学考研805真题C语言程序设计 第五大编程题答案2004-2019
  9. 【Matlab】混合整数规划
  10. email html css,在Email中防御性地使用HTML5和CSS3的指南