国际化是产品、应用程序或文档内容的设计和开发,它可以为不同文化、地区或语言的目标受众轻松实现本地化。国际化(Internationalization)通常用英文写成 i18n,其中 18 是英文单词中 i 和 n 之间的字母数。
任何一个面向全世界的软件都会面临多语言国际化的问题,尤其是跨境电商行业,在做自研系统必不可少的要实现多语言功能,就是在数据展示给用户之前,替换成用户可识别的语言,这里以spring i18n 国际化来介绍一下。
basename:以逗号分隔的基名列表(本质上是一个完全限定的类路径位置),每个基名都遵循 ResourceBundle 约定,对基于斜杠的位置提供宽松的支持,它将从类路径根目录解析。
cache-duration:加载的资源包文件缓存的持续时间。如果没有设置,捆绑包将被永久缓存。如果没有指定持续时间后缀,将使用秒。
spring:
# 资源信息
messages:
encoding: utf-8
# 国际化资源文件路径(配置文件路径),多模块也可以用逗号分隔
basename: i18n/messages
messages.properties
exception.name.can.not.null=exception name can not null,the name is {0}
exception.insert=exception insert
messages_zh_CN.properties
exception.name.can.not.null=用户名不能为空,用户名为:{0}
exception.insert=新增异常
@Component
@Slf4j
public class I18nUtils {
// 如果当前bean不加@Component注解,则messageSource无法注入,始终为null
private static MessageSource messageSource;
@Autowired
public void setMessageSource(MessageSource messageSource) {
I18nUtils.messageSource = messageSource;
}
/**
* 解析code对应的信息进行返回,如果对应的code不能被解析则抛出异常NoSuchMessageException
*
* @param code 需要进行解析的code,对应资源文件中的一个属性名
* @param args 当对应code对应的信息不存在时需要返回的默认值
* @return 国际化翻译值
*/
public static String i18n(String code, Object... args) {
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
}
/**
* 解析code对应的信息进行返回,如果对应的code不能被解析则返回默认信息defaultMessage。
*
* @param code 需要进行解析的code,对应资源文件中的一个属性名
* @param defaultMessage 当对应code对应的信息不存在时需要返回的默认值
* @param args 需要用来替换code对应的信息中包含参数的内容,如:{0},{1,date},{2,time}
* @return 对应的Locale
*/
public static String i18nOrDefault(String code, String defaultMessage, Object... args) {
return messageSource.getMessage(code, args, defaultMessage, LocaleContextHolder.getLocale());
}
/**
* 因为i18n方法如果获取不到对应的键值,会抛异常NoSuchMessageException
* 本方法是对i18n方法的封装。当报错时并不抛出异常,而是返回source
*
* @param source 模板
* @param args 参数
* @return 返回I18n(正常结束)或者source(抛出异常)
* @see #i18n(String, Object...)
*/
@NonNull
public static String tryI18n(@NonNull String source, @NonNull Object... args) {
String res;
try {
res = i18n(source, args);
} catch (Exception ignored) {
res = source;
}
return res;
}
}
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(CommonException.class)
public ApiResponse<Object> handleCommonException(CommonException e) {
log.error(e.getMessage(), e);
return new ApiResponse<>(-1,"error",e.getMessage());
}
}
@Service
public class UserService {
@Resource
private UserDao userDao;
public void insertUser(UserQo userQo){
UserEntity userEntity = userDao.findByName(userQo.getName());
if(Objects.nonNull(userEntity)){
// i18n带有参数
String name = userQo.getName();
throw new CommonException("exception.name.can.not.null",name);
}
userEntity = new UserEntity();
BeanUtils.copyProperties(userQo,userEntity);
userEntity.setCreateTime(new Date());
int count = userDao.insert(userEntity);
if(count==1){
// i18n不带有参数
throw new CommonException("exception.insert");
}
}
}
这里只是简单的演示了 spring i18n 的功能,可以满足一些简单场景的需求,可以适当扩展,比如:如果使用了 nacos 等配置中心,则需要去注册中心手动拉取 i18n 的 properties 文件内容,并加载到应用程序的内存里。