`find-unused`: Finds translation keys in en.ts that are not used in the source code
()
| 532 | |
| 533 | /** `find-unused`: Finds translation keys in en.ts that are not used in the source code */ |
| 534 | async function findUnused() { |
| 535 | console.log('Finding unused translation keys...\n'); |
| 536 | |
| 537 | const manifest = loadJson(MANIFEST_PATH); |
| 538 | |
| 539 | if (Object.keys(manifest).length === 0) { |
| 540 | console.error('❌ Error: No manifest found. Run "npm run i18n:sync" first.'); |
| 541 | process.exit(1); |
| 542 | } |
| 543 | |
| 544 | // Get all keys from manifest |
| 545 | const allKeys = Object.keys(manifest); |
| 546 | |
| 547 | // Find all translation function calls in the codebase |
| 548 | const patterns = [ |
| 549 | `rg --no-filename --no-heading --no-line-number '\\bt\\(["\\x27]([^"\\x27]+)["\\x27]\\)' -o -r '\$1' src/`, |
| 550 | `rg --no-filename --no-heading --no-line-number 'i18n\\.translate\\(["\\x27]([^"\\x27]+)["\\x27]' -o -r '\$1' src/`, |
| 551 | `rg --no-filename --no-heading --no-line-number 'this\\.t\\(["\\x27]([^"\\x27]+)["\\x27]' -o -r '\$1' src/`, |
| 552 | `rg --no-filename --no-heading --no-line-number 'this\\.translate\\(["\\x27]([^"\\x27]+)["\\x27]' -o -r '\$1' src/`, |
| 553 | `rg --no-filename --no-heading --no-line-number '\\btranslate\\(["\\x27]([^"\\x27]+)["\\x27]' -o -r '\$1' src/` |
| 554 | ]; |
| 555 | |
| 556 | const usedKeys = new Set(); |
| 557 | |
| 558 | for (const pattern of patterns) { |
| 559 | try { |
| 560 | const output = execSync(pattern, { encoding: 'utf8', shell: '/bin/bash' }); |
| 561 | output.trim().split('\n').filter(Boolean).forEach(key => usedKeys.add(key)); |
| 562 | } catch (error) { |
| 563 | // No matches is fine |
| 564 | } |
| 565 | } |
| 566 | |
| 567 | // Find keys in manifest but not used in source |
| 568 | const unusedKeys = allKeys.filter(key => !usedKeys.has(key)); |
| 569 | |
| 570 | console.log(`📊 Statistics:`); |
| 571 | console.log(` Total keys in en.ts: ${allKeys.length}`); |
| 572 | console.log(` Keys found in source code: ${usedKeys.size}`); |
| 573 | console.log(` Potentially unused keys: ${unusedKeys.length}`); |
| 574 | console.log(` Coverage: ${Math.round((usedKeys.size / allKeys.length) * 100)}%\n`); |
| 575 | |
| 576 | if (unusedKeys.length > 0) { |
| 577 | console.log('⚠️ Potentially unused keys (not found in source code):\n'); |
| 578 | |
| 579 | // Group by prefix for easier reading |
| 580 | const grouped = {}; |
| 581 | unusedKeys.forEach(key => { |
| 582 | const prefix = key.split('.')[0]; |
| 583 | if (!grouped[prefix]) grouped[prefix] = []; |
| 584 | grouped[prefix].push(key); |
| 585 | }); |
| 586 | |
| 587 | for (const [prefix, keys] of Object.entries(grouped).sort()) { |
| 588 | console.log(`[${prefix}] ${keys.length} keys:`); |
| 589 | keys.forEach(key => console.log(` - ${key}`)); |
| 590 | console.log(''); |
| 591 | } |
no test coverage detected