Files
element-plus/scripts/sync-locale.ts
Noblet Ouways 899eeab499 chore: migrate fast-glob to tinyglobby (#23020)
* chore: migrate fast-glob to tinyglobby

* fix: ssr tests
2025-12-09 10:52:09 +01:00

244 lines
6.8 KiB
TypeScript

import { readFileSync, writeFileSync } from 'node:fs'
import { basename, resolve } from 'node:path'
import { globSync } from 'tinyglobby'
import consola from 'consola'
import { isArray, isObject, isString } from 'lodash-unified'
import { localeRoot, normalizePath } from '@element-plus/build-utils'
type TranslatePair = {
[key: string]: string | string[] | TranslatePair
}
const langRoot = resolve(localeRoot, 'lang')
const enFile = normalizePath(resolve(langRoot, 'en.ts'))
const localePath = normalizePath(resolve(`${langRoot}/!(en).ts`))
const commentInfo = '// to be translated'
const untranslatedRexExp = new RegExp(
`\\w+:(\\s+|\\n\\s+).*,?\\s*${commentInfo}$`,
'gm'
)
function parseLocaleFile(content: string): TranslatePair {
try {
const objectContent = content
.replace(/^export\s+default\s+/, '')
.replace(/;\s*$/, '')
.replaceAll(untranslatedRexExp, '')
return new Function(`return ${objectContent}`)()
} catch (error) {
consola.error('Failed to parse locale file:', error)
return {}
}
}
function mergeLocaleObjects(
source: TranslatePair,
target: TranslatePair,
path = ''
): TranslatePair {
const result: TranslatePair = {}
Object.entries(source).forEach(([key, value]) => {
const currentPath = path ? `${path}.${key}` : key
if (isArray(value)) {
if (isArray(target[key])) {
result[key] = mergeLocaleArrays(
value as Array<string>,
target[key] as Array<string>,
currentPath
)
} else {
result[key] = value.map((item, index) => {
consola.log(`📝 Added new array: ${currentPath}[${index}]`)
return `${item} ${commentInfo}`
})
}
} else if (isObject(value)) {
if (isObject(target[key]) && !isArray(target[key])) {
result[key] = mergeLocaleObjects(
value as TranslatePair,
target[key] as TranslatePair,
currentPath
)
} else {
result[key] = addTranslationComments(value)
}
} else if (key in target) {
result[key] = target[key]
} else if (isString(value)) {
result[key] = `${value} ${commentInfo}`
consola.log(`📝 Added new field: ${currentPath}`)
}
})
Object.entries(target).forEach(([key, value]) => {
if (!(key in source)) {
result[key] = value
consola.warn(
`Found extra field in target: ${path ? `${path}.${key}` : key}`
)
}
})
return result
}
function mergeLocaleArrays(
sourceArray: Array<string>,
targetArray: Array<string>,
path: string
): Array<string> {
const result: Array<string> = []
sourceArray.forEach((sourceItem, index) => {
if (index < targetArray.length) {
result[index] = targetArray[index]
} else {
result[index] = `${sourceItem} ${commentInfo}`
consola.log(`📝 Added new array item: ${path}[${index}]`)
}
})
if (targetArray.length > sourceArray.length) {
for (let i = sourceArray.length; i < targetArray.length; i++) {
result[i] = targetArray[i]
consola.warn(`Found extra array item in target: ${path}[${i}]`)
}
}
return result
}
function addTranslationComments(obj: TranslatePair): TranslatePair {
return Object.entries(obj).reduce((all, [key, value]) => {
if (isArray(value)) {
all[key] = value.map((item) => `${item} ${commentInfo}`)
} else if (isObject(value)) {
all[key] = addTranslationComments(value)
} else if (isString(value)) {
all[key] = `${value} ${commentInfo}`
}
return all
}, {} as TranslatePair)
}
function objectToTypescript(obj: TranslatePair, indent = 0): string {
const spaces = ' '.repeat(indent)
const items: string[] = []
Object.entries(obj).forEach(([key, value]) => {
if (isArray(value)) {
let needComment = false
const arrayItems = value.map((item) => {
if (item.includes(commentInfo)) {
const [actualValue] = item.split(commentInfo)
const escapedValue = actualValue.trim().replace(/'/g, "\\'")
needComment = true
return `'${escapedValue}'`
} else {
const escapedValue = item.replace(/'/g, "\\'")
return `'${escapedValue}'`
}
})
items.push(
`${spaces} ${key}: [${arrayItems.join(', ')}],${
needComment ? ` ${commentInfo}` : ''
}`
)
} else if (isObject(value)) {
items.push(
`${spaces} ${key}: {\n${objectToTypescript(
value as TranslatePair,
indent + 1
)}\n${spaces} },`
)
} else if (isString(value)) {
if (value.includes(commentInfo)) {
const [actualValue] = value.split(commentInfo)
const escapedValue = actualValue.trim().replace(/'/g, "\\'")
items.push(`${spaces} ${key}: '${escapedValue}', ${commentInfo}`)
} else {
const escapedValue = value.replace(/'/g, "\\'")
items.push(`${spaces} ${key}: '${escapedValue}',`)
}
}
})
return items.join('\n')
}
function generateTypescriptFile(obj: TranslatePair): string {
return `export default {\n${objectToTypescript(obj)}\n}\n`
}
function countKeys(obj: TranslatePair): number {
return Object.values(obj).reduce((all, value) => {
if (isObject(value) && !isArray(value)) {
all += countKeys(value)
} else {
all++
}
return all
}, 0)
}
function main() {
consola.start('Starting to synchronize content from English files...')
const enContent = readFileSync(enFile, 'utf-8')
const enObject = parseLocaleFile(enContent)
const localeFiles = globSync(localePath)
let totalUpdated = 0
let totalAdded = 0
consola.info(`Found ${localeFiles.length} locale files to process`)
localeFiles.forEach((filePath) => {
const fileName = basename(filePath)
consola.start(`Processing ${fileName}...`)
try {
const content = readFileSync(filePath, 'utf-8')
const targetObject = parseLocaleFile(content)
const originalKeys = countKeys(targetObject.el as TranslatePair)
targetObject.el = mergeLocaleObjects(
enObject.el as TranslatePair,
targetObject.el as TranslatePair
)
const newKeys = countKeys(targetObject.el as TranslatePair)
const addedKeys = newKeys - originalKeys
if (addedKeys > 0) {
const newContent = generateTypescriptFile(targetObject)
writeFileSync(filePath, newContent, 'utf-8')
consola.success(
`Updated ${fileName} - ${addedKeys} fields waiting to be translated`
)
totalUpdated++
totalAdded += addedKeys
} else {
consola.success(`${fileName} is up to date`)
}
} catch (error) {
consola.error(`Failed to process ${fileName}:`, error)
}
})
consola.log(`🎉 Synchronization completed!`)
consola.log(
`📊 Updated ${totalUpdated} files - ${totalAdded} fields waiting to be translated`
)
}
main()