AST的应用:自动化zh-CN.json

160 阅读1分钟

前言: 在面对多语言环境的时候,我们通常采取多语言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')