伴随着 halo 的迭代发展,也开始慢慢有了一些国外友人。但直到目前为止, Halo 的国际化进程都很不理想,主要原因是国际化需要考虑的东西有点多,也没有一个系统的讨论及思路。因此本文档将探讨关于 Halo 国际化开发的一些设想以及草案。本文档由 LIlGG 编写

一、国际化注意点

国际化一般来说需要注意如下几点:

  1. 页面文案国际化,而且可以细分为如下几部分
    1. label 普通文本
    2. placeholder 提示文本
    3. 字段校验提示信息(该部分有可能由后端传入)
    4. href 超链接
  2. 页面样式国际化,对于不同的文字,样式可能会出现不同的变化
  3. 日期、数字格式化,尤其是对于入库的数据(可以考虑直接使用服务器的日期,但可能并不合适)
  4. LTR / RTL ,对于某些国家而言,阅读习惯可能是从右往左(如果不考虑少部分国家可以忽略)
  5. 编码问题(目前来说,将所有编码设置为 UTF-8 就够用了)

额外的,对于 Halo 来言,需要考虑一些特殊的国际化需求

  1. markdown 编辑器国际化(归类到 admin 国际化)
  2. 主题配置文件国际化(归类到后端国际化)
  3. 主题国际化(由后端驱动,即在渲染期间就完成国际化处理,而主题只需要提供对应的国际化文案即可)
  4. 评论组件国际化(归类到其他国际化)

另外,我认为国际化最好只在 view 层实现,避免在进行逻辑处理时接触到国际化的问题。例如,对于日期、数字等,在前端进行处理,之后再传递到后端。

二、整体国际化架构选择

实际上,主流的翻译还是页面文案国际化,根据我所了解到的知识,目前主流的国际化框架有两种。文案 - 翻译,以及 文案 - 配置平台 - 翻译

文案 - 翻译

“文案 - 翻译”是最基本、最传统的国际化解决方案:先将代码中的文案提取到资源文件,例如(zh-CN.json),之后将资源文件交给翻译,翻译给出各国语言版本,每种语言对应一个资源文件。

  • 适用场景
    • 页面上需要提取的文件比较少
    • 支持的国际化语言较少
    • 不会发生大的需求变动
  • 优点
    • 最基本的方式,一次到位
    • 成本低,适合于快速支持国际化需求
  • 缺点
    • 开发与翻译耦合,翻译修改之后,只能等待下个版本发布
    • 无法协同翻译
    • 单一系统,对于跨语言的系统无法使用

文案 - 配置平台 - 翻译

“文案 - 配置平台 - 翻译 ”是较为现代化的一种国际化解决方案,使用了额外的配置平台解决了文案和翻译过于耦合的问题:仍旧现将代码中的文案提取出来,之后将提取出来的文案上传至“配置平台”,翻译团队可以在任意时间段在“配置平台”上进行翻译,而前端代码可以定时或采用其他策略从“配置平台”获取翻译到的最新文案。

  • 适用场景
    • 有较为大的国际化需求,且词条数较多
    • 可能需要具有文案版本控制的情况
    • 有后续将其作为通用翻译平台的想法
    • 跨语言、跨端国际化处理的需求
  • 优点
    • 开发人员与翻译人员分离,文案录入与文案翻译相分离
    • 可以进行协同开发
    • 翻译内容可以随时可见,无须等待版本发布
    • 可以具有文案版本控制
    • 可以进行多端统一国际化
  • 缺点
    • 投入巨大,在国际化的基础上,还需要额外增加配置平台

三、Halo 的国际化方向

第一版本的国际化尽量保持最简单的实现方式,因此先以最简单的方式来考虑 Halo 的国际化方向。而且整个 Halo 的国际化可以分为三个阶段:

  1. Admin
  2. 后端
  3. 主题及评论组件

而这三个不同的阶段,可能会产生一些耦合,如下:

  • admin 中关于主题的设置
  • admin 中后端报错的提示语
  • 主题的国际化渲染,需要由后端驱动
  • 评论组件需要根据后端,或者 Admin 的缓存来校验国际化语言

以下分阶段构思一下

admin 国际化方向

admin 是本次国际化的重点。也是第一阶段必须完成的事情。需要国际化的位置如下:

  1. 各种页面上的静态文案,包括代码内的提示信息,下拉选项等
  2. 动态文案,比如从后端传入的日期等
  3. 编辑器上标签的提示信息
  4. 主题设置的内容

如上即为目前而言,Admin 需要国际化的方向。

后端国际化方向

尽管后端不像 Admin 那样有太多的地方需要国际化,但一些核心的东西,还是需要进行国际化的。例如提示信息,主题国际化解决方案等,还需要同其他国际化阶段进行配合改造。具体方向如下:

  1. 提示信息、异常信息等
  2. 日志信息(日志信息比较特别,可以不进行国际化)
  3. 入库数据,日期等(这个地方是特别要注意的。一般入库数据由 admin 或者 评论组件传入)
  4. 由于是其他模块的核心,比如主题和 Admin,因此需要考虑与这两方面的交互性。

主题及评论组件国际化方向

主题与评论组件实际上与后端是比较耦合的。且对于主题和评论组件,需要明确其国际化面向人群【不同于 admin,admin 仅面向个人,而主题和评论将面向所有浏览者,因此国际化的对象究竟是 个人还是所有使用者,这个很重要】。

针对于主题,主要的国际化方向如下所示:

  1. 文案渲染。
  2. 根据 admin 的需求,提供相应的国际化配置文件

对于评论组件而言,由于其是独立于 后端和主题的,因此也需要有自己的一套国际化标准。主要国际化的方向也是文案。

方向总结

总结而言,整个国际化方向如下图所示

画板

四、具体实施方案

前三个大标题部分,通过设想以及构思,分析了一下目前的国际化开发方向。下面就将针对于上述的所有方面,进行一些细节性的开发描述。在复杂的地方将会配图来叙述。同样的,也分为三个大阶段来进行分析,其中某些阶段,可能互相之间会有所耦合。

预期期望

对于 Halo 的国际化方案,目前来说我的期望如下:

首先,用户打开 Halo Admin。无论是否为初次使用的用户,都应该在页面的某个位置有语言切换的选项。之后,该用户选择自己熟悉的语言,例如中文,当然用户如果很懒也可以不选择,我们会默认匹配浏览器的语言。随后前端应该将用户的选择保存在任意位置(例如 cookie 或者 localStore)。之后,无论该用户在 Admin 上执行什么操作,均需要转为对应的语言进行呈现。

当用户想配置主题时,点开 Admin 的主题设置,后端将能识别当前用户的语言选择,然后向他推荐合适的主题配置。(如果主题作者并没有提供对应的语言配置文件,那就只能使用默认的了 -_-!)。

当任意用户查看自己的站点时,将会进行后端驱动式的国际化。如果目标用户不存在国际化语言设置,那么会为他设置一个与浏览器语言相匹配的语言。如果主题作者没有设置语言切换选项,那么该用户浏览时将处于此语言下。当然,这一切的前提是主题作者提供了对应的国际化语言配置, Halo 的功能仅仅是在渲染期间替换对应主题的国际化文件。这里有一些需要注意的事项,例如不同渲染引擎的主题实现方案,这在后续的主题国际化设计方案中会详细介绍。

整体方案

Halo 目前的方向是后端与其他所有项目分离,仅作为内容服务器提供 API。这样做的好处是生态环境可扩展,但也造就了其太过于灵活,导致很多国际化传统方案无法在 Halo 上实施。你无法预料用户的 Admin 端是什么,也无法确定用户的主题是用什么语言写的,更无法确定用户的评论是怎么实现的,我们不会去考虑这些无法控制的事情,但好在我们有一些可控制的方面,例如:

  • RestAPI
  • 基于模板引擎的主题
  • 默认的 Admin
  • 默认的评论组件

我们的国际化方案会完全适配这些。而对于其他基于 API 的服务,我们仅仅能够通过协商、约定来实现国际化方案并且按照标准流程处理,但具体是否要按照约定来进行国际化处理,需要其作者来考虑。

请求值携带方案:

目前我所了解的基于 API 的国际化方案请求携带参数有如下几种方式:

  1. 基于 URL 方案,在请求链接参数位置携带语言,例如 https://xxx.com?lang=en
  2. 基于 URL 方案,在请求链接主机位置携带语言,例如 https://xxx.com/en/
  3. 基于 Cookie 方案,用户请求时,在 Cookie 中携带对应的 lang Cookie 字段
  4. 基于 Header 标识字段,用户请求时,在 Header 中定义 Language 字段(accept-language),用于携带用户语言信息。
这些方式各有优缺点,具体使用哪一种或多种,需要讨论之后做决定。【本人更推荐最后一种,这种是现代浏览器普遍支持的方式】 国际化值标准: 后端处理的将是详细到地区的语言值,例如 zh-CN、en-US 等,对于不符的情况,需要将其转换为常用语言类型。例如 zh 转为 zh-CN。 默认国际化值: 优先采用浏览器设置,如果浏览器设置不存在,则默认为 zh-CN。 ## 后端国际化 后端作为核心部分,且承载着其他国际化的中心位置,是需要首先考虑国际化的地方。整个后端国际化贯穿 Halo 的生命周期,它的国际化基本流程如下所示: 画板 其中,国际化需要处理的就是其中的获取语言类型、查找国际化方案、处理国际化内容这三个主要方向。 ### 获取语言类型 获取语言类型是国际化处理最基础的一步,并且获取在单次请求之中可能会被多次调用,因此需要注意效率问题。 由于 Halo 未来极有可能会涉及到多用户及授权,并且后端会极大概率并行处理多个请求,因此后端并不建议持久化保存某个用户的语言选项,这个责任交由前端来处理是最理想的方式,后端仅仅会在各个请求中去获取请求所需的语言。获取语言值的方式如下步骤 1. 从请求中获取语言类型的方式有很多,这里推荐使用拦截器统一解析语言值,也可以采用 AOP 的方式定制化。 2. 而保存语言值采用类似于 RequestContext 来保存当前请求所需的语言,使用 ThreadLocal 为不同线程的请求保存语言类型是较为理想的设计方案。当然,对于单个请求而言,开发者有可能也会主动使用线程处理请求,那么可以采用类似 TaskDecorator 的方式为每个线程缓存当前请求的 RequestContext。 3. 在需要国际化的地方,可以使用工具类从当前请求线程的 RequestContext 中获取语言值组装成 Local 并返回。(也可以在初次保存的时候组装) 如上整体逻辑如下图所示 画板 特别需要注意的,当获取到的语言值不包含地区时,例如 en、zh,需要转为对应的常用地区值,例如 enUS、zhCN。 ### 查找对应的国际化方案 目前 SpringBoot 上主流的方案是将所有国际化内容放在 resources 包下的 i18n 文件夹内。为了能够享受到编辑器的优化措施等, Halo 的 i18n 也将采用此种方式。 目录结构示例如下: plain ├── resources # 资源文件夹 │ ├── i18n # 国际化语言文件夹 │ │ ├── error # 异常类型国际化语言文件夹 │ │ │ ├── error.properties # 异常类型默认国际化文件 │ │ │ ├── error_zh_CN.properties # 异常类型汉语-中国国际化文件 │ │ │ └── error_en_US.properties # 异常类型英文-美国国际化文件 │ │ ├── info │ │ │ ├── info.properties # 信息类型默认国际化文件 │ │ │ ├── info_zh_CN.properties # 信息类型汉语-中国国际化文件 │ │ │ └── info_en_US.properties # 信息类型英文-美国国际化文件 │ │ ├── default.properties # 默认的国际化语言文件 │ │ ├── default_zh_CN.properties # 默认的汉语-中国国际化文件 │ │ ├── default_en_US.properties # 默认的英文-美国国际化文件 │ │ └── ... 其中关于各个文件夹分类是以什么为标准还需要进行详细讨论。默认的 default 国际化文件作为基础文件,将作为保底措施。

在需要用到国际化内容的地方,将根据不同的语言值,自动获取语言下的配置信息,例如 zh_CN 语言值对应 error_zh_CN.properties。

properties 内容大致如下:

  1. # error_zh_CN.properties
  2. hello=你好
  3. # error_en_US.properties
  4. hello=hello

如果想查找到 error 下的 hello,需要如下 key:error.hello。同理,如果想查找 info 下的 hello,需要 info.hello。比较特殊的,对于 default,无需使用 xxx.hello 这种方式,可以直接采用 hello 来实现。(当然,也可以直接写 hello.info 这样的key)

无需担心多个不同的文件夹有重名现象,因为最终处理的时候会加上文件夹名称,例如 error.hello、info.hello 。如果确实有重名,将会使用覆盖的方式。

需要注意的点:根据包状态的不同,读取国际化文件的方式也不同。例如开发环境下从文件读取,而 Jar 包环境下需要从 Jar 包内读取。

处理国际化内容

根据不同的需求,处理方式也不同。以下按需说明

一、文本

对于后端的文本,例如提示信息,直接采用自定义 MessageUtil.getMessage(String msg) 方法进行替换即可。

二、日期

日期可以根据时区进行时间偏移,目前还是推荐数据库中保存格林威治时间,这样做国际化时只需要根据格林威治和时区进行相应的偏移量即可。

三、主题

主题目前的国际化目前是后端国际化进程中比较复杂的一项,因为需要考虑多个维度,目前考虑的有如下方面:

  1. 基于模板引擎的主题
  2. 基于 Content API 的主题
  3. 主题配置

下面就根据这三个方面的处理,分别进行阐述。

1、基于模板引擎的主题国际化

基于模板引擎的主题可以使用后端驱动的方式进行国际化,针对各个不同的模板引擎,具有不同的国际化处理方式。但整体操作中,国际化模板需要读取对应主题下的国际化语言文件而不是后端的国际化语言文件,这块要特别注意。

对于读取主题下的国际化语言文件规定如下标准:

  • 对于整个系统来说,即便后续实现多用户方案,激活的主题也永远只会有一个。因此,主题国际化无论何时内存中只能存在一份,应当在用户切换主题后进行主题国际化数据的重新加载。
  • 在初次启动系统时,应当读取当前激活主题的国际化。如果用户还未进行博客系统安装,则读取默认主题国际化数据。
  • 用户更新主题后,应当重新加载主题国际化数据。
  • 主题国际化数据允许不存在,未读取到也不应报错。
基于模板引擎的主题国际化文件存储位置应当在讨论之后确定。

① Freemarker

Freemarker 国际化默认会读取后端的配置,因此需要进行一定的处理,让其读取主题下的国际化文件。与后端不同的是,主题中可能会出现如下的情形

  1. 1. 主题作者未添加国际化文件。
  2. 2. 主题作者添加了无法读取的国际化文件。
  3. 3. 主题作者添加了正确的国际化文件,但缺少浏览者所需要的语言文件。

以上情形作为常见的国际化文件异常情况,应当予以关注。另外,在处理时均需确定国际化文件夹是否存在

对于 Freemarker 的国际化翻译实现方式,使用自定义方法即可实现。其中方法名称待定,默认 _

2、基于 Content API 的主题国际化

基于 ContentAPI 的主题后端没有任何的可控性,因此只能由前端自行设计国际化方案,后端仅仅会在 Context API 中处理国际化内容,其余的国际化内容由主题开发者自行处理。

3、主题配置

主题配置与 Admin 紧密关联,而且无论是以任何方式编写的主题,主题配置是约定俗成的,因此此处属于后端可控的国际化方案。

主题配置国际化将根据 Admin 的配置读取 API,在对应的主题配置文件中读取对应的语言配置文件即可。如果不存在对应的语言配置文件,则返回默认配置文件。

admin 国际化

主题国际化

评论国际化

五、参考文献

Internationalization

国际化 - 通用 LTR/RTL 布局解决方案