Skip to content

Commit

Permalink
feat: i18n base code
Browse files Browse the repository at this point in the history
  • Loading branch information
Linkontoask committed Jun 30, 2021
1 parent 9d9b9ea commit b4fc8a7
Show file tree
Hide file tree
Showing 9 changed files with 654 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ module.exports = {
env: {
node: true
},
globals: {
j18n: true
},
extends: [
'plugin:vue/vue3-essential',
'plugin:vue/vue3-strongly-recommended',
Expand Down
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "I18n Render Json",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/script/render.js"
},
{
"name": "Launch Program",
"program": "${file}",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"conventional-changelog-cli": "^2.1.1",
"cross-env": "^7.0.2",
"cross-spawn": "^7.0.3",
"crypto-js": "^4.0.0",
"cypress": "^6.2.0",
"dayjs": "^1.9.4",
"electron": "^12.0.4",
Expand Down
363 changes: 363 additions & 0 deletions script/core/core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
const Type = {
CHAR: 'CHAR',
HTML: 'HTML',
JSX: 'JSX'
}

const renderKey = (filePath, rootPath) => {
const key = filePath
.replace(rootPath, '')
.slice(1)
.replace(/\//g, '__')
.replace(/\..+$/, '')
return key
}

const checkInExpression = (type, content, start) => {
const isVue = false
const beforeContent = content.slice(0, start)
const isAttributeExpression = isVue
? /:[a-zA-Z-]*=".*/.test(beforeContent)
: /[a-zA-Z]={/.test(beforeContent)
return isAttributeExpression
}

const checkJSXExpression = (type, content, start) => {
const beforeContent = content.slice(0, start)
return /.*{$/.test(beforeContent)
}

const charParser = (content, regexp, line, isAttribute, type, cb) => {
const match = content.match(regexp)
const variable = []
const chinese = /[\u4e00-\u9fa5]/
const attr = isAttribute > 0
let i = 0
if (!match) {
return null
}
const r = match
.filter(item => chinese.test(item))
.map(item => {
const start = content.indexOf(item)
const end = start + item.length

const isAttributeExpression = attr
? checkInExpression(type, content, start)
: false
const isJSXNodeExpression = !attr
? checkJSXExpression(type, content, start)
: false

// 处理模版字符串
const output = item.replace(/\$\{.+?\}/g, match => {
variable.push(match.slice(2, -1))
return `{${i++}}`
})
return {
source: content,
line,
output,
variable,
start,
end,
type: Type.CHAR,
isAttribute: attr,
isAttributeExpression,
isJSXNodeExpression
}
})
cb()
return r
}

const tagParser = (content, regexp, isVue, type, line, isAttribute, cb) => {
const variable = []
let i = 0
const match = content.match(regexp)
let output = match[0]
const start = match.index
const end = start + output.length
if (isVue) {
// 处理 vue html 里的变量
output = output.replace(/\{\{.+?\}\}/g, match => {
variable.push(match.slice(2, -2))
return `{${i++}}`
})
}
cb()
return {
source: content,
line,
output,
variable,
start,
end,
isVueTag: isVue,
type: type,
isAttribute: isAttribute > 0
}
}

const renderVueExpressionCode = (source, code, start, end) => {
const tags = source
.split('')
.map((item, index) => {
if (item === ' ') {
return index
}
return null
})
.filter(item => typeof item === 'number')
.filter(item => item < start)
const tagIndex = tags[tags.length - 1]
const rewriteCode =
source.slice(0, tagIndex) +
' :' +
source.slice(tagIndex + 1, start) +
code +
source.slice(end)
return rewriteCode
}

exports.renderOutputCode = (normalized, file) => {
// {
// source: ' title: `{{${code}.${autoSheetOptions(event)[0].value}}} 异常警告`,',
// rewriteCode: ' title: j18n.expand(j18n.load('src__components__automationDrawer__model__action___199', code, autoSheetOptions(event)[0].value)),',
// fileType: 'js',
// type: 'CHAR',
// isVueTag: false,
// output: '`{{{0}.{1}}} 异常警告`',
// filePath: '/Users/inkl/Desktop/work/console/src/components/automationDrawer/model/action.js',
// line: 199,
// code: 'j18n.expand(j18n.load('src__components__automationDrawer__model__action___199', code, autoSheetOptions(event)[0].value))'
// variable?: ['code', 'autoSheetOptions(event)[0].value'],
// src__components__automationDrawer__model__action___199: '`{{{0}.{1}}} 异常警告`',
// key: 'src__components__automationDrawer__model__action___199'
// }
if (normalized.length) {
const code = file.split('\n')
for (let index = 0; index < normalized.length; index++) {
const item = normalized[index]
if (normalized[index + 1] && item.line === normalized[index + 1].line) {
// 处理相同行数的中文
const [current, next] = [item, normalized[index + 1]]
const { source, start, end } = current
const rCode =
source.slice(0, start) +
current.code +
source.slice(end, next.start) +
next.code +
next.source.slice(next.end)
code[item.line] = rCode
// 跳过下一行的写入
index++
} else {
code[item.line] = item.rewriteCode
}
}
return code.join('\n')
}
return null
}

exports.trackNormalized = (filetrack, rootPath) => {
const json = filetrack.map((track, index) => {
const prefixKey = renderKey(track.filePath, rootPath)
// 处理头尾带引号的字符串
const output = track.output.replace(/^["'`]/, '').replace(/["'`]$/, '')
// 同一行中有两个中文字符串
if (filetrack[index + 1] && track.line === filetrack[index + 1].line) {
const key = `${prefixKey}___${track.line}____${index}`
return {
[key]: output,
key: key,
output
}
}
const key = `${prefixKey}___${track.line}`
return {
[key]: output,
key: key,
output
}
})
return json.map((item, index) => {
let code,
filling = '',
rewriteCode = ''
const itemTrack = filetrack[index]
const {
variable,
fileType,
type,
start,
end,
source,
isVueTag,
isAttribute,
isAttributeExpression,
isJSXNodeExpression
} = itemTrack
if (Array.isArray(variable) && variable.length) {
// filling expressions
filling = `, ${variable.join(', ')}`
}
if (fileType === 'tsx' || fileType === 'ts') {
if (type === Type.CHAR && !isAttribute) {
code = `j18n.load('${item.key}'${filling})`
} else {
// isJSXNodeExpression 表示当前内容是否为 {`xxx`} 模式
if ((isAttribute && isAttributeExpression) || isJSXNodeExpression) {
code = `j18n.load('${item.key}'${filling})`
} else {
code = `{ j18n.load('${item.key}'${filling}) }`
}
}
}
rewriteCode =
rewriteCode || source.slice(0, start) + code + source.slice(end)

return {
...itemTrack,
...item,
code: code,
rewriteCode: rewriteCode
}
})
}

exports.parserCore = (content, path) => {
// const regexp = /["'`]?[\u4e00-\u9fa5]+(.+)?[\u4e00-\u9fa5]+(\?|!)?["'`)]?/;
const double = /".*?"/g
const single = /'.*?'/g
// eslint-disable-next-line no-unused-vars
const backtick = /`.*?`/g
const charRegexp = /["'`].*?["'`]/g
const htmlChineseRegexp = /(?<=>).{0,}[\u4e00-\u9fa5].{0,}(?=<)/
const allChineseRegexp = /[\u4e00-\u9fa5]{1,}.*?[\u4e00-\u9fa5]{1,}/
const type = path.match(/\.(tsx|ts|js)$/)[0].slice(1)

const isVue = false

let isAttribute = 0,
processed = false
if (
charRegexp.test(content) ||
htmlChineseRegexp.test(content) ||
allChineseRegexp.test(content)
) {
return content
.split('\n')
.map((lineContent, line) => {
// jsx / vue 中的语义处理 < 中的属性值(中文字符串),应该加上 ‘{}’ 符号
if (/<[a-zA-Z]+/.test(lineContent)) {
isAttribute++
}

const tagClose = () => {
// 处理完当前行了之后再决定标签是否闭合
const close =
/<.+>/.test(lineContent) ||
/^>/.test(lineContent.trim()) ||
/\/>/.test(lineContent)
if (close) {
isAttribute = isAttribute > 0 ? isAttribute - 1 : 0
}
}

if (
isVue &&
isAttribute &&
double.test(lineContent) &&
single.test(lineContent)
) {
// vue template ExpressionStatement
// 总共只有13处需要处理,手动替换
return charParser(
lineContent,
single,
line,
isAttribute,
type,
tagClose
)
}

// 处理 <><> tag 中的中文
if (htmlChineseRegexp.test(lineContent)) {
return tagParser(
lineContent,
htmlChineseRegexp,
isVue,
Type.HTML,
line,
isAttribute,
tagClose
)
}

if (backtick.test(lineContent)) {
return charParser(
lineContent,
backtick,
line,
isAttribute,
type,
tagClose
)
}

// 处理字符串中的中文
if (charRegexp.test(lineContent)) {
const parser = charParser(
lineContent,
charRegexp,
line,
isAttribute,
type,
tagClose
)
processed = true
if (parser.length) {
return parser
}
}

// 处理 jsx / vue 中的中文
if (
allChineseRegexp.test(lineContent) &&
!lineContent.includes('//') &&
!lineContent.includes('* ')
) {
return tagParser(
lineContent,
allChineseRegexp,
isVue,
Type.JSX,
line,
isAttribute,
tagClose
)
}

// 没有匹配到中文也需要检查标签是否已关闭
!processed && tagClose()

// 当前行处理完,标记量回退
processed = false

return null
})
.filter(item => {
if (Array.isArray(item)) {
return item.length > 0
}
return !!item
})
.flat()
.map(item => ({ ...item, filePath: path, fileType: type }))
}

return []
}
Loading

0 comments on commit b4fc8a7

Please sign in to comment.