diff --git a/common/translations-cli/constants.js b/common/translations-cli/constants.js new file mode 100644 index 000000000..b37a3e98e --- /dev/null +++ b/common/translations-cli/constants.js @@ -0,0 +1,5 @@ +const defaultLanguage = 'en-US'; + +module.exports = { + defaultLanguage, +}; diff --git a/common/translations-cli/extract.js b/common/translations-cli/extract.js index 5131336fd..99ad0633c 100644 --- a/common/translations-cli/extract.js +++ b/common/translations-cli/extract.js @@ -27,13 +27,18 @@ async function extractTranslationsFromFile(file) { return translations; } +/** @typedef {{ ignoreDuplicates?: boolean }} ExtractOptions */ + /** * @param {string[]} directories * @param {string[]} extensions + * @param {ExtractOptions} options * * @returns {Promise>} */ -async function extractAllTranslations(directories, extensions) { +async function extractAllTranslations(directories, extensions, options = {}) { + const { ignoreDuplicates } = options; + try { /** @type {Record} */ const allTranslations = {}; @@ -53,7 +58,7 @@ async function extractAllTranslations(directories, extensions) { translationKeyToFiles[key].push(file); - if (allTranslations[key] && allTranslations[key] !== fileTranslations[key]) { + if (!ignoreDuplicates && allTranslations[key] && allTranslations[key] !== fileTranslations[key]) { console.error( `Different translations for the same key [${key}] found. ${file}: ${ fileTranslations[key] diff --git a/common/translations-cli/helpers.js b/common/translations-cli/helpers.js index 3bcc9180f..c5217bdea 100644 --- a/common/translations-cli/helpers.js +++ b/common/translations-cli/helpers.js @@ -1,6 +1,7 @@ //@ts-check const path = require('path'); const fs = require('fs'); +const { defaultLanguage } = require('./constants'); /** * @param {string} file @@ -122,8 +123,6 @@ const getTranslationChanges = (existingTranslations, newTranslations) => { return { added, removed, updated }; }; -const defaultLanguage = 'en-US'; - function getDefaultTranslations() { return getLanguageTranslations(defaultLanguage); } @@ -149,6 +148,17 @@ function setLanguageTranslations(language, translations) { fs.writeFileSync(file, JSON.stringify(translations, null, 2)); } +/** + * @param {string} language + * @param {Record} newTranslations + */ +function updateLanguageTranslations(language, newTranslations) { + const translations = getLanguageTranslations(language); + const updatedTranslations = { ...translations, ...newTranslations }; + + setLanguageTranslations(language, updatedTranslations); +} + function getAllLanguages() { const dir = resolveFile('translations'); @@ -174,6 +184,7 @@ module.exports = { getDefaultTranslations, getLanguageTranslations, setLanguageTranslations, + updateLanguageTranslations, getAllLanguages, getAllNonDefaultLanguages, }; diff --git a/common/translations-cli/program.js b/common/translations-cli/program.js index c12532abf..3fed3d244 100644 --- a/common/translations-cli/program.js +++ b/common/translations-cli/program.js @@ -9,20 +9,22 @@ const { getTranslationChanges, setLanguageTranslations, getAllNonDefaultLanguages, + updateLanguageTranslations, + getDefaultTranslations, } = require('./helpers'); const { extractAllTranslations } = require('./extract'); const { getMissingTranslations } = require('./addMissing'); +const { defaultLanguage } = require('./constants'); /** - * @typedef {{ extensions: string[], directories: string[], outputFile: string}} ExtractConfig - * @typedef {ExtractConfig & { verbose?: boolean }} ExtractOptions + * @typedef {{ extensions: string[], directories: string[]}} ExtractConfig + * @typedef {ExtractConfig & { verbose?: boolean, removeUnused?: boolean }} ExtractOptions */ /** @type {ExtractConfig} */ const defaultConfig = { extensions: ['.js', '.ts', '.svelte'], directories: ['app', 'packages/web'], - outputFile: './translations/en-US.json', }; 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') .option('-d, --directories ', 'directories to search', defaultConfig.directories) .option('-e, --extensions ', 'file extensions to process', defaultConfig.extensions) - .option('-o, --outputFile ', 'output file path', defaultConfig.outputFile) + .option('-r, --removeUnused', 'Remove unused keys from the output file') .option('-v, --verbose', 'verbose mode') .action(async (/** @type {ExtractOptions} */ options) => { try { - const { directories, extensions, outputFile, verbose } = options; + const { directories, extensions, verbose, removeUnused } = options; const resolvedRirectories = resolveDirs(directories); const resolvedExtensions = resolveExtensions(extensions); - const translations = await extractAllTranslations(resolvedRirectories, resolvedExtensions); + const extractedTranslations = await extractAllTranslations(resolvedRirectories, resolvedExtensions); + const defaultTranslations = getDefaultTranslations(); - const resolvedOutputFile = resolveFile(outputFile); - ensureFileDirExists(resolvedOutputFile); - - /** @type {Record} */ - let existingTranslations = {}; - if (fs.existsSync(resolvedOutputFile)) { - existingTranslations = JSON.parse(fs.readFileSync(resolvedOutputFile, 'utf-8')); - } - - const { added, removed, updated } = getTranslationChanges(existingTranslations, translations); + const { added, removed, updated } = getTranslationChanges(defaultTranslations, extractedTranslations); console.log('\nTranslation changes:'); 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(`- Total: ${Object.keys(translations).length} keys`); + console.log(`- Total: ${Object.keys(extractedTranslations).length} keys`); if (verbose) { if (added.length > 0) { @@ -75,13 +69,26 @@ program console.log('\nUpdated keys:'); updated.forEach(key => { console.log(` ~ ${key}`); - console.log(` Old: ${existingTranslations[key]}`); - console.log(` New: ${translations[key]}`); + console.log(` Old: ${defaultLanguage[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) { console.error(error); console.error('Error during extraction:', error.message);