feat: add --removeUnused flag to extract translations

This commit is contained in:
Nybkox
2025-02-20 15:09:12 +01:00
parent e9779a3d2f
commit ea5e2f660b
4 changed files with 53 additions and 25 deletions

View File

@@ -0,0 +1,5 @@
const defaultLanguage = 'en-US';
module.exports = {
defaultLanguage,
};

View File

@@ -27,13 +27,18 @@ async function extractTranslationsFromFile(file) {
return translations; return translations;
} }
/** @typedef {{ ignoreDuplicates?: boolean }} ExtractOptions */
/** /**
* @param {string[]} directories * @param {string[]} directories
* @param {string[]} extensions * @param {string[]} extensions
* @param {ExtractOptions} options
* *
* @returns {Promise<Record<string, string>>} * @returns {Promise<Record<string, string>>}
*/ */
async function extractAllTranslations(directories, extensions) { async function extractAllTranslations(directories, extensions, options = {}) {
const { ignoreDuplicates } = options;
try { try {
/** @type {Record<string, string>} */ /** @type {Record<string, string>} */
const allTranslations = {}; const allTranslations = {};
@@ -53,7 +58,7 @@ async function extractAllTranslations(directories, extensions) {
translationKeyToFiles[key].push(file); translationKeyToFiles[key].push(file);
if (allTranslations[key] && allTranslations[key] !== fileTranslations[key]) { if (!ignoreDuplicates && allTranslations[key] && allTranslations[key] !== fileTranslations[key]) {
console.error( console.error(
`Different translations for the same key [${key}] found. ${file}: ${ `Different translations for the same key [${key}] found. ${file}: ${
fileTranslations[key] fileTranslations[key]

View File

@@ -1,6 +1,7 @@
//@ts-check //@ts-check
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { defaultLanguage } = require('./constants');
/** /**
* @param {string} file * @param {string} file
@@ -122,8 +123,6 @@ const getTranslationChanges = (existingTranslations, newTranslations) => {
return { added, removed, updated }; return { added, removed, updated };
}; };
const defaultLanguage = 'en-US';
function getDefaultTranslations() { function getDefaultTranslations() {
return getLanguageTranslations(defaultLanguage); return getLanguageTranslations(defaultLanguage);
} }
@@ -149,6 +148,17 @@ function setLanguageTranslations(language, translations) {
fs.writeFileSync(file, JSON.stringify(translations, null, 2)); fs.writeFileSync(file, JSON.stringify(translations, null, 2));
} }
/**
* @param {string} language
* @param {Record<string, string>} newTranslations
*/
function updateLanguageTranslations(language, newTranslations) {
const translations = getLanguageTranslations(language);
const updatedTranslations = { ...translations, ...newTranslations };
setLanguageTranslations(language, updatedTranslations);
}
function getAllLanguages() { function getAllLanguages() {
const dir = resolveFile('translations'); const dir = resolveFile('translations');
@@ -174,6 +184,7 @@ module.exports = {
getDefaultTranslations, getDefaultTranslations,
getLanguageTranslations, getLanguageTranslations,
setLanguageTranslations, setLanguageTranslations,
updateLanguageTranslations,
getAllLanguages, getAllLanguages,
getAllNonDefaultLanguages, getAllNonDefaultLanguages,
}; };

View File

@@ -9,20 +9,22 @@ const {
getTranslationChanges, getTranslationChanges,
setLanguageTranslations, setLanguageTranslations,
getAllNonDefaultLanguages, getAllNonDefaultLanguages,
updateLanguageTranslations,
getDefaultTranslations,
} = require('./helpers'); } = require('./helpers');
const { extractAllTranslations } = require('./extract'); const { extractAllTranslations } = require('./extract');
const { getMissingTranslations } = require('./addMissing'); const { getMissingTranslations } = require('./addMissing');
const { defaultLanguage } = require('./constants');
/** /**
* @typedef {{ extensions: string[], directories: string[], outputFile: string}} ExtractConfig * @typedef {{ extensions: string[], directories: string[]}} ExtractConfig
* @typedef {ExtractConfig & { verbose?: boolean }} ExtractOptions * @typedef {ExtractConfig & { verbose?: boolean, removeUnused?: boolean }} ExtractOptions
*/ */
/** @type {ExtractConfig} */ /** @type {ExtractConfig} */
const defaultConfig = { const defaultConfig = {
extensions: ['.js', '.ts', '.svelte'], extensions: ['.js', '.ts', '.svelte'],
directories: ['app', 'packages/web'], directories: ['app', 'packages/web'],
outputFile: './translations/en-US.json',
}; };
program.name('dbgate-translations-cli').description('CLI tool for managing translation').version('1.0.0'); program.name('dbgate-translations-cli').description('CLI tool for managing translation').version('1.0.0');
@@ -32,33 +34,25 @@ program
.description('Extract translation keys from source files') .description('Extract translation keys from source files')
.option('-d, --directories <directories...>', 'directories to search', defaultConfig.directories) .option('-d, --directories <directories...>', 'directories to search', defaultConfig.directories)
.option('-e, --extensions <extensions...>', 'file extensions to process', defaultConfig.extensions) .option('-e, --extensions <extensions...>', 'file extensions to process', defaultConfig.extensions)
.option('-o, --outputFile <file>', 'output file path', defaultConfig.outputFile) .option('-r, --removeUnused', 'Remove unused keys from the output file')
.option('-v, --verbose', 'verbose mode') .option('-v, --verbose', 'verbose mode')
.action(async (/** @type {ExtractOptions} */ options) => { .action(async (/** @type {ExtractOptions} */ options) => {
try { try {
const { directories, extensions, outputFile, verbose } = options; const { directories, extensions, verbose, removeUnused } = options;
const resolvedRirectories = resolveDirs(directories); const resolvedRirectories = resolveDirs(directories);
const resolvedExtensions = resolveExtensions(extensions); const resolvedExtensions = resolveExtensions(extensions);
const translations = await extractAllTranslations(resolvedRirectories, resolvedExtensions); const extractedTranslations = await extractAllTranslations(resolvedRirectories, resolvedExtensions);
const defaultTranslations = getDefaultTranslations();
const resolvedOutputFile = resolveFile(outputFile); const { added, removed, updated } = getTranslationChanges(defaultTranslations, extractedTranslations);
ensureFileDirExists(resolvedOutputFile);
/** @type {Record<string, string>} */
let existingTranslations = {};
if (fs.existsSync(resolvedOutputFile)) {
existingTranslations = JSON.parse(fs.readFileSync(resolvedOutputFile, 'utf-8'));
}
const { added, removed, updated } = getTranslationChanges(existingTranslations, translations);
console.log('\nTranslation changes:'); console.log('\nTranslation changes:');
console.log(`- Added: ${added.length} keys`); console.log(`- Added: ${added.length} keys`);
console.log(`- Removed: ${removed.length} keys`); console.log(`- ${removeUnused ? 'Removed' : 'Unused'}: ${removed.length} keys`);
console.log(`- Updated: ${updated.length} keys`); console.log(`- Updated: ${updated.length} keys`);
console.log(`- Total: ${Object.keys(translations).length} keys`); console.log(`- Total: ${Object.keys(extractedTranslations).length} keys`);
if (verbose) { if (verbose) {
if (added.length > 0) { if (added.length > 0) {
@@ -75,13 +69,26 @@ program
console.log('\nUpdated keys:'); console.log('\nUpdated keys:');
updated.forEach(key => { updated.forEach(key => {
console.log(` ~ ${key}`); console.log(` ~ ${key}`);
console.log(` Old: ${existingTranslations[key]}`); console.log(` Old: ${defaultLanguage[key]}`);
console.log(` New: ${translations[key]}`); console.log(` New: ${extractedTranslations[key]}`);
}); });
} }
} }
fs.writeFileSync(resolvedOutputFile, JSON.stringify(translations, null, 2)); if (removeUnused) {
console.log('Unused keys were removed.\n');
setLanguageTranslations(defaultLanguage, extractedTranslations);
} else {
console.log('New translations were saved. Unused keys are kept.\n');
updateLanguageTranslations(defaultLanguage, extractedTranslations);
if (verbose) {
console.log('\nUnused keys:');
for (const key of removed) {
console.log(`${key}: "${defaultTranslations[key]}"`);
}
}
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
console.error('Error during extraction:', error.message); console.error('Error during extraction:', error.message);