mi-intl 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,25 +29,7 @@ import { MiIntlProvider, MiIntlMessage, IntlConsumer } from 'mi-intl'
29
29
 
30
30
  // define tag for intl-provider
31
31
  define('mi-intl-provider', MiIntlProvider)
32
-
33
- // connects to MiIntlProvider using IntlConsumer
34
- define(
35
- 'mi-message',
36
- class extends MiIntlMessage {
37
- static get properties () {
38
- return { label: {}, value: {} }
39
- }
40
-
41
- render() {
42
- this.update()
43
- }
44
-
45
- update() {
46
- // this.t() is provided by MiIntlMessage
47
- this.renderRoot.textContent = this.t(this.label, { value: this.value })
48
- }
49
- }
50
- )
32
+ define('mi-message', MiIntlMessage)
51
33
 
52
34
  // define lang selector
53
35
  define(
package/dist/cookie.js CHANGED
@@ -1,13 +1,15 @@
1
1
  function cookieParse(cookieStr = "") {
2
- const parts = cookieStr.split(/\s*;\s*/), cookies = {};
2
+ const parts = cookieStr.split(/\s*;\s*/), cookies = Object.create(null);
3
3
  for (const part of parts) {
4
4
  const [key, val] = part.split('=');
5
- if (key) {
5
+ if (key && !Object.prototype.hasOwnProperty.call(Object.prototype, key)) {
6
6
  const value = decodeURIComponent(val);
7
7
  cookies[key] = value;
8
8
  }
9
9
  }
10
- return cookies;
10
+ return {
11
+ ...cookies
12
+ };
11
13
  }
12
14
 
13
15
  const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
@@ -1,15 +1,39 @@
1
- import { MiElement } from 'mi-element';
1
+ import { MiElement, escHtml } from 'mi-element';
2
2
 
3
3
  import { IntlConsumer } from './intl-consumer.js';
4
4
 
5
5
  class MiIntlMessage extends MiElement {
6
6
  #context;
7
+ static get properties() {
8
+ return {
9
+ label: {},
10
+ value: {},
11
+ unsafeHtml: {
12
+ type: Boolean,
13
+ initial: !1
14
+ }
15
+ };
16
+ }
7
17
  constructor() {
8
18
  super(), this.#context = new IntlConsumer(this);
9
19
  }
10
20
  t(label, value) {
11
21
  return this.#context.value.t(label, value);
12
22
  }
23
+ update() {
24
+ if ('string' == typeof this.value) try {
25
+ this.value = JSON.parse(this.value);
26
+ } catch {
27
+ this.value = {
28
+ value: this.value
29
+ };
30
+ }
31
+ if (this.unsafeHtml) {
32
+ const escValue = {};
33
+ for (const [key, val] of Object.entries(this.value || {})) escValue[key] = escHtml(val);
34
+ this.renderRoot.innerHTML = this.t(this.label, escValue);
35
+ } else this.renderRoot.textContent = this.t(this.label, this.value);
36
+ }
13
37
  }
14
38
 
15
39
  export { MiIntlMessage };
@@ -46,13 +46,12 @@ class MiIntlProvider extends MiElement {
46
46
  this.i18n = new I18n(options), this.changeLanguage(options?.lng).catch(console.error);
47
47
  }
48
48
  _contextValue() {
49
- const {t: t, lng: lng, getLanguages: getLanguages} = this.i18n ?? {
50
- t: () => '',
49
+ const {lng: lng, getLanguages: getLanguages} = this.i18n ?? {
51
50
  lng: '',
52
51
  getLanguages: () => []
53
52
  };
54
53
  return {
55
- t: t,
54
+ t: (label, values) => this.i18n?.t(label, values) ?? '',
56
55
  lng: lng,
57
56
  getLanguages: getLanguages,
58
57
  changeLanguage: this.changeLanguage.bind(this),
@@ -62,7 +61,15 @@ class MiIntlProvider extends MiElement {
62
61
  async changeLanguage(lng) {
63
62
  this.loading = !0, await (this.i18n?.changeLanguage(lng).finally(() => {
64
63
  this.requestUpdate();
65
- })), await requestAnimationFrameP(), await requestAnimationFrameP(), this.loading = !1;
64
+ }));
65
+ for (let i = 0; i < 2; i++) await requestAnimationFrameP();
66
+ this.dispatchEvent(new CustomEvent('language-changed', {
67
+ detail: {
68
+ lng: lng
69
+ },
70
+ bubbles: !0,
71
+ composed: !0
72
+ })), this.loading = !1;
66
73
  }
67
74
  render() {
68
75
  const {version: version, lng: lng, defaultNs: defaultNs, localesPath: localesPath, useLabel: useLabel, debug: debug, supportedLngs: supportedLngs, ns: ns} = this;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mi-intl",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "i18n for mi-element",
5
5
  "keywords": [
6
6
  "i18n",
@@ -36,8 +36,9 @@
36
36
  "types"
37
37
  ],
38
38
  "dependencies": {
39
- "intl-messageformat-tiny": "^1.1.0",
40
- "mi-element": "0.7.0"
39
+ "intl-messageformat-tiny": "^1.2.0",
40
+ "mi-signal": "^0.9.0",
41
+ "mi-element": "0.9.0"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@eslint/js": "^9.39.2",
package/src/cookie.js CHANGED
@@ -8,15 +8,15 @@
8
8
  */
9
9
  export function cookieParse(cookieStr = '') {
10
10
  const parts = cookieStr.split(/\s*;\s*/)
11
- const cookies = {}
11
+ const cookies = Object.create(null)
12
12
  for (const part of parts) {
13
13
  const [key, val] = part.split('=')
14
- if (key) {
14
+ if (key && !Object.prototype.hasOwnProperty.call(Object.prototype, key)) {
15
15
  const value = decodeURIComponent(val)
16
16
  cookies[key] = value
17
17
  }
18
18
  }
19
- return cookies
19
+ return { ...cookies }
20
20
  }
21
21
 
22
22
  /**
@@ -1,4 +1,4 @@
1
- import { MiElement } from 'mi-element'
1
+ import { MiElement, escHtml } from 'mi-element'
2
2
  import { IntlConsumer } from './intl-consumer.js'
3
3
 
4
4
  /**
@@ -8,34 +8,54 @@ import { IntlConsumer } from './intl-consumer.js'
8
8
  * import { define } from 'mi-element'
9
9
  * import { MiIntlMessage } from 'mi-intl'
10
10
  *
11
- * define('mi-message',
12
- * class extends MiIntlMessage {
13
- * static get properties() {
14
- * return { label: {}, value: {} }
15
- * }
16
- * update() {
17
- * this.renderRoot.textContent = this.t(this.label, this.value)
18
- * }
19
- * }
20
- * )
11
+ * define('mi-message', MiIntlMessage)
12
+ * // then use <mi-message label="my.label" value='{ "count": 5 }'></mi-message> in your template
21
13
  * ```
22
14
  */
23
15
  export class MiIntlMessage extends MiElement {
24
16
  #context
25
17
 
18
+ static get properties() {
19
+ return {
20
+ label: {},
21
+ value: {},
22
+ unsafeHtml: { type: Boolean, initial: false }
23
+ }
24
+ }
25
+
26
26
  constructor() {
27
27
  super()
28
28
  this.#context = new IntlConsumer(this)
29
29
  }
30
30
 
31
+ /**
32
+ * @param {string} label
33
+ * @param {object} [value]
34
+ * @returns
35
+ */
31
36
  t(label, value) {
32
37
  return this.#context.value.t(label, value)
33
38
  }
34
39
 
35
- /*
36
40
  update() {
37
- // update will be called on every #context.value change
38
- // use this.t() there or within a effect
41
+ // ensure value is an object for interpolation, if it's a string
42
+ // try to parse it as JSON otherwise use it as a value property
43
+ if (typeof this.value === 'string') {
44
+ try {
45
+ this.value = JSON.parse(this.value)
46
+ } catch {
47
+ this.value = { value: this.value }
48
+ }
49
+ }
50
+ if (this.unsafeHtml) {
51
+ // escape values for interpolation to prevent XSS
52
+ const escValue = {}
53
+ for (const [key, val] of Object.entries(this.value || {})) {
54
+ escValue[key] = escHtml(val)
55
+ }
56
+ this.renderRoot.innerHTML = this.t(this.label, escValue)
57
+ } else {
58
+ this.renderRoot.textContent = this.t(this.label, this.value)
59
+ }
39
60
  }
40
- */
41
61
  }
@@ -48,8 +48,10 @@ export class MiIntlProvider extends MiElement {
48
48
  * @returns {IntlContext}
49
49
  */
50
50
  _contextValue() {
51
- const { t, lng, getLanguages } = this.i18n ?? {
52
- t: () => '',
51
+ const t = (label, values) => this.i18n?.t(label, values) ?? ''
52
+
53
+ const { lng, getLanguages } = this.i18n ?? {
54
+ t,
53
55
  lng: '',
54
56
  getLanguages: () => []
55
57
  }
@@ -71,9 +73,17 @@ export class MiIntlProvider extends MiElement {
71
73
  await this.i18n?.changeLanguage(lng).finally(() => {
72
74
  this.requestUpdate()
73
75
  })
74
- // need to wait two animations frames until all updates have been propagated
75
- await requestAnimationFrameP()
76
- await requestAnimationFrameP()
76
+ // need to wait two animation frames until all updates have been propagated
77
+ for (let i = 0; i < 2; i++) {
78
+ await requestAnimationFrameP()
79
+ }
80
+ this.dispatchEvent(
81
+ new CustomEvent('language-changed', {
82
+ detail: { lng },
83
+ bubbles: true,
84
+ composed: true
85
+ })
86
+ )
77
87
  this.loading = false
78
88
  }
79
89
 
@@ -5,20 +5,27 @@
5
5
  * import { define } from 'mi-element'
6
6
  * import { MiIntlMessage } from 'mi-intl'
7
7
  *
8
- * define('mi-message',
9
- * class extends MiIntlMessage {
10
- * static get properties() {
11
- * return { label: {}, value: {} }
12
- * }
13
- * update() {
14
- * this.renderRoot.textContent = this.t(this.label, this.value)
15
- * }
16
- * }
17
- * )
8
+ * define('mi-message', MiIntlMessage)
9
+ * // then use <mi-message label="my.label" value='{ "count": 5 }'></mi-message> in your template
18
10
  * ```
19
11
  */
20
12
  export class MiIntlMessage extends MiElement {
21
- t(label: any, value: any): any;
13
+ static get properties(): {
14
+ label: {};
15
+ value: {};
16
+ unsafeHtml: {
17
+ type: BooleanConstructor;
18
+ initial: boolean;
19
+ };
20
+ };
21
+ /**
22
+ * @param {string} label
23
+ * @param {object} [value]
24
+ * @returns
25
+ */
26
+ t(label: string, value?: object): any;
27
+ update(): void;
28
+ value: any;
22
29
  #private;
23
30
  }
24
31
  import { MiElement } from 'mi-element';