前言: 在面对多语言环境的时候,我们通常采取多语言json方案去保存语料。
开发一个系统通常会面临一个问题,在代码写好之后,需要往zh-CN.json文件添加相应的语料。
比如一串包含文本的代码:
const Login = () => {
const intl = useIntl()
return <div>
<input type="text" />
<input type="password" />
<button>
{intl.formatMessage({ id: 'login', defaultMessage: '登录' })}
</button>
</div>
}
export default Login
在写好代码之后,还需要往json文件里面录入相应的key和文本
{
"hellow.world": "你好世界",
"login": "登录"
}
现在发现一个问题“语料的信息重复的出现在代码以及json文件里面”
是否可以通过技术手段实现这段人工重复录入的过程呢? 答案是肯定的。
思路是
从代码里面提取key和文本,然后保存到一个对象里面,再将最终汇总的对象生成json文件。
难点是如何将代码中的i18n信息提取出来汇总成对象,这个需求可以使用AST进行实现:
import traverse, { NodePath } from '@babel/traverse'
import { parse } from '@babel/parser'
const findI18n = (ast) => {
traverse(ast, {
enter(nodePath) {
// 找到 formatMessage 方法
if (nodePath.isIdentifier({ name: 'formatMessage' })) {
// 找到参数
const parent = nodePath.findParent(
it => it.isCallExpression() || it.isOptionalCallExpression()
) as NodePath<t.CallExpression | t.OptionalCallExpression>
const { properties } = (parent.node.arguments[0] as t.ObjectExpression) || {}
const idNode = properties.find((prop: any) => prop.key.name === 'id') as t.ObjectProperty
const messageNode = properties.find(
(prop: any) => prop.key.name === 'defaultMessage'
) as t.ObjectProperty
}
}
})
}
export const find18nByFile = (fileName: string) => {
const code = readFileSync(fileName, { encoding: 'utf-8' })
const ast = parse(code, {
sourceType: 'module',
plugins: ['typescript', 'jsx', 'classProperties']
})
findI18n(ast)
}
这里通过AST找到了单个文件的formatMessage以及对应的key和defaultMessage
现在只需要递归遍历所有文件夹就可以获取到需要的汇总对象
// 递归遍历文件夹并过滤
const getFiles = (dir: string) => {
const entrys = readdirSync(dir, { withFileTypes: true })
entrys.forEach(dirent => {
const filePath = resolve(dir, dirent.name)
if (/^((?!node_modules|dist\/|__tests__|locales).)*$/.test(filePath)) {
if (dirent.isDirectory()) {
getFiles(filePath)
} else if (/^((?!\.d\.ts).)*(\.ts|\.tsx)$/.test(filePath)) {
files.push(filePath)
}
}
})
}
最终把对象写到json文件里面
writeFileSync(resolve(rootDir, './src/locales/zh-CN.json'), JSON.stringify(result, null, 2), 'utf-8')