mi-intl 0.6.3

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024-present, commenthol and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ [![npm-badge][npm-badge]][npm]
2
+ ![types-badge][types-badge]
3
+
4
+ # mi-intl
5
+
6
+ Formats strings using [ICU Message Syntax][icu-syntax] for [mi-element][].
7
+
8
+ **Table of contents**
9
+
10
+ <!-- !toc (minlevel=2) -->
11
+
12
+ - [Usage](#usage)
13
+
14
+ <!-- toc! -->
15
+
16
+ ## Usage
17
+
18
+ In your project:
19
+
20
+ ```
21
+ npm i mi-intl mi-element
22
+ ```
23
+
24
+ /app.js
25
+
26
+ ```js
27
+ import { define, MiElement } from 'mi-element'
28
+ import { MiIntlProvider, MiIntlMessage, IntlConsumer } from 'mi-intl'
29
+
30
+ // define tag for intl-provider
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 attributes() {
38
+ return { label: String, value: String }
39
+ }
40
+ update() {
41
+ // this.t() is provided by MiIntlMessage
42
+ this.renderRoot.textContent = this.t(this.label, { value: this.value })
43
+ }
44
+ }
45
+ )
46
+
47
+ // define lang selector
48
+ define(
49
+ 'mi-language-selector',
50
+ class extends MiElement {
51
+ #context
52
+
53
+ static template = `<slot></slot>`
54
+
55
+ render() {
56
+ // connect to intl-provider
57
+ this.#context = new IntlConsumer(this)
58
+ this.ref = this.renderRoot.querySelector('select')
59
+ this.ref.addEventListener('change', (ev) => {
60
+ this.#context.get().changeLanguage(ev.target.value).catch(console.error)
61
+ })
62
+ }
63
+
64
+ update() {
65
+ this.ref.value = this.#context.get().lng
66
+ }
67
+ }
68
+ )
69
+ ```
70
+
71
+ /index.html
72
+
73
+ ```html
74
+ <html>
75
+ <body>
76
+ <mi-intl-provider
77
+ version="1.0.0"
78
+ supportedLngs="en,en-US,es"
79
+ localesPath="/locales/{lng}/{ns}.json?version={version}"
80
+ >
81
+ <mi-language-selector>
82
+ <select>
83
+ <option value="en">πŸ‡¬πŸ‡§</option>
84
+ <option value="en-US">πŸ‡ΊπŸ‡Έ</option>
85
+ <option value="es">πŸ‡ͺπŸ‡Έ</option>
86
+ </select>
87
+ </mi-language-selector>
88
+ <mi-message label="Hello {value}!" value="world">
89
+ <mi-message label="lift">
90
+ </mi-intl-provider>
91
+ <script type="module" src="/app.js"></script>
92
+ </body>
93
+ </html>
94
+ ```
95
+
96
+ /locales/en/translations.json
97
+
98
+ ```json
99
+ {
100
+ "lift": "lift",
101
+ "Hello {value}!": "Hello {value}!"
102
+ }
103
+ ```
104
+
105
+ /locales/en-US/translations.json
106
+
107
+ ```json
108
+ {
109
+ "lift": "elevator",
110
+ "Hello {value}!": "Hello {value}!"
111
+ }
112
+ ```
113
+
114
+ /locales/es/translations.json
115
+
116
+ ```json
117
+ {
118
+ "lift": "ascensor",
119
+ "Hello {value}!": "Β‘Hola {value}!"
120
+ }
121
+ ```
122
+
123
+ Check folder ./example for a more advanced example.
124
+ Run with `npm run example`
125
+
126
+ # License
127
+
128
+ MIT licensed
129
+
130
+ [icu-syntax]: https://formatjs.github.io/docs/core-concepts/icu-syntax
131
+ [mi-element]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element
132
+ [npm-badge]: https://badgen.net/npm/v/mi-intl
133
+ [npm]: https://www.npmjs.com/package/mi-intl
134
+ [types-badge]: https://badgen.net/npm/types/mi-intl
package/dist/cookie.js ADDED
@@ -0,0 +1,26 @@
1
+ function cookieParse(cookieStr = "") {
2
+ const parts = cookieStr.split(/\s*;\s*/), cookies = {};
3
+ for (const part of parts) {
4
+ const [key, val] = part.split('=');
5
+ if (key) {
6
+ const value = decodeURIComponent(val);
7
+ cookies[key] = value;
8
+ }
9
+ }
10
+ return cookies;
11
+ }
12
+
13
+ const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
14
+
15
+ function cookieSerialize(name, value, options) {
16
+ const {maxAge: maxAge, domain: domain, path: path, expires: expires, httpOnly: httpOnly = !1, secure: secure = !1, sameSite: sameSite = "Strict"} = options || {};
17
+ if (!name || !fieldContentRegExp.test(name)) throw TypeError('invalid name');
18
+ const parts = [ `${name}=${encodeURIComponent(value)}` ];
19
+ return !isNaN(maxAge - 0) && isFinite(maxAge) && parts.push(`Max-Age=${maxAge}`),
20
+ domain && fieldContentRegExp.test(domain) && parts.push(`Domain=${domain}`), path && fieldContentRegExp.test(path) && parts.push(`Path=${path}`),
21
+ isNaN(new Date(expires).getTime()) || parts.push(`Expires=${new Date(expires).toUTCString()}`),
22
+ httpOnly && parts.push('HttpOnly'), secure && parts.push('Secure'), sameSite && parts.push(`SameSite=${sameSite}`),
23
+ parts.join('; ');
24
+ }
25
+
26
+ export { cookieParse, cookieSerialize };
package/dist/i18n.js ADDED
@@ -0,0 +1,138 @@
1
+ import { cached } from 'intl-messageformat-tiny';
2
+
3
+ import { cookieSerialize, cookieParse } from './cookie.js';
4
+
5
+ const format = cached(), isBrowser = globalThis?.navigator?.language && globalThis?.document, defaultOptions = {
6
+ supportedLngs: [],
7
+ version: '',
8
+ ns: [ "translations" ],
9
+ defaultNs: "translations",
10
+ localesPath: '/locales/{lng}/{ns}.json?v={version}',
11
+ useLabel: !1,
12
+ debug: !1,
13
+ cookie: {
14
+ name: 'lc',
15
+ path: '/'
16
+ }
17
+ };
18
+
19
+ let log = {
20
+ debug: (..._args) => {},
21
+ error: (..._args) => {}
22
+ };
23
+
24
+ const uniq = arr => [ ...new Set(arr.filter(Boolean)) ], mainLng = lng => (lng || '').split('-', 1)[0], reduceSupportedLangs = (lng, supportedLngs, browserLngs = []) => uniq([ lng, ...browserLngs ].filter(Boolean).reduce((curr, nextLng) => (curr.push(nextLng, mainLng(nextLng)),
25
+ curr), [])).reduce((curr, nextLng) => (supportedLngs.includes(nextLng) && curr.push(nextLng),
26
+ curr), []);
27
+
28
+ class I18n {
29
+ constructor(options) {
30
+ const {supportedLngs: supportedLngs, lng: lng, defaultNs: defaultNs, fallbackLng: fallbackLng = supportedLngs[0], resources: resources = {}, debug: debug, ...opts} = {
31
+ ...defaultOptions,
32
+ ...options
33
+ };
34
+ if (!supportedLngs?.length) throw new Error('no supportedLngs');
35
+ this.options = opts, this.defaultNs = defaultNs, this.fallbackLng = fallbackLng,
36
+ this.lng = lng || this.fallbackLng, this.supportedLngs = supportedLngs, this.userLng = lng,
37
+ this.resources = resources, debug && (log = opts.log || console), this.t = this.t.bind(this),
38
+ this.getLanguages = this.getLanguages.bind(this), this._setLanguage();
39
+ }
40
+ t(label, values = {}) {
41
+ const {lng: language, fallbackLng: fallbackLng, defaultNs: defaultNs} = this, {lng: lng = language, ns: ns = defaultNs, ...msgValues} = values, message = this.resources[lng]?.[ns]?.[label] || this.resources[fallbackLng]?.[ns]?.[label] || (this.options.useLabel ? label : '');
42
+ return format(message, msgValues, lng);
43
+ }
44
+ getUserLanguage() {
45
+ if (!isBrowser) return this.fallbackLng;
46
+ const [cookieLng, browserLng] = this._getSettings();
47
+ return log.debug('getUserLanguage: cookieLng:%s browserLng:%s', cookieLng, browserLng),
48
+ cookieLng || browserLng;
49
+ }
50
+ resetUserLanguage() {
51
+ if (!isBrowser) return;
52
+ const {name: name, ...opts} = this.options.cookie;
53
+ log.debug('reset cookie'), document.cookie = cookieSerialize(name, '', {
54
+ ...opts,
55
+ expires: 0
56
+ }), this._setLanguage();
57
+ }
58
+ _getSettings() {
59
+ const {name: name} = this.options.cookie, cookieLng = cookieParse(document.cookie)[name], browserLng = globalThis?.navigator?.language;
60
+ return console.log(document.cookie, cookieLng, browserLng), [ cookieLng, browserLng ];
61
+ }
62
+ _setCookie() {
63
+ if (!isBrowser) return;
64
+ const [cookieLng, browserLng] = this._getSettings(), {userLng: language = ""} = this;
65
+ if (cookieLng === language) return;
66
+ let expires, value = language;
67
+ [ browserLng, mainLng(browserLng) ].includes(language) && (expires = 0, value = '');
68
+ const {name: name, ...opts} = this.options.cookie;
69
+ log.debug('_setCookie: %s:%s', 0 === expires ? 'reset' : 'set', language), document.cookie = cookieSerialize(name, value, {
70
+ ...opts,
71
+ expires: expires
72
+ });
73
+ }
74
+ _setLanguage(lng) {
75
+ const _lng = lng || this.getUserLanguage(), {supportedLngs: supportedLngs} = this, browserLngs = globalThis?.navigator?.languages || [], variants = reduceSupportedLangs(_lng, supportedLngs, browserLngs);
76
+ if (variants.length) this.userLng = _lng, this.lng = variants[0]; else {
77
+ this.userLng = this.lng = this.fallbackLng;
78
+ const [main] = this.lng.split('-');
79
+ variants.push(...uniq([ this.lng, main ]));
80
+ }
81
+ return this._setCookie(), variants;
82
+ }
83
+ getLanguages() {
84
+ return this.supportedLngs;
85
+ }
86
+ async changeLanguage(lng) {
87
+ const variants = this._setLanguage(lng);
88
+ log.debug('changeLanguage: lng:%s variants:%s', lng, variants), await this.loadLanguages(variants);
89
+ let loadedLng = this.fallbackLng;
90
+ for (const variant of variants) if (this.resources[variant]?.[this.defaultNs]) {
91
+ loadedLng = variant;
92
+ break;
93
+ }
94
+ log.debug('changeLanguage: loadedLng:%s', loadedLng);
95
+ const mainLoadedLng = mainLng(loadedLng);
96
+ if (mainLng(lng || variants[0]) !== mainLoadedLng) {
97
+ const matchLng = variants.find(variant => mainLng(variant) === mainLoadedLng);
98
+ this._setLanguage(matchLng || loadedLng);
99
+ }
100
+ this._setCookie();
101
+ }
102
+ async changeNamespace(ns) {
103
+ this.defaultNs = ns, await this.loadLanguages([ this.lng, this.fallbackLng ]);
104
+ }
105
+ async loadLanguages(lngs = [], ns = []) {
106
+ const _lngs = uniq(lngs), _ns = uniq([ ...ns, this.defaultNs ]);
107
+ if (!_lngs.length || !_ns.length) return log.error("can't load without languages or namespaces"),
108
+ [];
109
+ const loaders = [];
110
+ for (const lng of _lngs) for (const ns of _ns) this.resources[lng]?.[ns] || loaders.push(this._load({
111
+ lng: lng,
112
+ ns: ns
113
+ }).catch(err => log.error(err)));
114
+ return await Promise.all(loaders);
115
+ }
116
+ async _load({lng: lng, ns: ns}) {
117
+ const {version: version} = this.options, url = format(this.options.localesPath, {
118
+ lng: lng,
119
+ ns: ns,
120
+ version: version
121
+ }), res = await fetch(url).catch(err => log.error(err));
122
+ this.resources[lng] = this.resources[lng] || {};
123
+ const {ok: ok = !1, status: status = -1} = res || {};
124
+ if (ok && res) {
125
+ log.debug('_load: lng=%s ns=%s', lng, ns);
126
+ const labels = await res.json();
127
+ this.resources[lng][ns] = labels;
128
+ } else log.error('_load: error loading url=%s', url);
129
+ return {
130
+ ok: ok,
131
+ status: status,
132
+ lng: lng,
133
+ ns: ns
134
+ };
135
+ }
136
+ }
137
+
138
+ export { I18n, reduceSupportedLangs };
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { I18n } from './i18n.js';
2
+
3
+ export { cookieParse, cookieSerialize } from './cookie.js';
4
+
5
+ export { INTL_CONTEXT, MiIntlProvider } from './intl-provider.js';
6
+
7
+ export { MiIntlMessage } from './intl-message.js';
8
+
9
+ export { IntlConsumer } from './intl-consumer.js';
@@ -0,0 +1 @@
1
+ import{cached as s}from"intl-messageformat-tiny";import{MiElement as t,ContextProvider as e,ContextConsumer as n}from"mi-element";function o(s=""){const t=s.split(/\s*;\s*/),e={};for(const s of t){const[t,n]=s.split("=");if(t){const s=decodeURIComponent(n);e[t]=s}}return e}const a=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function i(s,t,e){const{maxAge:n,domain:o,path:i,expires:g,httpOnly:r=!1,secure:l=!1,sameSite:u="Strict"}=e||{};if(!s||!a.test(s))throw TypeError("invalid name");const c=[`${s}=${encodeURIComponent(t)}`];return!isNaN(n-0)&&isFinite(n)&&c.push(`Max-Age=${n}`),o&&a.test(o)&&c.push(`Domain=${o}`),i&&a.test(i)&&c.push(`Path=${i}`),isNaN(new Date(g).getTime())||c.push(`Expires=${new Date(g).toUTCString()}`),r&&c.push("HttpOnly"),l&&c.push("Secure"),u&&c.push(`SameSite=${u}`),c.join("; ")}const g=s(),r=globalThis?.navigator?.language&&globalThis?.document,l="translations",u={supportedLngs:[],version:"",ns:[l],defaultNs:l,localesPath:"/locales/{lng}/{ns}.json?v={version}",useLabel:!1,debug:!1,cookie:{name:"lc",path:"/"}};let c={debug:(...s)=>{},error:(...s)=>{}};const h=s=>[...new Set(s.filter(Boolean))],d=s=>(s||"").split("-",1)[0];class p{constructor(s){const{supportedLngs:t,lng:e,defaultNs:n,fallbackLng:o=t[0],resources:a={},debug:i,...g}={...u,...s};if(!t?.length)throw new Error("no supportedLngs");this.options=g,this.defaultNs=n,this.fallbackLng=o,this.lng=e||this.fallbackLng,this.supportedLngs=t,this.userLng=e,this.resources=a,i&&(c=g.log||console),this.t=this.t.bind(this),this.getLanguages=this.getLanguages.bind(this),this._setLanguage()}t(s,t={}){const{lng:e,fallbackLng:n,defaultNs:o}=this,{lng:a=e,ns:i=o,...r}=t,l=this.resources[a]?.[i]?.[s]||this.resources[n]?.[i]?.[s]||(this.options.useLabel?s:"");return g(l,r,a)}getUserLanguage(){if(!r)return this.fallbackLng;const[s,t]=this._getSettings();return c.debug("getUserLanguage: cookieLng:%s browserLng:%s",s,t),s||t}resetUserLanguage(){if(!r)return;const{name:s,...t}=this.options.cookie;c.debug("reset cookie"),document.cookie=i(s,"",{...t,expires:0}),this._setLanguage()}_getSettings(){const{name:s}=this.options.cookie,t=o(document.cookie)[s],e=globalThis?.navigator?.language;return console.log(document.cookie,t,e),[t,e]}_setCookie(){if(!r)return;const[s,t]=this._getSettings(),{userLng:e=""}=this;if(s===e)return;let n,o=e;[t,d(t)].includes(e)&&(n=0,o="");const{name:a,...g}=this.options.cookie;c.debug("_setCookie: %s:%s",0===n?"reset":"set",e),document.cookie=i(a,o,{...g,expires:n})}_setLanguage(s){const t=s||this.getUserLanguage(),{supportedLngs:e}=this,n=((s,t,e=[])=>h([s,...e].filter(Boolean).reduce((s,t)=>(s.push(t,d(t)),s),[])).reduce((s,e)=>(t.includes(e)&&s.push(e),s),[]))(t,e,globalThis?.navigator?.languages||[]);if(n.length)this.userLng=t,this.lng=n[0];else{this.userLng=this.lng=this.fallbackLng;const[s]=this.lng.split("-");n.push(...h([this.lng,s]))}return this._setCookie(),n}getLanguages(){return this.supportedLngs}async changeLanguage(s){const t=this._setLanguage(s);c.debug("changeLanguage: lng:%s variants:%s",s,t),await this.loadLanguages(t);let e=this.fallbackLng;for(const s of t)if(this.resources[s]?.[this.defaultNs]){e=s;break}c.debug("changeLanguage: loadedLng:%s",e);const n=d(e);if(d(s||t[0])!==n){const s=t.find(s=>d(s)===n);this._setLanguage(s||e)}this._setCookie()}async changeNamespace(s){this.defaultNs=s,await this.loadLanguages([this.lng,this.fallbackLng])}async loadLanguages(s=[],t=[]){const e=h(s),n=h([...t,this.defaultNs]);if(!e.length||!n.length)return c.error("can't load without languages or namespaces"),[];const o=[];for(const s of e)for(const t of n)this.resources[s]?.[t]||o.push(this._load({lng:s,ns:t}).catch(s=>c.error(s)));return await Promise.all(o)}async _load({lng:s,ns:t}){const{version:e}=this.options,n=g(this.options.localesPath,{lng:s,ns:t,version:e}),o=await fetch(n).catch(s=>c.error(s));this.resources[s]=this.resources[s]||{};const{ok:a=!1,status:i=-1}=o||{};if(a&&o){c.debug("_load: lng=%s ns=%s",s,t);const e=await o.json();this.resources[s][t]=e}else c.error("_load: error loading url=%s",n);return{ok:a,status:i,lng:s,ns:t}}}const L="mi-intl",f=()=>new Promise(s=>requestAnimationFrame(s));class m extends t{static get attributes(){return{version:"",lng:String,defaultNs:"translations",ns:"translations",supportedLngs:"",localesPath:"/locales/{lng}/{ns}.json?v={version}",useLabel:Boolean,debug:!1}}static get properties(){return{loading:!1}}static template="<slot></slot>";set options(s){this.i18n=new p(s),this.changeLanguage(s?.lng).catch(console.error)}_contextValue(){const{t:s,lng:t,getLanguages:e}=this.i18n??{t:()=>"",lng:"",getLanguages:()=>[]};return{t:s,lng:t,getLanguages:e,changeLanguage:this.changeLanguage.bind(this),loading:this.loading??!1}}async changeLanguage(s){this.loading=!0,await(this.i18n?.changeLanguage(s).finally(()=>{this.requestUpdate()})),await f(),await f(),this.loading=!1}render(){const s=this.supportedLngs.split(","),t=this.ns.split(",");this.i18n=new p({...this,supportedLngs:s,ns:t}),this.provider=new e(this,L,this._contextValue()),this.changeLanguage(this.lng).catch(console.error)}update(){this.provider?.set(this._contextValue())}}class b extends n{constructor(s,t=!0){super(s,L,{subscribe:t})}}class k extends t{#s;constructor(){super(),this.#s=new b(this)}t(s,t){return this.#s.value.t(s,t)}}export{p as I18n,L as INTL_CONTEXT,b as IntlConsumer,k as MiIntlMessage,m as MiIntlProvider,o as cookieParse,i as cookieSerialize};
@@ -0,0 +1,13 @@
1
+ import { ContextConsumer } from 'mi-element';
2
+
3
+ import { INTL_CONTEXT } from './intl-provider.js';
4
+
5
+ class IntlConsumer extends ContextConsumer {
6
+ constructor(miElement, subscribe = !0) {
7
+ super(miElement, INTL_CONTEXT, {
8
+ subscribe: subscribe
9
+ });
10
+ }
11
+ }
12
+
13
+ export { IntlConsumer };
@@ -0,0 +1,15 @@
1
+ import { MiElement } from 'mi-element';
2
+
3
+ import { IntlConsumer } from './intl-consumer.js';
4
+
5
+ class MiIntlMessage extends MiElement {
6
+ #context;
7
+ constructor() {
8
+ super(), this.#context = new IntlConsumer(this);
9
+ }
10
+ t(label, value) {
11
+ return this.#context.value.t(label, value);
12
+ }
13
+ }
14
+
15
+ export { MiIntlMessage };
@@ -0,0 +1,62 @@
1
+ import { MiElement, ContextProvider } from 'mi-element';
2
+
3
+ import { I18n } from './i18n.js';
4
+
5
+ const INTL_CONTEXT = 'mi-intl', requestAnimationFrameP = () => new Promise(resolve => requestAnimationFrame(resolve));
6
+
7
+ class MiIntlProvider extends MiElement {
8
+ static get attributes() {
9
+ return {
10
+ version: '',
11
+ lng: String,
12
+ defaultNs: 'translations',
13
+ ns: 'translations',
14
+ supportedLngs: '',
15
+ localesPath: '/locales/{lng}/{ns}.json?v={version}',
16
+ useLabel: Boolean,
17
+ debug: !1
18
+ };
19
+ }
20
+ static get properties() {
21
+ return {
22
+ loading: !1
23
+ };
24
+ }
25
+ static template='<slot></slot>';
26
+ set options(options) {
27
+ this.i18n = new I18n(options), this.changeLanguage(options?.lng).catch(console.error);
28
+ }
29
+ _contextValue() {
30
+ const {t: t, lng: lng, getLanguages: getLanguages} = this.i18n ?? {
31
+ t: () => '',
32
+ lng: '',
33
+ getLanguages: () => []
34
+ };
35
+ return {
36
+ t: t,
37
+ lng: lng,
38
+ getLanguages: getLanguages,
39
+ changeLanguage: this.changeLanguage.bind(this),
40
+ loading: this.loading ?? !1
41
+ };
42
+ }
43
+ async changeLanguage(lng) {
44
+ this.loading = !0, await (this.i18n?.changeLanguage(lng).finally(() => {
45
+ this.requestUpdate();
46
+ })), await requestAnimationFrameP(), await requestAnimationFrameP(), this.loading = !1;
47
+ }
48
+ render() {
49
+ const supportedLngs = this.supportedLngs.split(','), ns = this.ns.split(',');
50
+ this.i18n = new I18n({
51
+ ...this,
52
+ supportedLngs: supportedLngs,
53
+ ns: ns
54
+ }), this.provider = new ContextProvider(this, "mi-intl", this._contextValue()),
55
+ this.changeLanguage(this.lng).catch(console.error);
56
+ }
57
+ update() {
58
+ this.provider?.set(this._contextValue());
59
+ }
60
+ }
61
+
62
+ export { INTL_CONTEXT, MiIntlProvider };
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "mi-intl",
3
+ "version": "0.6.3",
4
+ "description": "Translations",
5
+ "keywords": [
6
+ "i18n",
7
+ "translations"
8
+ ],
9
+ "homepage": "https://github.com/commenthol/mi-element/tree/main/packages/mi-intl",
10
+ "bugs": {
11
+ "url": "https://github.com/commenthol/mi-element/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/commenthol/mi-element.git",
16
+ "directory": "packages/mi-intl"
17
+ },
18
+ "license": "MIT",
19
+ "author": "commenthol <commenthol@gmail.com>",
20
+ "maintainers": [
21
+ "commenthol <commenthol@gmail.com>"
22
+ ],
23
+ "sideEffects": false,
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "development": "./src/index.js",
28
+ "types": "./types/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ },
31
+ "./min": {
32
+ "types": "./types/index.d.ts",
33
+ "default": "./dist/index.min.js"
34
+ }
35
+ },
36
+ "main": "src/index.js",
37
+ "files": [
38
+ "src",
39
+ "dist",
40
+ "types"
41
+ ],
42
+ "dependencies": {
43
+ "intl-messageformat-tiny": "^1.1.0",
44
+ "mi-element": "0.6.3"
45
+ },
46
+ "devDependencies": {
47
+ "@eslint/js": "^9.34.0",
48
+ "@rollup/plugin-terser": "^0.4.4",
49
+ "@testing-library/dom": "^10.4.1",
50
+ "@types/node": "^24.3.0",
51
+ "@vitest/browser": "^3.2.4",
52
+ "@vitest/coverage-istanbul": "^3.2.4",
53
+ "eslint": "^9.34.0",
54
+ "globals": "^16.3.0",
55
+ "npm-run-all2": "^8.0.4",
56
+ "playwright": "^1.55.0",
57
+ "prettier": "^3.6.2",
58
+ "rimraf": "^6.0.1",
59
+ "rollup": "^4.48.1",
60
+ "typescript": "^5.9.2",
61
+ "vite": "^7.1.3",
62
+ "vitest": "^3.2.4",
63
+ "mi-html": "0.6.3"
64
+ },
65
+ "scripts": {
66
+ "all": "npm-run-all pretty lint test build types",
67
+ "build": "rimraf dist && rollup -c && gzip -k dist/index.min.js && ls -al dist && rimraf dist/index.min.js.gz",
68
+ "dev": "npm run test:browser",
69
+ "docs": "cd docs; for f in *.md; do markedpp -s -i $f; done",
70
+ "example": "vite --force --open /example/",
71
+ "lint": "eslint",
72
+ "pretty": "prettier -w **/*.js",
73
+ "setup": "pnpm exec playwright install",
74
+ "test": "vitest run --coverage",
75
+ "test:browser": "vitest --coverage",
76
+ "types": "rimraf types; tsc"
77
+ }
78
+ }
package/src/cookie.js ADDED
@@ -0,0 +1,90 @@
1
+ // SPDX-License-Identifier: Unlicense
2
+ // from https://github.com/commenthol/snippets
3
+
4
+ /**
5
+ * parses a cookie string
6
+ * @param {string} cookieStr
7
+ * @returns {{[cookieName: string]: string}|{}}
8
+ */
9
+ export function cookieParse(cookieStr = '') {
10
+ const parts = cookieStr.split(/\s*;\s*/)
11
+ const cookies = {}
12
+ for (const part of parts) {
13
+ const [key, val] = part.split('=')
14
+ if (key) {
15
+ const value = decodeURIComponent(val)
16
+ cookies[key] = value
17
+ }
18
+ }
19
+ return cookies
20
+ }
21
+
22
+ /**
23
+ * RegExp to match field-content in RFC 7230 sec 3.2
24
+ *
25
+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
26
+ * field-vchar = VCHAR / obs-text
27
+ * obs-text = %x80-FF
28
+ */
29
+ // eslint-disable-next-line no-control-regex
30
+ const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/
31
+
32
+ const isDate = (d) => !isNaN(new Date(d).getTime())
33
+
34
+ /**
35
+ * serializes a cookie
36
+ * @param {string} name
37
+ * @param {any} value
38
+ * @param {object} options
39
+ * @param {number} [options.maxAge]
40
+ * @param {string} [options.domain]
41
+ * @param {string} [options.path]
42
+ * @param {number|string|Date} [options.expires]
43
+ * @param {boolean} [options.httpOnly=true]
44
+ * @param {boolean} [options.secure=false]
45
+ * @param {string|'Strict'|'Lax'|'None'|''|false} [options.sameSite='Strict']
46
+ * @returns {string}
47
+ */
48
+ export function cookieSerialize(name, value, options) {
49
+ const {
50
+ maxAge,
51
+ domain,
52
+ path,
53
+ expires,
54
+ httpOnly = false,
55
+ secure = false,
56
+ sameSite = 'Strict'
57
+ } = options || {}
58
+
59
+ if (!name || !fieldContentRegExp.test(name)) {
60
+ throw TypeError('invalid name')
61
+ }
62
+
63
+ const parts = [`${name}=${encodeURIComponent(value)}`]
64
+
65
+ // @ts-expect-error
66
+ if (!isNaN(maxAge - 0) && isFinite(maxAge)) {
67
+ parts.push(`Max-Age=${maxAge}`)
68
+ }
69
+ if (domain && fieldContentRegExp.test(domain)) {
70
+ parts.push(`Domain=${domain}`)
71
+ }
72
+ if (path && fieldContentRegExp.test(path)) {
73
+ parts.push(`Path=${path}`)
74
+ }
75
+ if (isDate(expires)) {
76
+ // @ts-expect-error
77
+ parts.push(`Expires=${new Date(expires).toUTCString()}`)
78
+ }
79
+ if (httpOnly) {
80
+ parts.push('HttpOnly')
81
+ }
82
+ /* istanbul ignore next 3 */
83
+ if (secure) {
84
+ parts.push('Secure')
85
+ }
86
+ if (sameSite) {
87
+ parts.push(`SameSite=${sameSite}`)
88
+ }
89
+ return parts.join('; ')
90
+ }