From d76f861e72f23a730f5051c261256be7efde4e87 Mon Sep 17 00:00:00 2001 From: "Nicholas Carrigan (he/him)" Date: Thu, 31 Dec 2020 01:17:45 -0800 Subject: [PATCH] tools(client): Translation Key usage validation (#40585) --- client/i18n/validate-keys.js | 62 ++++++++++++++++++++++++++++++++++++ client/package.json | 3 +- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 client/i18n/validate-keys.js diff --git a/client/i18n/validate-keys.js b/client/i18n/validate-keys.js new file mode 100644 index 0000000000..b162ee3a0c --- /dev/null +++ b/client/i18n/validate-keys.js @@ -0,0 +1,62 @@ +const fs = require('fs'); +const path = require('path'); +const translationsObject = require('./locales/english/translations.json'); + +/** + * Function to flatten a nested object. Written specifically for + * our translation flow, the `namespace` value is used to create the + * property chains that are used in the i18n replacement scripts. + * @param {Object} obj + * @param {string} namespace + */ +const flattenAnObject = (obj, namespace = '') => { + const flattened = {}; + Object.keys(obj).forEach(key => { + if (Array.isArray(obj[key])) { + flattened[namespace ? `${namespace}.${key}` : key] = obj[key]; + } else if (typeof obj[key] === 'object') { + Object.assign( + flattened, + flattenAnObject(obj[key], namespace ? `${namespace}.${key}` : key) + ); + } else { + flattened[namespace ? `${namespace}.${key}` : key] = obj[key]; + } + }); + return flattened; +}; + +const flattenedSchema = flattenAnObject(translationsObject); + +const keyStrings = Object.keys(flattenedSchema); + +/** + * Recursively read through the directory, grabbing .js files + * in each nested subdirectory and concatenating them all in + * to one string. + * @param {String} filePath + */ +const readComponentCode = filePath => { + let code = ''; + const isItFolder = fs.lstatSync(filePath).isDirectory(); + if (isItFolder) { + const contents = fs.readdirSync(filePath); + contents.forEach(file => { + code += readComponentCode(path.join(filePath + '/' + file)); + }); + } else { + if (!filePath.endsWith('.js') || filePath.endsWith('.test.js')) { + return ''; + } + code += fs.readFileSync(filePath); + } + return code; +}; + +const clientCodebase = readComponentCode(path.join(process.cwd() + '/src')); + +for (const key of keyStrings) { + if (!clientCodebase.includes(key)) { + console.warn(`The translation key '${key}' appears to be unused.`); + } +} diff --git a/client/package.json b/client/package.json index fea2f796e9..0cad4d3e3b 100644 --- a/client/package.json +++ b/client/package.json @@ -98,7 +98,8 @@ "prestand-alone": "npm run prebuild", "stand-alone": "gatsby develop", "serve": "gatsby serve -p 8000", - "test": "jest" + "test": "jest", + "validate-keys": "node ./i18n/validate-keys.js" }, "devDependencies": { "@testing-library/jest-dom": "^5.11.9",