You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
6.5 KiB
222 lines
6.5 KiB
// //////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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])
|
|
}
|
|
|