Skip to content

Commit

Permalink
fix(ui): move vite plugin to repo
Browse files Browse the repository at this point in the history
  • Loading branch information
zoubingwu committed Sep 22, 2023
1 parent 7ad8876 commit d8ab7f4
Show file tree
Hide file tree
Showing 10 changed files with 840 additions and 15 deletions.
2 changes: 1 addition & 1 deletion dm/ui/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
extends: ['@ti-fe/eslint-config'],
ignorePatterns: ['src/routes.tsx'],
ignorePatterns: ['src/routes.tsx', 'plugins/**/*'],
env: {
// Your custom env variables, e.g., browser: true, jest: true
},
Expand Down
4 changes: 2 additions & 2 deletions dm/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@
"rollup-plugin-visualizer": "^5.5.2",
"typescript": "^5.1.6",
"vite": "^2.9.16",
"vite-plugin-i18next-scanner": "^0.4.0",
"vite-plugin-i18next-scanner": "file:./plugin/vite-plugin-i18next-scanner",
"vite-plugin-imp": "^2.1.3",
"vite-plugin-next-react-router": "^0.6.1",
"vite-plugin-next-react-router": "file:./plugin/vite-plugin-next-react-router",
"vite-plugin-windicss": "^1.5.4",
"vite-tsconfig-paths": "^3.3.17",
"windicss": "^3.3.0"
Expand Down
300 changes: 300 additions & 0 deletions dm/ui/plugins/vite-plugin-i18next-scanner/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
// src/context.ts
import fs2 from 'fs'
import { Parser } from 'i18next-scanner'
import debug from 'debug'
import workerpool from 'workerpool'

// src/options.ts
import path from 'path'
import { defaults, defaultsDeep } from 'lodash'
var defaultOptions = {
input: ['src/**/*.{js,jsx,ts,tsx}'],
output: './',
options: {
debug: false,
removeUnusedKeys: true,
sort: true,
attr: {
list: ['data-i18n'],
extensions: ['.html', '.htm'],
},
func: {
list: ['t', 'i18next.t', 'i18n.t'],
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
trans: {
component: 'Trans',
i18nKey: 'i18nKey',
defaultsKey: 'defaults',
extensions: [],
fallbackKey: false,
},
lngs: ['en'],
defaultLng: 'en',
defaultValue: function (_, __, key) {
return key
},
resource: {
loadPath: './locales/{{lng}}.json',
savePath: './locales/{{lng}}.json',
jsonIndent: 2,
lineEnding: '\n',
},
nsSeparator: ':',
keySeparator: '.',
pluralSeparator: '_',
contextSeparator: '_',
contextDefaultValues: [],
interpolation: {
prefix: '{{',
suffix: '}}',
},
},
}
var defaultPluginOptions = {
langs: ['en'],
outDir: 'locales',
includes: ['src/**/*.{js,jsx,ts,tsx}'],
}
function mergePluginOptionToScannerOption(a, b) {
const o = defaults(b, defaultPluginOptions)
a.input = o.includes
a.options.lngs = o.langs
a.options.resource.savePath = path.join(o.outDir, '{{lng}}.json')
a.options.resource.loadPath = path.join(o.outDir, '{{lng}}.json')
return a
}
function normalizeOptions(o = {}) {
const options = defaultsDeep({}, defaultOptions)
return mergePluginOptionToScannerOption(options, o)
}

// src/context.ts
import path2 from 'path'

// src/fs.ts
import fs from 'fs'

// node_modules/.pnpm/[email protected]/node_modules/detect-indent/index.js
var INDENT_REGEX = /^(?:( )+|\t+)/
var INDENT_TYPE_SPACE = 'space'
var INDENT_TYPE_TAB = 'tab'
function makeIndentsMap(string, ignoreSingleSpaces) {
const indents = new Map()
let previousSize = 0
let previousIndentType
let key
for (const line of string.split(/\n/g)) {
if (!line) {
continue
}
let indent
let indentType
let weight
let entry
const matches = line.match(INDENT_REGEX)
if (matches === null) {
previousSize = 0
previousIndentType = ''
} else {
indent = matches[0].length
indentType = matches[1] ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB
if (
ignoreSingleSpaces &&
indentType === INDENT_TYPE_SPACE &&
indent === 1
) {
continue
}
if (indentType !== previousIndentType) {
previousSize = 0
}
previousIndentType = indentType
weight = 0
const indentDifference = indent - previousSize
previousSize = indent
if (indentDifference === 0) {
weight++
} else {
const absoluteIndentDifference =
indentDifference > 0 ? indentDifference : -indentDifference
key = encodeIndentsKey(indentType, absoluteIndentDifference)
}
entry = indents.get(key)
entry = entry === void 0 ? [1, 0] : [++entry[0], entry[1] + weight]
indents.set(key, entry)
}
}
return indents
}
function encodeIndentsKey(indentType, indentAmount) {
const typeCharacter = indentType === INDENT_TYPE_SPACE ? 's' : 't'
return typeCharacter + String(indentAmount)
}
function decodeIndentsKey(indentsKey) {
const keyHasTypeSpace = indentsKey[0] === 's'
const type = keyHasTypeSpace ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB
const amount = Number(indentsKey.slice(1))
return { type, amount }
}
function getMostUsedKey(indents) {
let result
let maxUsed = 0
let maxWeight = 0
for (const [key, [usedCount, weight]] of indents) {
if (usedCount > maxUsed || (usedCount === maxUsed && weight > maxWeight)) {
maxUsed = usedCount
maxWeight = weight
result = key
}
}
return result
}
function makeIndentString(type, amount) {
const indentCharacter = type === INDENT_TYPE_SPACE ? ' ' : ' '
return indentCharacter.repeat(amount)
}
function detectIndent(string) {
if (typeof string !== 'string') {
throw new TypeError('Expected a string')
}
let indents = makeIndentsMap(string, true)
if (indents.size === 0) {
indents = makeIndentsMap(string, false)
}
const keyOfMostUsedIndent = getMostUsedKey(indents)
let type
let amount = 0
let indent = ''
if (keyOfMostUsedIndent !== void 0) {
;({ type, amount } = decodeIndentsKey(keyOfMostUsedIndent))
indent = makeIndentString(type, amount)
}
return {
amount,
type,
indent,
}
}

// src/fs.ts
var DEFAULT_INDENT = ' '
function readJsonFile(path3) {
const file = fs.readFileSync(path3, 'utf8') || '{}'
const indent = detectIndent(path3).indent || DEFAULT_INDENT
return {
path: path3,
json: JSON.parse(file),
indent,
}
}

// src/context.ts
var dbg = debug('vite-plugin-i18next-scanner:context')
var Context = class {
constructor(options = {}) {
this.server = null
this.pool = null
this.pluginOptions = options
this.scannerOptions = normalizeOptions(options)
dbg('scannerOptions: %o', this.scannerOptions)
}
async startScanner(server) {
if (this.server === server) {
return
}
if (this.pool) {
await this.pool.terminate()
}
this.server = server
this.pool = workerpool.pool(__dirname + '/worker.js', {
minWorkers: 'max',
maxWorkers: 1,
})
await this.scanAll()
this.watch(server.watcher)
}
watch(watcher) {
watcher.on('change', p => this.handleFileChange(p))
watcher.on('unlink', p => this.handleFileUnlink(p))
}
passExtensionCheck(p) {
const extname = path2.extname(p)
return (
this.scannerOptions.options.func.extensions.includes(extname) ||
this.scannerOptions.options.attr.extensions.includes(extname) ||
this.scannerOptions.options.trans.extensions.includes(extname)
)
}
async handleFileUnlink(p) {
if (this.passExtensionCheck(p)) {
await this.scanAll()
}
}
async handleFileChange(p) {
dbg(`scanning ${p}`)
if (!this.passExtensionCheck(p)) {
return
}
const content = fs2.readFileSync(p, 'utf8')
const parser = new Parser(this.scannerOptions.options)
if (!content) {
return
}
parser.parseFuncFromString(content)
const translations = parser.get()
const resourceFromFile = Object.keys(translations).reduce((acc, key) => {
acc[key] = translations[key].translation
return acc
}, {})
dbg('resource from file: %o', resourceFromFile)
const hasKey = Object.keys(resourceFromFile).some(lang => {
return Object.keys(resourceFromFile[lang]).length > 0
})
if (!hasKey) {
dbg('no key found')
return
}
let shouldScanAll = false
Object.keys(resourceFromFile).forEach(lang => {
const languageResource = path2.resolve(
this.scannerOptions.options.resource.savePath.replace('{{lng}}', lang)
)
const { json } = readJsonFile(languageResource)
if (Object.keys(resourceFromFile[lang]).some(key => !(key in json))) {
shouldScanAll = true
}
})
if (shouldScanAll) {
await this.scanAll()
} else {
dbg('no need to scan all')
}
}
async scanAll() {
if (!this.pool) {
return
}
dbg('scanning and regenerating all resources...')
const worker = await this.pool.proxy()
await worker.scanAndGenerateResource(
this.scannerOptions.input,
this.scannerOptions.output,
this.pluginOptions
)
dbg('done scanning and regenerating all resources')
}
}

// src/index.ts
function i18nextScanner(options) {
const ctx = new Context(options)
return {
name: 'vite-plugin-i18next-scanner',
apply: 'serve',
async configureServer(server) {
await ctx.startScanner(server)
},
}
}
export { i18nextScanner }
37 changes: 37 additions & 0 deletions dm/ui/plugins/vite-plugin-i18next-scanner/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "vite-plugin-i18next-scanner",
"version": "0.4.0",
"main": "index.js",
"module": "./index.mjs",
"exports": {
".": {
"require": "./index.js",
"import": "./index.mjs",
"types": "./index.d.ts"
}
},
"dependencies": {
"debug": "^4.3.3",
"i18next-scanner": "^3.1.0",
"lodash": "^4.17.21",
"vinyl-fs": "^3.0.3",
"workerpool": "^6.1.5"
},
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/lodash": "^4.14.177",
"@types/node": "^16.11.10",
"@types/vinyl-fs": "^2.4.12",
"@types/workerpool": "^6.1.0",
"detect-indent": "^7.0.0",
"husky": "^7.0.4",
"prettier": "^2.5.0",
"pretty-quick": "^3.1.2",
"tsup": "^5.10.0",
"typescript": "^4.5.2",
"vite": "^2.6.14"
},
"peerDependencies": {
"vite": ">=2"
}
}
Loading

0 comments on commit d8ab7f4

Please sign in to comment.