@brightspace-ui/core 2.101.2 → 2.102.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
@@ -71,7 +71,7 @@ npm install @brightspace-ui/core
71
71
  * [FormElementMixin](components/form/docs/form-element-mixin.md): allow components to participate in forms and validation
72
72
  * [InteractiveMixin](mixins/interactive-mixin.md): enables toggling interactive elements inside of nested grids
73
73
  * [LabelledMixin](mixins/labelled-mixin.md): label custom elements by referencing elements across DOM scopes
74
- * [LocalizeMixin](mixins/localize-mixin.md): localize text in your components
74
+ * [LocalizeMixin](mixins/localize/): localize text in your components
75
75
  * [ProviderMixin](mixins/provider-mixin.md): provide and consume data across elements in a DI-like fashion
76
76
  * [RtlMixin](mixins/rtl-mixin.md): enable components to define RTL styles
77
77
  * [SkeletonMixin](components/skeleton/): make components skeleton-aware
@@ -412,7 +412,7 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
412
412
 
413
413
  await this._updateSize();
414
414
  this._state = 'showing';
415
- await this._updateComplete;
415
+ await this.updateComplete;
416
416
 
417
417
  // edge case: no children were focused, try again after one redraw
418
418
  const activeElement = getComposedActiveElement();
@@ -1,6 +1,6 @@
1
- import { LocalizeDynamicMixin } from '../mixins/localize-dynamic-mixin.js';
1
+ import { LocalizeMixin } from '../mixins/localize/localize-mixin.js';
2
2
 
3
- export const LocalizeCoreElement = superclass => class extends LocalizeDynamicMixin(superclass) {
3
+ export const LocalizeCoreElement = superclass => class extends LocalizeMixin(superclass) {
4
4
 
5
5
  static get localizeConfig() {
6
6
  return {
@@ -1,6 +1,6 @@
1
1
  # Localization Mixins
2
2
 
3
- The `LocalizeDynamicMixin` and `LocalizeStaticMixin` allow you to localize text in your components and have it displayed to the user in their preferred language.
3
+ The `LocalizeMixin` and `LocalizeStaticMixin` allow you to localize text in your components and have it displayed to the user in their preferred language.
4
4
 
5
5
  ## Providing Resources
6
6
 
@@ -27,14 +27,14 @@ Always provide language resources for base languages (e.g. `en`, `fr`, `pt`, etc
27
27
 
28
28
  ### Static vs. Dynamic Resources
29
29
 
30
- For components with local resources, use the `LocalizeStaticMixin` and implement a `static` `resources` getter that returns the local resources synchronously. To get resources asynchronously, use the `LocalizeDynamicMixin` and implement a `static` `localizeConfig` getter that returns details about where to find your resources.
30
+ For components with local resources, use the `LocalizeStaticMixin` and implement a `static` `resources` getter that returns the local resources synchronously. To get resources asynchronously, use the `LocalizeMixin` and implement a `static` `localizeConfig` getter that returns details about where to find your resources.
31
31
 
32
32
  #### Example 1: Static Resources
33
33
 
34
34
  If your component has a small number of translations, it may make sense to store them locally within the component in a constant.
35
35
 
36
36
  ```javascript
37
- import { LocalizeStaticMixin } from '@brightspace-ui/core/mixins/localize-static-mixin.js';
37
+ import { LocalizeStaticMixin } from '@brightspace-ui/core/mixins/localize/localize-mixin.js';
38
38
 
39
39
  class MyComponent extends LocalizeStaticMixin(LitElement) {
40
40
 
@@ -68,9 +68,9 @@ export default {
68
68
 
69
69
  Then create your `localizeConfig` getter:
70
70
  ```javascript
71
- import { LocalizeDynamicMixin } from '@brightspace-ui/core/mixins/localize-dynamic-mixin.js';
71
+ import { LocalizeMixin } from '@brightspace-ui/core/mixins/localize/localize-mixin.js';
72
72
 
73
- class MyComponent extends LocalizeDynamicMixin(LitElement) {
73
+ class MyComponent extends LocalizeMixin(LitElement) {
74
74
 
75
75
  static get localizeConfig() {
76
76
  return {
@@ -102,13 +102,13 @@ static get localizeConfig() {
102
102
  }
103
103
  ```
104
104
 
105
- **Note:** If using `LocalizeCoreElement` or a mixin that utilizes `LocalizeCoreElement` as well as `LocalizeDynamicMixin` or a mixin that uses `LocalizeDynamicMixin`, `LocalizeDynamicMixin` **must** appear before `LocalizeCoreElement` in the chain. For example:
105
+ **Note:** If using `LocalizeCoreElement` or a mixin that utilizes `LocalizeCoreElement` as well as `LocalizeMixin` or a mixin that uses `LocalizeMixin`, `LocalizeMixin` **must** appear before `LocalizeCoreElement` in the chain. For example:
106
106
 
107
107
  ```javascript
108
108
  import { LocalizeCoreElement } from '@brightspace-ui/core/helpers/localize-core-element.js';
109
- import { LocalizeDynamicMixin } from '@brightspace-ui/core/mixins/localize-dynamic-mixin.js';
109
+ import { LocalizeMixin } from '@brightspace-ui/core/mixins/localize/localize-mixin.js';
110
110
 
111
- class MyComponent extends LocalizeDynamicMixin(LocalizeCoreElement(LitElement)) {
111
+ class MyComponent extends LocalizeMixin(LocalizeCoreElement(LitElement)) {
112
112
  ...
113
113
  }
114
114
  ```
@@ -121,6 +121,66 @@ If your localized string contains arguments, pass them as a key-value object as
121
121
 
122
122
  ```javascript
123
123
  render() {
124
- return html`<p>${this.localize('hello', {firstName: 'Mary'})}</p>`;
124
+ return html`<p>${this.localize('hello', { firstName: 'Mary' })}</p>`;
125
125
  }
126
126
  ```
127
+
128
+ ## `localizeHTML()`
129
+
130
+ Rich formatting can be included in localization resources and safely converted to HTML with the `localizeHTML()` method.
131
+
132
+ ### Basic Formatting
133
+
134
+ The following formatting elements are supported out-of-the-box:
135
+
136
+ * `<p>paragraphs</p>`
137
+ * `line<br></br>breaks` (note the end tag is required)
138
+ * `<b>bold</b>`
139
+ * `<strong>strong</strong>`
140
+ * `<i>italic</i>`
141
+ * `<em>emphasis</em>`
142
+
143
+ Remember that `<strong>` is for content of greater importance (browsers show this visually using bold), while `<b>` only bolds the text visually without increasing its importance.
144
+
145
+ Similarly `<em>` *emphasizes* a particular piece of text (browsers show this visually using italics), whereas `<i>` only italicizes the text visually without emphasis.
146
+
147
+ ### Links
148
+
149
+ To wrap text in [a link](../../components/link/), define a unique tag in the localization resource:
150
+
151
+ ```json
152
+ {
153
+ "myTerm": "Create a <linkNew>new assignment</linkNew>."
154
+ }
155
+ ```
156
+
157
+ Then import the `generateLink` helper and use it to provide the `href` and optional `target` as replacements:
158
+
159
+ ```javascript
160
+ import { generateLink } from '@brightspace-ui/core/mixins/localize/localize-mixin.js';
161
+
162
+ this.localizeHTML('myTerm', {
163
+ linkNew: generateLink({ href: 'new.html', target: '_blank' })
164
+ });
165
+ ```
166
+
167
+ ### Help Tooltips
168
+
169
+ To use a [help tooltip](../../components/tooltip/), define a unique tag in the localization resource in addition to the tooltip's text:
170
+
171
+ ```json
172
+ {
173
+ "octopus": "An octopus is a member of the <tooltip>cephalopod</tooltip> family.",
174
+ "cephalopodTooltip": "Cephalopods are members of the molluscan class Cephalopoda"
175
+ }
176
+ ```
177
+
178
+ Then import `generateTooltipHelp` and pass it the tooltip term value:
179
+
180
+ ```javascript
181
+ import { generateTooltipHelp } from '@brightspace-ui/core/mixins/localize/localize-mixin.js';
182
+
183
+ this.localizeHTML('octopus', {
184
+ tooltip: generateTooltipHelp({ contents: this.localize('cephalopodTooltip') })
185
+ });
186
+ ```
@@ -0,0 +1,39 @@
1
+ import { html, LitElement } from 'lit';
2
+ import { LocalizeMixin } from '../localize-mixin.js';
3
+
4
+ class Greeting extends LocalizeMixin(LitElement) {
5
+
6
+ static get properties() {
7
+ return {
8
+ name: { type: String }
9
+ };
10
+ }
11
+
12
+ static get localizeConfig() {
13
+ const langResources = {
14
+ 'ar': { 'hello': 'مرحبا {name}' },
15
+ 'de': { 'hello': 'Hallo {name}' },
16
+ 'en': { 'hello': 'Hello, {name}' },
17
+ 'en-gb': { 'hello': '\'Ello, {name}' },
18
+ 'es': { 'hello': 'Hola {name}' },
19
+ 'fr': { 'hello': 'Bonjour, {name}' },
20
+ 'ja': { 'hello': 'こんにちは {name}' },
21
+ 'ko': { 'hello': '안녕하세요 {name}' },
22
+ 'pt-br': { 'hello': 'Olá {name}' },
23
+ 'tr': { 'hello': 'Merhaba {name}' },
24
+ 'zh-cn': { 'hello': '你好 {name}' },
25
+ 'zh-tw': { 'hello': '你好 {name}' }
26
+ };
27
+ return {
28
+ importFunc: async lang => langResources[lang]
29
+ };
30
+ }
31
+
32
+ render() {
33
+ return html`
34
+ <p>${this.localize('hello', { name: this.name })}</p>
35
+ `;
36
+ }
37
+ }
38
+
39
+ customElements.define('d2l-greeting', Greeting);
@@ -0,0 +1,29 @@
1
+ import { generateLink, localizeMarkup, LocalizeMixin } from '../localize-mixin.js';
2
+ import { LitElement } from 'lit';
3
+
4
+ class Mission extends LocalizeMixin(LitElement) {
5
+
6
+ static get localizeConfig() {
7
+ const langResources = {
8
+ 'en': { mission: '<p><link1>Transforming</link1> the way</p><link2> <b>{name}</b></link2> learns. \'<div></div>\'' },
9
+ 'fr': { mission: '<p><link1>Transformer</link1> la façon dont</p><link2> <br></br> <b>{name}</b></link2> apprend' }
10
+ };
11
+ return {
12
+ importFunc: async lang => langResources[lang]
13
+ };
14
+ }
15
+
16
+ render() {
17
+ const surname = 'Smith';
18
+ const surnameMarkup = localizeMarkup`<i>${surname}</i>`;
19
+ const replacements = {
20
+ name: 'Bill',
21
+ link1: generateLink({ href: 'https://wikipedia.org/wiki/Culture_change', target: '_blank' }),
22
+ link2: chunks => localizeMarkup`<d2l-link href="https://wikipedia.org/wiki/Earth" target="_blank"><em>${chunks}</em> ${surnameMarkup}</d2l-link>`
23
+ };
24
+
25
+ return this.localizeHTML('mission', replacements);
26
+ }
27
+ }
28
+
29
+ customElements.define('d2l-mission', Mission);
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
+ <meta charset="UTF-8">
6
+ <link rel="stylesheet" href="../../../components/demo/styles.css" type="text/css">
7
+ <script type="module">
8
+ import '../../../components/demo/demo-page.js';
9
+ import './localize-mixin-greeting.js';
10
+ import './localize-mixin-mission.js';
11
+ </script>
12
+ </head>
13
+ <body unresolved>
14
+ <d2l-demo-page page-title="localize-mixin">
15
+
16
+ <h2>localize()</h2>
17
+ <d2l-demo-snippet>
18
+ <d2l-greeting name="Bill"></d2l-greeting>
19
+ </d2l-demo-snippet>
20
+
21
+ <h2>localizeHTML()</h2>
22
+ <d2l-demo-snippet>
23
+ <d2l-mission></d2l-mission>
24
+ </d2l-demo-snippet>
25
+
26
+ </d2l-demo-page>
27
+ </body>
28
+ </html>
@@ -0,0 +1,288 @@
1
+ import '@formatjs/intl-pluralrules/dist-es6/polyfill-locales.js';
2
+ import { dedupeMixin } from '@open-wc/dedupe-mixin';
3
+ import { getDocumentLocaleSettings } from '@brightspace-ui/intl/lib/common.js';
4
+ import { getLocalizeOverrideResources } from '../../helpers/getLocalizeResources.js';
5
+ import { html } from 'lit';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
7
+ import IntlMessageFormat from 'intl-messageformat';
8
+
9
+ const fallbackLang = 'en';
10
+ const supportedLangpacks = ['ar', 'cy', 'da', 'de', 'en', 'en-gb', 'es', 'es-es', 'fr', 'fr-fr', 'fr-on', 'hi', 'ja', 'ko', 'nl', 'pt', 'sv', 'tr', 'zh-cn', 'zh-tw'];
11
+
12
+ export const _LocalizeMixinBase = dedupeMixin(superclass => class LocalizeMixinClass extends superclass {
13
+
14
+ static get properties() {
15
+ return {
16
+ __resources: { type: Object, attribute: false }
17
+ };
18
+ }
19
+
20
+ static documentLocaleSettings = getDocumentLocaleSettings();
21
+
22
+ constructor() {
23
+ super();
24
+ this.__resourcesLoadedPromise = new Promise((resolve) => {
25
+ let first = true;
26
+ this.__languageChangeCallback = () => {
27
+ if (!this._hasResources()) return;
28
+ const localizeResources = this.constructor._getAllLocalizeResources();
29
+ const resourcesLoadedPromise = Promise.all(localizeResources);
30
+ resourcesLoadedPromise
31
+ .then((results) => {
32
+ if (results.length === 0) {
33
+ return;
34
+ }
35
+ const resources = {};
36
+ for (const res of results) {
37
+ const language = res.language;
38
+ for (const [key, value] of Object.entries(res.resources)) {
39
+ resources[key] = { language, value };
40
+ }
41
+ }
42
+ this.__resources = resources;
43
+ this._onResourcesChange();
44
+ if (first) {
45
+ resolve();
46
+ first = false;
47
+ }
48
+ });
49
+ };
50
+ });
51
+
52
+ this.__updatedProperties = new Map();
53
+ }
54
+
55
+ connectedCallback() {
56
+ super.connectedCallback();
57
+ this.constructor.documentLocaleSettings.addChangeListener(this.__languageChangeCallback);
58
+ this.__languageChangeCallback();
59
+ }
60
+
61
+ disconnectedCallback() {
62
+ super.disconnectedCallback();
63
+ this.constructor.documentLocaleSettings.removeChangeListener(this.__languageChangeCallback);
64
+ this.__updatedProperties.clear();
65
+ }
66
+
67
+ shouldUpdate(changedProperties) {
68
+
69
+ const hasResources = this._hasResources();
70
+ if (!hasResources) {
71
+ return super.shouldUpdate(changedProperties);
72
+ }
73
+
74
+ const ready = this.__resources !== undefined;
75
+ if (!ready) {
76
+ changedProperties.forEach((oldValue, propName) => {
77
+ this.__updatedProperties.set(propName, oldValue);
78
+ });
79
+ return false;
80
+ }
81
+
82
+ this.__updatedProperties.forEach((oldValue, propName) => {
83
+ if (!changedProperties.has(propName)) {
84
+ changedProperties.set(propName, oldValue);
85
+ }
86
+ });
87
+ this.__updatedProperties.clear();
88
+
89
+ return super.shouldUpdate(changedProperties);
90
+
91
+ }
92
+
93
+ async getUpdateComplete() {
94
+ await super.getUpdateComplete();
95
+ const hasResources = this._hasResources();
96
+ const resourcesLoaded = this.__resources !== undefined;
97
+ if (!hasResources || resourcesLoaded) {
98
+ return;
99
+ }
100
+ await this.__resourcesLoadedPromise;
101
+ }
102
+
103
+ localize(key) {
104
+
105
+ const { language, value } = this.__resources?.[key] ?? {};
106
+ if (!value) return '';
107
+
108
+ let params = {};
109
+ if (arguments.length > 1 && arguments[1].constructor === Object) {
110
+ // support for key-value replacements as a single arg
111
+ params = arguments[1];
112
+ } else {
113
+ // legacy support for localize-behavior replacements as many args
114
+ for (let i = 1; i < arguments.length; i += 2) {
115
+ params[arguments[i]] = arguments[i + 1];
116
+ }
117
+ }
118
+
119
+ const translatedMessage = new IntlMessageFormat(value, language);
120
+ let formattedMessage = value;
121
+ try {
122
+ if (Object.values(params).some(v => typeof v === 'function')) throw 'localize() does not support rich text.';
123
+ formattedMessage = translatedMessage.format(params);
124
+ } catch (e) {
125
+ console.error(e);
126
+ }
127
+
128
+ return formattedMessage;
129
+ }
130
+
131
+ localizeHTML(key, params = {}) {
132
+
133
+ const { language, value } = this.__resources?.[key] ?? {};
134
+ if (!value) return '';
135
+
136
+ const translatedMessage = new IntlMessageFormat(value, language);
137
+ let formattedMessage = value;
138
+ try {
139
+ formattedMessage = translatedMessage.format({
140
+ b: chunks => localizeMarkup`<b>${chunks}</b>`,
141
+ br: () => localizeMarkup`<br>`,
142
+ em: chunks => localizeMarkup`<em>${chunks}</em>`,
143
+ i: chunks => localizeMarkup`<i>${chunks}</i>`,
144
+ p: chunks => localizeMarkup`<p>${chunks}</p>`,
145
+ strong: chunks => localizeMarkup`<strong>${chunks}</strong>`,
146
+ ...params
147
+ });
148
+ validateMarkup(formattedMessage);
149
+ } catch (e) {
150
+ console.error(e);
151
+ }
152
+
153
+ return formattedMessage;
154
+ }
155
+
156
+ static _generatePossibleLanguages(config) {
157
+
158
+ if (config?.useBrowserLangs) return navigator.languages.map(e => e.toLowerCase()).concat('en');
159
+
160
+ const { language, fallbackLanguage } = this.documentLocaleSettings;
161
+ const langs = [ language, fallbackLanguage ]
162
+ .filter(e => e)
163
+ .map(e => [ e.toLowerCase(), e.split('-')[0] ])
164
+ .flat();
165
+
166
+ return Array.from(new Set([ ...langs, 'en-us', 'en' ]));
167
+ }
168
+
169
+ static _getAllLocalizeResources(config = this.localizeConfig) {
170
+ let resourcesLoadedPromises = [];
171
+ const superCtor = Object.getPrototypeOf(this);
172
+ // get imported terms for each config, head up the chain to get them all
173
+ if ('_getAllLocalizeResources' in superCtor) {
174
+ const superConfig = Object.prototype.hasOwnProperty.call(superCtor, 'localizeConfig') && superCtor.localizeConfig.importFunc ? superCtor.localizeConfig : config;
175
+ resourcesLoadedPromises = superCtor._getAllLocalizeResources(superConfig);
176
+ }
177
+ if (Object.prototype.hasOwnProperty.call(this, 'getLocalizeResources') || Object.prototype.hasOwnProperty.call(this, 'resources')) {
178
+ const possibleLanguages = this._generatePossibleLanguages(config);
179
+ const res = this.getLocalizeResources(possibleLanguages, config);
180
+ resourcesLoadedPromises.push(res);
181
+ }
182
+ return resourcesLoadedPromises;
183
+ }
184
+
185
+ _hasResources() {
186
+ return this.constructor['getLocalizeResources'] !== undefined;
187
+ }
188
+
189
+ _onResourcesChange() {
190
+ /** @ignore */
191
+ this.dispatchEvent(new CustomEvent('d2l-localize-resources-change'));
192
+ }
193
+
194
+ });
195
+
196
+ export const LocalizeMixin = superclass => class extends _LocalizeMixinBase(superclass) {
197
+
198
+ static async getLocalizeResources(langs, { importFunc, osloCollection, useBrowserLangs }) {
199
+
200
+ // in dev, don't request unsupported langpacks
201
+ if (!importFunc.toString().includes('switch') && !useBrowserLangs) {
202
+ langs = langs.filter(lang => supportedLangpacks.includes(lang));
203
+ }
204
+
205
+ for (const lang of [...langs, fallbackLang]) {
206
+
207
+ const resources = await Promise.resolve(importFunc(lang)).catch(() => {});
208
+
209
+ if (resources) {
210
+
211
+ if (osloCollection) {
212
+ return await getLocalizeOverrideResources(
213
+ lang,
214
+ resources,
215
+ () => osloCollection
216
+ );
217
+ }
218
+
219
+ return {
220
+ language: lang,
221
+ resources
222
+ };
223
+ }
224
+ }
225
+ }
226
+
227
+ static get localizeConfig() {
228
+ return {};
229
+ }
230
+
231
+ };
232
+
233
+ export const LocalizeStaticMixin = superclass => class extends _LocalizeMixinBase(superclass) {
234
+
235
+ static async getLocalizeResources(langs) {
236
+ let resolvedLang = fallbackLang;
237
+ const resolvedResources = Object.assign({}, this.resources[fallbackLang]);
238
+
239
+ langs.reverse().forEach((lang) => {
240
+ if (this.resources[lang]) {
241
+ resolvedLang = lang;
242
+ Object.assign(resolvedResources, this.resources[lang]);
243
+ }
244
+ });
245
+
246
+ return {
247
+ language: resolvedLang,
248
+ resources: resolvedResources
249
+ };
250
+ }
251
+
252
+ static get resources() {
253
+ return { 'en': {} };
254
+ }
255
+
256
+ };
257
+
258
+ export const allowedTags = Object.freeze(['d2l-link', 'd2l-tooltip-help', 'p', 'br', 'b', 'strong', 'i', 'em']);
259
+
260
+ const markupError = `localizeHTML() rich-text replacements must use localizeMarkup templates with only the following allowed elements: ${allowedTags}. For more information, see: https://github.com/BrightspaceUI/core/blob/main/mixins/localize/`;
261
+ const validTerminators = '([>\\s/]|$)';
262
+ const allowedAfterTriangleBracket = `/?(${allowedTags.join('|')})?${validTerminators}`;
263
+ const disallowedTagsRegex = new RegExp(`<(?!${allowedAfterTriangleBracket})`);
264
+
265
+ function validateMarkup(content, applyRegex) {
266
+ if (content) {
267
+ if (content.map) return content.forEach(item => validateMarkup(item));
268
+ if (content._localizeMarkup) return;
269
+ if (Object.hasOwn(content, '_$litType$')) throw markupError;
270
+ if (applyRegex && content.constructor === String && disallowedTagsRegex.test(content)) throw markupError;
271
+ }
272
+ }
273
+
274
+ export function localizeMarkup(strings, ...expressions) {
275
+ strings.forEach(str => validateMarkup(str, true));
276
+ expressions.forEach(exp => validateMarkup(exp, true));
277
+ return { ...html(strings, ...expressions), _localizeMarkup: true };
278
+ }
279
+
280
+ export function generateLink({ href, target }) {
281
+ import('../../components/link/link.js');
282
+ return chunks => localizeMarkup`<d2l-link href="${ifDefined(href)}" target="${ifDefined(target)}">${chunks}</d2l-link>`;
283
+ }
284
+
285
+ export function generateTooltipHelp({ contents }) {
286
+ import('../../components/tooltip/tooltip-help.js');
287
+ return chunks => localizeMarkup`<d2l-tooltip-help inherit-font-style text="${ifDefined(chunks)}">${contents}</d2l-tooltip-help>`;
288
+ }
@@ -1,42 +1 @@
1
- import { getLocalizeOverrideResources } from '../helpers/getLocalizeResources.js';
2
- import { LocalizeMixin } from './localize-mixin.js';
3
-
4
- const fallbackLang = 'en';
5
- const supportedLangpacks = ['ar', 'cy', 'da', 'de', 'en', 'en-gb', 'es', 'es-es', 'fr', 'fr-fr', 'fr-on', 'hi', 'ja', 'ko', 'nl', 'pt', 'sv', 'tr', 'zh-cn', 'zh-tw'];
6
-
7
- export const LocalizeDynamicMixin = superclass => class extends LocalizeMixin(superclass) {
8
-
9
- static async getLocalizeResources(langs, { importFunc, osloCollection, useBrowserLangs }) {
10
-
11
- // in dev, don't request unsupported langpacks
12
- if (!importFunc.toString().includes('switch') && !useBrowserLangs) {
13
- langs = langs.filter(lang => supportedLangpacks.includes(lang));
14
- }
15
-
16
- for (const lang of [...langs, fallbackLang]) {
17
-
18
- const resources = await importFunc(lang).catch(() => {});
19
-
20
- if (resources) {
21
-
22
- if (osloCollection) {
23
- return await getLocalizeOverrideResources(
24
- lang,
25
- resources,
26
- () => osloCollection
27
- );
28
- }
29
-
30
- return {
31
- language: lang,
32
- resources
33
- };
34
- }
35
- }
36
- }
37
-
38
- static get localizeConfig() {
39
- return {};
40
- }
41
-
42
- };
1
+ export { LocalizeMixin as LocalizeDynamicMixin } from './localize/localize-mixin.js';
@@ -1,169 +1 @@
1
- import '@formatjs/intl-pluralrules/dist-es6/polyfill-locales.js';
2
- import { dedupeMixin } from '@open-wc/dedupe-mixin';
3
- import { getDocumentLocaleSettings } from '@brightspace-ui/intl/lib/common.js';
4
- import IntlMessageFormat from 'intl-messageformat';
5
-
6
- export const LocalizeMixin = dedupeMixin(superclass => class extends superclass {
7
-
8
- static get properties() {
9
- return {
10
- __resources: { type: Object, attribute: false }
11
- };
12
- }
13
-
14
- static documentLocaleSettings = getDocumentLocaleSettings();
15
-
16
- constructor() {
17
- super();
18
- this.__resourcesLoadedPromise = new Promise((resolve) => {
19
- let first = true;
20
- this.__languageChangeCallback = () => {
21
- if (!this._hasResources()) return;
22
- const localizeResources = this.constructor._getAllLocalizeResources();
23
- const resourcesLoadedPromise = Promise.all(localizeResources);
24
- resourcesLoadedPromise
25
- .then((results) => {
26
- if (results.length === 0) {
27
- return;
28
- }
29
- const resources = {};
30
- for (const res of results) {
31
- const language = res.language;
32
- for (const [key, value] of Object.entries(res.resources)) {
33
- resources[key] = { language, value };
34
- }
35
- }
36
- this.__resources = resources;
37
- this._onResourcesChange();
38
- if (first) {
39
- resolve();
40
- first = false;
41
- }
42
- });
43
- };
44
- });
45
-
46
- this.__updatedProperties = new Map();
47
- }
48
-
49
- connectedCallback() {
50
- super.connectedCallback();
51
- this.constructor.documentLocaleSettings.addChangeListener(this.__languageChangeCallback);
52
- this.__languageChangeCallback();
53
- }
54
-
55
- disconnectedCallback() {
56
- super.disconnectedCallback();
57
- this.constructor.documentLocaleSettings.removeChangeListener(this.__languageChangeCallback);
58
- this.__updatedProperties.clear();
59
- }
60
-
61
- shouldUpdate(changedProperties) {
62
-
63
- const hasResources = this._hasResources();
64
- if (!hasResources) {
65
- return super.shouldUpdate(changedProperties);
66
- }
67
-
68
- const ready = this.__resources !== undefined;
69
- if (!ready) {
70
- changedProperties.forEach((oldValue, propName) => {
71
- this.__updatedProperties.set(propName, oldValue);
72
- });
73
- return false;
74
- }
75
-
76
- this.__updatedProperties.forEach((oldValue, propName) => {
77
- if (!changedProperties.has(propName)) {
78
- changedProperties.set(propName, oldValue);
79
- }
80
- });
81
- this.__updatedProperties.clear();
82
-
83
- return super.shouldUpdate(changedProperties);
84
-
85
- }
86
-
87
- async getUpdateComplete() {
88
- await super.getUpdateComplete();
89
- const hasResources = this._hasResources();
90
- const resourcesLoaded = this.__resources !== undefined;
91
- if (!hasResources || resourcesLoaded) {
92
- return;
93
- }
94
- await this.__resourcesLoadedPromise;
95
- }
96
-
97
- localize(key) {
98
-
99
- if (!key || !this.__resources) {
100
- return '';
101
- }
102
-
103
- const resource = this.__resources[key];
104
- if (!resource) {
105
- return '';
106
- }
107
- const translatedValue = resource.value;
108
-
109
- let params = {};
110
- if (arguments.length > 1 && typeof arguments[1] === 'object') {
111
- // support for key-value replacements as a single arg
112
- params = arguments[1];
113
- } else {
114
- // legacy support for localize-behavior replacements as many args
115
- for (let i = 1; i < arguments.length; i += 2) {
116
- params[arguments[i]] = arguments[i + 1];
117
- }
118
- }
119
-
120
- const translatedMessage = new IntlMessageFormat(translatedValue, resource.language);
121
- let formattedMessage = translatedValue;
122
- try {
123
- formattedMessage = translatedMessage.format(params);
124
- } catch (e) {
125
- console.error(e);
126
- }
127
- return formattedMessage;
128
-
129
- }
130
-
131
- static _generatePossibleLanguages(config) {
132
-
133
- if (config?.useBrowserLangs) return navigator.languages.map(e => e.toLowerCase()).concat('en');
134
-
135
- const { language, fallbackLanguage } = this.documentLocaleSettings;
136
- const langs = [ language, fallbackLanguage ]
137
- .filter(e => e)
138
- .map(e => [ e.toLowerCase(), e.split('-')[0] ])
139
- .flat();
140
-
141
- return Array.from(new Set([ ...langs, 'en-us', 'en' ]));
142
- }
143
-
144
- static _getAllLocalizeResources(config = this.localizeConfig) {
145
- let resourcesLoadedPromises = [];
146
- const superCtor = Object.getPrototypeOf(this);
147
- // get imported terms for each config, head up the chain to get them all
148
- if ('_getAllLocalizeResources' in superCtor) {
149
- const superConfig = Object.prototype.hasOwnProperty.call(superCtor, 'localizeConfig') && superCtor.localizeConfig.importFunc ? superCtor.localizeConfig : config;
150
- resourcesLoadedPromises = superCtor._getAllLocalizeResources(superConfig);
151
- }
152
- if (Object.prototype.hasOwnProperty.call(this, 'getLocalizeResources') || Object.prototype.hasOwnProperty.call(this, 'resources')) {
153
- const possibleLanguages = this._generatePossibleLanguages(config);
154
- const res = this.getLocalizeResources(possibleLanguages, config);
155
- resourcesLoadedPromises.push(res);
156
- }
157
- return resourcesLoadedPromises;
158
- }
159
-
160
- _hasResources() {
161
- return this.constructor['getLocalizeResources'] !== undefined;
162
- }
163
-
164
- _onResourcesChange() {
165
- /** @ignore */
166
- this.dispatchEvent(new CustomEvent('d2l-localize-resources-change'));
167
- }
168
-
169
- });
1
+ export { _LocalizeMixinBase as LocalizeMixin } from './localize/localize-mixin.js';
@@ -1,28 +1 @@
1
- import { LocalizeMixin } from './localize-mixin.js';
2
-
3
- const fallbackLang = 'en';
4
-
5
- export const LocalizeStaticMixin = superclass => class extends LocalizeMixin(superclass) {
6
-
7
- static async getLocalizeResources(langs) {
8
- let resolvedLang = fallbackLang;
9
- const resolvedResources = Object.assign({}, this.resources[fallbackLang]);
10
-
11
- langs.reverse().forEach((lang) => {
12
- if (this.resources[lang]) {
13
- resolvedLang = lang;
14
- Object.assign(resolvedResources, this.resources[lang]);
15
- }
16
- });
17
-
18
- return {
19
- language: resolvedLang,
20
- resources: resolvedResources
21
- };
22
- }
23
-
24
- static get resources() {
25
- return { 'en': {} };
26
- }
27
-
28
- };
1
+ export { LocalizeStaticMixin } from './localize/localize-mixin.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "2.101.2",
3
+ "version": "2.102.0",
4
4
  "description": "A collection of accessible, free, open-source web components for building Brightspace applications",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/BrightspaceUI/core.git",
@@ -46,6 +46,7 @@
46
46
  "devDependencies": {
47
47
  "@babel/eslint-parser": "^7",
48
48
  "@brightspace-ui/stylelint-config": "^0.7",
49
+ "@open-wc/semantic-dom-diff": "^0.19.9",
49
50
  "@open-wc/testing": "^3",
50
51
  "@rollup/plugin-dynamic-import-vars": "^2",
51
52
  "@rollup/plugin-node-resolve": "^15",
@@ -1,19 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
- <meta charset="UTF-8">
6
- <link rel="stylesheet" href="../../components/demo/styles.css" type="text/css">
7
- <script type="module">
8
- import '../../components/demo/demo-page.js';
9
- import './localize-test.js';
10
- </script>
11
- </head>
12
- <body unresolved>
13
- <d2l-demo-page page-title="localize-mixin">
14
- <d2l-demo-snippet>
15
- <d2l-test-localize name="Bill"></d2l-test-localize>
16
- </d2l-demo-snippet>
17
- </d2l-demo-page>
18
- </body>
19
- </html>
@@ -1,57 +0,0 @@
1
- import { html, LitElement } from 'lit';
2
- import { LocalizeDynamicMixin } from '../../mixins/localize-dynamic-mixin.js';
3
-
4
- class LocalizeTest extends LocalizeDynamicMixin(LitElement) {
5
-
6
- static get properties() {
7
- return {
8
- name: {
9
- type: String
10
- }
11
- };
12
- }
13
-
14
- static get localizeConfig() {
15
- const langResources = {
16
- 'ar': { 'hello': 'مرحبا {name}' },
17
- 'de': { 'hello': 'Hallo {name}' },
18
- 'en': {
19
- 'hello': 'Hello {name}',
20
- 'plural': 'You have {itemCount, plural, =0 {no items} one {1 item} other {{itemCount} items}}.'
21
- },
22
- 'en-ca': { 'hello': 'Hello, {name} eh' },
23
- 'es': { 'hello': 'Hola {name}' },
24
- 'fr': { 'hello': 'Bonjour {name}' },
25
- 'ja': { 'hello': 'こんにちは {name}' },
26
- 'ko': { 'hello': '안녕하세요 {name}' },
27
- 'pt-br': { 'hello': 'Olá {name}' },
28
- 'tr': { 'hello': 'Merhaba {name}' },
29
- 'zh-cn': { 'hello': '你好 {name}' },
30
- 'zh-tw': { 'hello': '你好 {name}' }
31
- };
32
- return {
33
- importFunc: async lang => {
34
- return new Promise((resolve) => {
35
- setTimeout(() => {
36
- resolve(langResources[lang]);
37
- }, 50);
38
- });
39
- }
40
- };
41
- }
42
-
43
- render() {
44
- requestAnimationFrame(
45
- () => this.dispatchEvent(new CustomEvent('d2l-test-localize-render', {
46
- bubbles: false,
47
- composed: false
48
- }))
49
- );
50
- return html`
51
- <p>${this.localize('hello', { name: this.name })}</p>
52
- `;
53
- }
54
-
55
- }
56
-
57
- customElements.define('d2l-test-localize', LocalizeTest);