package.configs.scripts.remove-unused-translation-keys.index.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neeto-commons-frontend Show documentation
Show all versions of neeto-commons-frontend Show documentation
A package encapsulating common code across neeto projects including initializers, utility functions, common components and hooks and so on.
#!/usr/bin/env node
/* eslint-disable no-console */
const fs = require("fs");
const path = require("path");
const { Command } = require("commander");
const { isEmpty, path: rPath } = require("ramda");
const {
I18NEXT_PLURALS,
INTERPOLATED_TRANSLATION_KEY_REGEX,
} = require("./constants");
const isI18nextPlural = key =>
I18NEXT_PLURALS.some(pluralKey => key.endsWith(`_${pluralKey}`));
const logUnusedKeysAndInterpolatedKeys = (
unusedTranslationKeys,
transFuncWithInterpolatedKeys
) => {
console.log("\nUnused translation keys");
console.log("-----------------------");
if (isEmpty(unusedTranslationKeys)) {
console.log("No unused keys found.");
} else {
const unusedKeyCount = unusedTranslationKeys.length;
unusedTranslationKeys.forEach(key => console.log(key));
console.log(`\nDeleted ${unusedKeyCount} unused keys successfully.`);
}
console.log("\n\nTranslation functions with interpolated keys");
console.log("--------------------------------------------");
if (isEmpty(transFuncWithInterpolatedKeys)) {
console.log("No translation functions with interpolated keys found.\n");
} else {
transFuncWithInterpolatedKeys.forEach(hash => {
console.log(`Code: ${hash.code}`);
console.log(`File path: ${hash.filePath}\n`);
});
console.log(
"Please verify whether the keys required by the above translation functions have been removed or not. If removed, please restore them.\n"
);
}
};
const deleteUnusedKeysFromData = (translationData, unusedTranslationKeys) => {
unusedTranslationKeys.forEach(translationKey => {
const keys = translationKey.split(".");
const lastKey = keys.pop();
if (isEmpty(keys)) {
delete translationData[lastKey];
} else {
const currentData = rPath(keys, translationData);
if (currentData) delete currentData[lastKey];
}
});
};
const deleteEmptyValuesFromData = translationData => {
for (const [key, value] of Object.entries(translationData)) {
if (typeof value === "object") {
if (isEmpty(value)) delete translationData[key];
else deleteEmptyValuesFromData(value);
}
}
};
const deleteUsedKeysFromKeysList = (dirPath, keys) => {
fs.readdirSync(dirPath).forEach(fileName => {
const filePath = path.join(dirPath, fileName);
if (fs.statSync(filePath).isFile()) {
const extension = path.extname(filePath);
if (![".js", ".jsx", ".rb", ".jbuilder"].includes(extension)) return;
const data = fs.readFileSync(filePath, "utf8");
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const isKeyFound =
data.includes(key) ||
(isI18nextPlural(key) && data.includes(key.replace(/_[^_]*$/, "")));
if (isKeyFound) {
keys.splice(i, 1);
i--;
}
}
} else if (fs.statSync(filePath).isDirectory()) {
deleteUsedKeysFromKeysList(filePath, keys);
}
});
};
const getUnusedKeys = (searchPath, translationKeys) => {
const keys = [...translationKeys];
deleteUsedKeysFromKeysList(searchPath, keys);
return keys;
};
const findTransFuncWithInterpolatedKeys = dirPath => {
const transFuncWithInterpolatedKeys = [];
fs.readdirSync(dirPath).forEach(fileName => {
const filePath = path.join(dirPath, fileName);
if (fs.statSync(filePath).isFile()) {
const extension = path.extname(filePath);
if (![".js", ".jsx"].includes(extension)) return;
const data = fs.readFileSync(filePath, "utf8");
const relativePath = path.relative(process.cwd(), filePath);
const matchedCodes = data.match(INTERPOLATED_TRANSLATION_KEY_REGEX);
matchedCodes?.forEach(code => {
transFuncWithInterpolatedKeys.push({
code,
filePath: relativePath,
});
});
} else if (fs.statSync(filePath).isDirectory()) {
transFuncWithInterpolatedKeys.push(
...findTransFuncWithInterpolatedKeys(filePath)
);
}
});
return transFuncWithInterpolatedKeys;
};
const generateTranslationKeys = (data, parentKey = null) => {
const translationKeys = [];
for (const [key, value] of Object.entries(data)) {
const currentKey = parentKey ? `${parentKey}.${key}` : key;
if (typeof value === "object") {
translationKeys.push(...generateTranslationKeys(value, currentKey));
} else {
translationKeys.push(currentKey);
}
}
return translationKeys;
};
const getCommandLineOptions = () => {
const program = new Command();
program
.option("--translation-path ", "Path to translation file")
.option("--search-path ", "Path to search for unused keys")
.parse(process.argv);
return program.opts();
};
// Script execution starts here
const {
translationPath = "app/javascript/src/translations/en.json",
searchPath = "app",
} = getCommandLineOptions();
console.log("\nPlease wait, this may take a few seconds...");
const translationData = JSON.parse(fs.readFileSync(translationPath, "utf8"));
const translationKeys = generateTranslationKeys(translationData);
const unusedTranslationKeys = getUnusedKeys(searchPath, translationKeys);
deleteUnusedKeysFromData(translationData, unusedTranslationKeys);
deleteEmptyValuesFromData(translationData);
fs.writeFileSync(
translationPath,
`${JSON.stringify(translationData, null, 2)}\n`
);
const transFuncWithInterpolatedKeys =
findTransFuncWithInterpolatedKeys(searchPath);
logUnusedKeysAndInterpolatedKeys(
unusedTranslationKeys,
transFuncWithInterpolatedKeys
);