Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

多语言需求及后续方案思考:2021-7-16 #29

Open
jsonz1993 opened this issue Jul 27, 2021 · 0 comments
Open

多语言需求及后续方案思考:2021-7-16 #29

jsonz1993 opened this issue Jul 27, 2021 · 0 comments

Comments

@jsonz1993
Copy link
Owner

本文为博客迁移过来,原文链接: 多语言需求及后续方案思考:2021-7-16

最近接到一个需求,是做tim系统某个模块的多语言,只是多语言还没到本地化的程度。因为这块主要是想解决海外的同事看不懂中文的问题,所以要求没有特别高。

因为目前团队开发和维护多个项目,所以想着多语言的框架可以尽可能的统一,最后选定了社区比较流行的 i18n。
这样虽然项目有些用react,老项目用angular,有一些项目混着angular和react,但是都可以基于i18n去开发,减少开发使用和配置的成本。对于工具这块不再过多赘述。

在上家公司也做过多语言,当时是有个管理后台维护,然后前端负责把词条写上去并且新建一个key去替换本地的文件。这种方案适合一些文案很多,维护灵活的项目,比如电商项目等就比较合适。

目前项目里的多语言都是放在前端维护,前端讲产品和供应商给出的翻译写成两个js文件,然后判断当前的语言环境选择对应的语言包来展示,适合词条比较少的,或者业务文案相对稳定的。比如投资系统就很适合,专业名词改动少,也很少出现大片大片的文案。

这次做的是某个模块的替换,给的时间3-4天,相对比较宽松。所以决定花点时间写个小小的工具来节约一点时间划水摸鱼...

需求分析

这里我负责是tim项目的某个模块,这个模块都是angular1.x实现的。文案绝大部分是写在模板 html中,少部分写在 js中,还有一些是参杂了变量的。

首先我们明确要做的事情:

  • 第一步是将产品给出的在线excel转换为我们的 xxx-en.js 和 xxx-zh-cn.js
  • 第二步将项目中html的词条替换成我们多语言中对应的key
  • 第三步将项目中js的词条也做对应的替换

这里第二步举例来讲就是<span>截止录入时间</span> 替换为 <span>{{ 'portfolioMonitoring:entry_deadline' | i18next }}</span>
这里的 {{ key | i18next }} 是angularJs中filter的写法,其他框架思路不变,key则是由 namespace和wordKey组成。

第三步是js替换,所以和其他框架没有什么区别,比如将

vm.crumbs = [{
	name: '截止录入时间',
	link: '...'
}];

vm.crumbs = [{
  name: $i18next.t('portfolioMonitoring:entry_deadline'),
  link: '...'
}];

实现

这次的目标是先写个小工具来简化一些工作。

第一步 excel转语言包:

这一块比较简单,先将产品给的在线文档,导出一份csv格式。然后直接用Node去把csv转成对应的两份语言包文件。
具体的做法是利用 csvtojson 将csv转成json,然后取里面英文翻译做一个 snakeCase 转换,比如 Entry Deadline转为 entry_deadline,作为中英文的key,最后生成两份文件:

// xxx-en.js
{
  'entry_deadline': 'Entry Deadline',
}

// xxx-zh-cn.js
{
  'entry_deadline': '截止录入时间',
}

第二步 html模板的替换:

读取目标模板,解析模板并对里面单块的文字进行提取(这里没有对值替换模板做处理,省事),然后根据这个文字在我们的语言包里面查找,如果找到的话,就做对应的key值替换。

具体做法是先读取语言包,生成一份中文与key匹配表, map = { '截止录入时间': 'entry_deadline' }。然后对模板文件进行语法分析,这里我用的 posthtml来解析html ast。然后对html中的 innerText 或 attr 属性进行取值匹配,如果匹配到了,就替换为对应的key值,最后将替换后的文件重新写入到对应的模板文件中。

第三步 js模板的替换:

js的替换其实和html的思路一样,只不过选择的解析工具是 @babel/parser,然后配合 @babel/traverse@babel/template 来实现替换。

具体实现是先解析js文件将其解析为 ast,然后用 traverse 来处理这段ast,对里面的 StringLiteral 类型进行处理,如果匹配到对应的中文语言包,则替换为对应的ast。最后将ast重新用 @babel/generator 生成js文件替换源文件。

const i18nCallExpression = template(`
  $i18n.t('I18N_KEY')
`)
traverse(ast, {
  StringLiteral(path) {
    ... 匹配 ...
    path.replaceWith(
      i18nCallExpression({
        I18N_KEY: `${namespace}:${key}`
      })
    )
  }
})

next

这个小组件花了半天时间写,跑完估摸着比手动替换节约了至少60%的工作量,投入产出比还是挺高的。

不过项目中必不可少的还是要人工过一遍,这和项目的性质有关,如果是一些比如文档类型的,这种很容易做词条替换。但是对于一个投资系统来说,里面有着大量的模板值替换,单复数形式目前还是要手动去替换,比如a company3 companies这种场景。所以下一步想把这块做深入,也不会考虑做构建后的替换,而是采取预构建替换,然后人工再审核一遍。

还有一个问题就是旧项目没有严格的 eslint和 prettier,所以用js转换之后,会出现大量的格式问题,虽然不会报错,但是在cr的时候会造成非常大的影响。
对于这次迭代我的解决方法是直接先用js跑一遍这样格式可以保持统一,然后提交cr,再跑一遍去替换。

这只是一个最基础的beta原型,我们接下来可以往几个方面去深入优化。

  1. 目前文案的收集是产品在页面上人工收集的,可以改为用脚本去扫项目,将识别到的中文提取出来转换为csv文件,减少人力成本。
  2. csv生成js的模式,目前是全量替换,后面应该是做到增量替换,比如我原本已经有一个语言包文件,转换完后我又去改了一个地方,但是后面有一份新的csv,这时候跑完应该是增量的添加到文件中而不是全量替换了。
  3. 对于 多语言方案,应该是有一个工具提供基础的服务,比如csvToJs, 文件提取中文,中文替换为i18nKey。然后对应的各个模块用插件去实现,比如 提取中文,可以是 html,也可以是 js或jsx,这些提取的能力都是插件去提供。比如我在tim项目中用的是angular的模板,那我的解析规则可能就和tpp项目中普通的html模板或者jsx不一样。再比如替换的规则,可能tpp和tip用的都是react,但是他们内部基于 i18next封装的组件有一点差异,这些都可以通过自定义插件去磨平这些差异。
  4. 定位难的问题。 做过多语言项目的人应该都知道,如果替换为词条有一个很蛋疼的问题就是,比如我在页面上发现一个问题,我要去改,最直接的方式就是搜一下这段文案。但是你都替换成词条了,这就很尴尬。所以如果是文档型的页面,你可以使用构建替换的方式去解决这个问题,写的时候常规的写,构建的时候再去替换。但是对于我们项目来说这不太行。解决的方法目前我能想到的有两个:
    • 将中文做为多语言的key,这样对于开发人员相当友好。但是会有一个问题,如果后面这个key改了的话,可能会很诡异,比如最后变成 '取消': '确定',让人摸不着头脑。
      // zh-cn.js
      {
        '截止录入时间': '截止录入时间',
      }
    
      // en.js
      {
        '截止录入时间': 'Entry Deadline'
      }
    
      // js
      const name = i18next.t('截止录入时间');
    
      // html
      <span>{{ '截止录入时间' | i18next }}</span>
    • 将中文作为默认的fallback,比如 `i18n.t('entry_deadline', '截止录入时间'),然后中文的维护可以不单独写一个语言包,这也是一个折中方案。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant