Files
PicGo/scripts/vite-plugin-i18n-types.ts
T
2026-07-01 10:22:36 +08:00

116 lines
3.2 KiB
TypeScript

import fs from 'node:fs'
import path from 'node:path'
import { parse } from 'yaml'
import type { Plugin } from 'vite'
const I18N_SOURCE_DIR = 'public/i18n'
const SOURCE_RELATIVE_PATH = `${I18N_SOURCE_DIR}/en.yml`
const UNIVERSAL_OUTPUT_RELATIVE_PATH = 'src/universal/types/i18n.d.ts'
const RENDERER_OUTPUT_RELATIVE_PATH = 'src/renderer/i18n/i18next.d.ts'
function formatKey (key: string) {
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key)
}
function readTranslationKeys (sourcePath: string) {
if (!fs.existsSync(sourcePath)) {
return []
}
const raw = fs.readFileSync(sourcePath, 'utf8')
const data = parse(raw)
if (!data || typeof data !== 'object' || Array.isArray(data)) {
return []
}
return Object.keys(data as Record<string, unknown>)
}
function buildUniversalTypeContent (keys: string[]) {
const uniqueKeys = Array.from(new Set(keys))
const lines = uniqueKeys.map((key) => ` ${formatKey(key)}: string`)
return `// This file is auto-generated by vite-plugin-i18n-types. Do not edit.
interface ILocales {
${lines.join('\n')}
}
type ILocalesKey = keyof ILocales
`
}
function buildRendererTypeContent () {
return `// This file is auto-generated by vite-plugin-i18n-types. Do not edit.
import "i18next"
declare module "i18next" {
interface CustomTypeOptions {
resources: {
translation: ILocales
}
}
}
`
}
function writeFileIfChanged (outputPath: string, nextContent: string) {
const previousContent = fs.existsSync(outputPath)
? fs.readFileSync(outputPath, 'utf8')
: ''
if (previousContent === nextContent) {
return
}
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
fs.writeFileSync(outputPath, nextContent, 'utf8')
}
function writeTypesFiles (sourcePath: string, universalOutputPath: string, rendererOutputPath: string) {
const keys = readTranslationKeys(sourcePath)
writeFileIfChanged(universalOutputPath, buildUniversalTypeContent(keys))
writeFileIfChanged(rendererOutputPath, buildRendererTypeContent())
}
export function i18nTypesPlugin (): Plugin {
let sourcePath = ''
let universalOutputPath = ''
let rendererOutputPath = ''
let projectRoot = ''
const generate = () => {
try {
writeTypesFiles(sourcePath, universalOutputPath, rendererOutputPath)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
console.error(`[vite-plugin-i18n-types] ${message}`)
}
}
return {
name: 'vite-plugin-i18n-types',
enforce: 'pre',
configResolved (config) {
projectRoot = path.resolve(config.root, '../..')
sourcePath = path.resolve(projectRoot, SOURCE_RELATIVE_PATH)
universalOutputPath = path.resolve(projectRoot, UNIVERSAL_OUTPUT_RELATIVE_PATH)
rendererOutputPath = path.resolve(projectRoot, RENDERER_OUTPUT_RELATIVE_PATH)
},
buildStart () {
generate()
},
configureServer (server) {
server.watcher.add(path.resolve(projectRoot, I18N_SOURCE_DIR))
},
handleHotUpdate ({ file }) {
const normalizedFile = path.resolve(file)
const normalizedSourceDir = path.resolve(path.dirname(sourcePath))
if (normalizedFile.startsWith(normalizedSourceDir)) {
generate()
}
}
}
}