Make tags translatable, move icons to frontend, a few other things (#5229)

* Make tags translatable, move icons to frontend, a few other things

* Migrate more things

* fix import

* more import fixes

* export tag-messages

* lint
This commit is contained in:
Prospector
2026-01-28 11:01:56 -08:00
committed by GitHub
parent 6d68d50699
commit 16ac2aae6b
121 changed files with 1532 additions and 229 deletions

View File

@@ -15,7 +15,12 @@ function toPascalCase(str: string): string {
.join('')
}
function generateIconExports(): { imports: string; exports: string } {
function generateIconExports(): {
imports: string
exports: string
categoryMap: string
loaderMap: string
} {
const packageRoot = path.resolve(__dirname, '..')
const iconsDir = path.join(packageRoot, 'icons')
@@ -23,10 +28,17 @@ function generateIconExports(): { imports: string; exports: string } {
throw new Error(`Icons directory not found: ${iconsDir}`)
}
const files = fs.readdirSync(iconsDir).filter((file) => file.endsWith('.svg'))
const icons: Array<{ importPath: string; pascalName: string; privateName: string }> = []
const categoryMapEntries: Array<{ key: string; value: string }> = []
const loaderMapEntries: Array<{ key: string; value: string }> = []
// Build icon data with import paths
const icons = files.map((file) => {
// Process top-level icons
const files = fs.readdirSync(iconsDir).filter((file) => {
const filePath = path.join(iconsDir, file)
return fs.statSync(filePath).isFile() && file.endsWith('.svg')
})
files.forEach((file) => {
const baseName = path.basename(file, '.svg')
let pascalName = toPascalCase(baseName)
@@ -38,16 +50,84 @@ function generateIconExports(): { imports: string; exports: string } {
pascalName += 'Icon'
}
return {
icons.push({
importPath: `./icons/${file}?component`,
pascalName,
privateName: `_${pascalName}`,
}
})
})
// Process tag icons from icons/tags/categories/
const categoriesDir = path.join(iconsDir, 'tags', 'categories')
if (fs.existsSync(categoriesDir)) {
const categoryFiles = fs.readdirSync(categoriesDir).filter((file) => file.endsWith('.svg'))
categoryFiles.forEach((file) => {
const baseName = path.basename(file, '.svg')
let pascalName = toPascalCase(baseName)
if (pascalName === '') {
pascalName = 'Unknown'
}
// Prefix with TagCategory
pascalName = `TagCategory${pascalName}`
if (!pascalName.endsWith('Icon')) {
pascalName += 'Icon'
}
icons.push({
importPath: `./icons/tags/categories/${file}?component`,
pascalName,
privateName: `_${pascalName}`,
})
// Add to category map (key is the original filename without extension, lowercase)
categoryMapEntries.push({
key: baseName.toLowerCase(),
value: pascalName,
})
})
}
// Process tag icons from icons/tags/loaders/
const loadersDir = path.join(iconsDir, 'tags', 'loaders')
if (fs.existsSync(loadersDir)) {
const loaderFiles = fs.readdirSync(loadersDir).filter((file) => file.endsWith('.svg'))
loaderFiles.forEach((file) => {
const baseName = path.basename(file, '.svg')
let pascalName = toPascalCase(baseName)
if (pascalName === '') {
pascalName = 'Unknown'
}
// Prefix with TagLoader
pascalName = `TagLoader${pascalName}`
if (!pascalName.endsWith('Icon')) {
pascalName += 'Icon'
}
icons.push({
importPath: `./icons/tags/loaders/${file}?component`,
pascalName,
privateName: `_${pascalName}`,
})
// Add to loader map (key is the original filename without extension, lowercase)
loaderMapEntries.push({
key: baseName.toLowerCase(),
value: pascalName,
})
})
}
// Sort by import path using simple-import-sort's algorithm
icons.sort((a, b) => compareImportSources(a.importPath, b.importPath))
// Sort map entries by key for consistent output
categoryMapEntries.sort((a, b) => a.key.localeCompare(b.key))
loaderMapEntries.sort((a, b) => a.key.localeCompare(b.key))
let imports = ''
let exports = ''
@@ -56,7 +136,21 @@ function generateIconExports(): { imports: string; exports: string } {
exports += `export const ${pascalName} = ${privateName}\n`
})
return { imports, exports }
// Generate category map
let categoryMap = 'export const categoryIconMap: Record<string, IconComponent> = {\n'
categoryMapEntries.forEach(({ key, value }) => {
categoryMap += `\t'${key}': ${value},\n`
})
categoryMap += '}\n'
// Generate loader map
let loaderMap = 'export const loaderIconMap: Record<string, IconComponent> = {\n'
loaderMapEntries.forEach(({ key, value }) => {
loaderMap += `\t'${key}': ${value},\n`
})
loaderMap += '}\n'
return { imports, exports, categoryMap, loaderMap }
}
function runTests(): void {
@@ -106,12 +200,19 @@ function generateFiles(): void {
try {
console.log('🔄 Generating icon exports...')
const { imports, exports } = generateIconExports()
const { imports, exports, categoryMap, loaderMap } = generateIconExports()
const output = `// Auto-generated icon imports and exports
// Do not edit this file manually - run 'pnpm run fix' to regenerate
import type { FunctionalComponent, SVGAttributes } from 'vue'
export type IconComponent = FunctionalComponent<SVGAttributes>
${imports}
${exports}`
${exports}
${categoryMap}
${loaderMap}`
const packageRoot = path.resolve(__dirname, '..')
const outputPath = path.join(packageRoot, 'generated-icons.ts')
@@ -146,10 +247,34 @@ function getExpectedIconExports(iconsDir: string): string[] {
return []
}
return fs
.readdirSync(iconsDir)
.filter((file) => file.endsWith('.svg'))
.map((file) => {
const exports: string[] = []
// Process top-level icons
const files = fs.readdirSync(iconsDir).filter((file) => {
const filePath = path.join(iconsDir, file)
return fs.statSync(filePath).isFile() && file.endsWith('.svg')
})
files.forEach((file) => {
const baseName = path.basename(file, '.svg')
let pascalName = toPascalCase(baseName)
if (pascalName === '') {
pascalName = 'Unknown'
}
if (!pascalName.endsWith('Icon')) {
pascalName += 'Icon'
}
exports.push(pascalName)
})
// Process tag icons from icons/tags/categories/
const categoriesDir = path.join(iconsDir, 'tags', 'categories')
if (fs.existsSync(categoriesDir)) {
const categoryFiles = fs.readdirSync(categoriesDir).filter((file) => file.endsWith('.svg'))
categoryFiles.forEach((file) => {
const baseName = path.basename(file, '.svg')
let pascalName = toPascalCase(baseName)
@@ -157,13 +282,37 @@ function getExpectedIconExports(iconsDir: string): string[] {
pascalName = 'Unknown'
}
pascalName = `TagCategory${pascalName}`
if (!pascalName.endsWith('Icon')) {
pascalName += 'Icon'
}
return pascalName
exports.push(pascalName)
})
.sort()
}
// Process tag icons from icons/tags/loaders/
const loadersDir = path.join(iconsDir, 'tags', 'loaders')
if (fs.existsSync(loadersDir)) {
const loaderFiles = fs.readdirSync(loadersDir).filter((file) => file.endsWith('.svg'))
loaderFiles.forEach((file) => {
const baseName = path.basename(file, '.svg')
let pascalName = toPascalCase(baseName)
if (pascalName === '') {
pascalName = 'Unknown'
}
pascalName = `TagLoader${pascalName}`
if (!pascalName.endsWith('Icon')) {
pascalName += 'Icon'
}
exports.push(pascalName)
})
}
return exports.sort()
}
function getActualIconExports(indexFile: string): string[] {