polaris-console
polaris-console copied to clipboard
[WIP]国际化
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>
}
貌似有一个专门的国际化开发分支,先提交PR到那边?
貌似有一个专门的国际化开发分支,先提交PR到那边?
代码差异有点多,merge 最新的 main 分支太麻烦了
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
okk
效果图