const loader = require("./loader.js").loader; const phrasebook = require("./phrasebook.js").phrasebook; /** * This class represents languages library. This store its location on the * server, and create loaders for them. It could also load langiage * library from json file. */ class languages { /** * @var {string} * This represents path to directory where phrasebooks had been stored. */ #path; /** * @var {Map} * This store languages and its files on server. */ #libs; /** * @var {bool} * This store that directory is in the local file system, or remote * server. When true, resources would be loaded by node:fs. When * false, resources would be fetched. */ #local; /** * This create new languages library. Next, languages could be added to * the library by command, or by loading index file. * * @throws {TypeError} - When parameters is not in correct format. * * @param {string} path - Path to phrasebooks on the server or filesystem. * @param {bool} local - True when phrasebooks dirs would be loaded by * node:fs module. False when would be fetch. */ constructor(path, local = false) { if (typeof(path) !== "string") { throw new TypeError("Path to the phrasebooks must be string."); } if (typeof(local) !== "boolean") { throw new TypeError("Local must be bool variable."); } this.#local = local; this.#path = path; this.#libs = new Map(); } /** * This add new language to the library by name. Name must be in form * like POSIX locale, like en_US, or pl_PL. That mean first two letter * mest be ISO 639-1 and second two letters mst be in ISO 3166-1 alpha-2 * 2 letter country code format. * * @see https://www.loc.gov/standards/iso639-2/php/code_list.php * @see https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 * @see https://en.wikipedia.org/wiki/Locale_(computer_software) * * @throws {TypeError} - When tpes of the parameters is not correct. * * @param {string} name - Name of the language, like "en_US". * @param {string} file - Name of the file in the directory. * @return {languages} - Instnace of this class to chain. */ add(name, file) { if (typeof(name) !== "string") { throw new TypeError("Name of the language must be sting."); } if (typeof(file) !== "string") { throw new TypeError("File on in the directory must be string."); } if (this.#libs.has(name)) { console.error("Language \"" + name + "\" already loaded."); console.error("It could not being loaded twice."); return this; } if (!this.#valid_locale(name)) { console.error("Language name \"" + name + "\" invalid formated."); console.error("It could not being loaded.") return this; } this.#libs.set(name, file); return this; } /** * This load all phrasebook given in the index file. Index must be * JSON file, which contain one object. That object properties must be * languages names in the notation like in add function. Valus of that * properties musts being strings which contains names of the phrasebook * files in the path directory. * * @example ``` { "pl_PL": "polish.json", "en_US": "english.json" } ``` * * @see add * * @param {string} index - Index file in the phrasebook directory. * @return {languages} - New languages instance with loaded index. */ async load(index) { if (typeof(index) !== "string") { throw new TypeError("Name of index file is not string."); } const response = await this.#load_index(index); const result = new languages(this.#path, this.#local); Object.keys(response).forEach(name => { if (typeof(name) !== "string") { console.error("Name of the language must be string."); console.error("Check languages index."); console.error("Skipping it.") return; } if (typeof(response[name]) !== "string") { console.error("Name of phrasebook file must be string."); console.error("Check languages index."); console.error("Skipping it."); return; } result.add(name, response[name]); }); return result; } /** * This load index object. That check, and when content must be loaded * from local filesystem, it use node:fs, or when it must be fetched from * remote, then use fetch API. * * @param {string} index - Name of the index file in library. * @returns {object} - Loaded index file content. */ async #load_index(index) { const path = this.#full_path(index); if (this.#local) { let fs = null; NODE: fs = require("node:fs/promises"); if (fs === null) { throw new Error("Could not use ndoe:fs in browser."); } return JSON.parse( await fs.readFile(path, { encoding: "utf-8" }) ); } const request = await fetch(path); return await request.json(); } /** * This check that language exists in languages library. * * @param {string} name - Name of the language to check. * @return {bool} - True when language exists, false when not */ has(name) { return this.#libs.has(name); } /** * This return all avairable languages. * * @return {Array} - List of all avairable languages. */ get avairable() { const alls = new Array(); this.#libs.keys().forEach(name => { alls.push(name); }); return alls; } /** * This load phrasebook with give name. * * @throws {TypeError} - Param type is not correct. * @throws {RangeError} - Language not exists in libs. * * @param {string} name - Name of the language to load. * @returns {phrasebook} - Phrasebook loaded from the file. */ select(name) { if (typeof(name) !== "string") { throw new TypeError("Name of the language must be string."); } if (!this.has(name)) { DEBUG: throw new RangeError( "Not found language \"" + name + "\"." ); return new phrasebook(new Map()); } const file = this.#libs.get(name); const path = this.#full_path(file); return new loader(path, this.#local).load(); } /** * This return full path to the file. * * @param {string} name - Name of the file to get its path * @return {string} - Full path of the file */ #full_path(name) { let glue = "/"; if (this.#path[this.#path.length - 1] === glue) { glue = ""; } return this.#path + glue + name; } /** * This check that format is valid POSIX like locale. * * @param {string} name - Name to check format of. * @return {bool} - True when format is valid, false when not. */ #valid_locale(name) { const splited = name.split("_"); if (splited.length !== 2) { return false; } const first = splited[0]; const second = splited[1]; if (first.toLowerCase() !== first || first.length !== 2) { return false; } if (second.toUpperCase() !== second || second.length !== 2) { return false; } return true; } } exports.languages = languages;