1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 03:12:42 +02:00
metamask-extension/development/verify-locale-strings.js

286 lines
8.1 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
2018-07-03 00:49:33 +02:00
// //////////////////////////////////////////////////////////////////////////////
2018-03-19 17:51:51 +01:00
//
// Locale verification script
//
// usage:
//
// node app/scripts/verify-locale-strings.js [<locale>] [--fix] [--quiet]
2018-03-19 17:51:51 +01:00
//
// This script will validate that locales have no unused messages. It will check
// the English locale against string literals found under `ui/`, and it will check
// other locales by comparing them to the English locale. It will also validate
// that non-English locales have all descriptions present in the English locale.
//
// A report will be printed to the console detailing any unused messages.
//
// The if the optional '--fix' argument is given, locales will be automatically
// updated to remove any unused messages.
2018-03-19 17:51:51 +01:00
//
// The optional '--quiet' argument reduces the verbosity of the output, printing
// just a single summary of results for each locale verified
//
2018-07-03 00:49:33 +02:00
// //////////////////////////////////////////////////////////////////////////////
2018-03-19 17:51:51 +01:00
const fs = require('fs');
const { promisify } = require('util');
const log = require('loglevel');
const glob = require('fast-glob');
const matchAll = require('string.prototype.matchall').getPolyfill();
const localeIndex = require('../app/_locales/index.json');
const {
compareLocalesForMissingDescriptions,
compareLocalesForMissingItems,
getLocale,
getLocalePath,
} = require('./lib/locales');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
2018-03-19 17:51:51 +01:00
log.setDefaultLevel('info');
2018-03-19 17:51:51 +01:00
let fix = false;
let specifiedLocale;
for (const arg of process.argv.slice(2)) {
if (arg === '--fix') {
fix = true;
} else if (arg === '--quiet') {
log.setLevel('error');
} else {
specifiedLocale = arg;
}
}
2020-11-03 00:41:28 +01:00
main().catch((error) => {
log.error(error);
process.exit(1);
});
2020-11-03 00:41:28 +01:00
async function main() {
if (specifiedLocale) {
log.info(`Verifying selected locale "${specifiedLocale}":\n`);
const localeEntry = localeIndex.find(
2020-11-03 00:41:28 +01:00
(localeMeta) => localeMeta.code === specifiedLocale,
);
if (!localeEntry) {
throw new Error(`No localize entry found for ${specifiedLocale}`);
}
2020-11-03 00:41:28 +01:00
const failed =
specifiedLocale === 'en'
2020-11-03 00:41:28 +01:00
? await verifyEnglishLocale()
: await verifyLocale(specifiedLocale);
if (failed) {
process.exit(1);
} else {
console.log('No invalid entries!');
}
} else {
log.info('Verifying all locales:\n');
let failed = await verifyEnglishLocale(fix);
const localeCodes = localeIndex
2020-02-15 21:34:12 +01:00
.filter((localeMeta) => localeMeta.code !== 'en')
.map((localeMeta) => localeMeta.code);
for (const code of localeCodes) {
const localeFailed = await verifyLocale(code, fix);
failed = failed || localeFailed;
}
if (failed) {
process.exit(1);
} else {
console.log('No invalid entries!');
}
}
}
2018-03-19 17:51:51 +01:00
2022-07-26 20:10:51 +02:00
// eslint-disable-next-line consistent-return
2020-11-03 00:41:28 +01:00
async function writeLocale(code, locale) {
try {
const localeFilePath = getLocalePath(code);
2020-11-03 00:41:28 +01:00
return writeFile(
localeFilePath,
`${JSON.stringify(locale, null, 2)}\n`,
'utf8',
);
} catch (e) {
if (e.code === 'ENOENT') {
log.error('Locale file not found');
} else {
log.error(`Error writing your locale ("${code}") file: `, e);
}
process.exit(1);
}
}
2020-11-03 00:41:28 +01:00
async function verifyLocale(code) {
const englishLocale = await getLocale('en');
const targetLocale = await getLocale(code);
2018-03-19 17:51:51 +01:00
2020-11-03 00:41:28 +01:00
const extraItems = compareLocalesForMissingItems({
base: targetLocale,
subject: englishLocale,
});
2018-03-19 17:51:51 +01:00
if (extraItems.length) {
console.log(`**${code}**: ${extraItems.length} unused messages`);
log.info('Extra items that should not be localized:');
extraItems.forEach(function (key) {
log.info(` - [ ] ${key}`);
});
}
const missingDescriptions = compareLocalesForMissingDescriptions({
englishLocale,
targetLocale,
});
if (missingDescriptions.length) {
console.log(
`**${code}**: ${missingDescriptions.length} missing descriptions`,
);
log.info('Messages with missing descriptions:');
missingDescriptions.forEach(function (key) {
log.info(` - [ ] ${key}`);
});
}
if (extraItems.length > 0 || missingDescriptions.length > 0) {
if (fix) {
const newLocale = { ...targetLocale };
for (const item of extraItems) {
delete newLocale[item];
}
for (const message of Object.keys(englishLocale)) {
if (englishLocale[message].description && targetLocale[message]) {
targetLocale[message].description =
englishLocale[message].description;
}
}
await writeLocale(code, newLocale);
}
return true;
}
return false;
2018-03-19 17:51:51 +01:00
}
2018-03-28 05:29:29 +02:00
2020-11-03 00:41:28 +01:00
async function verifyEnglishLocale() {
const englishLocale = await getLocale('en');
// As time allows we'll switch to only performing the strict search.
// In the meantime we'll use glob to specify which paths can be strict searched
// and gradually phase out the key based search
const globsToStrictSearch = [
'ui/components/app/metamask-translation/*.js',
'ui/components/app/metamask-translation/*.ts',
'ui/pages/confirmation/templates/*.js',
'ui/pages/confirmation/templates/*.ts',
];
const testGlob = '**/*.test.js';
const javascriptFiles = await glob(
[
'ui/**/*.js',
'ui/**/*.ts',
feat(srp): add a quiz to the SRP reveal (#19283) * feat(srp): add a quiz to the SRP reveal * fixed the popover header centering * lint fixes * converted from `ui/components/ui/popover` to `ui/components/component-library/modal` * responded to @darkwing review * added unit tests * renamed the folder to 'srp-quiz-modal' * responded to Monte's review * using i18n-helper in the test suite * small improvement to JSXDict comments * wrote a new webdriver.holdMouseDownOnElement() to assist with testing the "Hold to reveal SRP" button * Updating layout and some storybook naming and migrating to tsx * Apply suggestions from @georgewrmarshall Co-authored-by: George Marshall <george.marshall@consensys.net> * Unit test searches by data-testid instead of by text * new layout and copy for the Settings->Security page * now with 100% test coverage for /ui/pages/settings/security-tab fixes #16871 fixes #18140 * e2e tests to reveal SRP after quiz * e2e- Fix lint, remove unneeded extras * @coreyjanssen and @georgewrmarshall compromise Co-authored-by: George Marshall <george.marshall@consensys.net> Co-authored-by: Corey Janssen <corey.janssen@consensys.net> * trying isRequired again * transparent background on PNG * [e2e] moving functions to helpers and adding testid for SRP reveal quiz (#19481) * moving functions to helpers and adding testid * fix lint error * took out the IPFS gateway fixes * lint fix * translations of SRP Reveal Quiz * new Spanish translation from Guto * Update describe for e2e tests * Apply suggestion from @georgewrmarshall Co-authored-by: George Marshall <george.marshall@consensys.net> * fixed the Tab key problem --------- Co-authored-by: georgewrmarshall <george.marshall@consensys.net> Co-authored-by: Plasma Corral <32695229+plasmacorral@users.noreply.github.com> Co-authored-by: Corey Janssen <corey.janssen@consensys.net>
2023-06-20 20:27:10 +02:00
'ui/**/*.tsx',
'shared/**/*.js',
'shared/**/*.ts',
feat(srp): add a quiz to the SRP reveal (#19283) * feat(srp): add a quiz to the SRP reveal * fixed the popover header centering * lint fixes * converted from `ui/components/ui/popover` to `ui/components/component-library/modal` * responded to @darkwing review * added unit tests * renamed the folder to 'srp-quiz-modal' * responded to Monte's review * using i18n-helper in the test suite * small improvement to JSXDict comments * wrote a new webdriver.holdMouseDownOnElement() to assist with testing the "Hold to reveal SRP" button * Updating layout and some storybook naming and migrating to tsx * Apply suggestions from @georgewrmarshall Co-authored-by: George Marshall <george.marshall@consensys.net> * Unit test searches by data-testid instead of by text * new layout and copy for the Settings->Security page * now with 100% test coverage for /ui/pages/settings/security-tab fixes #16871 fixes #18140 * e2e tests to reveal SRP after quiz * e2e- Fix lint, remove unneeded extras * @coreyjanssen and @georgewrmarshall compromise Co-authored-by: George Marshall <george.marshall@consensys.net> Co-authored-by: Corey Janssen <corey.janssen@consensys.net> * trying isRequired again * transparent background on PNG * [e2e] moving functions to helpers and adding testid for SRP reveal quiz (#19481) * moving functions to helpers and adding testid * fix lint error * took out the IPFS gateway fixes * lint fix * translations of SRP Reveal Quiz * new Spanish translation from Guto * Update describe for e2e tests * Apply suggestion from @georgewrmarshall Co-authored-by: George Marshall <george.marshall@consensys.net> * fixed the Tab key problem --------- Co-authored-by: georgewrmarshall <george.marshall@consensys.net> Co-authored-by: Plasma Corral <32695229+plasmacorral@users.noreply.github.com> Co-authored-by: Corey Janssen <corey.janssen@consensys.net>
2023-06-20 20:27:10 +02:00
'shared/**/*.tsx',
'app/scripts/constants/**/*.js',
'app/scripts/constants/**/*.ts',
'app/scripts/platforms/**/*.js',
],
{
ignore: [...globsToStrictSearch, testGlob],
},
);
const javascriptFilesToStrictSearch = await glob(globsToStrictSearch, {
ignore: [testGlob],
});
2022-07-31 20:26:40 +02:00
const strictSearchRegex =
/\bt\(\s*'(\w+)'\s*\)|\btranslationKey:\s*'(\w+)'/gu;
// match "t(`...`)" because constructing message keys from template strings
// prevents this script from finding the messages, and then inappropriately
// deletes them
const templateStringRegex = /\bt\(`.*`\)/gu;
const templateUsage = [];
// match the keys from the locale file
const keyRegex = /'(\w+)'|"(\w+)"/gu;
const usedMessages = new Set();
for await (const fileContents of getFileContents(javascriptFiles)) {
for (const match of matchAll.call(fileContents, keyRegex)) {
usedMessages.add(match[1] || match[2]);
}
const templateMatches = fileContents.match(templateStringRegex);
if (templateMatches) {
templateMatches.forEach((match) => templateUsage.push(match));
}
}
for await (const fileContents of getFileContents(
javascriptFilesToStrictSearch,
)) {
for (const match of matchAll.call(fileContents, strictSearchRegex)) {
usedMessages.add(match[1] || match[2] || match[3] || match[4]);
}
const templateMatches = fileContents.match(templateStringRegex);
if (templateMatches) {
templateMatches.forEach((match) => templateUsage.push(match));
}
}
// never consider these messages as unused
const messageExceptions = [
'appName',
'appNameBeta',
'appNameFlask',
'appNameMmi',
'appDescription',
];
const englishMessages = Object.keys(englishLocale);
2020-11-03 00:41:28 +01:00
const unusedMessages = englishMessages.filter(
(message) =>
!messageExceptions.includes(message) && !usedMessages.has(message),
);
if (unusedMessages.length) {
console.log(`**en**: ${unusedMessages.length} unused messages`);
log.info(`Messages not present in UI:`);
unusedMessages.forEach(function (key) {
log.info(` - [ ] ${key}`);
});
}
if (templateUsage.length) {
log.info(`Forbidden use of template strings in 't' function:`);
templateUsage.forEach(function (occurrence) {
log.info(` - ${occurrence}`);
});
}
if (!unusedMessages.length && !templateUsage.length) {
return false; // failed === false
}
if (unusedMessages.length > 0 && fix) {
const newLocale = { ...englishLocale };
for (const key of unusedMessages) {
delete newLocale[key];
}
await writeLocale('en', newLocale);
}
return true; // failed === true
}
2020-11-03 00:41:28 +01:00
async function* getFileContents(filenames) {
for (const filename of filenames) {
yield readFile(filename, 'utf8');
}
}