diff --git a/CHANGELOG.md b/CHANGELOG.md index b459e890..413afd54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ _This release is scheduled to be released on 2023-04-01._ - Update dates in Calendar widgets every minute - Cleanup jest coverage for patches - Update `stylelint` dependencies, switch to `stylelint-config-standard` and handle `stylelint` issues +- Convert translator callbacks to async/await ### Fixed diff --git a/js/loader.js b/js/loader.js index 70ed32c5..d48188bc 100644 --- a/js/loader.js +++ b/js/loader.js @@ -160,7 +160,7 @@ const Loader = (function () { Log.log("Scripts loaded for: " + module.name); mObj.loadStyles(function () { Log.log("Styles loaded for: " + module.name); - mObj.loadTranslations(function () { + mObj.loadTranslations().then(() => { Log.log("Translations loaded for: " + module.name); moduleObjects.push(mObj); callback(); diff --git a/js/main.js b/js/main.js index 3eed494b..d310d3ca 100644 --- a/js/main.js +++ b/js/main.js @@ -485,8 +485,7 @@ const MM = (function () { Log.setLogLevel(config.logLevel); - Translator.loadCoreTranslations(config.language); - Loader.loadModules(); + Translator.loadCoreTranslations(config.language).then(() => Loader.loadModules()); }, /** diff --git a/js/module.js b/js/module.js index 6d154529..0c5445c3 100644 --- a/js/module.js +++ b/js/module.js @@ -302,10 +302,8 @@ const Module = Class.extend({ /** * Load all translations. - * - * @param {Function} callback Function called when done. */ - loadTranslations(callback) { + async loadTranslations() { const translations = this.getTranslations() || {}; const language = config.language.toLowerCase(); @@ -313,7 +311,6 @@ const Module = Class.extend({ const fallbackLanguage = languages[0]; if (languages.length === 0) { - callback(); return; } @@ -321,17 +318,14 @@ const Module = Class.extend({ const translationsFallbackFile = translations[fallbackLanguage]; if (!translationFile) { - Translator.load(this, translationsFallbackFile, true, callback); - return; + return Translator.load(this, translationsFallbackFile, true); } - Translator.load(this, translationFile, false, () => { - if (translationFile !== translationsFallbackFile) { - Translator.load(this, translationsFallbackFile, true, callback); - } else { - callback(); - } - }); + await Translator.load(this, translationFile, false); + + if (translationFile !== translationsFallbackFile) { + return Translator.load(this, translationsFallbackFile, true); + } }, /** diff --git a/js/translator.js b/js/translator.js index 77d4b8f0..104f2766 100644 --- a/js/translator.js +++ b/js/translator.js @@ -11,26 +11,28 @@ const Translator = (function () { * Load a JSON file via XHR. * * @param {string} file Path of the file we want to load. - * @param {Function} callback Function called when done. + * @returns {Promise} the translations in the specified file */ - function loadJSON(file, callback) { + async function loadJSON(file) { const xhr = new XMLHttpRequest(); - xhr.overrideMimeType("application/json"); - xhr.open("GET", file, true); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4 && xhr.status === 200) { - // needs error handler try/catch at least - let fileinfo = null; - try { - fileinfo = JSON.parse(xhr.responseText); - } catch (exception) { - // nothing here, but don't die - Log.error(" loading json file =" + file + " failed"); + return new Promise(function (resolve, reject) { + xhr.overrideMimeType("application/json"); + xhr.open("GET", file, true); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4 && xhr.status === 200) { + // needs error handler try/catch at least + let fileinfo = null; + try { + fileinfo = JSON.parse(xhr.responseText); + } catch (exception) { + // nothing here, but don't die + Log.error(" loading json file =" + file + " failed"); + } + resolve(fileinfo); } - callback(fileinfo); - } - }; - xhr.send(null); + }; + xhr.send(null); + }); } return { @@ -48,7 +50,7 @@ const Translator = (function () { * @returns {string} the translated key */ translate: function (module, key, variables) { - variables = variables || {}; //Empty object by default + variables = variables || {}; // Empty object by default /** * Combines template and variables like: @@ -101,21 +103,17 @@ const Translator = (function () { * @param {Module} module The module to load the translation file for. * @param {string} file Path of the file we want to load. * @param {boolean} isFallback Flag to indicate fallback translations. - * @param {Function} callback Function called when done. */ - load(module, file, isFallback, callback) { + async load(module, file, isFallback) { Log.log(`${module.name} - Load translation${isFallback ? " fallback" : ""}: ${file}`); if (this.translationsFallback[module.name]) { - callback(); return; } - loadJSON(module.file(file), (json) => { - const property = isFallback ? "translationsFallback" : "translations"; - this[property][module.name] = json; - callback(); - }); + const json = await loadJSON(module.file(file)); + const property = isFallback ? "translationsFallback" : "translations"; + this[property][module.name] = json; }, /** @@ -123,30 +121,26 @@ const Translator = (function () { * * @param {string} lang The language identifier of the core language. */ - loadCoreTranslations: function (lang) { + loadCoreTranslations: async function (lang) { if (lang in translations) { Log.log("Loading core translation file: " + translations[lang]); - loadJSON(translations[lang], (translations) => { - this.coreTranslations = translations; - }); + this.coreTranslations = await loadJSON(translations[lang]); } else { Log.log("Configured language not found in core translations."); } - this.loadCoreTranslationsFallback(); + await this.loadCoreTranslationsFallback(); }, /** - * Load the core translations fallback. + * Load the core translations' fallback. * The first language defined in translations.js will be used. */ - loadCoreTranslationsFallback: function () { + loadCoreTranslationsFallback: async function () { let first = Object.keys(translations)[0]; if (first) { Log.log("Loading core translation fallback file: " + translations[first]); - loadJSON(translations[first], (translations) => { - this.coreTranslationsFallback = translations; - }); + this.coreTranslationsFallback = await loadJSON(translations[first]); } } }; diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js index 4d2d1c1e..f389f9a4 100644 --- a/tests/e2e/translations_spec.js +++ b/tests/e2e/translations_spec.js @@ -21,8 +21,8 @@ describe("Translations", () => { server = app.listen(3000); }); - afterAll(() => { - server.close(); + afterAll(async () => { + await server.close(); }); it("should have a translation file in the specified path", () => { @@ -48,17 +48,15 @@ describe("Translations", () => { dom.window.onload = async () => { const { Translator, Module, config } = dom.window; config.language = "en"; - Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback()); + Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); Module.register("name", { getTranslations: () => translations }); const MMM = Module.create("name"); - const loaded = sinon.stub(); - MMM.loadTranslations(loaded); + await MMM.loadTranslations(); - expect(loaded.callCount).toBe(1); expect(Translator.load.args.length).toBe(1); - expect(Translator.load.calledWith(MMM, "translations/en.json", false, sinon.match.func)).toBe(true); + expect(Translator.load.calledWith(MMM, "translations/en.json", false)).toBe(true); done(); }; @@ -67,18 +65,16 @@ describe("Translations", () => { it("should load translation + fallback file", (done) => { dom.window.onload = async () => { const { Translator, Module } = dom.window; - Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback()); + Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); Module.register("name", { getTranslations: () => translations }); const MMM = Module.create("name"); - const loaded = sinon.stub(); - MMM.loadTranslations(loaded); + await MMM.loadTranslations(); - expect(loaded.callCount).toBe(1); expect(Translator.load.args.length).toBe(2); - expect(Translator.load.calledWith(MMM, "translations/de.json", false, sinon.match.func)).toBe(true); - expect(Translator.load.calledWith(MMM, "translations/en.json", true, sinon.match.func)).toBe(true); + expect(Translator.load.calledWith(MMM, "translations/de.json", false)).toBe(true); + expect(Translator.load.calledWith(MMM, "translations/en.json", true)).toBe(true); done(); }; @@ -88,17 +84,15 @@ describe("Translations", () => { dom.window.onload = async () => { const { Translator, Module, config } = dom.window; config.language = "--"; - Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback()); + Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); Module.register("name", { getTranslations: () => translations }); const MMM = Module.create("name"); - const loaded = sinon.stub(); - MMM.loadTranslations(loaded); + await MMM.loadTranslations(); - expect(loaded.callCount).toBe(1); expect(Translator.load.args.length).toBe(1); - expect(Translator.load.calledWith(MMM, "translations/en.json", true, sinon.match.func)).toBe(true); + expect(Translator.load.calledWith(MMM, "translations/en.json", true)).toBe(true); done(); }; @@ -112,10 +106,8 @@ describe("Translations", () => { Module.register("name", {}); const MMM = Module.create("name"); - const loaded = sinon.stub(); - MMM.loadTranslations(loaded); + await MMM.loadTranslations(); - expect(loaded.callCount).toBe(1); expect(Translator.load.callCount).toBe(0); done(); @@ -138,14 +130,13 @@ describe("Translations", () => {