Poyee 框架提供了完善的国际化(i18n)支持,使应用能够根据用户的语言偏好自动切换界面文本、错误消息和业务数据。国际化功能主要由 com.poyee.i18n 包和相关注解、工具类组成,支持多语言消息配置、动态语言切换和国际化数据处理。
i18n 注解i18n 注解用于标记需要国际化的字段、方法或类。
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface i18n {
// 是否启用国际化
boolean value() default true;
// Excel导出时的sheet名称
String sheetName() default "";
// 国际化格式
I18nFormat[] format() default {};
}
I18nUtils 工具类I18nUtils 是国际化功能的核心工具类,提供了获取国际化消息的方法。
@Configuration
public class I18nUtils {
// 定义占位符的正则表达式模式
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\d+)\\}");
private MessageProperties messageProperties;
// 存储消息的Map,键是消息键,值是包含语言和消息的Map
private static Map<String, Map<String, String>> messages;
// 初始化国际化工具类,并加载消息属性
@Autowired
public I18nUtils(MessageProperties messageProperties) {
this.messageProperties = messageProperties;
messages = messageProperties.init();
}
// 根据枚举和参数获取国际化消息
public static String get(I18nMessageEnums i18n, Object... args) {
String key = i18n.getCode();
Map<String, String> translations = messages.get(key);
String message;
if (Objects.isNull(translations)) {
// 如果没有找到对应的翻译,使用枚举中的默认消息
message = i18n.getMessage();
} else {
// 根据当前的locale获取对应的翻译
Locale locale = LocaleContextHolder.getLocale();
message = translations.getOrDefault(locale.getLanguage(), i18n.getLang());
}
// 替换消息中的占位符
return replacePlaceholders(message, args);
}
// 其他辅助方法...
}
MessageProperties 配置类MessageProperties 负责从配置文件中加载国际化消息。
@Component
public class MessageProperties {
@Autowired
private ResourceLoader resourceLoader;
public Map<String, Map<String, String>> init() {
return loadMessagesFromYaml();
}
public Map<String, Map<String, String>> loadMessagesFromYaml() {
Resource resource = resourceLoader.getResource("classpath:I18n/message.yml");
try (InputStream input = resource.getInputStream()) {
Yaml yaml = new Yaml();
Map map = yaml.loadAs(input, Map.class);
return map;
} catch (Exception e) {
throw new RuntimeException("Failed to load message.yml", e);
}
}
}
I18nMessageEnums 枚举I18nMessageEnums 定义了系统中所有的国际化消息。
@Getter
public enum I18nMessageEnums {
// 请求成功
SUCCESS("success", "成功", "zh"),
// 请求失败
REQUEST_ERROR("request_error", "请求失败", "zh"),
// 无权操作
NO_PERMISSION("no_permission", "无权操作{0}", "zh"),
// 更多消息...
private String code; // 多语言标识
private String message; // 默认信息
private String lang; // 默认语言
I18nMessageEnums(String code, String message, String lang) {
this.code = code;
this.message = message;
this.lang = lang;
}
}
i18nAspect 切面类i18nAspect 是一个切面类,用于处理国际化相关的逻辑。
@Slf4j
@Aspect
@Component
public class i18nAspect {
// 配置织入点,针对使用了i18n注解的方法
@Pointcut("@annotation(com.poyee.annotation.I18n)")
public void logPointCut() {
}
// 在控制器的方法执行前,处理国际化逻辑
@Before("execution(public * com.poyee.controller.*.*(..))")
public void doBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
checkI18n(method);
}
// 判断是否支持国际化,并根据情况设置会话属性
private void checkI18n(Method method) {
if (method.isAnnotationPresent(i18n.class)) {
i18n i18n = method.getAnnotation(i18n.class);
HttpServletRequest request = ServletUtils.getRequest();
String language = request.getHeader("language");
boolean isI18n = true;
if(StringUtils.isBlank(language)) {
language = LocaleContextHolder.getLocale().getLanguage();
isI18n = false;
}
try {
HttpSession session = ServletUtils.getSession(false);
if (session == null) {
log.warn("Session is null, cannot set I18n attributes.");
return;
}
// 根据方法上的i18n注解和请求头中的语言信息,决定是否启用国际化
if(i18n.value() && isI18n) {
session.setAttribute("language", language);
I18nFormat[] format = i18n.format();
if(format.length > 0) {
session.setAttribute("i18nFormat", i18n.format());
if (log.isInfoEnabled()) {
log.info("i18nFormat:{}", format[0].getMsg());
}
}
session.setAttribute("i18n", true);
} else {
// 如果 I18n.value() 为 false,但 isI18n 为 true,仍不启用国际化
session.setAttribute("i18n", false);
}
} catch (Exception e) {
log.error("Failed to set I18n attributes", e);
}
}
}
}
I18nFormat 枚举I18nFormat 定义了国际化格式的类型。
@Getter
public enum I18nFormat {
INSERT("新增"),
UPDATE("编辑"),
DELETE("删除"),
SEARCH("查询"),
EXPORT("导出"),
IMPORT("导入")
;
private String msg;
I18nFormat(String msg) {
this.msg = msg;
}
}
国际化消息配置文件位于 classpath:i18n/message.yml,采用 YAML 格式,结构如下:
success:
en: "Success"
zh: "成功"
request_error:
en: "Request failed"
zh: "请求失败"
no_permission:
en: "No permission to operate {0}"
zh: "无权操作{0}"
# 更多消息...
在控制器方法上添加 @i18n 注解,启用国际化支持:
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/list")
@i18n(format = {I18nFormat.SEARCH})
public Result<List<UserDto>> listUsers() {
// 业务逻辑...
return Result.success(userList);
}
@PostMapping("/create")
@i18n(format = {I18nFormat.INSERT})
public Result<Void> createUser(@RequestBody UserCreateReq req) {
// 业务逻辑...
return Result.success();
}
}
使用 I18nUtils.get() 方法获取国际化消息:
// 简单消息
String successMsg = I18nUtils.get(SUCCESS);
// 带参数的消息
String noPermissionMsg = I18nUtils.get(NO_PERMISSION, "用户管理");
// 在异常中使用
throw new ServiceException(I18nUtils.get(DATA_NOT_EXIST, "用户"));
// 在返回结果中使用
return Result.error(I18nUtils.get(PARAM_ERROR));
在实体类或DTO中,使用 @i18n 注解标记需要国际化的字段:
@Data
public class ProductDto extends BaseDto {
private Integer id;
private String code;
@TableField("name")
private String name;
@i18n
@TableField("name_i18n")
private String nameI18n;
// 其他字段...
}
在服务实现类中,可以根据是否启用国际化来决定是否查询国际化字段:
public class ProductServiceImpl extends BaseServiceImpl<ProductMapper, ProductReq, ProductDto> implements ProductService {
@Override
public Result<Page<ProductDto>> page(ProductPageReq req) {
MPJLambdaWrapper<ProductDto> wrapper = new MPJLambdaWrapper<>();
wrapper.selectAll(ProductDto.class);
// 根据是否启用国际化,决定是否查询国际化字段
checkI18n(wrapper, ProductDto.class);
// 其他查询条件...
return Result.success(baseMapper.selectPage(req.getPage(), wrapper));
}
}
所有国际化消息都应该在 I18nMessageEnums 中定义,避免硬编码消息字符串。
消息中的可变部分应使用占位符 {0}, {1} 等,而不是字符串拼接。
所有需要支持国际化的控制器方法都应添加 @i18n 注解,并指定适当的 format。
国际化字段通常以原字段名加 I18n 后缀命名,如 name 和 nameI18n。
I18nMessageEnums 中的定义。language 请求头指定语言,如 language: en。