-
-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
⚡ Add home-brewed i18n coverage reports and language file checkers to CI
- Loading branch information
1 parent
67f7595
commit 05b5bac
Showing
2 changed files
with
121 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
const i18nDir = './../../app/data/i18n'; | ||
const path = require('path'), | ||
fs = require('fs-extra'); | ||
const referenceLanguage = require(path.join(i18nDir, 'English.json')); | ||
|
||
const recursiveCountRemainingFields = node => { | ||
if (typeof node === 'string') { | ||
return 1; | ||
} | ||
let counted = 0; | ||
for (const i in node) { | ||
counted += recursiveCountRemainingFields(node[i]); | ||
} | ||
return counted; | ||
}; | ||
|
||
// Recursively counts keys in English.json, translated keys, | ||
// and collects paths to untranslated and excess keys | ||
const recursiveCheck = function(partial, langNode, referenceNode) { | ||
const untranslated = [], | ||
excess = []; | ||
let counted = 0, | ||
needed = 0; | ||
for (const i in referenceNode) { | ||
if (typeof referenceNode[i] === 'string') { | ||
needed++; | ||
if (!(i in langNode) || langNode[i].trim() === '') { | ||
untranslated.push(partial? `${partial}.${i}` : i); | ||
} else { | ||
counted++; | ||
} | ||
} else if (i in langNode) { | ||
const subresults = recursiveCheck(partial? `${partial}.${i}` : i, langNode[i], referenceNode[i]); | ||
if (subresults.untranslated.length) { | ||
untranslated.push(...subresults.untranslated); | ||
} | ||
if (subresults.excess.length) { | ||
excess.push(...subresults.excess); | ||
} | ||
counted += subresults.counted; | ||
needed += subresults.needed; | ||
} else { | ||
untranslated.push(partial? `${partial}.${i}` : i); | ||
needed += recursiveCountRemainingFields(referenceNode[i]); | ||
} | ||
} | ||
// Reverse check to catch excess keys | ||
// that are not present in English.json but are in another language file | ||
for (const i in langNode) { | ||
if (!(i in referenceNode) || (typeof referenceNode[i] === 'string' && referenceNode[i].trim() === '')) { | ||
excess.push(partial? `${partial}.${i}` : i); | ||
} | ||
} | ||
return { | ||
untranslated, | ||
excess, | ||
counted, | ||
needed | ||
}; | ||
}; | ||
|
||
const breakpoints = [ | ||
[95, '👏 Fabulous!'], | ||
[85, '✅ Good!'], | ||
[70, '😕 A decent coverage, but it could be better!'], | ||
[50, '👎 Meh'], | ||
[0, '🆘 Someone help, please!'] | ||
]; | ||
const getSuitableBreakpoint = percent => { | ||
for (const point of breakpoints) { | ||
if (percent >= point[0]) { | ||
return point[1]; | ||
} | ||
} | ||
return 'WTF?'; | ||
}; | ||
|
||
module.exports = async () => { | ||
const fileNames = (await fs.readdir('./app/data/i18n')).filter(file => | ||
path.extname(file) === '.json' && | ||
file !== 'Comments.json' && | ||
file !== 'English.json' && | ||
file !== 'Debug.json' | ||
); | ||
const report = { | ||
untranslated: {}, | ||
excess: {}, | ||
stats: {} | ||
}; | ||
for (const lang of fileNames) { | ||
const rootNode = require(path.join(i18nDir, lang)); | ||
const result = recursiveCheck('', rootNode, referenceLanguage); | ||
report.untranslated[lang] = result.untranslated; | ||
report.excess[lang] = result.excess; | ||
report.stats[lang] = Math.floor(result.counted / result.needed * 100); | ||
} | ||
const reportText = | ||
|
||
`\nTranslation report: | ||
===================\n\n` + fileNames.map(lang => | ||
`Translation file ${lang} | ||
-----------------${'-'.repeat(lang.length)}\n` + | ||
// eslint-disable-next-line no-nested-ternary | ||
`Coverage: ${report.stats[lang]}% ${getSuitableBreakpoint(report.stats[lang])} | ||
Not translated: ${report.untranslated[lang].length}` + | ||
(report.untranslated[lang].length > 0? '\n'+report.untranslated[lang].map(key => ` ${key}`).join('\n') : '') + | ||
`\nExcess: ${report.excess[lang].length} ${report.excess[lang].length? '⚠️ '.repeat(Math.min(10, report.excess[lang].length)) : '✅'}\n` + | ||
(report.excess[lang].length > 0? report.excess[lang].map(key => ` ${key}`).join('\n') : '') | ||
|
||
).join('\n\n'); | ||
|
||
for (const lang in report.excess) { | ||
if (report.excess[lang].length) { | ||
throw new Error(reportText); | ||
} | ||
} | ||
return reportText; | ||
}; |