Skip to content

Commit

Permalink
Feat: init project
Browse files Browse the repository at this point in the history
  • Loading branch information
YuMao233 committed Sep 23, 2022
1 parent ae52355 commit 91f8280
Show file tree
Hide file tree
Showing 7 changed files with 1,724 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
useTabs: false
tabWidth: 2
printWidth: 300
singleQuote: true
semi: false
trailingComma: 'all'
arrowParens: 'always'
bracketSpacing: true
203 changes: 203 additions & 0 deletions common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import path from 'path'

export function hasChinese(str = '') {
if (str.includes('$t(')) return false
return /[\u4E00-\u9FA5]+/g.test(str)
}

export function hasVueText(text = '') {
if (text.includes('{{') && text.includes('}}')) return true
return false
}

// 递归解析所有 Vue 表达式并生成数组
// ABC昵称{{ userInfo.A }}你好啊!{{ userInfo.B }}卧槽
// TO
// ["ABC昵称","{{ userInfo.A }}","你好啊!","{{ userInfo.B }}",卧槽]
export function htmlTextVueExpressParse(text = '') {
const result: string[] = []
function inner(point: number): number {
if (point > text.length - 1) return 0
const left = text.indexOf('{{', point)
const right = text.indexOf('}}', left)
if (right != -1 && left != -1) {
result.push(text.slice(point, left))
const vueExpress = text.slice(left, right + 2)
if (hasChinese(vueExpress)) throw new Error('The expression cannot contain Chinese characters')
result.push(vueExpress)
return inner(right + 2)
}
result.push(text.slice(point, text.length))
return 0
}
inner(0)
return result
}

export function hasVueAttr(attrName = '') {
const keywords = ['v-', '@', ':']
for (const iterator of keywords) {
if (attrName.indexOf(iterator) === 0) {
return true
}
}
return false
}

export function fillNumber(index = 0) {
if (index < 10) return `00${index}`
if (index < 100) return `0${index}`
if (index < 1000) return `${index}`
return String(index)
}

export function setObjectAttr(root: any, attrPath: string[], key: string, value: string) {
let current = root
for (let index = 0; index < attrPath.length; index++) {
const element = attrPath[index]
if (!current[element]) {
current[element] = {}
}
current = current[element]
}
current[key] = value
}

export function getObjectAttr(root: ObjectMap, attrPath: string[]): JsonMap | null {
let current = root
for (let index = 0; index < attrPath.length; index++) {
const element = attrPath[index]
if (!current[element]) return null
current = current[element]
}
return current
}

export function includeCommonLang(commonLang: JsonMap, text: string) {
const prefix = 'CommonText'
let i = 1
for (const key in commonLang) {
const cText = commonLang[key]
i++
if (cText === text) {
return `${prefix}.${key}`
}
}
const fullNumber = fillNumber(i)
commonLang[fullNumber] = text
return `${prefix}.${fullNumber}`
}

export function isExistsTextForLang(prefix: string, map: JsonMap, text: string): string {
for (const key in map) {
const element = map[key]
if (element === text) {
return `${prefix}.${key}`
}
}
return ''
}

export function includeLangMap(filePrefix: string, index: number, text: string, commonLang: JsonMap, outputLanguageConfig: JsonMap) {
let isBreak = false
let tFnKey = `${filePrefix}.${fillNumber(index)}`
if (text.length <= 4) {
tFnKey = includeCommonLang(commonLang, text)
isBreak = true
} else if (isExistsTextForLang(filePrefix, outputLanguageConfig, text) !== '') {
tFnKey = isExistsTextForLang(filePrefix, outputLanguageConfig, text)
isBreak = true
} else {
outputLanguageConfig[fillNumber(index)] = text
}
return {
tFnKey,
isBreak,
}
}

// 寻找文件中最大的编码序号,并同时忽略 CommonText 内容
// $t 不能换行
export function findMax$tNumber(code = '') {
const lines = code.split('\n')
let max = 0
lines.forEach((line) => {
function inner(start = 0) {
const sp = line.indexOf('$t(', start)
const commonTextP = line.indexOf('CommonText', start)
const ep = line.indexOf(')', sp)
if (sp > 0 && ep > 0 && (commonTextP == -1 || commonTextP > ep)) {
let keyExpress = line.slice(sp + 3, ep)
const keyExpressArr = keyExpress.replace(/\'/gim, '').split('')
let n = ''
let find = false
for (const ch of keyExpressArr) {
if (!isNaN(Number(ch))) {
find = true
n += String(ch)
} else if (find) {
break
}
}
if (Number(n) > max) max = Number(n)
inner(ep + 1)
}
}
inner()
})
return max
}

// 寻找文件中最大的编码序号,并同时忽略 CommonText 内容
// $t 不能换行
export function findMax$tNumberByLangFile(language: ObjectMap, prefix: string) {
let max = 0
const keyList = getObjectAttr(language, prefix.split('.'))
for (const key in keyList) {
if (!isNaN(Number(key)) && Number(key) > max) {
max = Number(key)
}
}
return max
}

// 取路径最后两项作为语言后缀
// /Users/wangkun/Documents/SupportProject/weplay-admin-huafu-project/src/view/Censor/components/guardList.vue
export function buildLangPrefix(targetPath: string) {
let prefix1 = ''
let prefix2 = ''
const prefix3 = targetPath.split('/').slice(-2)

let tmpFlag = false
for (const iterator of targetPath.split('/')) {
if (iterator === 'view') continue
if (iterator === 'src') {
tmpFlag = true
continue
}
if (tmpFlag) {
prefix1 = iterator
break
}
}
if (prefix3[0] === prefix1) prefix3.shift()
prefix2 = prefix3.join('_').replace(path.extname(prefix3.join('')), '')

// 语言文件最终前缀
let filePrefix = [prefix1, prefix2].join('.')
return filePrefix
}

export interface ReturnValue {
index: number
output: string
lang: { [key: string]: any }
}

export interface JsonMap {
[key: string]: string
}

export interface ObjectMap {
[key: string]: any
}
149 changes: 149 additions & 0 deletions html-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import fs from 'fs'
import * as htmlparser2 from 'htmlparser2'
import { render } from 'dom-serializer'
import { fillNumber, hasChinese, hasVueAttr, hasVueText, htmlTextVueExpressParse, includeCommonLang, includeLangMap, isExistsTextForLang, JsonMap, ReturnValue, setObjectAttr } from './common'

function htmlTextFilter(html: string) {
html = html.replace(/\n/gim, '')
html = html.trim()
return html
}

export function generatorHTML(html: string, filePrefix: string, index: number, commonLang: JsonMap): ReturnValue {
// DOM 树生成
const dom = htmlparser2.parseDocument(html, {
xmlMode: true,
lowerCaseTags: false,
lowerCaseAttributeNames: false,
})

const outputLanguageConfig: {
[key: string]: any
} = {}

// HTML 属性转换
function forEachAttr(node: any) {
for (const key in node.attribs) {
const text = node.attribs[key]
if (!hasChinese(text)) continue
if (hasVueAttr(key)) continue // 忽略 Vue 动态属性

const { tFnKey, isBreak } = includeLangMap(filePrefix, index, text, commonLang, outputLanguageConfig)
const textVueTemplate = `$t('${tFnKey}')`

// 删除旧的 HTML 属性,新增i18n绑定
delete node.attribs[key]
node.attribs[`:${key}`] = textVueTemplate

console.log('HTML 属性转换:', node.name, key, text, '->', textVueTemplate)

if (!isBreak) index += 1
}
}

// 文本常量转换
function constDefineOutput(keyword: string) {
if (hasVueText(keyword) || !hasChinese(keyword)) return keyword
const { tFnKey, isBreak } = includeLangMap(filePrefix, index, keyword, commonLang, outputLanguageConfig)
const textVueTemplate = `{{ $t('${tFnKey}') }}`
console.log('HTML 文本转换:', keyword, '->', textVueTemplate)
if (!isBreak) index += 1
return textVueTemplate
}

// Vue 表达式文本转换
function expressDefineOutput(keyword: string) {
let expressArray: string[] = []
try {
expressArray = htmlTextVueExpressParse(keyword)
} catch (error) {
return keyword
}
const vueParams: string[] = []
let paramsIndex = 0
for (let index = 0; index < expressArray.length; index++) {
const element = expressArray[index]
if (element.includes('{{') && element.includes('}}')) {
vueParams.push(element.slice(2, -2))
expressArray[index] = `{${paramsIndex}}`
paramsIndex++
}
}
const $tExpress = expressArray.join('')
const $tParams = JSON.stringify(vueParams).replace(/\"/gim, '')
const { tFnKey, isBreak } = includeLangMap(filePrefix, index, $tExpress, commonLang, outputLanguageConfig)
const textVueTemplate = `{{ $t('${tFnKey}', ${$tParams}) }}`
console.log('HTML 表达式转换:', keyword, '->', textVueTemplate)
if (!isBreak) index += 1
return textVueTemplate
}

// 递归DOM树,根据其特点批量转换所有节点
function walk(dom: any): any {
for (const node of dom.children) {
forEachAttr(node)
if (node.children?.length > 0) {
walk(node)
continue
}
if (node.type === 'text') {
const text = htmlTextFilter(node.data)
if (!hasChinese(text)) continue

if (!hasVueText(text)) {
node.data = constDefineOutput(text)
} else {
node.data = expressDefineOutput(text)
}
}
}
}

walk(dom)

// DOM 树生成 HTML
const outputHtml = render(dom, {
encodeEntities: false,
decodeEntities: false,
xmlMode: false,
selfClosingTags: false,
})

return {
index,
output: outputHtml,
lang: outputLanguageConfig,
}
}

// Node 类型
// Document {
// parent: null,
// prev: null,
// next: null,
// startIndex: null,
// endIndex: null,
// children: [
// Element {
// parent: [Circular *1],
// prev: null,
// next: [Text],
// startIndex: null,
// endIndex: null,
// children: [Array],
// name: 'template',
// attribs: {},
// type: 'tag'
// },
// Text {
// parent: [Circular *1],
// prev: [Element],
// next: null,
// startIndex: null,
// endIndex: null,
// data: '\n',
// type: 'text'
// }
// ],
// type: 'root'
// }
Loading

0 comments on commit 91f8280

Please sign in to comment.