@schukai/monster 3.7.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,16 +5,16 @@
5
5
  * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
6
6
  */
7
7
 
8
- import { internalSymbol } from "../../constants.mjs";
9
- import { extend } from "../../data/extend.mjs";
10
- import { getGlobalObject } from "../../types/global.mjs";
11
- import { isString } from "../../types/is.mjs";
12
- import { validateObject, validateString } from "../../types/validate.mjs";
13
- import { parseLocale } from "../locale.mjs";
14
- import { Provider } from "../provider.mjs";
15
- import { Translations } from "../translations.mjs";
8
+ import {internalSymbol} from "../../constants.mjs";
9
+ import {extend} from "../../data/extend.mjs";
10
+ import {getDocument} from "../../dom/util.mjs";
11
+ import {isString} from "../../types/is.mjs";
12
+ import {validateObject, validateString} from "../../types/validate.mjs";
13
+ import {parseLocale} from "../locale.mjs";
14
+ import {Provider} from "../provider.mjs";
15
+ import {Translations} from "../translations.mjs";
16
16
 
17
- export { Embed };
17
+ export {Embed};
18
18
 
19
19
  /**
20
20
  * The Embed provider retrieves a JSON file from the given Script Tag.
@@ -42,22 +42,27 @@ class Embed extends Provider {
42
42
  * new Embed('translations')
43
43
  * ```
44
44
  *
45
- * @param {string} id
45
+ * @param {HTMLElement|string} elementOrId
46
46
  * @param {Object} options
47
47
  */
48
- constructor(id, options) {
48
+ constructor(elementOrId, options) {
49
49
  super(options);
50
50
 
51
51
  if (options === undefined) {
52
52
  options = {};
53
53
  }
54
54
 
55
- validateString(id);
56
-
57
- /**
58
- * @property {string}
59
- */
60
- this.textId = id;
55
+ if (elementOrId instanceof HTMLElement) {
56
+ /**
57
+ * @property {HTMLElement|string}
58
+ */
59
+ this.translateElement = elementOrId;
60
+ } else {
61
+ /**
62
+ * @property {HTMLElement|string}
63
+ */
64
+ this.translateElement = getDocument().getElementById(validateString(elementOrId));
65
+ }
61
66
 
62
67
  /**
63
68
  * @private
@@ -86,16 +91,25 @@ class Embed extends Provider {
86
91
  }
87
92
 
88
93
  return new Promise((resolve, reject) => {
89
- let text = getGlobalObject("document").getElementById(this.textId);
90
94
 
91
- if (text === null) {
95
+ if (this.translateElement === null) {
92
96
  reject(new Error("Text not found"));
93
97
  return;
94
98
  }
95
99
 
100
+ if (!(this.translateElement instanceof HTMLScriptElement)) {
101
+ reject(new Error("Element is not a script tag"));
102
+ return;
103
+ }
104
+
105
+ if (this.translateElement.type !== "application/json") {
106
+ reject(new Error("Element is not a script tag with type application/json"));
107
+ return;
108
+ }
109
+
96
110
  let translations = null;
97
111
  try {
98
- translations = JSON.parse(text.innerHTML);
112
+ translations = JSON.parse(this.translateElement.innerHTML);
99
113
  } catch (e) {
100
114
  reject(e);
101
115
  return;
@@ -112,4 +126,38 @@ class Embed extends Provider {
112
126
  resolve(t);
113
127
  });
114
128
  }
129
+
130
+
131
+ /**
132
+ * Initializes the translations for the current document.
133
+ *
134
+ * `script[data-monster-role=translations]` is searched for and the translations are assigned to the element.
135
+ *
136
+ * @param element
137
+ * @returns {Promise<unknown[]>}
138
+ */
139
+ static assignTranslationsToElement(element) {
140
+ const d = getDocument()
141
+
142
+ if (!(element instanceof HTMLElement)) {
143
+ element = d.querySelector("body");
144
+ }
145
+
146
+ const list = d.querySelectorAll("script[data-monster-role=translations]");
147
+ if (list === null) {
148
+ return;
149
+ }
150
+
151
+ const promises = [];
152
+
153
+ let result
154
+
155
+ list.forEach((translationElement) => {
156
+ const p = new Embed(translationElement);
157
+ promises.push(p.assignToElement(undefined, element));
158
+ });
159
+
160
+ return Promise.all(promises);
161
+ }
162
+
115
163
  }
@@ -5,12 +5,17 @@
5
5
  * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
6
6
  */
7
7
 
8
- import { Base } from "../types/base.mjs";
9
- import { isObject, isString } from "../types/is.mjs";
10
- import { validateInstance, validateInteger, validateObject, validateString } from "../types/validate.mjs";
11
- import { Locale, parseLocale } from "./locale.mjs";
8
+ import { getLinkedObjects,hasObjectLink} from "../dom/attributes.mjs";
9
+ import {ATTRIBUTE_OBJECTLINK} from "../dom/constants.mjs";
10
+ import {getDocument} from "../dom/util.mjs";
11
+ import {Base} from "../types/base.mjs";
12
+ import {isObject, isString} from "../types/is.mjs";
13
+ import {validateInteger, validateObject, validateString} from "../types/validate.mjs";
14
+ import {Locale, parseLocale} from "./locale.mjs";
15
+ import {translationsLinkSymbol} from "./provider.mjs";
12
16
 
13
- export { Translations };
17
+
18
+ export {Translations, getDocumentTranslations};
14
19
 
15
20
  /**
16
21
  * With this class you can manage translations and access the keys.
@@ -30,11 +35,12 @@ class Translations extends Base {
30
35
  constructor(locale) {
31
36
  super();
32
37
 
33
- if (isString(locale)) {
34
- locale = parseLocale(locale);
38
+ if (locale instanceof Locale) {
39
+ this.locale = locale;
40
+ } else {
41
+ this.locale = parseLocale(validateString(locale));
35
42
  }
36
43
 
37
- this.locale = validateInstance(locale, Locale);
38
44
  this.storage = new Map();
39
45
  }
40
46
 
@@ -112,9 +118,9 @@ class Translations extends Base {
112
118
  * Set a text for a key
113
119
  *
114
120
  * ```
115
- * translations.setText("text1": "Make my day!");
121
+ * translations.setText("text1", "Make my day!");
116
122
  * // plural rules
117
- * translations.setText("text6": {
123
+ * translations.setText("text6", {
118
124
  * "zero": "There are no files on Disk.",
119
125
  * "one": "There is one file on Disk.",
120
126
  * "other": "There are files on Disk."
@@ -165,6 +171,13 @@ class Translations extends Base {
165
171
  */
166
172
  assignTranslations(translations) {
167
173
  validateObject(translations);
174
+
175
+ if (translations instanceof Translations) {
176
+ translations.storage.forEach((v, k) => {
177
+ this.setText(k, v);
178
+ });
179
+ return this;
180
+ }
168
181
 
169
182
  for (const [k, v] of Object.entries(translations)) {
170
183
  this.setText(k, v);
@@ -173,3 +186,41 @@ class Translations extends Base {
173
186
  return this;
174
187
  }
175
188
  }
189
+
190
+ /**
191
+ * Returns the translations for the current document.
192
+ *
193
+ * @param element
194
+ * @returns {*}
195
+ * @throws {Error} Element is not an HTMLElement
196
+ * @throws {Error} Missing translations
197
+ */
198
+ function getDocumentTranslations(element) {
199
+
200
+ const d = getDocument()
201
+
202
+ if (!(element instanceof HTMLElement)) {
203
+ element = d.querySelector('['+ATTRIBUTE_OBJECTLINK+'="' + translationsLinkSymbol.toString() + '"]');
204
+ }
205
+
206
+ if (!(element instanceof HTMLElement)) {
207
+ throw new Error("Element is not an HTMLElement");
208
+ }
209
+
210
+ if (!hasObjectLink(element, translationsLinkSymbol)) {
211
+ throw new Error("Missing translations");
212
+ }
213
+
214
+ let obj = getLinkedObjects(element, translationsLinkSymbol);
215
+
216
+ for (const t of obj) {
217
+ if (t instanceof Translations) {
218
+ return t;
219
+ }
220
+ }
221
+
222
+ throw new Error("Missing translations");
223
+
224
+ }
225
+
226
+
@@ -142,7 +142,7 @@ function getMonsterVersion() {
142
142
  }
143
143
 
144
144
  /** don't touch, replaced by make with package.json version */
145
- monsterVersion = new Version("3.7.0");
145
+ monsterVersion = new Version("3.9.0");
146
146
 
147
147
  return monsterVersion;
148
148
  }
@@ -4,7 +4,6 @@ import {expect} from "chai"
4
4
  import {RestAPI} from "../../../../../../application/source/data/datasource/server/restapi.mjs";
5
5
  import {validateObject} from "../../../../../../application/source/types/validate.mjs";
6
6
 
7
-
8
7
  describe('RestAPI', function () {
9
8
 
10
9
  let fetchReference;
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {expect} from "chai"
4
4
  import {Transformer} from "../../../../application/source/data/transformer.mjs";
5
+ import {Embed} from "../../../../application/source/i18n/providers/embed.mjs";
5
6
  import {initJSDOM} from "../../util/jsdom.mjs";
6
7
 
7
8
  describe('Transformer', function () {
@@ -10,7 +11,7 @@ describe('Transformer', function () {
10
11
 
11
12
  let promises = []
12
13
  promises.push(initJSDOM());
13
- if(!globalThis['crypto']) {
14
+ if (!globalThis['crypto']) {
14
15
  promises.push(import("@peculiar/webcrypto").then(m => {
15
16
  globalThis['crypto'] = new m.Crypto();
16
17
  return true;
@@ -197,4 +198,63 @@ describe('Transformer', function () {
197
198
  }).to.throw(TypeError);
198
199
  });
199
200
  });
201
+
202
+
203
+ describe('i18n', function () {
204
+
205
+ let html1 = `
206
+ <div id="mock-translations"></div>
207
+ <script type="application/json" data-monster-role="translations">
208
+ {
209
+ "test1": "xyz",
210
+ "test3": {
211
+ "other": "xyz"
212
+ }
213
+ }
214
+ </script>
215
+ `;
216
+
217
+ beforeEach((done) => {
218
+ let mocks = document.getElementById('mocks');
219
+ mocks.innerHTML = html1;
220
+ let elem = document.getElementById('mock-translations');
221
+ Embed.assignTranslationsToElement(elem).then((o) => {
222
+ done()
223
+ })
224
+
225
+
226
+ })
227
+
228
+ afterEach(() => {
229
+ let mocks = document.getElementById('mocks');
230
+ mocks.innerHTML = "";
231
+ })
232
+
233
+ before(function (done) {
234
+ initJSDOM().then(() => {
235
+ done()
236
+ });
237
+ });
238
+
239
+ [
240
+ [ 'i18n:test1',"", "xyz"],
241
+ [ 'i18n:',"test1", "xyz"], // key by value
242
+ [ 'i18n::',"test1", "xyz"], // key by value no default
243
+ [ 'i18n::eee',"test2", "eee"], // key by value with default
244
+ [ 'i18n::ddd',"test2", "ddd"], // key by value and default
245
+
246
+ ].forEach(function (data) {
247
+
248
+ let a = data.shift()
249
+ let b = data.shift()
250
+ let c = data.shift()
251
+
252
+ it('should transform('+a+').run('+b+') return ' + JSON.stringify(c), function () {
253
+ const t = new Transformer(a);
254
+ expect(t.run(b)).to.be.eql(c);
255
+ });
256
+ })
257
+ })
258
+
259
+
200
260
  });
@@ -33,24 +33,27 @@ describe('Attributes', function () {
33
33
  node.setAttribute('lang', a);
34
34
  expect(getLocaleOfDocument().toString()).to.be.equal(a);
35
35
  })
36
-
37
-
36
+
38
37
 
39
38
  });
40
39
 
41
40
 
42
41
  });
43
-
44
-
42
+
43
+
45
44
  it('return language en', function () {
46
45
  let html = document.getElementsByTagName('html');
47
46
  let node = html.item(0);
47
+ let lang= node.getAttribute('lang');
48
48
 
49
49
  node.removeAttribute('lang');
50
50
  const locale = getLocaleOfDocument();
51
51
  expect(locale).to.be.instanceOf(Locale);
52
- expect(locale.localeString).to.be.equal('en-US');
52
+ expect(locale.localeString).to.be.equal(navigator.language);
53
+
54
+ node.setAttribute('lang', lang);
55
+
53
56
  })
54
-
57
+
55
58
 
56
59
  })
@@ -1,12 +1,75 @@
1
1
  import {expect} from "chai"
2
+ import {ATTRIBUTE_OBJECTLINK} from "../../../../application/source/dom/constants.mjs";
3
+ import {getLinkedObjects} from "../../../../application/source/dom/attributes.mjs";
2
4
  import {Provider} from "../../../../application/source/i18n/provider.mjs";
5
+ import {initJSDOM} from "../../util/jsdom.mjs";
6
+ import {getDocumentTranslations, Translations} from "../../../../application/source/i18n/translations.mjs";
3
7
 
4
8
  describe('Provider', function () {
5
9
 
6
- describe('Instance and Init', function () {
7
10
 
8
- it('create instance', function () {
9
- expect((new Provider()).getTranslations('en')).is.instanceof(Promise);
11
+ let html1 = `
12
+ <div id="test1">
13
+ </div>
14
+ `;
15
+
16
+ beforeEach(() => {
17
+ let mocks = document.getElementById('mocks');
18
+ mocks.innerHTML = html1;
19
+
20
+
21
+ })
22
+
23
+ afterEach(() => {
24
+ let mocks = document.getElementById('mocks');
25
+ mocks.innerHTML = "";
26
+ })
27
+
28
+ before(function (done) {
29
+ initJSDOM().then(() => {
30
+ done()
31
+ });
32
+ });
33
+
34
+ describe('Provider and Dom', function () {
35
+
36
+ const translationsLinkSymbol = Symbol.for("@schukai/monster/i18n/translations@@link");
37
+
38
+ it('assignToElement', function (done) {
39
+ const element = document.getElementById('test1');
40
+ const p = new Provider();
41
+ const r = p.assignToElement(undefined, element);
42
+
43
+ r.then((e) => {
44
+ const s = element.getAttribute(ATTRIBUTE_OBJECTLINK);
45
+ if (s === null) {
46
+ done(new Error("Attribute not set"));
47
+ return;
48
+ }
49
+
50
+ const i = getLinkedObjects(element, translationsLinkSymbol)
51
+ if (i === null) {
52
+ done(new Error("No linked object found"));
53
+ return;
54
+ }
55
+ let counter = 0;
56
+
57
+ for (let v of i) {
58
+ counter++;
59
+ }
60
+
61
+ if (counter !== 1) {
62
+ done(new Error("No linked object found"));
63
+ return;
64
+ }
65
+
66
+ const docTrans = getDocumentTranslations(element)
67
+ expect(docTrans).is.instanceof(Translations);
68
+
69
+
70
+ done();
71
+ }).catch(e => done(e));
72
+
10
73
  });
11
74
 
12
75
  });
@@ -1,6 +1,12 @@
1
1
  import {expect} from "chai"
2
2
  import {parseLocale} from "../../../../application/source/i18n/locale.mjs";
3
- import {Translations} from "../../../../application/source/i18n/translations.mjs";
3
+ import {Embed} from "../../../../application/source/i18n/providers/embed.mjs";
4
+ import {
5
+ Translations,
6
+ getDocumentTranslations
7
+
8
+ } from "../../../../application/source/i18n/translations.mjs";
9
+ import {initJSDOM} from "../../util/jsdom.mjs";
4
10
 
5
11
  describe('Translations', function () {
6
12
 
@@ -53,5 +59,76 @@ describe('Translations', function () {
53
59
 
54
60
  });
55
61
 
62
+ /**
63
+ * initDocumentTranslation
64
+ */
65
+
66
+ describe("test initDocumentTranslation ", function () {
67
+
68
+
69
+ let html1 = `<div id="mock-translations"></div>
70
+
71
+ <script type="application/json" data-monster-role="translations">
72
+ {
73
+ "test1": "abc",
74
+ "test2": {
75
+ "other": "xyz"
76
+ }
77
+ }
78
+ </script>
79
+
80
+ <script type="application/json" data-monster-role="translations">
81
+ {
82
+ "test1": "xyz",
83
+ "test3": {
84
+ "other": "xyz"
85
+ }
86
+ }
87
+ </script>
88
+
89
+ `;
90
+
91
+ beforeEach(() => {
92
+ let mocks = document.getElementById('mocks');
93
+ mocks.innerHTML = html1;
94
+
95
+ })
96
+
97
+ afterEach(() => {
98
+ let mocks = document.getElementById('mocks');
99
+ mocks.innerHTML = "";
100
+ })
101
+
102
+ before(function (done) {
103
+ initJSDOM().then(() => {
104
+ done()
105
+ });
106
+ });
107
+
108
+
109
+ it('Init translations', function (done) {
110
+
111
+ let elem = document.getElementById('mock-translations');
112
+ Embed.assignTranslationsToElement(elem).then((o) => {
113
+
114
+ let mocks = document.getElementById('mocks');
115
+
116
+ // no exception because of default
117
+ expect(getDocumentTranslations(elem).getText('no-key','with-default'))
118
+ .is.equal('with-default');
119
+
120
+ expect(getDocumentTranslations(elem).getText('test1'))
121
+ .is.equal('xyz');
122
+
123
+ done();
124
+
125
+ }).catch((e) => {
126
+ done(e);
127
+ })
128
+
129
+
130
+ });
131
+ })
132
+
56
133
 
57
134
  });
@@ -7,7 +7,7 @@ describe('Monster', function () {
7
7
  let monsterVersion
8
8
 
9
9
  /** don´t touch, replaced by make with package.json version */
10
- monsterVersion = new Version("3.7.0")
10
+ monsterVersion = new Version("3.9.0")
11
11
 
12
12
  let m = getMonsterVersion();
13
13
 
@@ -4,7 +4,6 @@ import {extend} from "../../../application/source/data/extend.mjs";
4
4
  import {getGlobal} from "../../../application/source/types/global.mjs";
5
5
 
6
6
  export const isBrowser = new Function("try {return this===window;}catch(e){ return false;}");
7
-
8
7
  export const isNode = new Function("try {return this===global;}catch(e){return false;}");
9
8
 
10
9
 
@@ -25,7 +24,7 @@ function initJSDOM(options) {
25
24
  storageQuota: 10000000,
26
25
  runScripts: "dangerously",
27
26
  resources: "usable"
28
- }, options||{})
27
+ }, options || {})
29
28
 
30
29
  return import("jsdom").then(({JSDOM}) => {
31
30
  const {window} = new JSDOM(`<html>
@@ -28,6 +28,7 @@ import "../cases/dom/assembler.mjs";
28
28
  import "../cases/i18n/translations.mjs";
29
29
  import "../cases/i18n/locale.mjs";
30
30
  import "../cases/i18n/formatter.mjs";
31
+ import "../cases/i18n/providers/embed.mjs";
31
32
  import "../cases/i18n/providers/fetch.mjs";
32
33
  import "../cases/i18n/provider.mjs";
33
34
  import "../cases/net/webconnect/message.mjs";
@@ -14,8 +14,8 @@
14
14
  </head>
15
15
  <body>
16
16
  <div id="headline" style="display: flex;align-items: center;justify-content: center;flex-direction: column;">
17
- <h1 style='margin-bottom: 0.1em;'>Monster 3.4.0</h1>
18
- <div id="lastupdate" style='font-size:0.7em'>last update So 8. Jan 17:17:24 CET 2023</div>
17
+ <h1 style='margin-bottom: 0.1em;'>Monster 3.8.0</h1>
18
+ <div id="lastupdate" style='font-size:0.7em'>last update Do 2. Feb 11:38:37 CET 2023</div>
19
19
  </div>
20
20
  <div id="mocks"></div>
21
21
  <div id="mocha"></div>