# Poyee 国际化(i18n)功能说明 ## 1. 概述 Poyee 框架提供了完善的国际化(i18n)支持,使应用能够根据用户的语言偏好自动切换界面文本、错误消息和业务数据。国际化功能主要由 `com.poyee.i18n` 包和相关注解、工具类组成,支持多语言消息配置、动态语言切换和国际化数据处理。 ## 2. 核心组件 ### 2.1 `i18n` 注解 `i18n` 注解用于标记需要国际化的字段、方法或类。 ```java @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 {}; } ``` ### 2.2 `I18nUtils` 工具类 `I18nUtils` 是国际化功能的核心工具类,提供了获取国际化消息的方法。 ```java @Configuration public class I18nUtils { // 定义占位符的正则表达式模式 private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\d+)\\}"); private MessageProperties messageProperties; // 存储消息的Map,键是消息键,值是包含语言和消息的Map private static Map> 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 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); } // 其他辅助方法... } ``` ### 2.3 `MessageProperties` 配置类 `MessageProperties` 负责从配置文件中加载国际化消息。 ```java @Component public class MessageProperties { @Autowired private ResourceLoader resourceLoader; public Map> init() { return loadMessagesFromYaml(); } public Map> 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); } } } ``` ### 2.4 `I18nMessageEnums` 枚举 `I18nMessageEnums` 定义了系统中所有的国际化消息。 ```java @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; } } ``` ### 2.5 `i18nAspect` 切面类 `i18nAspect` 是一个切面类,用于处理国际化相关的逻辑。 ```java @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); } } } } ``` ### 2.6 `I18nFormat` 枚举 `I18nFormat` 定义了国际化格式的类型。 ```java @Getter public enum I18nFormat { INSERT("新增"), UPDATE("编辑"), DELETE("删除"), SEARCH("查询"), EXPORT("导出"), IMPORT("导入") ; private String msg; I18nFormat(String msg) { this.msg = msg; } } ``` ## 3. 国际化配置文件 国际化消息配置文件位于 `classpath:i18n/message.yml`,采用 YAML 格式,结构如下: ```yaml success: en: "Success" zh: "成功" request_error: en: "Request failed" zh: "请求失败" no_permission: en: "No permission to operate {0}" zh: "无权操作{0}" # 更多消息... ``` ## 4. 使用方法 ### 4.1 在控制器中启用国际化 在控制器方法上添加 `@i18n` 注解,启用国际化支持: ```java @RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/list") @i18n(format = {I18nFormat.SEARCH}) public Result> listUsers() { // 业务逻辑... return Result.success(userList); } @PostMapping("/create") @i18n(format = {I18nFormat.INSERT}) public Result createUser(@RequestBody UserCreateReq req) { // 业务逻辑... return Result.success(); } } ``` ### 4.2 获取国际化消息 使用 `I18nUtils.get()` 方法获取国际化消息: ```java // 简单消息 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)); ``` ### 4.3 标记国际化字段 在实体类或DTO中,使用 `@i18n` 注解标记需要国际化的字段: ```java @Data public class ProductDto extends BaseDto { private Integer id; private String code; @TableField("name") private String name; @i18n @TableField("name_i18n") private String nameI18n; // 其他字段... } ``` ### 4.4 在服务层处理国际化字段 在服务实现类中,可以根据是否启用国际化来决定是否查询国际化字段: ```java public class ProductServiceImpl extends BaseServiceImpl implements ProductService { @Override public Result> page(ProductPageReq req) { MPJLambdaWrapper wrapper = new MPJLambdaWrapper<>(); wrapper.selectAll(ProductDto.class); // 根据是否启用国际化,决定是否查询国际化字段 checkI18n(wrapper, ProductDto.class); // 其他查询条件... return Result.success(baseMapper.selectPage(req.getPage(), wrapper)); } } ``` ## 5. 最佳实践 ### 5.1 统一使用枚举定义消息 所有国际化消息都应该在 `I18nMessageEnums` 中定义,避免硬编码消息字符串。 ### 5.2 合理使用占位符 消息中的可变部分应使用占位符 `{0}`, `{1}` 等,而不是字符串拼接。 ### 5.3 控制器方法添加国际化注解 所有需要支持国际化的控制器方法都应添加 `@i18n` 注解,并指定适当的 `format`。 ### 5.4 国际化字段命名规范 国际化字段通常以原字段名加 `I18n` 后缀命名,如 `name` 和 `nameI18n`。 ## 6. 注意事项 1. 国际化功能依赖于 Session,确保在无状态环境(如API网关)中正确传递语言信息。 2. 国际化消息配置文件的修改需要重启应用才能生效。 3. 默认语言为中文(zh),如需更改默认语言,需修改 `I18nMessageEnums` 中的定义。 4. 在前端请求中,可通过 `language` 请求头指定语言,如 `language: en`。