UNPKG

@scoutello/i18n-magic

Version:

Intelligent CLI toolkit that automates internationalization workflows for JavaScript/TypeScript projects and auto-translates new string keys to other languages

136 lines • 5.62 kB
import prompts from "prompts"; import { getKeysWithNamespaces, getPureKey, loadLocalesFile, writeLocalesFile, } from "../lib/utils.js"; export const findUnusedKeys = async (config) => { const { globPatterns, namespaces, defaultNamespace, locales, loadPath } = config; const keysWithNamespaces = await getKeysWithNamespaces({ globPatterns, defaultNamespace, }); const keysByNamespace = {}; for (const { key, namespaces: keyNamespaces } of keysWithNamespaces) { for (const namespace of keyNamespaces) { if (!keysByNamespace[namespace]) { keysByNamespace[namespace] = new Set(); } const pureKey = getPureKey(key, namespace, namespace === defaultNamespace); const finalKey = pureKey || (!key.includes(":") ? key : null); if (finalKey) { keysByNamespace[namespace].add(finalKey); } } } const unusedKeys = []; let totalKeys = 0; for (const namespace of namespaces) { const usedKeysSet = keysByNamespace[namespace] || new Set(); const unusedInNamespace = new Map(); for (const locale of locales) { const existingKeys = await loadLocalesFile(loadPath, locale, namespace); for (const key of Object.keys(existingKeys)) { totalKeys++; if (!usedKeysSet.has(key)) { if (!unusedInNamespace.has(key)) { unusedInNamespace.set(key, []); } unusedInNamespace.get(key).push(locale); } } } for (const [key, keyLocales] of unusedInNamespace) { unusedKeys.push({ key, namespace, locales: keyLocales }); } } const uniqueUnusedKeys = new Map(); for (const item of unusedKeys) { const uniqueKey = `${item.namespace}:${item.key}`; if (!uniqueUnusedKeys.has(uniqueKey)) { uniqueUnusedKeys.set(uniqueKey, item); } } return { totalKeys, unusedCount: uniqueUnusedKeys.size, unusedKeys: Array.from(uniqueUnusedKeys.values()), }; }; export const removeUnusedKeys = async (config, options) => { const { globPatterns, namespaces, defaultNamespace, locales, loadPath, savePath, } = config; console.log("šŸ” Scanning for unused translation keys...\n"); const report = await findUnusedKeys(config); if (report.unusedCount === 0) { console.log(`āœ… No unused keys found in the project (${report.totalKeys} total key instances across all locales)`); return; } console.log(`\nāš ļø Found ${report.unusedCount} unused translation key(s):\n`); const maxKeysToShow = 20; const keysToShow = report.unusedKeys.slice(0, maxKeysToShow); for (const { key, namespace } of keysToShow) { console.log(` • ${namespace}:${key}`); } if (report.unusedKeys.length > maxKeysToShow) { console.log(` ... and ${report.unusedKeys.length - maxKeysToShow} more`); } console.log(""); if (!options?.skipConfirmation) { const { confirmed } = await prompts({ type: "confirm", name: "confirmed", message: `Do you want to remove these ${report.unusedCount} unused key(s)?`, initial: false, }); if (!confirmed) { console.log("\nāŒ Operation cancelled. No keys were removed."); return; } } console.log("\nšŸ—‘ļø Removing unused keys..."); const keysWithNamespaces = await getKeysWithNamespaces({ globPatterns, defaultNamespace, }); const keysByNamespace = {}; for (const { key, namespaces: keyNamespaces } of keysWithNamespaces) { for (const namespace of keyNamespaces) { if (!keysByNamespace[namespace]) { keysByNamespace[namespace] = new Set(); } const pureKey = getPureKey(key, namespace, namespace === defaultNamespace); const finalKey = pureKey || (!key.includes(":") ? key : null); if (finalKey) { keysByNamespace[namespace].add(finalKey); } } } const stats = { total: 0, removed: 0, }; const results = await Promise.all(namespaces.flatMap((namespace) => { const usedKeysSet = keysByNamespace[namespace] || new Set(); return locales.map(async (locale) => { const existingKeys = await loadLocalesFile(loadPath, locale, namespace); const existingKeysCount = Object.keys(existingKeys).length; const cleanedKeys = {}; let removedCount = 0; for (const [key, value] of Object.entries(existingKeys)) { if (usedKeysSet.has(key)) { cleanedKeys[key] = value; } else { removedCount++; } } if (removedCount > 0) { await writeLocalesFile(savePath, locale, namespace, cleanedKeys); console.log(` āœ“ Removed ${removedCount} unused keys from ${locale}:${namespace} (${Object.keys(cleanedKeys).length} keys remaining)`); } return { total: existingKeysCount, removed: removedCount }; }); })); for (const result of results) { stats.total += result.total; stats.removed += result.removed; } console.log(`\nāœ… Removed ${stats.removed} unused keys (out of ${stats.total} total keys)`); }; //# sourceMappingURL=clean.js.map