Skip to content

Commit

Permalink
⚡ Add home-brewed i18n coverage reports and language file checkers to CI
Browse files Browse the repository at this point in the history
  • Loading branch information
CosmoMyzrailGorynych committed Feb 26, 2020
1 parent 67f7595 commit 05b5bac
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 1 deletion.
4 changes: 3 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ const lintJS = () => {
.pipe(eslint.failAfterError());
};

const lint = gulp.series(lintJS, lintStylus);
const lintI18n = () => require('./node_requires/i18n')().then(console.log);

const lint = gulp.series(lintJS, lintStylus, lintI18n);

const launchApp = () => {
spawnise.spawn(npm, ['run', 'start'], {
Expand Down
118 changes: 118 additions & 0 deletions node_requires/i18n/index.js
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;
};

0 comments on commit 05b5bac

Please sign in to comment.