Skip to content

Commit

Permalink
feat(frontend): dynamic load i18n resources
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker committed Dec 15, 2024
1 parent 35af50c commit ab0689f
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 385 deletions.
4 changes: 2 additions & 2 deletions src/GZCTF/ClientApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"lint": "oxlint --fix && eslint",
"build": "tsc && vite build",
"preview": "vite preview",
"prettier": "prettier --write src",
"prettier": "prettier --write src --write plugins --write *.mjs --write *.mts",
"genapi": "swagger-typescript-api -p http://localhost:55000/openapi/v1.json -t template -o src --module-name-first-tag --sort-routes"
},
"dependencies": {
Expand Down Expand Up @@ -39,6 +39,7 @@
"fast-average-color": "^9.4.0",
"i18next": "^24.1.0",
"i18next-browser-languagedetector": "^8.0.2",
"i18next-resources-to-backend": "^1.2.1",
"katex": "^0.16.15",
"lz-string": "^1.5.0",
"marked": "^15.0.4",
Expand Down Expand Up @@ -91,7 +92,6 @@
"typescript": "5.7.2",
"vite": "^6.0.3",
"vite-plugin-banner": "^0.8.0",
"vite-plugin-i18next-loader": "^3.0.0",
"vite-plugin-optimize-css-modules": "^1.2.0",
"vite-plugin-pages": "^0.32.4",
"vite-plugin-prismjs": "^0.0.11",
Expand Down
110 changes: 110 additions & 0 deletions src/GZCTF/ClientApp/plugins/vite-i18n-virtual-manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import crypto from 'crypto'
import fs from 'fs'
import path from 'path'
import { createLogger } from 'vite'
import type { Plugin } from 'vite'

export default function i18nVirtualManifest(): Plugin {
let manifest: Record<string, string> = {}
let contents: Record<string, object> = {}
const logger = createLogger()
let localesDir: string
let outputDir: string

const reloadResources = () => {
const newManifest: Record<string, string> = {}
const newContents: Record<string, object> = {}

try {
fs.readdirSync(localesDir)
.filter(function (file) {
return fs.statSync(path.join(localesDir, file)).isDirectory()
})
.forEach((lang) => {
const langDir = path.join(localesDir, lang)
const files = fs.readdirSync(langDir).filter((file) => file.endsWith('.json'))

const merged = files.reduce((acc, file) => {
const key = file.replace('.json', '')
const filePath = path.join(langDir, file)
const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
return { ...acc, [key]: content }
}, {})

const contentString = JSON.stringify(merged)
const hash = crypto.createHash('md5').update(contentString).digest('hex').slice(0, 8)
const outputFileName = `${lang}.${hash}.json`

newManifest[lang.toLowerCase()] = outputFileName
newContents[outputFileName] = merged
})

manifest = newManifest
contents = newContents
} catch (e) {
logger.error(`Error reading locales directory: ${e}`)
}
}

return {
name: 'vite-i18n-virtual-manifest',

configResolved(config) {
localesDir = path.resolve(config.root, path.join('src', 'locales'))
outputDir = config.build.assetsDir
},

buildStart() {
reloadResources()

// emit files only in production mode
if (process.env.NODE_ENV === 'production') {
Object.keys(contents).forEach((file) => {
this.emitFile({
type: 'asset',
fileName: path.join(outputDir, file),
source: JSON.stringify(contents[file]),
})
})
}

this.addWatchFile(localesDir)
},

configureServer(server) {
// handle requests for i18n resources like `/static/${file}`
server.middlewares.use((req, res, next) => {
if (!req.url?.startsWith('/static/')) {
return next()
}

const file = req.url.slice('/static/'.length)
const content = contents[file]

if (!content) {
res.statusCode = 404
res.end('File not found')
return
}

res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(content))
})

// watch for changes in locales directory
server.watcher.add(localesDir).on('change', (file) => {
if (file.endsWith('.json') && file.includes('locales')) reloadResources()
})
},

resolveId(id) {
if (id === 'virtual:i18n-manifest') return id
return null
},

load(id) {
if (id !== 'virtual:i18n-manifest') return null
return `export default ${JSON.stringify(manifest)}`
},
}
}
Loading

0 comments on commit ab0689f

Please sign in to comment.