@oscarpalmer/oui 0.34.0 → 0.35.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/dist/js/combobox/constants.combobox.d.mts +1 -1
- package/dist/js/combobox/data.combobox.d.mts +1 -1
- package/dist/js/combobox/data.combobox.mjs +1 -1
- package/dist/js/combobox/dom.combobox.d.mts +1 -1
- package/dist/js/combobox/dom.combobox.mjs +1 -1
- package/dist/js/combobox/event.combobox.d.mts +1 -1
- package/dist/js/combobox/event.combobox.mjs +1 -1
- package/dist/js/combobox/form.combobox.d.mts +1 -1
- package/dist/js/combobox/form.combobox.mjs +1 -1
- package/dist/js/combobox/index.d.mts +1 -1
- package/dist/js/combobox/index.mjs +3 -2
- package/dist/js/combobox/misc.combobox.d.mts +2 -14
- package/dist/js/combobox/misc.combobox.mjs +2 -2
- package/dist/js/combobox/models.combobox.d.mts +1 -1
- package/dist/js/combobox/state.combobox.d.mts +4 -2
- package/dist/js/combobox/state.combobox.mjs +2 -2
- package/dist/js/{constants.combobox-DqNUGBLS.d.mts → constants.combobox-h_kyDegw.d.mts} +12 -7
- package/dist/js/{data.combobox-8RwBv5ey.mjs → data.combobox-B8HMkDOh.mjs} +245 -292
- package/dist/js/index.d.mts +1 -1
- package/dist/js/index.mjs +1 -1
- package/dist/js/l10n.d.mts +20 -0
- package/dist/js/l10n.mjs +80 -0
- package/package.json +1 -1
- package/src/js/combobox/dom.combobox.ts +57 -26
- package/src/js/combobox/form.combobox.ts +26 -25
- package/src/js/combobox/index.ts +10 -4
- package/src/js/combobox/misc.combobox.ts +50 -0
- package/src/js/combobox/state.combobox.ts +13 -1
- package/src/js/l10n.ts +192 -0
- package/dist/js/combobox/i18n.combobox.d.mts +0 -2
- package/dist/js/combobox/i18n.combobox.mjs +0 -2
- package/src/js/combobox/i18n.combobox.ts +0 -179
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GenericCallback } from "@oscarpalmer/atoms/models";
|
|
2
|
+
//#region src/js/l10n.d.ts
|
|
3
|
+
type LocalizationParameters<Localization> = Localization extends GenericCallback ? Parameters<Localization> : never;
|
|
4
|
+
type OuiLocalizationValues = Record<string, string | GenericCallback>;
|
|
5
|
+
declare class OuiLocalizations<Localizations extends OuiLocalizationValues> {
|
|
6
|
+
readonly defaults: Localizations;
|
|
7
|
+
readonly keys: Set<keyof Localizations>;
|
|
8
|
+
readonly values: Record<string, Partial<Localizations>>;
|
|
9
|
+
constructor(defaults: Localizations);
|
|
10
|
+
get<Key extends keyof Localizations>(element: Element, key: Key, localizations: Record<string, Partial<Localizations>>): string | GenericCallback;
|
|
11
|
+
set(language: string, localizations: Partial<Localizations>): void;
|
|
12
|
+
set(localizations: Record<string, Partial<Localizations>>): void;
|
|
13
|
+
}
|
|
14
|
+
declare function getLanguage(element: Element): string;
|
|
15
|
+
declare function getLocalization<Localizations extends OuiLocalizationValues, Key extends keyof Localizations>(localizations: OuiLocalizations<Localizations>, element: Element, key: Key, value: Record<string, Partial<Localizations>>): Localizations[Key] | GenericCallback;
|
|
16
|
+
declare function getLocalizations<Localizations extends OuiLocalizationValues>(defaults: Localizations): OuiLocalizations<Localizations>;
|
|
17
|
+
declare function localize<Localizations extends OuiLocalizationValues, Key extends keyof Localizations, Localization extends Localizations[Key]>(localization: Localization, data: Record<string, unknown>, parameters: LocalizationParameters<Localization>): string;
|
|
18
|
+
declare function updateLocalizations<Localizations extends OuiLocalizationValues>(instance: OuiLocalizations<Localizations>, values: Record<string, Partial<Localizations>>, destination?: Record<string, Partial<Localizations>>, onAfter?: () => void): void;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { OuiLocalizations, getLanguage, getLocalization, getLocalizations, localize, updateLocalizations };
|
package/dist/js/l10n.mjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getString } from "@oscarpalmer/atoms/string";
|
|
2
|
+
//#region src/js/l10n.ts
|
|
3
|
+
var OuiLocalizations = class {
|
|
4
|
+
defaults;
|
|
5
|
+
keys;
|
|
6
|
+
values = {};
|
|
7
|
+
constructor(defaults) {
|
|
8
|
+
this.defaults = defaults;
|
|
9
|
+
this.keys = new Set(Object.keys(defaults));
|
|
10
|
+
}
|
|
11
|
+
get(element, key, localizations) {
|
|
12
|
+
return getLocalization(this, element, key, localizations);
|
|
13
|
+
}
|
|
14
|
+
set(first, second) {
|
|
15
|
+
updateLocalizations(this, typeof first === "string" ? { [first]: second } : first, this.values);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function getLanguage(element) {
|
|
19
|
+
return (element.closest("[lang]")?.getAttribute("lang") ?? document.documentElement.lang).toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
function getLocalization(localizations, element, key, value) {
|
|
22
|
+
const language = getLanguage(element);
|
|
23
|
+
const values = [value, localizations.values];
|
|
24
|
+
const { length } = values;
|
|
25
|
+
let localization;
|
|
26
|
+
let shortLanguage;
|
|
27
|
+
for (let index = 0; index < length; index += 1) {
|
|
28
|
+
const value = values[index];
|
|
29
|
+
localization = value?.[language]?.[key];
|
|
30
|
+
if (localization != null) break;
|
|
31
|
+
shortLanguage ??= language.split("-")[0];
|
|
32
|
+
if (shortLanguage === language) continue;
|
|
33
|
+
localization = value?.[shortLanguage]?.[key];
|
|
34
|
+
if (localization != null) break;
|
|
35
|
+
}
|
|
36
|
+
return localization ?? localizations.defaults[key];
|
|
37
|
+
}
|
|
38
|
+
function getLocalizations(defaults) {
|
|
39
|
+
return new OuiLocalizations(defaults);
|
|
40
|
+
}
|
|
41
|
+
function localize(localization, data, parameters) {
|
|
42
|
+
if (typeof localization === "function") return localization(...parameters);
|
|
43
|
+
const keys = Object.keys(data);
|
|
44
|
+
const { length } = keys;
|
|
45
|
+
let localized = localization;
|
|
46
|
+
for (let index = 0; index < length; index += 1) {
|
|
47
|
+
const key = keys[index];
|
|
48
|
+
const value = data[key];
|
|
49
|
+
localized = localized.replaceAll(`{${key}}`, getString(value));
|
|
50
|
+
}
|
|
51
|
+
return localized;
|
|
52
|
+
}
|
|
53
|
+
function updateLocalizations(instance, values, destination, onAfter) {
|
|
54
|
+
if (typeof values !== "object" || values == null) return;
|
|
55
|
+
const localizations = destination ?? instance.values;
|
|
56
|
+
const languages = Object.keys(values);
|
|
57
|
+
const languagesLength = languages.length;
|
|
58
|
+
let updated = false;
|
|
59
|
+
for (let languagesIndex = 0; languagesIndex < languagesLength; languagesIndex += 1) {
|
|
60
|
+
const language = languages[languagesIndex];
|
|
61
|
+
const languageValue = values[language];
|
|
62
|
+
if (typeof languageValue !== "object" || languageValue == null) continue;
|
|
63
|
+
const normalizedLanguage = language.toLowerCase();
|
|
64
|
+
localizations[normalizedLanguage] ??= {};
|
|
65
|
+
const keys = Object.keys(languageValue);
|
|
66
|
+
const keysLength = keys.length;
|
|
67
|
+
for (let keysIndex = 0; keysIndex < keysLength; keysIndex += 1) {
|
|
68
|
+
const key = keys[keysIndex];
|
|
69
|
+
const keyValue = languageValue[key];
|
|
70
|
+
const normalizedKey = key.toLowerCase();
|
|
71
|
+
if (instance.keys.has(normalizedKey) && (typeof keyValue === "string" || typeof keyValue === "function")) {
|
|
72
|
+
updated = true;
|
|
73
|
+
localizations[normalizedLanguage][normalizedKey] = keyValue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (updated && typeof onAfter === "function") onAfter();
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { OuiLocalizations, getLanguage, getLocalization, getLocalizations, localize, updateLocalizations };
|
package/package.json
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
comboboxScrollOptions,
|
|
19
19
|
} from './constants.combobox';
|
|
20
20
|
import {updateComboboxValidity} from './form.combobox';
|
|
21
|
-
import {getComboboxLocalization} from './i18n.combobox';
|
|
22
21
|
import type {
|
|
23
22
|
OuiComboboxAutoComplete,
|
|
24
23
|
OuiComboboxAutoCompleteType,
|
|
@@ -29,6 +28,8 @@ import type {
|
|
|
29
28
|
OuiComboboxState,
|
|
30
29
|
OuiComboboxType,
|
|
31
30
|
} from './models.combobox';
|
|
31
|
+
import {getLocalization, localize} from '../l10n';
|
|
32
|
+
import {comboboxLocalizations} from './state.combobox';
|
|
32
33
|
|
|
33
34
|
// #region Types
|
|
34
35
|
|
|
@@ -471,17 +472,26 @@ export function createComboboxSelectionItem(
|
|
|
471
472
|
option: HTMLElement,
|
|
472
473
|
state?: OuiComboboxState,
|
|
473
474
|
): OuiComboboxSelectionItem {
|
|
475
|
+
const optionLabel = option.textContent;
|
|
476
|
+
const optionValue = getData(option, 'value', false) ?? '';
|
|
477
|
+
|
|
474
478
|
const element = createElement('li');
|
|
475
479
|
|
|
476
480
|
const localization =
|
|
477
|
-
state == null
|
|
481
|
+
state == null
|
|
482
|
+
? option.textContent
|
|
483
|
+
: getLocalization(comboboxLocalizations, state.host, 'remove', state.localizations);
|
|
478
484
|
|
|
479
485
|
const button = createElement('button', {
|
|
480
486
|
aria: {
|
|
481
|
-
label:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
:
|
|
487
|
+
label: localize(
|
|
488
|
+
localization,
|
|
489
|
+
{
|
|
490
|
+
label: optionLabel,
|
|
491
|
+
value: optionValue,
|
|
492
|
+
},
|
|
493
|
+
[optionLabel, optionValue],
|
|
494
|
+
),
|
|
485
495
|
},
|
|
486
496
|
attribute: {
|
|
487
497
|
[COMBOBOX_ATTRIBUTE_SELECTION_ITEM]: '',
|
|
@@ -598,15 +608,16 @@ export function updateComboboxAnchor(state: OuiComboboxState): void {
|
|
|
598
608
|
return;
|
|
599
609
|
}
|
|
600
610
|
|
|
601
|
-
const localization =
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
typeof localization === 'function'
|
|
607
|
-
? localization(state.options.selected.length)
|
|
608
|
-
: localization.replace('{amount}', state.options.selected.length.toString()),
|
|
611
|
+
const localization = getLocalization(
|
|
612
|
+
comboboxLocalizations,
|
|
613
|
+
state.host,
|
|
614
|
+
'selected',
|
|
615
|
+
state.localizations,
|
|
609
616
|
);
|
|
617
|
+
|
|
618
|
+
const {length} = state.options.selected;
|
|
619
|
+
|
|
620
|
+
setAria(state.anchor.element, 'label', localize(localization, {amount: length}, [length]));
|
|
610
621
|
}
|
|
611
622
|
|
|
612
623
|
export function updateComboboxAutocomplete(
|
|
@@ -648,16 +659,23 @@ export function updateComboboxMessage(state: OuiComboboxState, visible: number):
|
|
|
648
659
|
setAttribute(state.message, COMBOBOX_ATTRIBUTE_HIDDEN, visible > 0 ? '' : undefined);
|
|
649
660
|
|
|
650
661
|
if (visible === 0) {
|
|
651
|
-
const localization =
|
|
662
|
+
const localization = getLocalization(
|
|
663
|
+
comboboxLocalizations,
|
|
664
|
+
state.host,
|
|
665
|
+
'empty',
|
|
666
|
+
state.localizations,
|
|
667
|
+
);
|
|
652
668
|
|
|
653
|
-
state.message.textContent =
|
|
669
|
+
state.message.textContent = localize(localization, {}, []);
|
|
654
670
|
} else {
|
|
655
|
-
const localization =
|
|
671
|
+
const localization = getLocalization(
|
|
672
|
+
comboboxLocalizations,
|
|
673
|
+
state.host,
|
|
674
|
+
'results',
|
|
675
|
+
state.localizations,
|
|
676
|
+
);
|
|
656
677
|
|
|
657
|
-
state.message.textContent =
|
|
658
|
-
typeof localization === 'function'
|
|
659
|
-
? localization(visible)
|
|
660
|
-
: localization.replace('{amount}', visible.toString());
|
|
678
|
+
state.message.textContent = localize(localization, {amount: visible}, [visible]);
|
|
661
679
|
}
|
|
662
680
|
|
|
663
681
|
state.message.hidden = false;
|
|
@@ -701,7 +719,7 @@ export function updateComboboxSelection(
|
|
|
701
719
|
|
|
702
720
|
let options = active == null ? [] : [active];
|
|
703
721
|
|
|
704
|
-
if (selected != null) {
|
|
722
|
+
if (added && selected != null) {
|
|
705
723
|
options = selected;
|
|
706
724
|
|
|
707
725
|
const {length} = state.selection.items;
|
|
@@ -766,7 +784,12 @@ export function updateComboboxSelectionItems(state: OuiComboboxState): void {
|
|
|
766
784
|
return;
|
|
767
785
|
}
|
|
768
786
|
|
|
769
|
-
const localization =
|
|
787
|
+
const localization = getLocalization(
|
|
788
|
+
comboboxLocalizations,
|
|
789
|
+
state.host,
|
|
790
|
+
'remove',
|
|
791
|
+
state.localizations,
|
|
792
|
+
);
|
|
770
793
|
|
|
771
794
|
const {length} = state.selection.items;
|
|
772
795
|
|
|
@@ -781,12 +804,20 @@ export function updateComboboxSelectionItems(state: OuiComboboxState): void {
|
|
|
781
804
|
continue;
|
|
782
805
|
}
|
|
783
806
|
|
|
807
|
+
const optionLabel = option.textContent;
|
|
808
|
+
const optionValue = getData(option, 'value', false) ?? '';
|
|
809
|
+
|
|
784
810
|
setAria(
|
|
785
811
|
item.button,
|
|
786
812
|
'label',
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
813
|
+
localize(
|
|
814
|
+
localization,
|
|
815
|
+
{
|
|
816
|
+
label: optionLabel,
|
|
817
|
+
value: optionValue,
|
|
818
|
+
},
|
|
819
|
+
[optionLabel, optionValue],
|
|
820
|
+
),
|
|
790
821
|
);
|
|
791
822
|
}
|
|
792
823
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {dispatch} from '@oscarpalmer/toretto/event';
|
|
2
|
+
import {getLocalization, localize} from '../l10n';
|
|
2
3
|
import {updateComboboxAnchor} from './dom.combobox';
|
|
3
|
-
import {getComboboxLocalization} from './i18n.combobox';
|
|
4
4
|
import type {OuiComboboxState} from './models.combobox';
|
|
5
|
+
import {comboboxLocalizations} from './state.combobox';
|
|
5
6
|
|
|
6
7
|
// #region Functions
|
|
7
8
|
|
|
@@ -40,28 +41,29 @@ export function updateComboboxValidity(state: OuiComboboxState): void {
|
|
|
40
41
|
if (state.autocomplete.none) {
|
|
41
42
|
const valueMissing = state.required && state.filter.element.value.length === 0;
|
|
42
43
|
|
|
43
|
-
const localization =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
const localization = getLocalization(
|
|
45
|
+
comboboxLocalizations,
|
|
46
|
+
state.host,
|
|
47
|
+
'invalid',
|
|
48
|
+
state.localizations,
|
|
48
49
|
);
|
|
49
50
|
|
|
51
|
+
state.internals.setValidity({valueMissing}, localize(localization, {}, []));
|
|
52
|
+
|
|
50
53
|
state.internals.checkValidity();
|
|
51
54
|
|
|
52
55
|
return;
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
let data: Record<string, unknown> = {};
|
|
55
59
|
let error: 'badInput' | 'rangeOverflow' | 'rangeUnderflow' | undefined;
|
|
56
|
-
let
|
|
60
|
+
let key: 'input' | 'max' | 'min' | undefined;
|
|
61
|
+
let parameters: unknown[] = [];
|
|
57
62
|
|
|
58
63
|
switch (true) {
|
|
59
64
|
case state.required && state.options.selected.length === 0: {
|
|
60
65
|
error = 'badInput';
|
|
61
|
-
|
|
62
|
-
const localization = getComboboxLocalization('input', state);
|
|
63
|
-
|
|
64
|
-
message = typeof localization === 'function' ? localization() : localization;
|
|
66
|
+
key = 'input';
|
|
65
67
|
|
|
66
68
|
break;
|
|
67
69
|
}
|
|
@@ -69,14 +71,10 @@ export function updateComboboxValidity(state: OuiComboboxState): void {
|
|
|
69
71
|
case state.multiple &&
|
|
70
72
|
typeof state.max === 'number' &&
|
|
71
73
|
state.options.selected.length > state.max: {
|
|
74
|
+
data = {amount: state.max};
|
|
72
75
|
error = 'rangeOverflow';
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
message =
|
|
77
|
-
typeof localization === 'function'
|
|
78
|
-
? localization(state.max)
|
|
79
|
-
: localization.replace('{max}', state.max.toString());
|
|
76
|
+
key = 'max';
|
|
77
|
+
parameters = [state.max];
|
|
80
78
|
|
|
81
79
|
break;
|
|
82
80
|
}
|
|
@@ -84,14 +82,10 @@ export function updateComboboxValidity(state: OuiComboboxState): void {
|
|
|
84
82
|
case state.multiple &&
|
|
85
83
|
typeof state.min === 'number' &&
|
|
86
84
|
state.options.selected.length < state.min: {
|
|
85
|
+
data = {amount: state.min};
|
|
87
86
|
error = 'rangeUnderflow';
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
message =
|
|
92
|
-
typeof localization === 'function'
|
|
93
|
-
? localization(state.min)
|
|
94
|
-
: localization.replace('{min}', state.min.toString());
|
|
87
|
+
key = 'min';
|
|
88
|
+
parameters = [state.min];
|
|
95
89
|
|
|
96
90
|
break;
|
|
97
91
|
}
|
|
@@ -100,6 +94,13 @@ export function updateComboboxValidity(state: OuiComboboxState): void {
|
|
|
100
94
|
break;
|
|
101
95
|
}
|
|
102
96
|
|
|
97
|
+
const localization =
|
|
98
|
+
key == null
|
|
99
|
+
? undefined
|
|
100
|
+
: getLocalization(comboboxLocalizations, state.host, key, state.localizations);
|
|
101
|
+
|
|
102
|
+
const message = localization == null ? undefined : localize(localization, data, parameters);
|
|
103
|
+
|
|
103
104
|
state.internals.setValidity(
|
|
104
105
|
error == null
|
|
105
106
|
? {}
|
package/src/js/combobox/index.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import {parse} from '@oscarpalmer/atoms/string';
|
|
2
2
|
import {getAttribute} from '@oscarpalmer/toretto/attribute';
|
|
3
|
+
import {updateLocalizations} from '../l10n';
|
|
3
4
|
import {COMBOBOX_TAGNAME} from './constants.combobox';
|
|
4
5
|
import {setComboboxValue, setComboboxValues} from './data.combobox';
|
|
5
6
|
import {setComboboxDisabled, setComboboxReadonly, setComboboxRequired} from './dom.combobox';
|
|
6
|
-
import {updateComboboxLocalizations} from './i18n.combobox';
|
|
7
7
|
import {setComboboxFilter, setComboboxLimit} from './misc.combobox';
|
|
8
8
|
import type {OuiComboboxLocalizations, OuiComboboxState, OuiComboboxType} from './models.combobox';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
attachCombobox,
|
|
11
|
+
comboboxLocalizations,
|
|
12
|
+
createComboboxState,
|
|
13
|
+
detachCombobox,
|
|
14
|
+
} from './state.combobox';
|
|
10
15
|
|
|
11
16
|
// #region Types
|
|
12
17
|
|
|
@@ -269,7 +274,8 @@ export class OuiComboboxElement extends HTMLElement {
|
|
|
269
274
|
first: string | Record<string, Partial<OuiComboboxLocalizations>>,
|
|
270
275
|
second?: Partial<OuiComboboxLocalizations>,
|
|
271
276
|
): void {
|
|
272
|
-
|
|
277
|
+
updateLocalizations(
|
|
278
|
+
comboboxLocalizations,
|
|
273
279
|
typeof first === 'string'
|
|
274
280
|
? {
|
|
275
281
|
[first]: second!,
|
|
@@ -302,6 +308,6 @@ customElements.define(COMBOBOX_TAGNAME, OuiComboboxElement);
|
|
|
302
308
|
|
|
303
309
|
// #region Exports
|
|
304
310
|
|
|
305
|
-
export {setComboboxLocalizations} from './
|
|
311
|
+
export {setComboboxLocalizations} from './misc.combobox';
|
|
306
312
|
|
|
307
313
|
// #endregion
|
|
@@ -2,14 +2,22 @@ import {getAria} from '@oscarpalmer/toretto/aria';
|
|
|
2
2
|
import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
3
3
|
import type {AnyAriaBooleanAttribute} from '@oscarpalmer/toretto/models';
|
|
4
4
|
import type {OuiComboboxElement} from '.';
|
|
5
|
+
import {updateLocalizations} from '../l10n';
|
|
5
6
|
import {COMBOBOX_AUTOCOMPLETES} from './constants.combobox';
|
|
7
|
+
import {
|
|
8
|
+
updateComboboxAnchor,
|
|
9
|
+
updateComboboxMessage,
|
|
10
|
+
updateComboboxSelectionItems,
|
|
11
|
+
} from './dom.combobox';
|
|
6
12
|
import {onComboboxFilter} from './event.combobox';
|
|
7
13
|
import {updateComboboxValidity} from './form.combobox';
|
|
8
14
|
import type {
|
|
9
15
|
OuiComboboxAutoComplete,
|
|
10
16
|
OuiComboboxAutoCompleteType,
|
|
17
|
+
OuiComboboxLocalizations,
|
|
11
18
|
OuiComboboxState,
|
|
12
19
|
} from './models.combobox';
|
|
20
|
+
import {comboboxLocalizations, comboboxStates} from './state.combobox';
|
|
13
21
|
|
|
14
22
|
// #region Functions
|
|
15
23
|
|
|
@@ -129,6 +137,48 @@ export function setComboboxLimit(
|
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Set combobox localizations for a specific language
|
|
142
|
+
*
|
|
143
|
+
* @param language Language code
|
|
144
|
+
* @param localizations Localizations for language
|
|
145
|
+
*/
|
|
146
|
+
export function setComboboxLocalizations(
|
|
147
|
+
language: string,
|
|
148
|
+
localizations: Partial<OuiComboboxLocalizations>,
|
|
149
|
+
): void;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Set combobox localizations for one or more languages
|
|
153
|
+
*
|
|
154
|
+
* @param localizations Localizations for one or more languages
|
|
155
|
+
*/
|
|
156
|
+
export function setComboboxLocalizations(
|
|
157
|
+
localizations: Record<string, Partial<OuiComboboxLocalizations>>,
|
|
158
|
+
): void;
|
|
159
|
+
|
|
160
|
+
export function setComboboxLocalizations(
|
|
161
|
+
first: string | Record<string, Partial<OuiComboboxLocalizations>>,
|
|
162
|
+
second?: Partial<OuiComboboxLocalizations>,
|
|
163
|
+
): void {
|
|
164
|
+
updateLocalizations(
|
|
165
|
+
comboboxLocalizations,
|
|
166
|
+
typeof first === 'string'
|
|
167
|
+
? {
|
|
168
|
+
[first]: second!,
|
|
169
|
+
}
|
|
170
|
+
: first,
|
|
171
|
+
comboboxLocalizations.values,
|
|
172
|
+
() => {
|
|
173
|
+
for (const state of comboboxStates.set) {
|
|
174
|
+
updateComboboxAnchor(state);
|
|
175
|
+
updateComboboxMessage(state, getComboboxVisibleOptions(state).length);
|
|
176
|
+
updateComboboxSelectionItems(state);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
132
182
|
// #endregion
|
|
133
183
|
|
|
134
184
|
// #region Variables
|
|
@@ -3,6 +3,7 @@ import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
|
3
3
|
import {getData} from '@oscarpalmer/toretto/data';
|
|
4
4
|
import {on} from '@oscarpalmer/toretto/event';
|
|
5
5
|
import {createEmbeddedFloatable} from '../floatable/floatable.embedded';
|
|
6
|
+
import {getLocalizations} from '../l10n';
|
|
6
7
|
import {
|
|
7
8
|
COMBOBOX_MESSAGE_LABEL,
|
|
8
9
|
COMBOBOX_MESSAGE_SELECT,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
getComboboxVisibleOptions,
|
|
32
33
|
setComboboxLimit,
|
|
33
34
|
} from './misc.combobox';
|
|
34
|
-
import type {OuiComboboxState} from './models.combobox';
|
|
35
|
+
import type {OuiComboboxLocalizations, OuiComboboxState} from './models.combobox';
|
|
35
36
|
|
|
36
37
|
// #region Functions
|
|
37
38
|
|
|
@@ -227,6 +228,17 @@ export function detachCombobox(state: OuiComboboxState): void {
|
|
|
227
228
|
|
|
228
229
|
// #region Variables
|
|
229
230
|
|
|
231
|
+
export const comboboxLocalizations = getLocalizations<OuiComboboxLocalizations>({
|
|
232
|
+
empty: 'No results found',
|
|
233
|
+
input: 'Please enter a value',
|
|
234
|
+
invalid: 'Please select an option from the list',
|
|
235
|
+
max: max => `Please select fewer options (maximum is ${max})`,
|
|
236
|
+
min: min => `Please select more options (minimum is ${min})`,
|
|
237
|
+
remove: label => `Remove ${label}`,
|
|
238
|
+
results: amount => `${amount} results available`,
|
|
239
|
+
selected: amount => `${amount} selected`,
|
|
240
|
+
});
|
|
241
|
+
|
|
230
242
|
export const comboboxStates = {
|
|
231
243
|
map: new WeakMap<HTMLElement, OuiComboboxState>(),
|
|
232
244
|
set: new Set<OuiComboboxState>(),
|
package/src/js/l10n.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import {getString} from '@oscarpalmer/atoms/string';
|
|
3
|
+
|
|
4
|
+
// #region Types
|
|
5
|
+
|
|
6
|
+
type LocalizationParameters<Localization> = Localization extends GenericCallback
|
|
7
|
+
? Parameters<Localization>
|
|
8
|
+
: never;
|
|
9
|
+
|
|
10
|
+
type OuiLocalizationValues = Record<string, string | GenericCallback>;
|
|
11
|
+
|
|
12
|
+
export class OuiLocalizations<Localizations extends OuiLocalizationValues> {
|
|
13
|
+
readonly defaults: Localizations;
|
|
14
|
+
|
|
15
|
+
readonly keys: Set<keyof Localizations>;
|
|
16
|
+
|
|
17
|
+
readonly values: Record<string, Partial<Localizations>> = {};
|
|
18
|
+
|
|
19
|
+
constructor(defaults: Localizations) {
|
|
20
|
+
this.defaults = defaults;
|
|
21
|
+
this.keys = new Set(Object.keys(defaults) as Array<keyof Localizations>);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get<Key extends keyof Localizations>(
|
|
25
|
+
element: Element,
|
|
26
|
+
key: Key,
|
|
27
|
+
localizations: Record<string, Partial<Localizations>>,
|
|
28
|
+
): string | GenericCallback {
|
|
29
|
+
return getLocalization(this, element, key, localizations);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
set(language: string, localizations: Partial<Localizations>): void;
|
|
33
|
+
|
|
34
|
+
set(localizations: Record<string, Partial<Localizations>>): void;
|
|
35
|
+
|
|
36
|
+
set(
|
|
37
|
+
first: string | Record<string, Partial<Localizations>>,
|
|
38
|
+
second?: Partial<Localizations>,
|
|
39
|
+
): void {
|
|
40
|
+
updateLocalizations(
|
|
41
|
+
this,
|
|
42
|
+
typeof first === 'string'
|
|
43
|
+
? {
|
|
44
|
+
[first]: second!,
|
|
45
|
+
}
|
|
46
|
+
: first,
|
|
47
|
+
this.values,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// #endregion
|
|
53
|
+
|
|
54
|
+
// #region Functions
|
|
55
|
+
|
|
56
|
+
export function getLanguage(element: Element): string {
|
|
57
|
+
return (
|
|
58
|
+
element.closest('[lang]')?.getAttribute('lang') ?? document.documentElement.lang
|
|
59
|
+
).toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getLocalization<
|
|
63
|
+
Localizations extends OuiLocalizationValues,
|
|
64
|
+
Key extends keyof Localizations,
|
|
65
|
+
>(
|
|
66
|
+
localizations: OuiLocalizations<Localizations>,
|
|
67
|
+
element: Element,
|
|
68
|
+
key: Key,
|
|
69
|
+
value: Record<string, Partial<Localizations>>,
|
|
70
|
+
): Localizations[Key] | GenericCallback {
|
|
71
|
+
const language = getLanguage(element);
|
|
72
|
+
|
|
73
|
+
const values = [value, localizations.values];
|
|
74
|
+
const {length} = values;
|
|
75
|
+
|
|
76
|
+
let localization: Localizations[Key] | undefined;
|
|
77
|
+
let shortLanguage: string | undefined;
|
|
78
|
+
|
|
79
|
+
for (let index = 0; index < length; index += 1) {
|
|
80
|
+
const value = values[index];
|
|
81
|
+
|
|
82
|
+
localization = value?.[language]?.[key];
|
|
83
|
+
|
|
84
|
+
if (localization != null) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
shortLanguage ??= language.split('-')[0];
|
|
89
|
+
|
|
90
|
+
if (shortLanguage === language) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
localization = value?.[shortLanguage]?.[key];
|
|
95
|
+
|
|
96
|
+
if (localization != null) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return localization ?? localizations.defaults[key];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getLocalizations<Localizations extends OuiLocalizationValues>(
|
|
105
|
+
defaults: Localizations,
|
|
106
|
+
): OuiLocalizations<Localizations> {
|
|
107
|
+
return new OuiLocalizations(defaults);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function localize<
|
|
111
|
+
Localizations extends OuiLocalizationValues,
|
|
112
|
+
Key extends keyof Localizations,
|
|
113
|
+
Localization extends Localizations[Key],
|
|
114
|
+
>(
|
|
115
|
+
localization: Localization,
|
|
116
|
+
data: Record<string, unknown>,
|
|
117
|
+
parameters: LocalizationParameters<Localization>,
|
|
118
|
+
): string {
|
|
119
|
+
if (typeof localization === 'function') {
|
|
120
|
+
return localization(...parameters);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const keys = Object.keys(data);
|
|
124
|
+
const {length} = keys;
|
|
125
|
+
|
|
126
|
+
let localized = localization as string;
|
|
127
|
+
|
|
128
|
+
for (let index = 0; index < length; index += 1) {
|
|
129
|
+
const key = keys[index];
|
|
130
|
+
const value = data[key];
|
|
131
|
+
|
|
132
|
+
localized = localized.replaceAll(`{${key}}`, getString(value));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return localized;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function updateLocalizations<Localizations extends OuiLocalizationValues>(
|
|
139
|
+
instance: OuiLocalizations<Localizations>,
|
|
140
|
+
values: Record<string, Partial<Localizations>>,
|
|
141
|
+
destination?: Record<string, Partial<Localizations>>,
|
|
142
|
+
onAfter?: () => void,
|
|
143
|
+
): void {
|
|
144
|
+
if (typeof values !== 'object' || values == null) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const localizations = destination ?? instance.values;
|
|
149
|
+
|
|
150
|
+
const languages = Object.keys(values);
|
|
151
|
+
const languagesLength = languages.length;
|
|
152
|
+
|
|
153
|
+
let updated = false;
|
|
154
|
+
|
|
155
|
+
for (let languagesIndex = 0; languagesIndex < languagesLength; languagesIndex += 1) {
|
|
156
|
+
const language = languages[languagesIndex];
|
|
157
|
+
const languageValue = values[language];
|
|
158
|
+
|
|
159
|
+
if (typeof languageValue !== 'object' || languageValue == null) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const normalizedLanguage = language.toLowerCase();
|
|
164
|
+
|
|
165
|
+
localizations[normalizedLanguage] ??= {};
|
|
166
|
+
|
|
167
|
+
const keys = Object.keys(languageValue);
|
|
168
|
+
const keysLength = keys.length;
|
|
169
|
+
|
|
170
|
+
for (let keysIndex = 0; keysIndex < keysLength; keysIndex += 1) {
|
|
171
|
+
const key = keys[keysIndex];
|
|
172
|
+
const keyValue = languageValue[key];
|
|
173
|
+
|
|
174
|
+
const normalizedKey = key.toLowerCase() as keyof Localizations;
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
instance.keys.has(normalizedKey) &&
|
|
178
|
+
(typeof keyValue === 'string' || typeof keyValue === 'function')
|
|
179
|
+
) {
|
|
180
|
+
updated = true;
|
|
181
|
+
|
|
182
|
+
localizations[normalizedLanguage][normalizedKey] = keyValue as never;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (updated && typeof onAfter === 'function') {
|
|
188
|
+
onAfter();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// #endregion
|