imcuttle / personal-blog

personal-blog

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

国际化可视编辑(tiny-i18n)

imcuttle opened this issue · comments

背景

此前,前端项目中的国际化文本,由前端单独维护;这样带来的问题有:

  • 一旦涉及到文案的修改,需要前端介入,修改代码,CICD;
  • 修改起来不太直观,首先需要定位到页面对应文案的 key,通过 key 再进行修改

目的

  • 文案修改不经由前端处理,由 PM 自行控制;
  • 希望可以可视化,所见即所得地编辑

方案

为了改动量尽可能小,兼容老项目,所以先了解一下目前的国际化方案:

  1. 目前已有国际化字典为:
{
   apple: '苹果',
   apple_template: '${1} 吃了我的苹果'
}
  1. 加载国际化字典
  2. 前端使用字典
// _i 暴露在全局变量中
_i('apple') === '苹果'
_i('apple_template', '我') === '我 吃了我的苹果'

// 同时更多使用在 React Element 中
<div>{_i('apple')}</div>
<div title={_i('apple')}>{ _i('apple') + _i('apple_template', '我') }</div>

可视编辑方案

为了兼容老方案,所以基本使用不变;

  1. 在页面渲染的时候,能够察觉到对应的 React Element 中包含国际化文本(可能是 props 或者 children),进而渲染出可交互的控件;
    image
  2. 点击控件按钮(图中为左上角按钮),可以触发编辑
    image
  3. 编辑后进行保存,页面即可实现所见即所得的更新;

具体实现如下

  1. 拦截 _i 方法,原本只输出正常国际化字符串,处理后注入零宽字符,将 key 和 args 模板参数,使用零宽字符编码后,进行返回;这样 “看起来” _i 返回的字符串依然是正确的,只不过字符串长度对不上;
  2. 拦截 React.createElement 方法,判断一层长度,是否包含 _i 返回的字符串(需要校验是否能够支持 React 17 runtime transform 方法),进而注入额外交互和样式(红框和点击编辑),并且剔除掉 zwc(防止用户复制文本异常)
  3. 点击编辑后,希望能够高亮出使用了 当前编辑文本 的 DOM,和当前文本的 key 和参数,同时编辑完成后,希望同步生效;

其中的第 3 步,有两种实现方式:

原生 DOM 方式

在 React.createElement 拦截层,注入额外的 DOM attribute,表示当前 DOM 所使用到的 keyList 以及所对应的位置信息和参数信息(存在 nest/concat 等复杂关系,如 _i('a', _i('a', _i('a'))) _i('a') + _i('b'));
在点击编辑后,通过当前编辑的文本 key,通过 document.querySelectorAll([data-keylist]*=${key}) 来选择相关的 DOMList 进行高亮;
同时修改文本后,也可得到相关 DOMList 进而处理其相关属性成为修改后的文本,同时字典内的数据也需要更新;

这种方式有一定实现难度,因为其更新粒度细,需要考虑的 case 比较复杂;

React 数据流方式

使用 React Context API,在 root 使用 RootProivder(可以使用默认 Context.Provider),维护各个 key 对应组件内的 { forceUpdate, isActive } 的 Map 结构;
同时在组件内的 _i 方法来自于 useI18n() Hook,从而将各个离散组件的 forceUpdate 统一至 rootMap 中;
从而在对应 key 文本发生编辑改变后,同时字典内的数据也需要更新,触发对应的 forceUpdateList 调用,进而触发各个使用对应 key 国际化文本组件的更新;

这种方式对于原有代码有侵入,有改造成本;或者使用 Babel 进行编译处理,但是导致逻辑隐藏,为后面维护留坑;

结果

最终使用 “原生 DOM 方式” 进行实现,详见:https://github.com/imcuttle/tiny-i18n
已经在新项目中接入使用,搭配后端字典服务,已经在 2 个项目中正式使用