w3ctech

单页面应用的前端多语言处理

Teambition从今年年初开始做国际化,当时的目标是两个季度后能够在中文以外支持英文和日文两个语言,因此团队花了一些时间来尝试一些适合Single Page Web App的多语言方案。本文是Teambition的前端工程师陈涌总结的一篇文章,推荐给每一位希望打造全球化的网页应用的工程师看看=)

Teambition的所有产品线都是单页面应用, HTML 在前端渲染因此在需要支持中英文时, 多种语言都加载到前端去渲染. 在切换到这个方案以前, wang'ye的语言是在服务端跟代码一起进行转换的,下面写的是切换前遇到问题, 和切换过程我们采取的方案.

切换以前后端渲染的方案

最开始的方案当中, 在开发阶段, 代码编译, 语言替换, 都在服务端通过中间件完成, 当浏览器请求对应的代码时, 获取的就是已经编译好的 JavaScript, 已经对应的语言 因此模版也是在后端编译的, 而多语言的内容, 就是通过其中一个 i18n 中间件完成的.

比如, CoffeeScript, doT 源代码当中会有一些这样的内容, 用来标记多语言, 这类代码会在发送浏览器前会被处理, 编译成对应的中文版或者英文版:

errorMessage = "{{__essageMessage}}"

在编译上线的阶段, 整个编译过程我们会对两种语言都执行一次, 生成两种语言的版本. 用户请求应用时, 拿到的就是预先编译好, 放在 CDN 上的对应语言的代码.

后端编译的问题

开发阶段, 服务端编译, 因为 Node 本身性能问题, 加上应用体积增大, 响应的速度变得很慢. 加上前端调试普遍的做法就是改一行代码就刷新一次页面, 收到性能的影响就更大了.

除了性能, 还有个问题是资源打包, 在我们尝试去掉中间件转而用前端工具编程代码的过程中, 当语言是通过静态替换强制写入语言的, 意味着代码传输到客户端以前, 两种语言的源码文件已经存在, 于是, 上线之前就需要进行两次打包, 或者说就是打两份. 按上边说的, 会有一些性能上的麻烦.

更大的麻烦在于编译过程, 因为两种语言的存在, 出现了一些意外的复杂问题. 代码上线以前的编译, 大致的流程是:

1) CoffeeScript 先转换成 JavaScript, 2) 然后 JavaScript 经过 RequireJS 合并, 我们也会留一个环境测试一下合并的代码, 3) 最后代码上线之前, 会经过压缩,, 最后压缩的代码再上线.

编译过程中间有个调试过程, 这个调试过程, 基本上需要语言的, 也对应刚编译到 JavaScript 这一步. 这就需要在编译到 JavaScript 的时候, 就是上边的编译过程的第一步, 就已经做了多语言. 这之前还要加入一个步骤, 把最初的源代码编译出中英文两份. 最终, 开发目录就需要用到多个版本的项目代码:

1) 源码 2) 多语言的源码 3) 编译好的开发代码 4) 合并好的代码 5) 压缩准备上线的代码

注意, 从 2) 开始, 每个源码都需要有中英文两份, 通常整个编译过程写下来, 上百行的 Gruntfile 是有了, 随着两种语言两倍的代码量, 整个更加复杂. 中间还有 RequireJS 之类的问题, 受到源码复杂的路径影响, 具体这里不展开. 其他还有 LESS, doT, Jade 几种语言的编译, 以及相对位置处理, 都比较繁琐..

我们的方案

当然解决方案基本的思路也是从很多已有的项目里学的, 就是语言放到前端去渲染. 用这个方案, 代码编译的过程就不用编译多个版本了, 编译过程简化.. 同时修改一行语言, 就不用整个 CoffeeScript 文件都编译, 只要页面刷新就好了. 然后项目当中维护的文件跟着变少了, 相对维护多种语言的代码就轻松了很多

这个方案的思路是, 语言文件会被整理成两张 JSON 的表, 作为 RequireJS 模块一起打包. 前端代码运行过程中检测浏览器的语言环境, 或者写在设置里的语言, 根据这个语言, 从表中查询内容, 用在页面渲染. 比如这样的代码:

lang.getText('error-message')

当然浏览器当中, 模版一旦渲染完成, 多语言的内容就应该马上跟上, 否则用户看到当成乱码了. 这里可能有两种策略, 一种模仿后端, 在渲染模版时将语言作为数据传入. 另一种, 就是通过 DOM 操作, 在模版渲染后通过 jQuery 抓取节点渲染语言.

大致出于两个考虑, 我们选择了后一种:

1) 不想在模版引擎中嵌入太多逻辑, 而且前端的模版相对后端较弱, 频繁插入数据不那么方便, 2) 使用 DOM 操作就有可能在不进行页面刷新的情况下切换语言了, 更加灵活

于是为了语言的信息能在前端被 jQuery 正确识别, 我们用了这样的代码来标记:

<span data-lang-text="error-message"></span>

对这样的代码, 我们写了一个 CoffeeScript View 方法, 用来对当前 View 的 DOM 进行替换.

renderLocales: ->
  # 获取有 data-lang-* 属性的所有标签
  # 逐个标签读取
  # 取出属性当中的内容作为 key, 查询语言
  # 按照 data-lang-* 的语义, 替换对应的语言

这当中的星号一般是上边的 text, 也有可能是 placeholder, 甚至 value 或者其他内容. 极端的情况还可能是中英文版本的图片, 那种情况就特殊问题特殊处理吧...

另一个没有讨论的问题是, DOM 渲染的性能问题, 通过属性查询确实不认为是高性能的, 这里先不深入, 因为实际应用中, 这部分操作数量不大, 性能方面还不影响用户体验.

到这里, 多语言渲染的问题基本完成了.

w3ctech微信

扫码关注w3ctech微信公众号

共收到3条回复

  • 学习!

    回复此楼
  • 可以解决一部分需求,但是比如我在做阿拉伯语页面的时候,就无法避免编译成多语言,阿拉伯语是从右到左的 哈哈

    回复此楼
  • 前面一大部分都跟我的做法一样,除了最后渲染部分 我是先加载对应的语言文件之后,直接拼接到模板的原始字符串里,然后通过doT来生成模板。 好处是只需要拼接一次,就可以多次调用,不需要每次调用后都再处理一遍语言

    回复此楼