add content hash to individual language files

This commit is contained in:
Bruno Windels 2019-02-18 16:11:41 +01:00
parent 2e60037d9f
commit 878190ba27
2 changed files with 63 additions and 42 deletions

View File

@ -125,6 +125,7 @@
"karma-spec-reporter": "0.0.31", "karma-spec-reporter": "0.0.31",
"karma-summary-reporter": "^1.5.1", "karma-summary-reporter": "^1.5.1",
"karma-webpack": "4.0.0-beta.0", "karma-webpack": "4.0.0-beta.0",
"loader-utils": "^1.2.3",
"matrix-mock-request": "^1.2.2", "matrix-mock-request": "^1.2.2",
"matrix-react-test-utils": "^0.2.0", "matrix-react-test-utils": "^0.2.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",

View File

@ -1,5 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
const loaderUtils = require("loader-utils");
// copies the resources into the webapp directory. // copies the resources into the webapp directory.
// //
@ -61,12 +63,6 @@ const COPY_LIST = [
["./config.json", "webapp", { directwatch: 1 }], ["./config.json", "webapp", { directwatch: 1 }],
]; ];
INCLUDE_LANGS.forEach(function(l) {
COPY_LIST.push([
l.value, "webapp/i18n/", { lang: 1 },
]);
});
const parseArgs = require('minimist'); const parseArgs = require('minimist');
const Cpx = require('cpx'); const Cpx = require('cpx');
const chokidar = require('chokidar'); const chokidar = require('chokidar');
@ -77,8 +73,8 @@ const argv = parseArgs(
process.argv.slice(2), {} process.argv.slice(2), {}
); );
var watch = argv.w; const watch = argv.w;
var verbose = argv.v; const verbose = argv.v;
function errCheck(err) { function errCheck(err) {
if (err) { if (err) {
@ -136,39 +132,11 @@ function next(i, err) {
.on('change', copy) .on('change', copy)
.on('ready', cb) .on('ready', cb)
.on('error', errCheck); .on('error', errCheck);
} else if (opts.lang) {
const reactSdkFile = 'node_modules/matrix-react-sdk/src/i18n/strings/' + source + '.json';
const riotWebFile = 'src/i18n/strings/' + source + '.json';
// XXX: Use a debounce because for some reason if we read the language
// file immediately after the FS event is received, the file contents
// appears empty. Possibly https://github.com/nodejs/node/issues/6112
let makeLangDebouncer;
const makeLang = () => {
if (makeLangDebouncer) {
clearTimeout(makeLangDebouncer);
}
makeLangDebouncer = setTimeout(() => {
genLangFile(source, dest);
}, 500);
};
[reactSdkFile, riotWebFile].forEach(function(f) {
chokidar.watch(f)
.on('add', makeLang)
.on('change', makeLang)
//.on('ready', cb) We'd have to do this when both files are ready
.on('error', errCheck);
});
next(i + 1, err);
} else { } else {
cpx.on('watch-ready', cb); cpx.on('watch-ready', cb);
cpx.on("watch-error", cb); cpx.on("watch-error", cb);
cpx.watch(); cpx.watch();
} }
} else if (opts.lang) {
genLangFile(source, dest);
next(i + 1, err);
} else { } else {
cpx.copy(cb); cpx.copy(cb);
} }
@ -195,21 +163,28 @@ function genLangFile(lang, dest) {
translations = weblateToCounterpart(translations); translations = weblateToCounterpart(translations);
fs.writeFileSync(dest + lang + '.json', JSON.stringify(translations, null, 4)); const json = JSON.stringify(translations, null, 4);
const jsonBuffer = Buffer.from(json);
const digest = loaderUtils.getHashDigest(jsonBuffer, "sha512", "base64", 7);
const filename = `${lang}.${digest}.json`;
fs.writeFileSync(dest + filename, json);
if (verbose) { if (verbose) {
console.log("Generated language file: " + lang); console.log("Generated language file: " + filename);
} }
return filename;
} }
function genLangList() { function genLangList(langFileMap) {
const languages = {}; const languages = {};
INCLUDE_LANGS.forEach(function(lang) { INCLUDE_LANGS.forEach(function(lang) {
const normalizedLanguage = lang.value.toLowerCase().replace("_", "-"); const normalizedLanguage = lang.value.toLowerCase().replace("_", "-");
const languageParts = normalizedLanguage.split('-'); const languageParts = normalizedLanguage.split('-');
if (languageParts.length == 2 && languageParts[0] == languageParts[1]) { if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
languages[languageParts[0]] = {'fileName': lang.value + '.json', 'label': lang.label}; languages[languageParts[0]] = {'fileName': langFileMap[lang.value], 'label': lang.label};
} else { } else {
languages[normalizedLanguage] = {'fileName': lang.value + '.json', 'label': lang.label}; languages[normalizedLanguage] = {'fileName': langFileMap[lang.value], 'label': lang.label};
} }
}); });
fs.writeFile('webapp/i18n/languages.json', JSON.stringify(languages, null, 4), function(err) { fs.writeFile('webapp/i18n/languages.json', JSON.stringify(languages, null, 4), function(err) {
@ -257,5 +232,50 @@ function weblateToCounterpart(inTrs) {
return outTrs; return outTrs;
} }
genLangList(); /**
watch the input files for a given language,
regenerate the file, adding its content-hashed filename to langFileMap
and regenerating languages.json with the new filename
*/
function watchLanguage(lang, dest, langFileMap) {
const reactSdkFile = 'node_modules/matrix-react-sdk/src/i18n/strings/' + lang + '.json';
const riotWebFile = 'src/i18n/strings/' + lang + '.json';
// XXX: Use a debounce because for some reason if we read the language
// file immediately after the FS event is received, the file contents
// appears empty. Possibly https://github.com/nodejs/node/issues/6112
let makeLangDebouncer;
const makeLang = () => {
if (makeLangDebouncer) {
clearTimeout(makeLangDebouncer);
}
makeLangDebouncer = setTimeout(() => {
const filename = genLangFile(lang, dest);
langFileMap[lang]=filename;
genLangList(langFileMap);
}, 500);
};
[reactSdkFile, riotWebFile].forEach(function(f) {
chokidar.watch(f)
.on('add', makeLang)
.on('change', makeLang)
.on('error', errCheck);
});
}
// language resources
const I18N_DEST = "webapp/i18n/";
const I18N_FILENAME_MAP = INCLUDE_LANGS.reduce((m, l) => {
const filename = genLangFile(l.value, I18N_DEST);
m[l.value] = filename;
return m;
}, {});
genLangList(I18N_FILENAME_MAP);
if (watch) {
INCLUDE_LANGS.forEach(l => watchLanguage(l.value, I18N_DEST, I18N_FILENAME_MAP));
}
// non-language resources
next(0); next(0);