polaris-console icon indicating copy to clipboard operation
polaris-console copied to clipboard

[WIP]国际化

Open yidafu opened this issue 1 year ago • 5 comments

codemod

using jscodeshift

const CHINESE_REGEXP = /[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\U00020000-U0002EBEF]+/

function isContainChinese(word) {
  if (/^[\u0000-\u007F();:]+$/.test(word)) return false

  return CHINESE_REGEXP.test(word)
}
import { FileInfo, API, ASTPath, JSCodeshift } from 'jscodeshift'

export default function transformer(file: FileInfo, api: API, options: any) {
  const isJsx = file.path.endsWith('.tsx')
  const isDuck = file.path.includes('Duck.ts')

  let isModified = false

  let jsxModified = false
  const hasInserted = new Map()

  const j = api.jscodeshift
  const root = j(file.source)

  function isI18nFunCall(path: ASTPath) {
    if (path.parent?.value?.type === 'CallExpression' && path.parent?.value?.callee?.name === 't') {
      return true
    }
    if (
      path.parent?.value?.callee?.type === 'MemberExpression' &&
      path.parent?.value?.callee?.object?.type === 'ThisExpression'
    ) {
      return true
    }
    return false
  }

  function insertUseTranslation(path: ASTPath) {
    let node = path.parent
    let top = null
    while (node) {
      if (node.value.type === 'FunctionDeclaration') {
        top = node
      }
      node = node.parent
    }

    if (top) {
      const t = j.objectProperty(j.identifier('t'), j.identifier('t'))

      const expr = j.variableDeclaration('const', [
        j.variableDeclarator(j.objectPattern([t]), j.callExpression(j.identifier('useTranslation'), [])),
      ])
      !hasInserted.get(top) && isJsx && top.value.body.body.unshift(expr)
      hasInserted.set(top, true)
    }
  }

  root.find(j.StringLiteral).forEach(path => {
    if (isContainChinese(path.value.value)) {
      if (path.parent.value.type === 'JSXAttribute') {
        if (!isI18nFunCall(path)) {
          const expr = j.jsxExpressionContainer(
            j.callExpression(j.identifier(isDuck ? 'this.t' : 't'), [j.stringLiteral(path.value.value.trim())]),
          )
          insertUseTranslation(path)
          path.replace(expr)
          isModified = true
        }
      } else {
        if (!isI18nFunCall(path)) {
          const i18nCall = j.callExpression(j.identifier(isDuck ? 'this.t' : 't'), [path.node])
          insertUseTranslation(path)

          path.replace(i18nCall)
          isModified = true
        }
      }
    }
  })

  root.find(j.TemplateLiteral).forEach(path => {
    const rawString = path.node.quasis
      .map((element, idx) => {
        if (idx < path.node.quasis.length - 1) {
          return element.value.raw + `{{attr${idx}}}`
        }
        return element.value.raw
      })
      .join('')
    if (isContainChinese(rawString) && !isI18nFunCall(path)) {
      const arg1 = j.stringLiteral(rawString)

      const arg2 = j.objectExpression(
        path.node.expressions.map((expr, idx) => {
          return j.objectProperty(j.identifier('attr' + idx), expr)
        }),
      )
      const expr = j.callExpression(j.identifier(isDuck ? 'this.t' : 't'), [arg1, arg2])
      insertUseTranslation(path)

      path.replace(expr)
      isModified = true
    }
  })

  root.find(j.JSXText).forEach(path => {
    if (isContainChinese(path.value.value) && path.parent.value?.openingElement?.name?.name !== 'Trans') {
      const expr = j.jsxElement(
        j.jsxOpeningElement(j.jsxIdentifier('Trans')),
        j.jsxClosingElement(j.jsxIdentifier('Trans')),
        [path.node],
      )
      path.replace(expr)
      jsxModified = true
    }
  })

  return (
    (!isDuck && isModified ? "import { t } from 'i18next';\n" : '') +
    (jsxModified ? "import { Trans, useTranslation } from 'react-i18next'\n" : '') +
    root.toSource()
  )
}

usage

cd web && npx  jscodeshift -t extract-chinese.js src/**/*.{ts,tsx} --parser=tsx

after transform, you should run npx eslint . --ext=.ts,.tsx --fix to format codes.

example

before

import React from 'react'

export default function TestCom() {
  const str = '中文字符串'
  const strTml = `模板字符串${str}#${123 + 456}`

  return <div cn-label='中文属性'>{'中文内容'}</div>
}

after

import { t } from 'i18next'
import { Trans } from 'react-i18next'
import React from 'react'

export default function TestCom() {
  const str = t('中文字符串')
  const strTml = t('模板字符串{{attr0}}#{{attr1}}', {
    attr0: str,
    attr1: 123 + 456,
  })

  return <div cn-label={t('中文属性')}><Trans>中文内容</Trans></div>
}

yidafu avatar Mar 19 '23 16:03 yidafu

貌似有一个专门的国际化开发分支,先提交PR到那边?

chuntaojun avatar Mar 20 '23 01:03 chuntaojun

貌似有一个专门的国际化开发分支,先提交PR到那边?

代码差异有点多,merge 最新的 main 分支太麻烦了

yidafu avatar Mar 20 '23 04:03 yidafu

translate script

run it by zx.

import en from './en.json' assert { type: 'json' };

function sleep() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, (Math.round() * 100 * 10) | 0)
  })
}

const youdaoMap = {};
const bingMap = {};
for (let word of Object.keys(en)) {
  // try {
  //   const result = await fetch('http://fanyi.youdao.com/translate?&doctype=json&type=ZH_CN2EN&i=' + word).then(resp => resp.json());
  //   // { "type": "ZH_CN2EN", "errorCode": 0, "elapsedTime": 1, "translateResult": [[{ "src": "你好", "tgt": "hello" }]] }
  //   console.log('%s ==> %s', word, result?.translateResult?.[0]?.[0]?.tgt);
  //   youdaoMap[word] = result?.translateResult?.[0]?.[0]?.tgt ?? '';
  // } catch (err) {
  //   console.log(err)
  // }

  try {
    const result = await fetch('http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=AFC76A66CF4F434ED080D245C30CF1E71C22959C&from=&to=en&text=' + word).then(resp => resp.text())
    const target = /\>([^<]*)\</.exec(result)?.[1];
    console.log('%s ==> %s', word, target);
    bingMap[word] = target;
  } catch (err) {
    console.log(err);
  }

  await sleep()
}

// fs.writeFileSync('./youdao.json', JSON.stringify(youdaoMap, null, 2));
fs.writeFileSync('./bing.json', JSON.stringify(bingMap, null, 2));

translate result

bing.json.txt

youdao.json.txt

yidafu avatar Mar 20 '23 05:03 yidafu

okk

chuntaojun avatar Mar 20 '23 06:03 chuntaojun

效果图

image

yidafu avatar Mar 20 '23 16:03 yidafu