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

237 lines
6.5 KiB
JavaScript
Raw Normal View History

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.
//
// 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
2018-03-28 05:09:15 +02:00
const fs = require('fs')
const path = require('path')
const { promisify } = require('util')
const log = require('loglevel')
const matchAll = require('string.prototype.matchall').getPolyfill()
2018-03-28 05:29:29 +02:00
const localeIndex = require('../app/_locales/index.json')
const {
compareLocalesForMissingItems,
getLocale,
getLocalePath,
} = require('./lib/locales')
const readdir = promisify(fs.readdir)
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
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}`)
})
}
if (extraItems.length > 0) {
if (fix) {
const newLocale = { ...targetLocale }
for (const item of extraItems) {
delete newLocale[item]
}
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')
const uiJSFiles = await findJavascriptFiles(
2020-11-03 00:41:28 +01:00
path.resolve(__dirname, '..', 'ui'),
)
const sharedJSFiles = await findJavascriptFiles(
path.resolve(__dirname, '..', 'shared'),
)
const javascriptFiles = sharedJSFiles.concat(uiJSFiles)
// match "t(`...`)" because constructing message keys from template strings
// prevents this script from finding the messages, and then inappropriately
// deletes them
2020-11-03 00:41:28 +01:00
const templateStringRegex = /\bt\(`.*`\)/gu
const templateUsage = []
// match the keys from the locale file
2020-11-03 00:41:28 +01:00
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) {
// concat doesn't work here for some reason
2020-02-15 21:34:12 +01:00
templateMatches.forEach((match) => templateUsage.push(match))
}
}
// never consider these messages as unused
const messageExceptions = ['appName', '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 findJavascriptFiles(rootDir) {
const javascriptFiles = []
const contents = await readdir(rootDir, { withFileTypes: true })
for (const file of contents) {
if (file.isDirectory()) {
2020-11-03 00:41:28 +01:00
javascriptFiles.push(
...(await findJavascriptFiles(path.join(rootDir, file.name))),
)
} else if (file.isFile() && file.name.endsWith('.js')) {
javascriptFiles.push(path.join(rootDir, file.name))
}
}
return javascriptFiles
}
2020-11-03 00:41:28 +01:00
async function* getFileContents(filenames) {
for (const filename of filenames) {
yield readFile(filename, 'utf8')
}
}