[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 106 lines 3.8 kB view raw
1/* eslint-disable no-console */ 2import { join } from 'node:path' 3import { fileURLToPath } from 'node:url' 4import { createI18NReport, type I18NItem } from 'vue-i18n-extract' 5import { colors } from './utils/colors.ts' 6import { readdir, readFile, writeFile } from 'node:fs/promises' 7 8const LOCALES_DIRECTORY = fileURLToPath(new URL('../i18n/locales', import.meta.url)) 9const REFERENCE_FILE_NAME = 'en.json' 10const VUE_FILES_GLOB = './app/**/*.?(vue|ts|js)' 11 12type NestedObject = Record<string, unknown> 13 14/** Removes a key path (e.g. "foo.bar.baz") from a nested object. Cleans up empty parents. */ 15function removeKey(obj: NestedObject, path: string): boolean { 16 const parts = path.split('.') 17 if (parts.length === 1) { 18 if (path in obj) { 19 delete obj[path] 20 return true 21 } 22 return false 23 } 24 const [first, ...rest] = parts 25 const child = obj[first] 26 if (child && typeof child === 'object' && !Array.isArray(child)) { 27 const removed = removeKey(child as NestedObject, rest.join('.')) 28 if (removed && Object.keys(child as object).length === 0) { 29 delete obj[first] 30 } 31 return removed 32 } 33 return false 34} 35 36/** Removes multiple keys from a nested object. Sorts by depth (deepest first) to avoid parent/child conflicts. */ 37function removeKeysFromObject(obj: NestedObject, keys: string[]): number { 38 const sortedKeys = [...keys].sort((a, b) => b.split('.').length - a.split('.').length) 39 let removed = 0 40 for (const key of sortedKeys) { 41 if (removeKey(obj, key)) removed++ 42 } 43 return removed 44} 45 46async function run(): Promise<void> { 47 console.log(colors.bold('\n🔍 Removing unused i18n translations...\n')) 48 49 const referenceFilePath = join(LOCALES_DIRECTORY, REFERENCE_FILE_NAME) 50 51 const { unusedKeys } = await createI18NReport({ 52 vueFiles: VUE_FILES_GLOB, 53 languageFiles: referenceFilePath, 54 exclude: ['$schema'], 55 }) 56 57 if (unusedKeys.length === 0) { 58 console.log(colors.green('✅ No unused translations found. Nothing to remove.\n')) 59 return 60 } 61 62 const uniquePaths = [...new Set(unusedKeys.map((item: I18NItem) => item.path))] 63 64 // Remove from reference file 65 const referenceContent = JSON.parse(await readFile(referenceFilePath, 'utf-8')) as NestedObject 66 const refRemoved = removeKeysFromObject(referenceContent, uniquePaths) 67 await writeFile(referenceFilePath, JSON.stringify(referenceContent, null, 2) + '\n', 'utf-8') 68 69 // Remove from all other locale files 70 const localeFiles = (await readdir(LOCALES_DIRECTORY)).filter( 71 f => f.endsWith('.json') && f !== REFERENCE_FILE_NAME, 72 ) 73 74 const otherLocalesSummary: { file: string; removed: number }[] = [] 75 let totalOtherRemoved = 0 76 77 for (const localeFile of localeFiles) { 78 const filePath = join(LOCALES_DIRECTORY, localeFile) 79 const content = JSON.parse(await readFile(filePath, 'utf-8')) as NestedObject 80 const removed = removeKeysFromObject(content, uniquePaths) 81 if (removed > 0) { 82 await writeFile(filePath, JSON.stringify(content, null, 2) + '\n', 'utf-8') 83 otherLocalesSummary.push({ file: localeFile, removed }) 84 totalOtherRemoved += removed 85 } 86 } 87 88 // Summary 89 console.log(colors.green(`✅ Removed ${refRemoved} keys from ${REFERENCE_FILE_NAME}`)) 90 if (otherLocalesSummary.length > 0) { 91 console.log( 92 colors.green( 93 `✅ Removed ${totalOtherRemoved} keys from ${otherLocalesSummary.length} other locale(s)`, 94 ), 95 ) 96 for (const { file, removed } of otherLocalesSummary) { 97 console.log(colors.dim(` ${file}: ${removed} keys`)) 98 } 99 } 100 console.log(colors.dim(`\nTotal: ${uniquePaths.length} unique unused key(s) cleaned up\n`)) 101} 102 103run().catch((error: unknown) => { 104 console.error(colors.red('\n❌ Unexpected error:'), error) 105 process.exit(1) 106})