Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]国际化 #206

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

[WIP]国际化 #206

wants to merge 7 commits into from

Conversation

yidafu
Copy link
Collaborator

@yidafu yidafu commented Mar 19, 2023

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 yidafu added the enhancement New feature or request label Mar 19, 2023
@chuntaojun
Copy link
Member

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

@yidafu
Copy link
Collaborator Author

yidafu commented Mar 20, 2023

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

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

@yidafu
Copy link
Collaborator Author

yidafu commented Mar 20, 2023

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

@chuntaojun
Copy link
Member

okk

@yidafu
Copy link
Collaborator Author

yidafu commented Mar 20, 2023

效果图

image

yidafu added 4 commits March 21, 2023 00:52
1. 使用 jscodeshift 完成中文字面量的提取和转化
2. 校对了一部分的 t 函数的使用
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants