Spring i18n 抽象封装实践
Spring 国际化基本使用
- 首先 maven 引入
spring-web的依赖 - 在
resources目录下,创建一个放国际化文本的文件夹,例如/resources/i18n/,然后修改resources/application.yml配置文件
| |
basename 指定翻译文本的前缀,可以带相对路径,即 i18n 文件夹。 messages 是文本前缀,文本名示例:messages.properties(默认文本,messages_en.properties(英文,messages_zh.properties(简体中文
- 文本文件格式
有两种,一种直接 k-v 结构翻译,key = value,翻译时传 key,直接返回 value 的字符串;
另一种用占位符大括号 {0} {1} {2}…
messages_en_US.properties:
| |
messages_zh_CN.properties:
| |
- 使用
MessageSource获取 i18n 文本
- 前端传 url 参数 lang,指定语言
- 当用户访问
/greet?name=John&lang=zh_CN时,返回的消息将是你好,John!;当用户访问/greet?name=John&lang=en_US时,返回的消息将是Hello, John!。
| |
当然,为了前端方便也可以统一用 HTTP Header,然后后端获取创建 java.util.Locale 对象
| |
- 后端使用
org.springframework.context.MessageSource#getMessage(java.lang.String, java.lang.Object[], java.util.Locale)翻译即可 - 参数分别为:文本 key;渲染参数数组(按顺序,地区对象
| |
抽象国际化功能
以上做法是直接替换掉国际化的内容,如果内容不多,且没有一些嵌套的文本,可以直接替换。
嵌套文本指的是,key1 对应 value 里面,其中一个占位符的内容也是动态生成,拼接而成的。假如 {1} 也是一个国际化文本,对应另一个 key2 的内容,这中嵌套就需要先翻译 {1},再渲染 value。
可以写一个工具类,抽象 i18n 渲染功能。
定义接口
I18nKey只有一个方法,获取 key,实现类是一个枚举I18nMessage是一条 i18n 信息,除了获取 key,还有获取渲染值列表;类型上界限定为I18nKey实现类;getValues()由于渲染的值对象不止String,还可能是还是一个I18nMessage,需要递归地渲染,所以获取列表泛型只限制 Object
| |
简单 i18n 消息
- 简单 i18n key,对应简单 i18n message,即简单 k-v 结构,无渲染参数
- 实现
I18nKey接口,是一个枚举类- 下面是审批示例,设计三类,审批添加、操作和撤销的 i18n key
- 成员变量只有
String key,需要实现接口方法getKey - 为了实现 key 还原枚举对象,实现
fromKey方法
| |
- 简单 i18n 消息体
- 成员变量是泛型中同类型的
SimpleI18nKey枚举对象 - 实现
I18nMessage接口方法- 获取 key,直接返回成员变量
- 获取渲染参数列表,简单 kv 消息,无渲染参数,返回
null
- 成员变量是泛型中同类型的
| |
复杂 i18n 消息
复杂 i18n 消息,即带有渲染的 i18n 消息,渲染值可能带有 i18n message 需要递归渲染
以添加操作审批日志为例, I18nKey 实现类同样是一个枚举,只有 String key 一个成员变量;
I18nMessage 实现类,泛型 ApprovalAddAdditionOtherI18nKey 限制 i18n key 类型,待渲染信息有两个,放在成员变量位置,同 key,由有参构造方法统一设置;可以看到 description 变量对应第二个参数 {1} 同样是一个 I18nMessage,该 i18n message 泛型限制为简单 i18n key;获取渲染参数 getValues() 时,按顺序传入 approvalId 和 description
| |
i18n 工具类
| |
- 渲染的主要类是通过
org.springframework.context.MessageSource - 渲染的参数
args虽然是Object数组,不限制类型,但是传入Integer时,渲染千位以上的值时,会自动带上千分位分隔符,类似1,000,因此渲染千统一将args转为String,保持原数值格式 org.springframework.context.MessageSource#getMessage(java.lang.String, java.lang.Object[], java.util.Locale)找不到 i18n key,默认会抛出NoSuchMessageException异常,而不是返回null,需要注意org.springframework.context.MessageSource#getMessage(java.lang.String, java.lang.Object[], java.lang.String, java.util.Locale)提供defaultMessage参数,找不到 i18n key 不抛异常,返回默认字符串processI18nMessage(String key, Locale locale, Object... args)参数同getMessage,直接根据字符串 key 获取 i18n 消息,不建议使用,因为实际使用有大量魔法值,即 i18n key 是魔法值字符串,统一用枚举管理便于复用,注释和查看引用。processI18nMessage(@NonNull I18nMessage<?> i18nMessage, @NonNull Locale locale)渲染 i18n 信息方法,只需要 i18n message 对象和地区对象Locale;I18nMessage会带有 key 信息,因此只需要一个对象即可resolveMessage(@NonNull I18nMessage<?> i18nMessage, @NonNull Locale locale)处理 i18n message 的核心方法,先判断 i18n message 是否为简单 i18n 消息SimpleI18nMessage,如果是,直接 getMessage;如果不是,需要将参数 args 依次获取,判断instanceof I18nMessage,是 i18n message 递归处理消息即可,直到所有国际化消息都被渲染
使用
| |
getShowAddition传入Locale地区对象- 先用
org.springframework.beans.factory.BeanFactory#getBean(java.lang.Class<T>)获取 i18n 工具类 - 创建简单 i18n 消息,指定其 i18n 枚举 key
- 创建复杂 i18n 消息,先指定复杂 i18n key,然后按渲染顺序传入成员变量,其中 description 是上面创建的简单 i18n 消息
总结
以上递归渲染的抽象工具类,在复杂的递归渲染场景可以比较方便地使用,语义和可读性都较高。
如只有简单的 i18n 消息渲染场景,直接使用 MessageSource 渲染即可,无需过度封装;注意管理好 i18n key 的魔法值。
