1/* [MsbLanguage] a singleton class for loading/maintaining language resources. 2 * 3 * NOTES: As different resource requests are loaded (via AJAX), the list of resources is 4 * stored in the user's session to avoid excessive loading. When subsequent requests 5 * are made, only the unloaded resources (if any) are added to the list. 6 * 7 * If the language changes [see #getLocale() below] then the saved list will start over. 8 * 9 * Usages: 10 * 11 * It is recommended to not use this module directly! Instead, create a new instance of 12 * [MsbMessages] and use it to access language resources. 13 * Examples: 14 * let messages = new MsbMessages([ 15 * {id:'add', key:'common.words.add', value:'Add'}, 16 * {id:'cancel', key:'common.words.cancel', value:'Cancel'}, 17 * {id:'delete', key:'common.words.delete', value:'Delete'}, 18 * {id:'unexpectedError', key:'shared.errors.unexpectederror.msg', value:'An unexpected error has occurred'} 19 * ]); 20 * 21 * Use the messages instance anywhere in script like the following: 22 * messages.get('add'); //returns 'Add' 23 * messages.get('cancel'); //returns 'Cancel' 24 * messages.get('delete'); //returns 'Delete' 25 * messages.get('unexpectedError'); //returns 'An unexpected error has occurred' 26 * 27 * If you think you really have to use this moduel (although you should not), 28 * the public/non-private function [getMessages] can be called with either an array of strings 29 * that represent the resource keys or an array of objects with a key field. 30 * 31 * Examples: 32 * MsbLanguage.getInstance().getMessages(['common.words.id','common.words.name'...], callback); 33 * MsbLanguage.getInstance().getMessages([{key:'common.words.name'},{key:'resource.key'}...], callback); 34 * 35 * If an array of objects is passed to getMessages, the objects themselves will have 36 * the values updated and the array of objects is returned to the callback function. 37 */ 38class MsbLanguage { 39 sessionName = 'msbStoredLanguageValues'; 40 list = []; 41 locale = 'en'; 42 sessionToken = null; 43 44 constructor() { 45 if (!((new Error).stack.indexOf("getInstance") > -1)) { 46 throw new Error("Constructor is private. Use static method getInstance."); 47 } 48 this.constructor.instance = this; 49 this.size = 1; 50 } 51 static getInstance() { 52 if (this.instance) { 53 return this.instance; 54 } 55 return new this; 56 } 57 58 async #restoreValues() { 59 let res = null; 60 if (JSON && (typeof window.sessionStorage !== 'undefined')) { 61 res = JSON.parse(window.sessionStorage.getItem(this.sessionName)); 62 } 63 if (res && res.list) { 64 if (!res.locale || (res.locale == this.#getLocale())) 65 this.list = res.list; 66 } 67 } 68 69 async #saveValues() { 70 if (JSON && (typeof window.sessionStorage !== 'undefined')) { 71 let res = { list:this.list, locale:this.#getLocale() }; 72 window.sessionStorage.setItem(this.sessionName, JSON.stringify(res)); 73 } 74 } 75 #getLocale() { 76 const localeField = document.querySelector("#msbLocale"); //try to get it from the hidden field in layout.jsp 77 if (localeField) this.locale = localeField.value; 78 return this.locale; 79 } 80 #hasValues() { 81 return !_.isEmpty(this.list); 82 } 83 #hasValue(key) { 84 if (key && this.#hasValues()) { 85 return !_.isEmpty(_.find(this.list, {'key':key})); 86 } 87 return false; 88 } 89 #getValue(key) { 90 if (key && this.#hasValues(key)) { 91 return _.find(this.list, {'key':key}); 92 } 93 return false; 94 } 95 #addValue(key,value) { 96 if (key && value) { 97 let res = this.#getValue(key); 98 if (res) { 99 res.value = value; 100 //console.log('found value:',res) 101 } else { 102 res = {'key':key,'value':value}; 103 this.list.push(res); 104 } 105 return res; 106 } 107 return false; 108 } 109 #isArrayOfStrings(keys) { 110 if (!keys) 111 return false; 112 return _.every(keys, function (item) { return _.isString(item); }); 113 } 114 #isArrayOfObjects(keys) { 115 if (!keys || this.#isArrayOfStrings(keys)) 116 return false; 117 let keyList = _.map(keys, 'key'); 118 return (keyList && (keyList.length == keys.length)); 119 } 120 #isTrue (val) { 121 return (_.isBoolean(val) && (val == true)) || (_.isString(val) && !_.isEmpty(val) && val.toLowerCase() == 'true') 122 } 123 124 async #loadMessages(loadKeys) { 125 let results = []; 126 if (loadKeys) { 127 let json, reqData = {'keyList':loadKeys}; 128 //console.log('msbLanguage.#loadMessages - reqData:',reqData) 129 130 try { 131 json = await this.#postData('/ver2/etc/getMessages', reqData); 132 } catch (e) { 133 console.error('msbLanguage.#loadMessages - error:'+e); 134 } 135 136 if (json) { 137 if (!this.#isTrue(json.hasErrors) && json.resources) { 138 //console.log('msbLanguage._loadMessages - json.resources:',json.resources) 139 _.forEach(json.resources, (value, key) => { 140 results.push(this.#addValue(key, value)); 141 }); 142 //console.log('msbLanguage.#loadMessages - results:',results) 143 this.#saveValues(); 144 } 145 } 146 } 147 return results; 148 } 149 150 async #postData(url = '', data = {}) { 151 const urlSearchParams = new URLSearchParams();
152 Object.entries(data).forEach(([key, value]) => { 153 if (value) { 154 if (Array.isArray(value)) { 155 //for arrays, pass the same key multiple times for each value (same as jQuery ajax) 156 _.forEach(value, function(v, k) { 157 urlSearchParams.append(key, v); 158 }); 159 } 160 else urlSearchParams.append(key, value); 161 } 162 }); 163 //console.log("#postData urlSearchParams:",urlSearchParams.toString()); 164 165 const fetchHeaders = { 166 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 167 sessionToken: this.#getSessionToken() 168 }; 169 170 const response = await fetch(url, { 171 accept: 'application/json, text/javascript, */*; q=0.01', 172 method: 'POST', 173 cache: 'no-cache', 174 headers: fetchHeaders, 175 body: urlSearchParams 176 }); 177 if (!response.ok) { 178 throw new Error(response.status); 179 } 180 return response.json(); 181 } 182 183 #getSessionToken() { 184 if (this.sessionToken == null) { 185 const elm = document.querySelector("#msbSessionToken"); 186 if (elm) { 187 this.sessionToken = elm.value; 188 } 189 } 190 return this.sessionToken; 191 } 192 193 async #getMessages(keys, callback) { 194 //console.log('msbLanguage._getMessages - keys:',keys,' - callback:',callback); 195 196 if (!this.#hasValues()) { 197 await this.#restoreValues(); 198 } 199 200 let isArrayOfStrings = this.#isArrayOfStrings(keys), 201 isArrayOfObjects = this.#isArrayOfObjects(keys); 202 203 if (!isArrayOfStrings && !isArrayOfObjects) { 204 console.error('msbLanguage argument [keys] requires an array of Strings or objects with a {key:vale} pair'); 205 return false; 206 } 207 208 let results = [], loadKeys = [], 209 keyList = (isArrayOfStrings ? keys : _.map(keys, 'key')); 210 211 if (this.#hasValues()) { 212 /* No need to load them if we already have them, 213 * so we create a list of any missing keys. */ 214 _.forEach(keyList, (key) => { 215 if (!this.#hasValue(key)) 216 loadKeys.push(key); 217 }); 218 } else { 219 loadKeys = keyList; 220 } 221 222 if (!_.isEmpty(loadKeys)) { 223 // We have missing keys that need loading 224 //console.log('msbLanguage.#getMessages - loadKeys:',loadKeys); 225 let newResults = await this.#loadMessages(loadKeys); 226 } 227 228 if (this.#hasValues()) { 229 _.forEach(keyList, (key) => { 230 let res = this.#getValue(key); 231 if (res) { 232 if (isArrayOfObjects) { 233 //ensure we return the objects as passed (to maintain any extra fields such as [id]) 234 let r = _.find(keys, {'key':key}); 235 if (r) { 236 //returning the original object with value set 237 if (res.value == key) { 238 //resource not found and returned the key 239 res.value = r.value; 240 } else { 241 r.value = res.value; 242 } 243 results.push(r); 244 } 245 } else { 246 results.push(res); 247 } 248 } 249 }); 250 } else { 251 results = keys; 252 } 253 254 //console.log('msbLanguage._getMessages - results:',results); 255 256 if (callback) { 257 callback(results); 258 } 259 } 260 261 async getMessages(keys, callback) { 262 if (!keys || (!this.#isArrayOfStrings(keys) && !this.#isArrayOfObjects(keys))) { 263 console.error('MsbLanguage argument [keys] requires an array of Strings or objects with a {key:vale} pair'); 264 return false; 265 } else if (callback && !_.isFunction(callback)) { 266 console.error('MsbLanguage.getMessages argument [callback] must be a function'); 267 return false; 268 } 269 270 await this.#getMessages(keys, callback); 271 } 272 273}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.