Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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

Expand Down
2 changes: 1 addition & 1 deletion js/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
3 changes: 1 addition & 2 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,8 +485,7 @@ const MM = (function () {

Log.setLogLevel(config.logLevel);

Translator.loadCoreTranslations(config.language);
Loader.loadModules();
Translator.loadCoreTranslations(config.language).then(() => Loader.loadModules());
},

/**
Expand Down
20 changes: 7 additions & 13 deletions js/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,36 +302,30 @@ 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();

const languages = Object.keys(translations);
const fallbackLanguage = languages[0];

if (languages.length === 0) {
callback();
return;
}

const translationFile = translations[language];
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);
}
},

/**
Expand Down
66 changes: 30 additions & 36 deletions js/translator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<object>} 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 {
Expand All @@ -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:
Expand Down Expand Up @@ -101,52 +103,44 @@ 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;
},

/**
* Load the core translations.
*
* @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]);
}
}
};
Expand Down
63 changes: 26 additions & 37 deletions tests/e2e/translations_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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();
};
Expand All @@ -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();
};
Expand All @@ -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();
};
Expand All @@ -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();
Expand All @@ -138,14 +130,13 @@ describe("Translations", () => {
<script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = () => {
dom.window.onload = async () => {
const { Translator } = dom.window;

Translator.load(mmm, translations[language], false, () => {
expect(typeof Translator.translations[mmm.name]).toBe("object");
expect(Object.keys(Translator.translations[mmm.name]).length).toBeGreaterThanOrEqual(1);
done();
});
await Translator.load(mmm, translations[language], false);
expect(typeof Translator.translations[mmm.name]).toBe("object");
expect(Object.keys(Translator.translations[mmm.name]).length).toBeGreaterThanOrEqual(1);
done();
};
});
}
Expand All @@ -161,13 +152,12 @@ describe("Translations", () => {
<script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = () => {
dom.window.onload = async () => {
const { Translator } = dom.window;

Translator.load(mmm, translations.de, false, () => {
base = Object.keys(Translator.translations[mmm.name]).sort();
done();
});
await Translator.load(mmm, translations.de, false);
base = Object.keys(Translator.translations[mmm.name]).sort();
done();
};
});

Expand All @@ -191,13 +181,12 @@ describe("Translations", () => {
<script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = () => {
dom.window.onload = async () => {
const { Translator } = dom.window;

Translator.load(mmm, translations[language], false, () => {
keys = Object.keys(Translator.translations[mmm.name]).sort();
done();
});
await Translator.load(mmm, translations[language], false);
keys = Object.keys(Translator.translations[mmm.name]).sort();
done();
};
});

Expand Down
Loading