languages.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. const loader = require("./loader.js").loader;
  2. const phrasebook = require("./phrasebook.js").phrasebook;
  3. /**
  4. * This class represents languages library. This store its location on the
  5. * server, and create loaders for them. It could also load langiage
  6. * library from json file.
  7. */
  8. class languages {
  9. /**
  10. * @var {string}
  11. * This represents path to directory where phrasebooks had been stored.
  12. */
  13. #path;
  14. /**
  15. * @var {Map}
  16. * This store languages and its files on server.
  17. */
  18. #libs;
  19. /**
  20. * @var {bool}
  21. * This store that directory is in the local file system, or remote
  22. * server. When true, resources would be loaded by node:fs. When
  23. * false, resources would be fetched.
  24. */
  25. #local;
  26. /**
  27. * This create new languages library. Next, languages could be added to
  28. * the library by command, or by loading index file.
  29. *
  30. * @throws {TypeError} - When parameters is not in correct format.
  31. *
  32. * @param {string} path - Path to phrasebooks on the server or filesystem.
  33. * @param {bool} local - True when phrasebooks dirs would be loaded by
  34. * node:fs module. False when would be fetch.
  35. */
  36. constructor(path, local = false) {
  37. if (typeof(path) !== "string") {
  38. throw new TypeError("Path to the phrasebooks must be string.");
  39. }
  40. if (typeof(local) !== "boolean") {
  41. throw new TypeError("Local must be bool variable.");
  42. }
  43. this.#local = local;
  44. this.#path = path;
  45. this.#libs = new Map();
  46. }
  47. /**
  48. * This add new language to the library by name. Name must be in form
  49. * like POSIX locale, like en_US, or pl_PL. That mean first two letter
  50. * mest be ISO 639-1 and second two letters mst be in ISO 3166-1 alpha-2
  51. * 2 letter country code format.
  52. *
  53. * @see https://www.loc.gov/standards/iso639-2/php/code_list.php
  54. * @see https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
  55. * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
  56. * @see https://en.wikipedia.org/wiki/Locale_(computer_software)
  57. *
  58. * @throws {TypeError} - When tpes of the parameters is not correct.
  59. *
  60. * @param {string} name - Name of the language, like "en_US".
  61. * @param {string} file - Name of the file in the directory.
  62. * @return {languages} - Instnace of this class to chain.
  63. */
  64. add(name, file) {
  65. if (typeof(name) !== "string") {
  66. throw new TypeError("Name of the language must be sting.");
  67. }
  68. if (typeof(file) !== "string") {
  69. throw new TypeError("File on in the directory must be string.");
  70. }
  71. if (this.#libs.has(name)) {
  72. console.error("Language \"" + name + "\" already loaded.");
  73. console.error("It could not being loaded twice.");
  74. return this;
  75. }
  76. if (!this.#valid_locale(name)) {
  77. console.error("Language name \"" + name + "\" invalid formated.");
  78. console.error("It could not being loaded.")
  79. return this;
  80. }
  81. this.#libs.set(name, file);
  82. return this;
  83. }
  84. /**
  85. * This load all phrasebook given in the index file. Index must be
  86. * JSON file, which contain one object. That object properties must be
  87. * languages names in the notation like in add function. Valus of that
  88. * properties musts being strings which contains names of the phrasebook
  89. * files in the path directory.
  90. *
  91. * @example ``` { "pl_PL": "polish.json", "en_US": "english.json" } ```
  92. *
  93. * @see add
  94. *
  95. * @param {string} index - Index file in the phrasebook directory.
  96. * @return {languages} - New languages instance with loaded index.
  97. */
  98. async load(index) {
  99. if (typeof(index) !== "string") {
  100. throw new TypeError("Name of index file is not string.");
  101. }
  102. const response = await this.#load_index(index);
  103. const result = new languages(this.#path, this.#local);
  104. Object.keys(response).forEach(name => {
  105. if (typeof(name) !== "string") {
  106. console.error("Name of the language must be string.");
  107. console.error("Check languages index.");
  108. console.error("Skipping it.")
  109. return;
  110. }
  111. if (typeof(response[name]) !== "string") {
  112. console.error("Name of phrasebook file must be string.");
  113. console.error("Check languages index.");
  114. console.error("Skipping it.");
  115. return;
  116. }
  117. result.add(name, response[name]);
  118. });
  119. return result;
  120. }
  121. /**
  122. * This load index object. That check, and when content must be loaded
  123. * from local filesystem, it use node:fs, or when it must be fetched from
  124. * remote, then use fetch API.
  125. *
  126. * @param {string} index - Name of the index file in library.
  127. * @returns {object} - Loaded index file content.
  128. */
  129. async #load_index(index) {
  130. const path = this.#full_path(index);
  131. if (this.#local) {
  132. let fs = null;
  133. NODE: fs = require("node:fs/promises");
  134. if (fs === null) {
  135. throw new Error("Could not use ndoe:fs in browser.");
  136. }
  137. return JSON.parse(
  138. await fs.readFile(path, { encoding: "utf-8" })
  139. );
  140. }
  141. const request = await fetch(path);
  142. return await request.json();
  143. }
  144. /**
  145. * This check that language exists in languages library.
  146. *
  147. * @param {string} name - Name of the language to check.
  148. * @return {bool} - True when language exists, false when not
  149. */
  150. has(name) {
  151. return this.#libs.has(name);
  152. }
  153. /**
  154. * This return all avairable languages.
  155. *
  156. * @return {Array} - List of all avairable languages.
  157. */
  158. get avairable() {
  159. const alls = new Array();
  160. this.#libs.keys().forEach(name => {
  161. alls.push(name);
  162. });
  163. return alls;
  164. }
  165. /**
  166. * @returns {string} - Default
  167. */
  168. get default() {
  169. const avairable = this.avairable;
  170. if (avairable.length === 0) {
  171. throw new Error("Languages list is empty. Can not load default.");
  172. }
  173. return avairable[0];
  174. }
  175. /**
  176. * This load phrasebook with give name.
  177. *
  178. * @throws {TypeError} - Param type is not correct.
  179. * @throws {RangeError} - Language not exists in libs.
  180. *
  181. * @param {string} name - Name of the language to load.
  182. * @returns {phrasebook} - Phrasebook loaded from the file.
  183. */
  184. select(name) {
  185. if (typeof(name) !== "string") {
  186. throw new TypeError("Name of the language must be string.");
  187. }
  188. if (!this.has(name)) {
  189. DEBUG: throw new RangeError(
  190. "Not found language \"" + name + "\"."
  191. );
  192. return new phrasebook(new Map());
  193. }
  194. const file = this.#libs.get(name);
  195. const path = this.#full_path(file);
  196. return new loader(path, this.#local).load();
  197. }
  198. /**
  199. * This return full path to the file.
  200. *
  201. * @param {string} name - Name of the file to get its path
  202. * @return {string} - Full path of the file
  203. */
  204. #full_path(name) {
  205. let glue = "/";
  206. if (this.#path[this.#path.length - 1] === glue) {
  207. glue = "";
  208. }
  209. return this.#path + glue + name;
  210. }
  211. /**
  212. * This check that format is valid POSIX like locale.
  213. *
  214. * @param {string} name - Name to check format of.
  215. * @return {bool} - True when format is valid, false when not.
  216. */
  217. #valid_locale(name) {
  218. const splited = name.split("_");
  219. if (splited.length !== 2) {
  220. return false;
  221. }
  222. const first = splited[0];
  223. const second = splited[1];
  224. if (first.toLowerCase() !== first || first.length !== 2) {
  225. return false;
  226. }
  227. if (second.toUpperCase() !== second || second.length !== 2) {
  228. return false;
  229. }
  230. return true;
  231. }
  232. }
  233. exports.languages = languages;