如何优雅地和译者协作开发多语言 WebApp

因为工作需要,未来开发的大部分 WebApp、H5 都需要国际化。现有工作流程比较简单:不同国家的译者在 Google Docs 的表格上翻译,开发者手动拷贝文本到前端项目中。对开发而言,由于看不懂文档中的大多数语言,既费时费力又容易出错。

所以我迫切需要一个能优化流程的工具。首先要使翻译的同事们不需要做太多改变 —— 总不能强求非开发人员学习 JSON 吧,又必须能让我显著减少浪费在拷贝和校对文本上的时间,专注于功能开发。

于是我写了一个叫 i18nCSV 的工具,主要做几件事情:

  • 解析 CSV 翻译文件
  • 返回多语言 Object
  • 输出各个语言的 .json.yml 文件
  • 字符串处理

译者仍然在 Google Docs 上工作,我只需把表格存为 CSV 格式再用工具跑一下,就能得到我要的东西。

安装

1
npm install i18n-csv --save-dev

解析

CSV 文件内容:

Field name en zh-cn jp
title Hello World 你好世界 こんにちは世界
subtitle The weather is great ! 天气真好! 天_はとても良いです!

使用 i18nCSV 解析:

1
2
const i18nCSV = require('i18n-csv')
i18nCSV.parse({ input: './file1.csv' })

返回的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
en: {
title: 'Hello World',
subtitle: 'The weather is great !'
},
'zh-cn': {
title: '你好世界',
subtitle: '天气真好!'
},
jp: {
title: 'こんにちは世界',
subtitle: '天_はとても良いです!'
}
}

设置起始列

默认使用第 2 行为正文的起始行,第 2 列作为正文的起始列。

有时我们需要指定正文的起始列。例如,为了和翻译人员协作,我们需要在正文前面插入两列描述内容,但是不希望这两列被当作语言解析到对象中。

CSV 文件内容:

Field name Description (Chinese) Description (English) en zh-cn jp
title 标题 main title Hello World 你好世界 こんにちは世界
subtitle 副标题 subtitle The weather is great ! 天气真好! 天_はとても良いです!

此时可以使用 startCol=4 指定正文从第 4 列开始:

1
2
3
4
i18nCSV.parse({
input: './file2.csv',
startCol: 4
})

类似的,使用 startRow 可以设置正文的起始行。

设置语言代码

默认使用正文的列标题(第一行)作为语言代码:enzh-cnjp,并用作输出的文件名:en.jsonzh-cn.jsonjp.json

当需要和其他同事,比如翻译或运营人员协作时,列标题应该更加友好、通俗易懂。此时可以用 # 设置语言代码。列标题中包含多个 # 时将始终使用最后一个 # 后的内容作为语言代码。

示例:

Field name English #en Chinese #zh-cn Japanese #jp
title Hello World 你好世界 こんにちは世界
subtitle The weather is great ! 天气真好! 天_はとても良いです!

输出

使用 ouput 指定输出目录,解析后会在目录下生成各个语言的 .json 文件。

1
2
3
4
i18nCSV.parse({
input: './file1.csv',
output: 'path/to/output'
})

输出完毕打印的日志:

1
2
3
4
5
Multi-language files has been generated !

source: ./file1.csv
output: path/to/output
format: JSON

如无意外,path/to/output 目录下已经新增了 en.jsonzh-cn.jsonjp.json 三个文件。

生成的 en.json 文件内容:

1
2
3
4
5
// path/to/output/en.json
{
"title": "Hello World",
"subtitle": "The weather is great !"
}

使用 saveAsYAML=true 可以输出更简洁、易于修改的 YAML 格式:

1
2
3
4
5
i18nCSV.parse({
input: './file1.csv',
output: 'path/to/output',
saveAsYAML: true
})

生成的 en.yml 文件内容:

1
2
3
# path/to/output/en.yml
title: Hello World
subtitle: The weather is great !

替换

假设有 CSV 文件内容如下:

Field name en zh-cn jp
title Hello World 你好世界 こんにちは世界
subtitle Share trips with #trips 参与话题 #trips 分享旅程 #trips とのシェアトリップ

为了自定义文本中的 #trips 的样式,我们需要对文本进行一些处理。

replace 用于添加 替换任务,它的每一个元素都是一个任务。任务(数组)的第一个元素是被替换内容,可以是正则表达式或字符串,第二个元素是替换内容。

例如,将所有 #trips<hashtag> 标签包裹起来:

1
2
3
4
5
6
i18nCSV.parse({
input: './file4.csv',
replace: [
[/(#\w+)/g, '<hashtag>$1</hashtag>']
]
})

返回的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
en: {
title: 'Hello World',
subtitle: 'Share trips with <hashtag>#trips</hashtag>'
},
'zh-cn': {
title: '你好世界',
subtitle: '参与话题 <hashtag>#trips</hashtag> 分享旅程'
},
jp: {
title: 'こんにちは世界',
subtitle: '<hashtag>#trips</hashtag> とのシェアトリップ'
}
}

任务的第三个元素可以指定仅对某个字段有效:

1
2
3
4
5
6
i18nCSV.parse({
input: './file4.csv',
replace: [
[/(#\w+)/g, '<hashtag>$1</hashtag>', 'subtitle']
]
})

提示

  1. CSV 文件的第一列始终作为每一行文本的 Key 。
  2. CSV 文件的编码必须是 UTF-8 ,否则可能出现解析后的内容包含乱码。Mac 下转换编码:使用 Numbers 打开 CSV 文件,点击菜单中的 文件 > 导出到 > CSV,在 高级选项 中将 文本编码 修改为 UTF-8 后导出。

相关环境:macOS 10.13 / Node.js 8.11 / npm 5.6