@mailbutler/i18n-utils
Version:
Manage i18n localization with static analysis
79 lines (78 loc) • 3.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractI18NReport = extractI18NReport;
exports.writeReportToFile = writeReportToFile;
const fs_1 = __importDefault(require("fs"));
function stripBounding(item) {
return {
path: item.path,
file: item.file,
line: item.line
};
}
function mightBeDynamic(item) {
return item.path.includes('${') && !!item.previousCharacter.match(/`/g) && !!item.nextCharacter.match(/`/g);
}
function dynamicRegex(item) {
if (!item.path.match(/[\w\-]\$\{[^}]+\}/))
return new RegExp('^$'); // there must be more to the string then just the interpolated part
return new RegExp(item.path.replace(/\$\{[^}]+\}/g, `\\w+`), 'i');
}
// Looping through the arays multiple times might not be the most effecient, but it's the easiest to read and debug. Which at this scale is an accepted trade-off.
function extractI18NReport(i18nItems, languageFiles) {
const missingKeys = [];
const unusedKeys = [];
const duplicatedKeys = [];
const maybeDynamicKeys = i18nItems
.filter((item) => mightBeDynamic(item))
.map((item) => stripBounding(item));
const dynamicRegexes = maybeDynamicKeys.filter((item) => !!dynamicRegex(item)).map((item) => dynamicRegex(item));
Object.keys(languageFiles).forEach((language) => {
const languageItems = languageFiles[language];
const duplicatedKeysInLanguage = languageItems.reduce((acc, languageItem) => {
if (acc.find((item) => item.path === languageItem.path))
return acc;
const duplicates = languageItems.filter((item) => item.path === languageItem.path);
if (duplicates.length > 1) {
acc.push({
path: languageItem.path,
files: duplicates.map(({ file }) => file !== null && file !== void 0 ? file : 'unknown'),
language
});
}
return acc;
}, []);
const missingKeysInLanguage = i18nItems
.filter((item) => !mightBeDynamic(item))
.filter((item) => !languageItems.some((languageItem) => item.path === languageItem.path))
.map((item) => ({ ...stripBounding(item), language }));
const unusedKeysInLanguage = languageItems
.filter((languageItem) => !i18nItems.some((item) => languageItem.path === item.path || languageItem.path.startsWith(item.path + '.')) &&
!dynamicRegexes.some((regex) => regex.test(languageItem.path)))
.map((languageItem) => ({ ...languageItem, language }));
missingKeys.push(...missingKeysInLanguage);
unusedKeys.push(...unusedKeysInLanguage);
duplicatedKeys.push(...duplicatedKeysInLanguage);
});
return {
missingKeys,
unusedKeys,
duplicatedKeys,
maybeDynamicKeys
};
}
async function writeReportToFile(report, writePath) {
const reportString = JSON.stringify(report);
return new Promise((resolve, reject) => {
fs_1.default.writeFile(writePath, reportString, (err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}