#!/usr/bin/env node // This file does a few things to ensure that the Locales are present and valid: // - Ensures that the name of the locale exists in the language list // - Ensures that each locale contains the translations used in the application // - Ensures that there are no unused translations in the locale files // - Also checks the error messages returned by the backend const allLocales = [ ["en", "en-US"], ["de", "de-DE"], ["fa", "fa-IR"], ]; const ignoreUnused = [ /^acmesh\..*$/, /^acmesh-property\..*$/, /^capability\..*$/, /^status\..*$/, /^type\..*$/, ]; const ignoreMissing = [/^acmesh\..*$/]; const { spawnSync } = require("child_process"); const fs = require("fs"); const tmp = require("tmp"); // Parse backend errors const BACKEND_ERRORS_FILE = "../backend/internal/errors/errors.go"; const BACKEND_ERRORS = []; try { const backendErrorsContent = fs.readFileSync(BACKEND_ERRORS_FILE, "utf8"); const backendErrorsContentRes = [ ...backendErrorsContent.matchAll(/(?:errors|eris)\.New\("([^"]+)"\)/g), ]; backendErrorsContentRes.map((item) => { BACKEND_ERRORS.push("error." + item[1]); return null; }); } catch (err) { console.log("\x1b[31m%s\x1b[0m", err); process.exit(1); } // get all translations used in frontend code const tmpobj = tmp.fileSync({ postfix: ".json" }); spawnSync("yarn", ["locale-extract", "--out-file", tmpobj.name]); const allLocalesInProject = require(tmpobj.name); // get list og language names and locales const langList = require("./src/locale/src/lang-list.json"); // store a list of all validation errors const allErrors = []; const allWarnings = []; const allKeys = []; const checkLangList = (fullCode) => { const key = "locale-" + fullCode; if (typeof langList[key] === "undefined") { allErrors.push( "ERROR: `" + key + "` language does not exist in lang-list.json", ); } }; const compareLocale = (locale) => { const projectLocaleKeys = Object.keys(allLocalesInProject); // Check that locale contains the items used in the codebase projectLocaleKeys.map((key) => { if (typeof locale.data[key] === "undefined") { allErrors.push( "ERROR: `" + locale[0] + "` does not contain item: `" + key + "`", ); } return null; }); // Check that locale contains all error.* items BACKEND_ERRORS.forEach((key) => { if (typeof locale.data[key] === "undefined") { allErrors.push( "ERROR: `" + locale[0] + "` does not contain item: `" + key + "`", ); } return null; }); // Check that locale does not contain items not used in the codebase const localeKeys = Object.keys(locale.data); localeKeys.map((key) => { let ignored = false; ignoreUnused.map((regex) => { if (key.match(regex)) { ignored = true; } return null; }); if (!ignored && typeof allLocalesInProject[key] === "undefined") { // ensure this key doesn't exist in the backend errors either if (!BACKEND_ERRORS.includes(key)) { allErrors.push( "ERROR: `" + locale[0] + "` contains unused item: `" + key + "`", ); } } // Add this key to allKeys if (allKeys.indexOf(key) === -1) { allKeys.push(key); } return null; }); }; // Checks for any keys missing from this locale, that // have been defined in any other locales const checkForMissing = (locale) => { allKeys.forEach((key) => { if (typeof locale.data[key] === "undefined") { let ignored = false; ignoreMissing.map((regex) => { if (key.match(regex)) { ignored = true; } return null; }); if (!ignored) { allWarnings.push( "WARN: `" + locale[0] + "` does not contain item: `" + key + "`", ); } } return null; }); }; // Local all locale data allLocales.map((locale, idx) => { checkLangList(locale[1]); allLocales[idx].data = require("./src/locale/src/" + locale[0] + ".json"); return null; }); // Verify all locale data allLocales.map((locale) => { compareLocale(locale); checkForMissing(locale); return null; }); if (allErrors.length) { allErrors.map((err) => { console.log("\x1b[31m%s\x1b[0m", err); return null; }); } if (allWarnings.length) { allWarnings.map((err) => { console.log("\x1b[33m%s\x1b[0m", err); return null; }); } if (allErrors.length) { process.exit(1); } console.log("\x1b[32m%s\x1b[0m", "Locale check passed"); process.exit(0);