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 的魔法值。