import { readFileSync, readdirSync, statSync } from 'fs' import { join, relative } from 'path' import { fileURLToPath } from 'url' import { dirname } from 'path' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) const root = join(__dirname, '..') // Extract dictionary keys from en.ts const enPath = join( root, 'projects/shared/src/i18n/dictionaries/en.ts', ) const enSource = readFileSync(enPath, 'utf-8') const validKeys = new Set() for (const match of enSource.matchAll(/^\s+'(.+?)':\s*\d+/gm)) { validKeys.add(match[1]) } if (validKeys.size === 0) { console.error('ERROR: Could not parse any keys from en.ts') process.exit(1) } console.log(`Loaded ${validKeys.size} i18n keys from en.ts`) // Collect all .ts and .html files under projects/ function walk(dir, files = []) { for (const entry of readdirSync(dir)) { const full = join(dir, entry) if (entry === 'node_modules' || entry === 'dist') continue const stat = statSync(full) if (stat.isDirectory()) { walk(full, files) } else if (full.endsWith('.ts') || full.endsWith('.html')) { files.push(full) } } return files } const projectsDir = join(root, 'projects') const files = walk(projectsDir) const errors = [] for (const file of files) { // Skip the dictionary files themselves if (file.includes('/i18n/dictionaries/')) continue const source = readFileSync(file, 'utf-8') const lines = source.split('\n') for (let i = 0; i < lines.length; i++) { const line = lines[i] // Pattern 1: i18n.transform('Key') or i18n.transform("Key") for (const m of line.matchAll(/i18n\.transform\(\s*'([^']+)'\s*\)/g)) { if (!validKeys.has(m[1])) { errors.push({ file, line: i + 1, key: m[1] }) } } for (const m of line.matchAll(/i18n\.transform\(\s*"([^"]+)"\s*\)/g)) { if (!validKeys.has(m[1])) { errors.push({ file, line: i + 1, key: m[1] }) } } // Pattern 2: 'Key' | i18n or "Key" | i18n (Angular templates) for (const m of line.matchAll(/'([^']+)'\s*\|\s*i18n/g)) { if (!validKeys.has(m[1])) { errors.push({ file, line: i + 1, key: m[1] }) } } for (const m of line.matchAll(/"([^"]+)"\s*\|\s*i18n/g)) { if (!validKeys.has(m[1])) { errors.push({ file, line: i + 1, key: m[1] }) } } } } if (errors.length > 0) { console.error(`\nFound ${errors.length} invalid i18n key(s):\n`) for (const { file, line, key } of errors) { const rel = relative(root, file) console.error(` ${rel}:${line} "${key}"`) } console.error() } // Check that all numeric keys in en.ts exist in every non-English dictionary const enNumericKeys = new Set() for (const match of enSource.matchAll(/^\s+'[^']+?':\s*(\d+)/gm)) { enNumericKeys.add(Number(match[1])) } const dictDir = join(root, 'projects/shared/src/i18n/dictionaries') const otherLangs = ['de', 'es', 'fr', 'pl'] const dictErrors = [] for (const lang of otherLangs) { const dictPath = join(dictDir, `${lang}.ts`) const dictSource = readFileSync(dictPath, 'utf-8') const dictKeys = new Set() for (const match of dictSource.matchAll(/^\s*(\d+):/gm)) { dictKeys.add(Number(match[1])) } const missing = [...enNumericKeys].filter(k => !dictKeys.has(k)).sort((a, b) => a - b) if (missing.length > 0) { dictErrors.push({ lang, missing }) } } if (dictErrors.length > 0) { console.error(`\nMissing i18n dictionary keys:\n`) for (const { lang, missing } of dictErrors) { console.error(` ${lang}.ts is missing keys: ${missing.join(', ')}`) } console.error() } if (errors.length > 0 || dictErrors.length > 0) { process.exit(1) } else { console.log('All i18n keys are valid.') }