写点什么

干货 | 用 JAVA 实现多语言翻译组件

用户头像
LigaAI
关注
发布于: 21 小时前
干货 | 用JAVA实现多语言翻译组件

不同领域对国际化的定义各不相同,这里谈论的是 W3C 国际化活动材料的高级工作定义。有些人使用其他术语(例如全球化)来指代同一概念。


国际化是产品、应用程序或文档内容的设计和开发,它可以为不同文化、地区或语言的目标受众轻松实现本地化。国际化(Internationalization)通常用英文写成 i18n,其中 18 是英文单词中 i 和 n 之间的字母数。[1]


本文主要基于 java,针对语言国际化进行阐述。 任何一个面向全世界的软件都会面临多语言国际化的问题,对于 java web 应用,要实现国际化功能,就是在数据展示给用户之前,替换成用户可识别的语言。


使用 spring 自带的 i18n(国际化)

这部分比较简单,在网上搜索就可以找到教程,这里将会手动配置一次 spring i18n 国际化来介绍一下。以下是笔者使用的 spring boot 版本号。



1-1.在 properties 或 yml 资源文件里面配置 i18n

笔者用的是 yml 文件,可以自行转换成 properties 文件格式。


basename:以逗号分隔的基名列表(本质上是一个完全限定的类路径位置),每个基名都遵循 ResourceBundle 约定,对基于斜杠的位置提供宽松的支持。如果它不包含包限定符(例如 “org.mypackage” ),它将从类路径根目录解析。


cache-duration:加载的资源包文件缓存的持续时间。如果没有设置,捆绑包将被永久缓存。如果没有指定持续时间后缀,将使用秒。



1-2.在 resources 文件夹下新增 i18n 文件夹,并新建相应的国际化文件


spring 容器启动的时候,会根据配置的 basename 去对应的路径加载资源文件到 MessageSource 里,至于是怎么加载到 MessageSource 里的,在这里就不展开阐述了。


文件配置如图 1-1 所示。

注意:红色框里的就是 i18n 资源文件的配置,绿色框是 idea 自动生成的文件夹,实际并不存在,无视就行。

图 1-1 i18n 资源文件





1-3.在代码中使用 i18n 进行国际化

这里演示的是比较简单的手动参数替换,还有更好一些的方法,比如说在响应数据写入流的时候进行参数替换。


1-4.测试 spring i18n

启动项目后,查看调用/test/console 接口返回的数据,调用三次,分别设置 header 中的语言:默认、中文、英文。笔者用的是 idea 的 HTTP Client,以下是请求参数:


以下是响应参数:

1-5.分析执行结果

1-5-1. 测试默认和测试中文的响应数据是一样的,可以确定系统默认使用的中文环境。

1-5-2. 调用 getMessage() 方法时,不传第 2 个参数就是无参替换;否则,反之。

1-5-3. 使用有参替换时,还可以在 properties 文件里加入 date、time 等参数,spring 可以自动格式化成对应的日期和时间。

1-6.结论

这里只是简单的演示了 spring i18n 的功能,可以满足一些简单场景的需求,如果需要进行扩展的话,有几种思路。

1-6-1. 如果使用了 nacos 等配置中心,则需要去注册中心手动拉取 i18n 的 properties 文件内容,并加载到应用程序的内存里,也可以在本地用户文件夹存放一份。

1-6-2. 如果需要一些正则翻译的话,则需要自己动手写正则替换的表达式。

1-6-3. 该例子展示的是在 controller 里进行替换,更好一点的方式是在 filter,甚至是在响应数据写入流的时候进行替换,比如说指定某个响应对象的某个属性的序列化类(@JsonSerialize(using = TestJsonSerializer.class)),则该字段序列化的时候,就会使用 TestJsonSerializer.class 进行序列化。在这个类里面就可以针对性地做我们想要的替换了。

自定义 i18n


spring i18n 的功能较为好用,但是面对复杂的业务需求,还不够强大。比如,用户想添加一种语言;递归替换;布局可以自定义,用户添加布局字段时,针对该项目或组织的不同地区的人员,设置不同的翻译内容等等。


基于各种各样的原因,扩展 i18n 已是必须要做的事。那么怎么扩展呢?


国际化的本质就是将 key 替换成不同语言的 value ,这句话中有几个关键点:**key、替换、语言、value。**其中 key 、语言、value 都是名词,代表着具体的数据;替换是动词,代表具体的翻译逻辑。那我们就需要针对这几个点进行设计与实现。

2-1.设计数据表

思路:通过一种语言和键找到对应的值。表结构设计比较简单,key、value、语言各建一个表,如图 2-1 所示。


图 2-1 国际化表结构设计


其中每个表只展示了主键编号字段,其实还有一些字段没展示出来,比如 code、name,这些可以根据自己的风格去设计。如果是多租户的系统,在每张表后面加入对应的租户 id,即可进行数据隔离。

2-1-1. 如果用户新增的字段需要翻译,往语言键里增加一条数据,以及往语言值里增加与语言定义相同数量的记录即可。

2-1-2. 如果用户新增语言定义,则往语言值里面增加与语言键相同数量的记录即可。

2-1-3. 更新、删除同理。

2-2.数据缓存设计


在一个面向世界的应用里面,翻译的频率是很高的,而且随着时间的流逝,翻译的数据肯定会越来越多,如果每次响应数据的翻译都去查询数据库的话,那势必会造成数据库性能以及应用本身性能的浪费。对于这种修改频率不算高的数据,咱们可以缓存起来,用空间换时间。


这里打算用两级缓存的设计来适应该翻译场景,一级是 redis,二级是应用内存。


step1: 将用到的数据从数据库缓存在 redis 里面,并且生成一个更新标志放入 redis。应用获取翻译数据的时候先判断 redis 更新标志是否为空。

step2: 若为空,则代表 redis 尚未缓存翻译数据,将翻译数据从数据库拉取到内存,且推送到 redis;若不为空,则代表 redis 已缓存翻译数据,然后再比对 redis 的更新标志和应用内存的更新标志是否一致。step3: 若不一致,则说明翻译数据已经改变,需要从 redis 重新拉取一次翻译数据,缓存在应用内存中;若一致,则说明翻译数据尚未改变,可以直接使用应用内存中的翻译数据。

step4: 将最后拿到的翻译数据(key-value)返回给实现翻译逻辑的组件。


如图 2-2 所示。



图 2-2 国际化两级缓存设计

代码如下所示。






2-3.将替换逻辑嵌入 spring 的 filter 或者序列化

笔者在这里只演示简单的 key->value 替换,至于递归替换、正则替换可以自行考虑加上。

A. 当一个请求进来的时候,首先需要做一些前置处理。

B.根据请求的语言设置当前线程的语言环境。

C.更新一次当前应用内存的语言缓存数据。

D.当返回响应的时候,通过序列化对响应数据进行替换。

代码如下所示。






2-4.测试自定义 i18n

启动项目后,查看调用 test/custom-i18n 接口返回的数据,调用三次,分别设置 header 中的语言:默认、中文、英文。

笔者用的是 idea 的 HTTP Client,以下是请求参数:


以下是返回参数:

2-5.分析执行结果

2-5-1.测试默认和测试中文的响应数据是一样的,可以确定系统默认使用的中文环境。

2-5-2.对于使用了 @JsonSerialize(using = I18nJsonSerializer.class)注解的属性,会根据 key 自动替换成对应的值。

2-5-3.根据 key 没找到值时,还是会使用原本的 key。

2-6.结论

这里只是简单的演示了自定义 i18n 的功能,但是已然支持用户新增语言、自定义翻译后的值、多机部署等。如果想要支持正则替换、递归翻译也可以自行扩展。

总结

这里演示了两种 i18n 的实现方案,具体想用哪种就见仁见智了。图方便,开箱即用,那就选 spring i18n;图灵活,可扩展性强,那就选自定义 i18n。自然,肯定还有很多我没想到的方案,期待交流。


后续 LigaAI 会继续分享更多技术干货的文章,欢迎关注 LigaAI@infoq~更多详情,请点击我们的官方网站 LigaAI-新一代智能研发管理平台


[1] "Localization vs. Internationalization".W3C

作者:rookie0peng

原文链接:https://www.jianshu.com/p/95e0f1df8edf

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

发布于: 21 小时前阅读数: 25
用户头像

LigaAI

关注

新一代智能研发管理平台 2021.02.23 加入

AI赋能工作场景,想要做最懂开发者的智能研发管理平台~

评论

发布
暂无评论
干货 | 用JAVA实现多语言翻译组件