locale.service.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /**
  2. * @file Provides a service to handle languages and localizations.
  3. * @author Inigo Valentin
  4. * @since 4.0.0
  5. */
  6. const logger = require('pino')();
  7. const Lang = require("../models").langs;
  8. const Text = require("../models").texts;
  9. /**
  10. * Handles localization of elements.
  11. */
  12. class LocaleService {
  13. /**
  14. * List of available languages.
  15. *
  16. * The one at index 0 is the default language.
  17. */
  18. #availableLanguages = Array();
  19. /**
  20. * Constructor.
  21. *
  22. * @constructor
  23. */
  24. constructor(){
  25. this.#init();
  26. }
  27. /**
  28. * Initializes the service.
  29. *
  30. * Called automatically from the constructor, retrieves available languages.
  31. */
  32. async #init(){
  33. const data = await Lang.findAll({where: {active: true}, order: [['priority', 'ASC']]})
  34. if (data) for (const r of data) this.#availableLanguages.push(r.code.toLowerCase().substring(0, 2));
  35. if (this.#availableLanguages.length == 0){
  36. logger.error("No languages retrieved from database, defaulting to 'en'.");
  37. this.#availableLanguages.push("en");
  38. }
  39. }
  40. /**
  41. * Selects the language to use.
  42. *
  43. * If the language is not in the request, using the GET parameter "lang", the default one set
  44. * in the database will be used.
  45. *
  46. * @param req The request received by the server.
  47. * @return Two letter language code to use.
  48. */
  49. selectLanguage(req){
  50. if (!req.query.lang && !req.body.lang) return this.#availableLanguages[0];
  51. let requestedLang = this.#availableLanguages[0];
  52. if (req.query.lang && req.body.lang && (req.query.lang != req.body.lang)){
  53. // Different languages in query and body, body has priority.
  54. requestedLang = req.body.lang.toLowerCase().substring(0, 2)
  55. if (this.#availableLanguages.indexOf(requestedLang) != -1) return requestedLang;
  56. else{
  57. requestedLang = req.query.lang.toLowerCase().substring(0, 2);
  58. if (this.#availableLanguages.indexOf(requestedLang) != -1) return requestedLang;
  59. }
  60. }
  61. else{
  62. if (req.body.lang) requestedLang = req.body.lang.toLowerCase().substring(0, 2)
  63. else if (req.query.lang) requestedLang = req.query.lang.toLowerCase().substring(0, 2)
  64. if (this.#availableLanguages.indexOf(requestedLang) != -1) return requestedLang;
  65. else return this.#availableLanguages[0];
  66. }
  67. return this.#availableLanguages[0];
  68. }
  69. /**
  70. * Decodes a text from the database.
  71. *
  72. * @param result Result of a query to the table 'texts'. Must have at least the columns 'text' and 'file'.
  73. * @return The text.
  74. */
  75. async #decodeText(key, lang){
  76. const t = await Text.findOne({where: {id: key, lang: lang}});
  77. if (!t) return "";
  78. if (t.text) return "" + t.text;
  79. if (t.file) return "" + t.file; // TODO: Return file CONTENTS
  80. return "";
  81. }
  82. /**
  83. * Generates a localized object for text storing in database.
  84. *
  85. * @param obj The object with the text in multiple languages. Language codes must be keys.
  86. * @param key The key to assign to the text.
  87. * @param section The section identifier for the text.
  88. * @return An object with those values:
  89. * - valid: True if the object is valid and can be stored in database.
  90. * - key: Identifier for the text.
  91. * - section: Identifier fot the text section.
  92. * - texts{}: An array for the text in different languages. Language codes are keys.
  93. */
  94. generateLocalizedObject(obj, key, section){
  95. let l = {valid: true, key: key, section: section, texts: {}};
  96. if (!key) l.valid = false;
  97. if (!section) l.valid = false;
  98. l.key = key;
  99. l.section = section;
  100. let body;
  101. try{
  102. body = JSON.parse(obj);
  103. for (let i = 0; i < this.#availableLanguages.length; i ++)
  104. if (body[this.#availableLanguages[i]] != undefined) l.texts[this.#availableLanguages[i]] = body[this.#availableLanguages[i]];
  105. if (Reflect.ownKeys(l.texts).length == 0) l.valid = false;
  106. }
  107. catch(err){
  108. l.valid = false;
  109. }
  110. return l;
  111. }
  112. /**
  113. * Saves a localized object in the database.
  114. *
  115. * @param obj The object, as provided by {@see generateLocalizedObject}.
  116. * @return True on success, false on error.
  117. */
  118. async saveLocalizedObject(obj){
  119. if (!obj.valid || obj.valid != true || !obj.texts || obj.texts.length < 1) return false;
  120. for (const [lang, text] of Object.entries(obj.texts)){
  121. const t = {id: obj.key, lang: lang, section: obj.section, text: text, file: null};
  122. await Text.create(t)
  123. .catch(err => {logger.error("Error creating text with key " + id + " in lang " + lang + ": " + err);});
  124. }
  125. return true;
  126. }
  127. /**
  128. * Localizes a project.
  129. *
  130. * Localizes all localizable items in a project, using the language provided in the request or
  131. * the default one.
  132. *
  133. * @param data The project object to localize.
  134. * @param req The request received by the server.
  135. * @return The project object, with all of the fields localized.
  136. */
  137. async localizeProject(data, req){
  138. var lang = this.selectLanguage(req);
  139. data.dataValues.title = await this.#decodeText(data.dataValues.title, lang);
  140. data.dataValues.header = await this.#decodeText(data.dataValues.header, lang);
  141. data.dataValues.text = await this.#decodeText(data.dataValues.text, lang);
  142. data.dataValues.comment = await this.#decodeText(data.dataValues.comment, lang);
  143. if (data.dataValues.type){
  144. data.dataValues.type.title = await this.#decodeText(data.dataValues.type.title, lang);
  145. data.dataValues.type.summary = await this.#decodeText(data.dataValues.type.summary, lang);
  146. }
  147. if (data.dataValues.license){
  148. data.dataValues.license.summary = await this.#decodeText(data.dataValues.license.summary, lang);
  149. data.dataValues.license.legal = await this.#decodeText(data.dataValues.license.legal, lang);
  150. }
  151. if (data.dataValues.tags)
  152. for (var i = 0; i < data.dataValues.tags.length; i ++)
  153. data.dataValues.tags[i].tag = await this.#decodeText(data.dataValues.tags[i].tag, lang);
  154. if (data.dataValues["project-urls"])
  155. for (var i = 0; i < data.dataValues["project-urls"].length; i ++){
  156. data.dataValues["project-urls"][i].dataValues.type.dataValues.title = await this.#decodeText(data.dataValues["project-urls"][i].dataValues.type.dataValues.title, lang);
  157. data.dataValues["project-urls"][i].dataValues.type.dataValues.summary = await this.#decodeText(data.dataValues["project-urls"][i].dataValues.type.dataValues.summary, lang);
  158. }
  159. if (data.dataValues["project-images"])
  160. for (var i = 0; i < data.dataValues["project-images"].length; i ++)
  161. data.dataValues["project-images"][i].dataValues.alt = await this.#decodeText(data.dataValues["project-images"][i].dataValues.alt, lang);
  162. return data;
  163. }
  164. /**
  165. * Localizes a list of projects.
  166. *
  167. * Localizes all localizable items in all projects, using the language provided in the request or
  168. * the default one.
  169. *
  170. * @param data The project object list to localize.
  171. * @param req The request received by the server.
  172. * @return The project list, with all of their fields localized.
  173. */
  174. async localizeProjects(data, req){
  175. for (var d of data) d = await this.localizeProject(d, req);
  176. return data;
  177. }
  178. /**
  179. * Localizes a license.
  180. *
  181. * Localizes all localizable items in a license, using the language provided in the request or
  182. * the default one.
  183. *
  184. * @param data The license object to localize.
  185. * @param req The request received by the server.
  186. * @return The license object, with all of the fields localized.
  187. */
  188. async localizeLicense(data, req){
  189. var lang = this.selectLanguage(req);
  190. data.dataValues.summary = await this.#decodeText(data.dataValues.summary, lang);
  191. data.dataValues.legal = await this.#decodeText(data.dataValues.legal, lang);
  192. return data;
  193. }
  194. /**
  195. * Localizes a list of projects licenses.
  196. *
  197. * Localizes all localizable items in all licenses, using the language provided in the request or
  198. * the default one.
  199. *
  200. * @param data The license object list to localize.
  201. * @param req The request received by the server.
  202. * @return The license list, with all of their fields localized.
  203. */
  204. async localizeLicenses(data, req){
  205. for (var d of data) d = await this.localizeLicense(d, req);
  206. return data;
  207. }
  208. /**
  209. * Localizes a project type.
  210. *
  211. * Localizes all localizable items in a project type, using the language provided in the request or
  212. * the default one.
  213. *
  214. * @param data The project type object to localize.
  215. * @param req The request received by the server.
  216. * @return The project type object, with all of the fields localized.
  217. */
  218. async localizeProjectType(data, req){
  219. var lang = this.selectLanguage(req);
  220. data.dataValues.title = await this.#decodeText(data.dataValues.title, lang);
  221. data.dataValues.summary = await this.#decodeText(data.dataValues.summary, lang);
  222. return data;
  223. }
  224. /**
  225. * Localizes a list of projects project types.
  226. *
  227. * Localizes all localizable items in all project types, using the language provided in the request or
  228. * the default one.
  229. *
  230. * @param data The project type object list to localize.
  231. * @param req The request received by the server.
  232. * @return The project type list, with all of their fields localized.
  233. */
  234. async localizeProjectTypes(data, req){
  235. for (var d of data) d = await this.localizeProjectType(d, req);
  236. return data;
  237. }
  238. /**
  239. * Localizes a user.
  240. *
  241. * Localizes all localizable items in a user, using the language provided in the request or
  242. * the default one.
  243. *
  244. * @param data The user object to localize.
  245. * @param req The request received by the server.
  246. * @return The user object, with all of the fields localized.
  247. */
  248. async localizeUser(data, req){
  249. var lang = this.selectLanguage(req);
  250. if (data.dataValues.texts)
  251. for (var i = 0; i < data.dataValues.texts.length; i ++)
  252. data.dataValues.texts[i].text = await this.#decodeText(data.dataValues.texts[i].text, lang);
  253. if (data.dataValues.urls)
  254. for (var i = 0; i < data.dataValues.urls.length; i ++){
  255. data.dataValues.urls[i].name = await this.#decodeText(data.dataValues.urls[i].name, lang);
  256. data.dataValues.urls[i].description = await this.#decodeText(data.dataValues.urls[i].description, lang);
  257. }
  258. return data;
  259. }
  260. /**
  261. * Localize a list of users project types.
  262. *
  263. * Localize all localizable items in all users, using the language provided in the request or
  264. * the default one.
  265. *
  266. * @param data The user object list to localize.
  267. * @param req The request received by the server.
  268. * @return The user list, with all of their fields localized.
  269. */
  270. async localizeUserss(data, req){
  271. for (var d of data) d = await this.localizeUser(d, req);
  272. return data;
  273. }
  274. }
  275. module.exports = LocaleService;