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

Angular1.x旧项目多语言改造方案 #31

Open
jsonz1993 opened this issue Aug 7, 2021 · 0 comments
Open

Angular1.x旧项目多语言改造方案 #31

jsonz1993 opened this issue Aug 7, 2021 · 0 comments

Comments

@jsonz1993
Copy link
Owner

jsonz1993 commented Aug 7, 2021

在之前的文章 #29 里面介绍了做一个多语言需求衍生出来的一个小工具,本文是基于第一个需求后衍生出来的一套完整的多语言改造方案,主要聚焦于方案与过程,不包括具体的代码实现。

背景

在以往的多语言改造需求里面,一般的流程是这样的

  1. 开发或产品或供应商的同事先去页面中找对应的文案: 比如这里检索到有:"投资组合监控" 的文案

  1. 把检索到的文案以及对应的翻译写在一个地方维护,比如共享文档等

  1. 开发介入先把词条转换为项目多语言配置,并且去人工查找这些文案,并替换为对应的多语言表达式

这种原始的方法且不说存在严重的效率问题,还有一个比较致命的问题就是文案难以发现,因为靠人工去页面收集词条,很难完全走完一个流程,必然会有很多遗漏的文案没有收集到,后面开发去替换的时候发现一些新的文案,再提交给供应商翻译,这种异步工作效率极其低下,拖慢项目进展。

在上一篇#29 中,虽然有用小工具可以去处理一些机械化的工作,但是也没有解决文案提取的问题,而且如果只是单纯的中文匹配提取,也会存在第二个问题,比如文案中参杂变量的情况:
demo1
如果按照纯中文提取,那么就会出现:相关公司 两个词条,翻译后变成 companies Home,这与一开始想表达的意思截然不同。

多语言方案

基于以上问题,我们探索出一套行之有效可维护的多语言提取替换方案,可以比较好的处理老项目中没有做多语言设计的问题。

大致的思路可以提取为:
image

  • 先扫描对应的文件目录,根据目录分层来定义当前的模块,后面的处理可以理解为都是以模块为单位的处理,比如有 views/authscripts/signoff
  • 根据扫描到的文件后缀来确定是用哪种方式解析提取替换,项目中大体可以分为类js的脚本文件和类html的模板文件。
  • 根据文件解析提取对应的文案后,直接把文案对应的多语言表达式替换原文案写入文件中。
  • 另一方面把提取出来的文案根据一定的规则写入到excel,给供应商翻译。
  • 基于excel生成一份项目中各个模块需要的多语言配置包,比如 signoff/i18n/zh.json 等等

这一套方案主要做的事情可以归纳为三个:

  1. 提取项目中的中文文案,并写入到excel
  2. 将提取文案的为止替换为多语言表达式
  3. 将excel转换为项目中需要的多语言配置文件
    这里说明一下为什么没有采用编译替换的方案,而是采用直接修改源码的方案。如果是一个偏静态网站或者只有少量文案的项目,那么直接写个loader在webpack编译的时候去提取替换是可以的,但是对于一个投资系统来说,里面大量复杂的业务逻辑,很难直接用一套静态编译的方案去完美的解决这种问题。
    采用修改源码的方式可以更好,更稳妥的处理与调试多语言问题。

方案思路捋好之后,最麻烦的地方有两处,一处是提取机制,另一处是更新机制。这里只展开说提取机制。在这个老项目中,主要的技术栈用的是 angular1.x,所以模板中参杂着大量的变量与angular表达式,在提取这一部分以angular html模板的提取展开说明。

文案提取

demo2

ast在线demo

先看一下一个比较常见angular模板是怎样子的,这里可以看到他的文案大致可以分为三类
第一类是纯文本类型,第二类是带变量的文案,第三类是表达式内的文案。

我们还需要考虑的一点就是,不是所有的文案我们都需要去提取替换的,比如

  • 文案中没有中文 <span>DL</span>
  • 注释类的中文 <!-- 测试 -->
  • 多语言表达式中的文案提示 <div> {{ 'module:id:测试' | i18next }}</div>

纯文本文案

纯文本文案的比较简单,直接对整句文案做提取,如果文案中包含了英文或标点符号也一并提取。

input:
<div >类型:投资组合监控。</div>
output:
<div>{{"portfolio:8e615e31:类型:投资组合监控。" | i18next2 }}</div>
{
  originString: '类型:投资组合监控。',
  zh: '类型:投资组合监控。',
  replace: '{{"portfolio:8e615e31:类型:投资组合监控。" | i18next2 }}'
}

带变量的文案

带变量类型的文案,这种文案在系统中相当常见,比如表格中的total提示信息等,对于这种情况,一开始处理是最小变量和中文的提取,后面发现说提取之后很容易缺少对原本语境的理解,最后还是确定了整一句文案都提取出来,然后提取文案中的变量作为i18next参数传入。

这里一开始变量是保留了原来的变量名,只是把一些关键的字符做替换,比如 {{vm.count}} 会被替换为 [vm@count],后面发现项目中很多变量不是这么简单的,比如说 {{vm.a.b.c.d.slice(0,2).join('x')}}等等解析完无法作为 i18next传参的key,所以最后还是决定直接用一个代号来代替原有的变量名。

input:
<span> 共{{vm.count}}条签署</span>
<span>运营:{{vm.operator}}</span>


output:
<span> {{ 'portfolio:8142267e:共[$0]条签署' | i18next: { '$0': vm.count } }}</span>
{
  originString: '共{{$0}}条签署',
  zh: '共{{$0}}条签署',
  replace: '{{ "portfolio:8142267e:共[$0]条签署" | i18next: { "$0": vm.count } }}'
}

表达式文案

表达式的文案这种在angular模板中最常见的就是 三元运算符与过滤器形式出现

input:
<span> {{ vm.count? '有签署': '暂无数据' }} </span>
<span> {{ vm.count | filter:'暂无数据' }} </span>
output:
<span> {{ vm.count? ("portfolio:a9f715c7:有签署" | i18next2 ): ("portfolio:21efd88b:暂无数据" | i18next2 ) }}  </span>
<span> {{ vm.count | filter: ("portfolio:21efd88b:暂无数据" | i18next2 ) }}  </span>
{
  originString: '{{ vm.count? "有签署": "暂无数据" }}',
  zh: ['有签署', '暂无数据'],
  replace: '{{ vm.count? ("portfolio:a9f715c7:有签署" | i18next2 ): ("portfolio:21efd88b:暂无数据" | i18next2 ) }} '
}

以上三种情况已经覆盖了项目中七八成的模板文案情况,当然期间也有一些其他的坑,比如某些angular属性不应该用来文案替换,比如对 ng-if 需要做忽略的操作,比如解析过程中发现有很多以前遗留下来的模板嵌套错误或者闭合错误的问题,又或者模板中如果有引号替换之后会不会出现一些语法错误等等,这里拎一个 angular 指令的问题来具体说明。

angular 指令

下面是一个很常见的angular 指令定义,我定义了一个叫 timDemo 的指令,其中包含了两个属性传值,一个是 string 用的是@,另一个expression用的是 =,这两个scope符号代表着单向和双向绑定,但是这里我们直接简单理解他们的差异就是。一个传的值直接当成字符串解析,另一个传的值会直接当成表达式来解析。

// 组件
app.directive('timDemo', () => ({
	template: `
    <div>
      <p>{{string}}</p>
      <p>{{expression}}</p>
    </div>
  `,
	strict: 'E',
	scope: {
		string: '@',
		expression: '='
	}
}))

// 业务
vm.world = 'world'

举个例子,这里我传 string的时候,直接传 hello world,他会当成是字符串直接渲染在组件中。但是我在 expression这个属性要传字符串的 hello world 时,就需要人为的在外面包一层引号来表示这是一个字符串,而不是一个表达式的变量。所以以下几种写法最终处理后的结果都是一样的,

<tim-demo
	string="hello world"
	expression="'hello world'"
>
</tim-demo>
<tim-demo
	string="{{'hello ' + vm.world}}"
	expression="'hello ' + vm.world"
>
</tim-demo>

demo3

这就很麻烦了,虽然这种运行时的状态严格来讲我们也可以通过多个文件去解析来得到这个属性到底是按字符串解析还是按表达式来解析,但是这个的工作量就会加大n倍。这里我直接去猜哪一种更有可能是属于表达式的类型,而不是通过文件解析去查找这个属性属于什么类型。

首先枚举了项目中 angularJs模板属性传参的几种类型,存在以下六种情况的属性传参:

<div
	expression1="'你好'"
	expression2="'你好' + vm.world"
	expression3="vm.count? '你好': '世界'"
	string1="{{'你好' + vm.world }}" 带变量(已实现)
	string2="你好,世界" 普通文本(已实现)
	string3="{{ vm.count? '你好': '世界'}}" 表达式文本(已实现)
>
</div>

于是归纳出以下几个规律,符合的才是一个表达式的解析形式:

  • 自定义的组件,组件的tag是以 tim-
  • 这个文案必须是一个属性值,而不是一个组件的 content
  • 这个文案里面包含的中文,是用引号括起来的

对于符合以上三种情况的文案,我都以表达式的模式去重新解析提取替换。

这时候我们会发现其实 expression1 和 expression3 我们之前已经处理过了,就是纯文本的类型和表达式内的文案提取:

input:
<tim-demo 
  expression1="'你好'"
  expression5="vm.count? '你好': '世界'"
></tim-demo>

output:
<tim-demo 
  expression1="('portfolio:7eca689f:你好' | i18next2 )"
  expression5='vm.count? ("portfolio:7eca689f:你好" | i18next2 ): ("portfolio:c086b300:世界" | i18next2 )'
></tim-demo>
{
  originString: '"你好"',
  zh: ['你好'],
  replace: '("portfolio:7eca689f:你好" | i18next2 )'
}
{
  originString: 'vm.count? "你好": "世界"',
  zh: ['你好', '世界'],
  replace: 'vm.count? ("portfolio:7eca689f:你好" | i18next2 ): ("portfolio:c086b300:世界" | i18next2 )'
}

小型语法解析器

回顾一下,我们开头的ast其实可以看到,不管是 content内容还是 attribute属性值,在 posthtml的解析中都是一个字符串出现,
posthtml也是基于 parserhtml2 封装的,所以不管是posthtml还是parserhtml2,我们拿到手的都是一个字符串。
所以上述的工作中,我的提取逻辑绝大部分是靠着正则去匹配提取以及替换的。
但是对于expression2的这种情况,正则确实已经无法做到准确的匹配提取出想要的结果。

<tim-demo
  expression2="'奇 巧' + vm.count +  '计程车'"
>
</tim-demo>

所以这里我老老实实的写了一个小的解析器,去解析这串字符串,将他转换为理想的表达式替换。

{
  originString: "'奇 巧' + vm.count +  '计程车'",
  zh: '奇 巧{{$0}}计程车',
  replace: '("portfolio:611c3ea3:奇 巧[$0]计程车" | i18next2:{"$0": vm.count, }  )'
}

至此,angular的html模板解析以及文案替换的工作做了七七八八了,但是还存在两个问题:
一个是 有一些文案特别复杂,确实没办法或者很难用脚本的方式去提取他,这种应该怎么办?
第二个是以上方案我绝大部分是用正则匹配,后面才用一个小的解析器去解析,但是实际上应该是要和 js一样,借助更强大的ast解析来做能达到更颗粒化的处理,那是不是应该把这些逻辑转换为用一个解析器去解析处理?

对于第一个问题我目前的做法是,提供一个忽略的机制,如果遇到一些比较难处理的,人为加上一个标志,解析的时候忽略他,后面人工处理。

第二个问题我也认为用解析器会更好的控制,也可以更加灵活的调整文案和语序,但是为什么现在还没有做呢?
因为...leader给我一周的时间让我把整个pc站文案提取出来...目前已经没有时间去处理了....

@jsonz1993 jsonz1993 changed the title 旧项目多语言改造方案 Angular1.x旧项目多语言改造方案 Aug 7, 2021
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