const phrasebook = require("./phrasebook.js").phrasebook; /** * This class fetch and prepare phrasebook from JSON file. That JSON file * could be simple flat JSON, which contain string phrases as keys, and * its translatedequivalents as values. Also avalirable is format, where * phrases not being used, but JSON contain nested objects. End value must * being translated string, but then notation like "a.b.c" could being used. * * @example simple flat phrasebook ```JSON * JSON: * { * "phrase a": "Translated phrase a", * "phrase b": "Translated phrase b" * } * * Results: * "phrase a" -> "Translated phrase a", * "phrase b" -> "Translated phrase b" * ``` * * @example simple nested phrasebook ```JSON * JSON: * { * "phrases": { * "phrase a": "Translated phrase a" * }, * "objects": { * "a": { * "b": { * "c": "Second object notation" * } * } * } * } * * Results: * "phrase a" -> "Translated phrase a", * "a.b.c" -> "Second object notation" * ``` */ class loader { /** * @var {string} * This is location of the phrasebook on the server. */ #path; /** * @var {bool} * This is true, when must load local file, or false when fetch. */ #local; /** * This create new loader of the phrasebook. * * @param {string} path - Location of the phrasebook to fetch. * @param {bool} local - False when must fetch from remote. */ constructor(path, local = false) { if (typeof(path) !== "string") { throw new TypeError("Path of the file must be string."); } if (typeof(local) !== "boolean") { throw new TypeError("Local must be bool variable."); } this.#path = path; this.#local = local; } /** * This load file from path given in the constructor, parse and return it. * * @returns {phrasebook} - New phrasebook with content from JSON file. */ async #load_remote() { const request = await fetch(this.#path); const response = await request.json(); return this.#parse(response); } /** * This load file from path given in the constructor, parse and return it. * * @returns {phrasebook} - New phrasebook with content from JSON file. */ async #load_local() { let fs = null; NODE: fs = require("node:fs/promises"); if (fs === null) { throw new Error("Could not use ndoe:fs in browser."); } const content = await fs.readFile(this.#path, { encoding: 'utf8' }); const response = JSON.parse(content); return this.#parse(response); } /** * This load file from path given in the constructor, parse and return it. * * @returns {phrasebook} - New phrasebook with content from JSON file. */ load() { if (this.#local) { return this.#load_local(); } return this.#load_remote(); } /** * This parse phrasebook. When phrasebook contain "phrases" or "objects" * keys, and also "objects" is not string, then parse it as nested file, * in the other way parse it as flat. * * @param {object} content - Fetched object with translations. * @returns {phrasebook} - Loaded phrasebook. */ #parse(content) { const has_objects = ( "objects" in content && typeof(content["objects"]) === "object" ); const has_phrases = ( "phrases" in content && typeof(content["phrases"]) === "object" ); const is_nested = has_objects || has_phrases; if (is_nested) { const phrases = has_phrases ? content["phrases"] : {}; const objects = has_objects ? content["objects"] : {}; return new phrasebook( this.#parse_phrases(phrases), objects ); } return new phrasebook(this.#parse_phrases(content)); } /** * This parse flat phrases object to map. * * @param {object} content - Flat phrases object to pase. * @returns {Map} - Phrases parsed as Map. */ #parse_phrases(content) { const phrases = new Map(); Object.keys(content).forEach(phrase => { const name = phrasebook.prepare(phrase); const translation = content[phrase]; phrases.set(name, translation); }); return phrases; } } exports.loader = loader;