All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.configs.scripts.remove-unused-translation-keys.index.js Maven / Gradle / Ivy

Go to download

A package encapsulating common code across neeto projects including initializers, utility functions, common components and hooks and so on.

There is a newer version: 4.12.3
Show newest version
#!/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
);




© 2015 - 2024 Weber Informatics LLC | Privacy Policy