1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00
metamask-extension/development/verify-locale-strings.js
Mark Stacey 48bf2f8731
Remove unused locale messages (#7190)
* Switch to using string literals for locale keys

Various message keys were being specified with a string template
instead of a string literal. They have been switched to use string
literals so that the script for detecting unused messages can find
them.

* Remove unused locale messages

A number of unused locale messages have been removed - probably
leftover from old UI elements that have since been removed.

The `verify_locale_strings` script has been augmented to search the UI
for string literals, and match those against the locale message keys in
the `en` locale. Any messages without a corresponding string literal
are assumed to be unused.

The script has also been updated with an optional `--fix` parameter,
which will automatically delete any unused messages from locales.

148 unused messages were found in this case, out of a total of about
650 messages. Another 70 messages are _potentially_ unused and require
further investigation, but weren't as easy to rule out because they
were found in string literals.

* Remove additional unused locale messages

The following messages were more difficult to rule out because they
were present as string literals in the UI. They do appear to be
unused as locale keys though.
2019-09-18 20:29:46 -03:00

223 lines
6.5 KiB
JavaScript

// //////////////////////////////////////////////////////////////////////////////
//
// Locale verification script
//
// usage:
//
// node app/scripts/verify-locale-strings.js [<locale>] [--fix]
//
// 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 locales, and also
// any missing messages in the non-English locales.
//
// The if the optional '--fix' parameter is given, locales will be automatically
// updated to remove any unused messages.
//
// //////////////////////////////////////////////////////////////////////////////
const fs = require('fs')
const path = require('path')
const { promisify } = require('util')
const matchAll = require('string.prototype.matchall').getPolyfill()
const localeIndex = require('../app/_locales/index.json')
const readdir = promisify(fs.readdir)
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
console.log('Locale Verification')
let fix = false
let specifiedLocale
if (process.argv[2] === '--fix') {
fix = true
specifiedLocale = process.argv[3]
} else {
specifiedLocale = process.argv[2]
if (process.argv[3] === '--fix') {
fix = true
}
}
main(specifiedLocale, fix)
.catch(error => {
console.error(error)
process.exit(1)
})
async function main (specifiedLocale, fix) {
if (specifiedLocale) {
console.log(`Verifying selected locale "${specifiedLocale}":\n\n`)
const locale = localeIndex.find(localeMeta => localeMeta.code === specifiedLocale)
const failed = locale.code === 'en' ?
await verifyEnglishLocale(fix) :
await verifyLocale(locale, fix)
if (failed) {
process.exit(1)
}
} else {
console.log('Verifying all locales:\n\n')
let failed = await verifyEnglishLocale(fix)
const localeCodes = localeIndex
.filter(localeMeta => localeMeta.code !== 'en')
.map(localeMeta => localeMeta.code)
for (const code of localeCodes) {
const localeFailed = await verifyLocale(code, fix)
failed = failed || localeFailed
console.log('\n')
}
if (failed) {
process.exit(1)
}
}
}
function getLocalePath (code) {
return path.resolve(__dirname, '..', 'app', '_locales', code, 'messages.json')
}
async function getLocale (code) {
try {
const localeFilePath = getLocalePath(code)
const fileContents = await readFile(localeFilePath, 'utf8')
return JSON.parse(fileContents)
} catch (e) {
if (e.code === 'ENOENT') {
console.log('Locale file not found')
} else {
console.log(`Error opening your locale ("${code}") file: `, e)
}
process.exit(1)
}
}
async function writeLocale (code, locale) {
try {
const localeFilePath = getLocalePath(code)
return writeFile(localeFilePath, JSON.stringify(locale, null, 2) + '\n', 'utf8')
} catch (e) {
if (e.code === 'ENOENT') {
console.log('Locale file not found')
} else {
console.log(`Error writing your locale ("${code}") file: `, e)
}
process.exit(1)
}
}
async function verifyLocale (code, fix = false) {
const englishLocale = await getLocale('en')
const targetLocale = await getLocale(code)
const extraItems = compareLocalesForMissingItems({ base: targetLocale, subject: englishLocale })
const missingItems = compareLocalesForMissingItems({ base: englishLocale, subject: targetLocale })
const englishEntryCount = Object.keys(englishLocale).length
const coveragePercent = 100 * (englishEntryCount - missingItems.length) / englishEntryCount
console.log(`Status of **${code}** ${coveragePercent.toFixed(2)}% coverage:`)
if (extraItems.length) {
console.log('\nExtra items that should not be localized:')
extraItems.forEach(function (key) {
console.log(` - [ ] ${key}`)
})
} else {
// console.log(` all ${counter} strings declared in your locale ("${code}") were found in the english one`)
}
if (missingItems.length) {
console.log(`\nMissing items not present in localized file:`)
missingItems.forEach(function (key) {
console.log(` - [ ] ${key}`)
})
} else {
// console.log(` all ${counter} english strings were found in your locale ("${code}")!`)
}
if (!extraItems.length && !missingItems.length) {
console.log('Full coverage : )')
}
if (extraItems.length > 0) {
if (fix) {
const newLocale = Object.assign({}, targetLocale)
for (const item of extraItems) {
delete newLocale[item]
}
await writeLocale(code, newLocale)
}
return true
}
}
async function verifyEnglishLocale (fix = false) {
const englishLocale = await getLocale('en')
const javascriptFiles = await findJavascriptFiles(path.resolve(__dirname, '..', 'ui'))
const regex = /'(\w+)'/g
const usedMessages = new Set()
for await (const fileContents of getFileContents(javascriptFiles)) {
for (const match of matchAll.call(fileContents, regex)) {
usedMessages.add(match[1])
}
}
// never consider these messages as unused
const messageExceptions = ['appName', 'appDescription']
const englishMessages = Object.keys(englishLocale)
const unusedMessages = englishMessages
.filter(message => !messageExceptions.includes(message) && !usedMessages.has(message))
console.log(`Status of **English (en)** ${unusedMessages.length} unused messages:`)
if (unusedMessages.length === 0) {
console.log('Full coverage : )')
return false
}
console.log(`\nMessages not present in UI:`)
unusedMessages.forEach(function (key) {
console.log(` - [ ] ${key}`)
})
if (unusedMessages.length > 0 && fix) {
const newLocale = Object.assign({}, englishLocale)
for (const key of unusedMessages) {
delete newLocale[key]
}
await writeLocale('en', newLocale)
}
return true
}
async function findJavascriptFiles (rootDir) {
const javascriptFiles = []
const contents = await readdir(rootDir, { withFileTypes: true })
for (const file of contents) {
if (file.isDirectory()) {
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
}
async function * getFileContents (filenames) {
for (const filename of filenames) {
yield readFile(filename, 'utf8')
}
}
function compareLocalesForMissingItems ({ base, subject }) {
return Object.keys(base).filter((key) => !subject[key])
}