@brightspace-ui/core 2.101.3 → 2.103.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 +1 -1
- package/components/button/button-icon.js +0 -1
- package/components/button/button-move.js +245 -0
- package/components/button/demo/button-move.html +46 -0
- package/components/list/list-item-drag-handle.js +93 -125
- package/custom-elements.json +60 -0
- package/helpers/localize-core-element.js +2 -2
- package/mixins/{localize-mixin.md → localize/README.md} +69 -9
- package/mixins/localize/demo/localize-mixin-greeting.js +39 -0
- package/mixins/localize/demo/localize-mixin-mission.js +29 -0
- package/mixins/localize/demo/localize-mixin.html +28 -0
- package/mixins/localize/localize-mixin.js +288 -0
- package/mixins/localize-dynamic-mixin.js +1 -42
- package/mixins/localize-mixin.js +1 -169
- package/mixins/localize-static-mixin.js +1 -28
- package/package.json +2 -1
- package/tools/dom-test-helpers.js +4 -3
- package/mixins/demo/localize-mixin.html +0 -19
- package/mixins/demo/localize-test.js +0 -57
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LocalizeMixin } from '../mixins/localize/localize-mixin.js';
|
|
2
2
|
|
|
3
|
-
export const LocalizeCoreElement = superclass => class extends
|
|
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 `
|
|
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 `
|
|
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-
|
|
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 {
|
|
71
|
+
import { LocalizeMixin } from '@brightspace-ui/core/mixins/localize/localize-mixin.js';
|
|
72
72
|
|
|
73
|
-
class MyComponent extends
|
|
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 `
|
|
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 {
|
|
109
|
+
import { LocalizeMixin } from '@brightspace-ui/core/mixins/localize/localize-mixin.js';
|
|
110
110
|
|
|
111
|
-
class MyComponent extends
|
|
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
|
-
|
|
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';
|