vgapp 1.4.7 → 1.4.9
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 +24 -2
- package/app/modules/base-module.js +14 -2
- package/app/modules/module-fn.js +5 -4
- package/app/modules/vgalert/js/vgalert.js +21 -1
- package/app/modules/vgalert/scss/vgalert.scss +48 -0
- package/app/modules/vgdropdown/js/vgdropdown.js +58 -27
- package/app/modules/vgfilepreview/js/renderers/video-modal.js +5 -1
- package/app/modules/vgfilepreview/js/renderers/video.js +6 -1
- package/app/modules/vgfilepreview/js/vgfilepreview.js +5 -1
- package/app/modules/vgfiles/js/base.js +14 -5
- package/app/modules/vgfiles/js/droppable.js +37 -31
- package/app/modules/vgfiles/js/vgfiles.js +5 -2
- package/app/modules/vgfiles/scss/_variables.scss +3 -1
- package/app/modules/vgfiles/scss/vgfiles.scss +55 -10
- package/app/modules/vgformsender/js/vgformsender.js +5 -2
- package/app/modules/vgloadmore/js/vgloadmore.js +35 -7
- package/app/modules/vgnestable/js/vgnestable.js +140 -0
- package/app/modules/vgnestable/scss/_variables.scss +16 -9
- package/app/modules/vgnestable/scss/vgnestable.scss +35 -2
- package/app/modules/vgtable/index.js +3 -0
- package/app/modules/vgtable/js/_columns.js +567 -0
- package/app/modules/vgtable/js/_expandable.js +242 -0
- package/app/modules/vgtable/js/_filters.js +264 -0
- package/app/modules/vgtable/js/_fixed-columns.js +249 -0
- package/app/modules/vgtable/js/_i18n.js +75 -0
- package/app/modules/vgtable/js/_options.js +428 -0
- package/app/modules/vgtable/js/_pagination.js +491 -0
- package/app/modules/vgtable/js/_panning.js +124 -0
- package/app/modules/vgtable/js/_params-groups.js +21 -0
- package/app/modules/vgtable/js/_remote.js +395 -0
- package/app/modules/vgtable/js/_row-reorder.js +268 -0
- package/app/modules/vgtable/js/_search.js +134 -0
- package/app/modules/vgtable/js/_selection.js +113 -0
- package/app/modules/vgtable/js/_skeleton.js +123 -0
- package/app/modules/vgtable/js/_sorting.js +340 -0
- package/app/modules/vgtable/js/_states.js +143 -0
- package/app/modules/vgtable/js/_sticky-header.js +193 -0
- package/app/modules/vgtable/js/_url-state.js +178 -0
- package/app/modules/vgtable/js/vgtable.js +1444 -0
- package/app/modules/vgtable/readme.md +922 -0
- package/app/modules/vgtable/scss/_columns.scss +52 -0
- package/app/modules/vgtable/scss/_expandable.scss +54 -0
- package/app/modules/vgtable/scss/_fixed-columns.scss +117 -0
- package/app/modules/vgtable/scss/_pagination.scss +191 -0
- package/app/modules/vgtable/scss/_row-reorder.scss +45 -0
- package/app/modules/vgtable/scss/_skeleton.scss +65 -0
- package/app/modules/vgtable/scss/_sorting.scss +89 -0
- package/app/modules/vgtable/scss/_states.scss +70 -0
- package/app/modules/vgtable/scss/_sticky-header.scss +121 -0
- package/app/modules/vgtable/scss/_variables.scss +296 -0
- package/app/modules/vgtable/scss/vgtable.scss +206 -0
- package/app/utils/js/components/placement.js +64 -10
- package/app/vgapp.js +3 -3
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js +1 -1
- package/build/vgapp.js.map +1 -1
- package/index.js +1 -1
- package/index.scss +3 -3
- package/package.json +5 -5
- package/app/modules/vgdynamictable/index.js +0 -4
- package/app/modules/vgdynamictable/js/editable.js +0 -438
- package/app/modules/vgdynamictable/js/expandable.js +0 -248
- package/app/modules/vgdynamictable/js/filters.js +0 -450
- package/app/modules/vgdynamictable/js/fixed.js +0 -566
- package/app/modules/vgdynamictable/js/options.js +0 -650
- package/app/modules/vgdynamictable/js/pagination.js +0 -623
- package/app/modules/vgdynamictable/js/search.js +0 -82
- package/app/modules/vgdynamictable/js/skeleton.js +0 -144
- package/app/modules/vgdynamictable/js/sortable.js +0 -442
- package/app/modules/vgdynamictable/js/summary-footer.js +0 -284
- package/app/modules/vgdynamictable/js/table-remote.js +0 -821
- package/app/modules/vgdynamictable/js/table-state.js +0 -243
- package/app/modules/vgdynamictable/js/table-url-state.js +0 -444
- package/app/modules/vgdynamictable/js/utils/common.js +0 -48
- package/app/modules/vgdynamictable/js/vgdynamictable.js +0 -3870
- package/app/modules/vgdynamictable/js/viewport.js +0 -322
- package/app/modules/vgdynamictable/readme.md +0 -251
- package/app/modules/vgdynamictable/scss/_actions.scss +0 -196
- package/app/modules/vgdynamictable/scss/_pagination.scss +0 -186
- package/app/modules/vgdynamictable/scss/_skeleton.scss +0 -63
- package/app/modules/vgdynamictable/scss/_sortable.scss +0 -66
- package/app/modules/vgdynamictable/scss/_table.scss +0 -137
- package/app/modules/vgdynamictable/scss/_variables.scss +0 -138
- package/app/modules/vgdynamictable/scss/vgdynamictable.scss +0 -264
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Описание: нативная фиксация колонок VGTable при горизонтальной прокрутке.
|
|
3
|
+
* Возможности: left/right sticky-колонки, последовательный stack-режим, синхронные тени и совместимость с Fixed Header.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const CELL_ATTRIBUTE = 'data-vg-table-fixed-side';
|
|
7
|
+
const EDGE_ATTRIBUTE = 'data-vg-table-fixed-edge';
|
|
8
|
+
const OFFSET_PROPERTY = '--vg-table-fixed-offset';
|
|
9
|
+
const WRAPPER_CLASSES = [
|
|
10
|
+
'vg-table-wrapper--fixed-columns',
|
|
11
|
+
'vg-table-wrapper--fixed-columns-stack',
|
|
12
|
+
'is-vg-table-fixed-scrolled-start',
|
|
13
|
+
'is-vg-table-fixed-scrolled-end',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
class _fixedColumns {
|
|
17
|
+
constructor(element, wrapper, params = {}, stickyHeader = null) {
|
|
18
|
+
this._element = element;
|
|
19
|
+
this._wrapper = wrapper;
|
|
20
|
+
this._params = params;
|
|
21
|
+
this._stickyHeader = stickyHeader;
|
|
22
|
+
this._headerTable = stickyHeader?.getHeaderTable?.() || element;
|
|
23
|
+
this._scrollHost = stickyHeader?.getBody?.() || element.closest('.vg-table-container') || wrapper;
|
|
24
|
+
this._columns = {left: [], right: []};
|
|
25
|
+
this._widths = [];
|
|
26
|
+
this._previousClasses = new Map();
|
|
27
|
+
this._activeEdges = {left: null, right: null};
|
|
28
|
+
this._resizeObserver = null;
|
|
29
|
+
this._boundScroll = this._syncScrollState.bind(this);
|
|
30
|
+
this._boundResize = this.refresh.bind(this);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
init() {
|
|
34
|
+
if (!this._wrapper || !this._scrollHost || !this._headerTable?.tHead) return this;
|
|
35
|
+
|
|
36
|
+
WRAPPER_CLASSES.forEach((className) => {
|
|
37
|
+
this._previousClasses.set(className, this._wrapper.classList.contains(className));
|
|
38
|
+
});
|
|
39
|
+
this._wrapper.classList.add('vg-table-wrapper--fixed-columns');
|
|
40
|
+
this._wrapper.classList.toggle('vg-table-wrapper--fixed-columns-stack', this._params.mode === 'stack');
|
|
41
|
+
this._element.setAttribute('data-vg-table-fixed-columns', this._params.mode);
|
|
42
|
+
this._scrollHost.addEventListener('scroll', this._boundScroll, {passive: true});
|
|
43
|
+
|
|
44
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
45
|
+
this._resizeObserver = new ResizeObserver(this._boundResize);
|
|
46
|
+
this._resizeObserver.observe(this._scrollHost);
|
|
47
|
+
this._resizeObserver.observe(this._element);
|
|
48
|
+
} else if (typeof window !== 'undefined') {
|
|
49
|
+
window.addEventListener('resize', this._boundResize);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.refresh();
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
refresh() {
|
|
57
|
+
this._headerTable = this._stickyHeader?.getHeaderTable?.() || this._element;
|
|
58
|
+
this._scrollHost = this._stickyHeader?.getBody?.() || this._element.closest('.vg-table-container') || this._wrapper;
|
|
59
|
+
const headers = this._getHeaders();
|
|
60
|
+
if (!headers.length) return this;
|
|
61
|
+
|
|
62
|
+
this._clearCells();
|
|
63
|
+
this._columns = this._resolveColumns(headers);
|
|
64
|
+
this._widths = headers.map((header, index) => this._measureColumn(header, index));
|
|
65
|
+
this._applySide('left', this._columns.left, headers);
|
|
66
|
+
this._applySide('right', [...this._columns.right].reverse(), headers);
|
|
67
|
+
this._syncScrollState(true);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getState() {
|
|
72
|
+
return {
|
|
73
|
+
mode: this._params.mode,
|
|
74
|
+
left: [...this._columns.left],
|
|
75
|
+
right: [...this._columns.right],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
syncScroll() {
|
|
80
|
+
this._syncScrollState();
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
dispose() {
|
|
85
|
+
this._scrollHost?.removeEventListener('scroll', this._boundScroll);
|
|
86
|
+
this._resizeObserver?.disconnect();
|
|
87
|
+
if (!this._resizeObserver && typeof window !== 'undefined') {
|
|
88
|
+
window.removeEventListener('resize', this._boundResize);
|
|
89
|
+
}
|
|
90
|
+
this._clearCells();
|
|
91
|
+
WRAPPER_CLASSES.forEach((className) => {
|
|
92
|
+
this._wrapper.classList.toggle(className, this._previousClasses.get(className) === true);
|
|
93
|
+
});
|
|
94
|
+
this._element.removeAttribute('data-vg-table-fixed-columns');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_getHeaders() {
|
|
98
|
+
const rows = Array.from(this._headerTable.tHead?.rows || []);
|
|
99
|
+
return rows.length ? Array.from(rows[rows.length - 1].cells) : [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_resolveColumns(headers) {
|
|
103
|
+
const result = {left: [], right: []};
|
|
104
|
+
const add = (side, value) => {
|
|
105
|
+
const index = this._resolveIndex(value, headers);
|
|
106
|
+
if (index >= 0 && !result[side].includes(index)) result[side].push(index);
|
|
107
|
+
};
|
|
108
|
+
const configured = this._params.columns;
|
|
109
|
+
|
|
110
|
+
if (configured && typeof configured === 'object' && !Array.isArray(configured)) {
|
|
111
|
+
['left', 'right'].forEach((side) => {
|
|
112
|
+
const values = Array.isArray(configured[side]) ? configured[side] : String(configured[side] || '').split(',');
|
|
113
|
+
values.forEach((value) => add(side, value));
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
String(configured || '').split(';').forEach((group) => {
|
|
117
|
+
const [rawSide, rawValues = ''] = group.split(':');
|
|
118
|
+
const side = String(rawSide || '').trim().toLowerCase();
|
|
119
|
+
if (!['left', 'right'].includes(side)) return;
|
|
120
|
+
rawValues.split(',').forEach((value) => add(side, value));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
headers.forEach((header, index) => {
|
|
125
|
+
const side = String(header.getAttribute('data-fixed') || header.getAttribute('data-fixed-column') || '').toLowerCase();
|
|
126
|
+
if (['left', 'right'].includes(side)) add(side, index);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
result.left.sort((a, b) => a - b);
|
|
130
|
+
result.right.sort((a, b) => a - b);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
_resolveIndex(value, headers) {
|
|
135
|
+
const normalized = String(value ?? '').trim();
|
|
136
|
+
if (!normalized) return -1;
|
|
137
|
+
if (/^\d+$/.test(normalized)) {
|
|
138
|
+
const numeric = Number.parseInt(normalized, 10);
|
|
139
|
+
return numeric >= 0 && numeric < headers.length ? numeric : -1;
|
|
140
|
+
}
|
|
141
|
+
return headers.findIndex((header) => String(header.dataset.field || '').trim() === normalized);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_measureColumn(header, index) {
|
|
145
|
+
const bodyRow = Array.from(this._element.tBodies[0]?.rows || [])
|
|
146
|
+
.find((row) => !row.hasAttribute('data-vg-table-state-row'));
|
|
147
|
+
const bodyCell = bodyRow?.cells[index];
|
|
148
|
+
const col = this._element.querySelector(`:scope > colgroup > col:nth-child(${index + 1})`);
|
|
149
|
+
const measured = bodyCell?.getBoundingClientRect?.().width
|
|
150
|
+
|| header.getBoundingClientRect?.().width
|
|
151
|
+
|| bodyCell?.offsetWidth
|
|
152
|
+
|| header.offsetWidth;
|
|
153
|
+
if (measured > 0) return measured;
|
|
154
|
+
const declared = Number.parseFloat(col?.style.width || col?.getAttribute('width') || '0');
|
|
155
|
+
return Number.isFinite(declared) ? declared : 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_applySide(side, indices, headers) {
|
|
159
|
+
let offset = 0;
|
|
160
|
+
indices.forEach((index) => {
|
|
161
|
+
this._cellsAt(index, headers).forEach((cell) => {
|
|
162
|
+
cell.setAttribute(CELL_ATTRIBUTE, side);
|
|
163
|
+
cell.style.setProperty(OFFSET_PROPERTY, `${offset}px`);
|
|
164
|
+
});
|
|
165
|
+
offset += this._widths[index] || 0;
|
|
166
|
+
if (this._params.mode === 'stack') offset += this._params.stackGap;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_cellsAt(index, headers = this._getHeaders()) {
|
|
171
|
+
const cells = [];
|
|
172
|
+
if (headers[index]) cells.push(headers[index]);
|
|
173
|
+
Array.from(this._element.tBodies || []).forEach((tbody) => {
|
|
174
|
+
Array.from(tbody.rows || []).forEach((row) => {
|
|
175
|
+
if (row.hasAttribute('data-vg-table-state-row')) return;
|
|
176
|
+
if (row.cells[index]) cells.push(row.cells[index]);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
Array.from(this._element.tFoot?.rows || []).forEach((row) => {
|
|
180
|
+
if (row.cells[index]) cells.push(row.cells[index]);
|
|
181
|
+
});
|
|
182
|
+
return cells;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_syncScrollState(force = false) {
|
|
186
|
+
if (!this._scrollHost) return;
|
|
187
|
+
const max = Math.max(0, this._scrollHost.scrollWidth - this._scrollHost.clientWidth);
|
|
188
|
+
const position = Math.max(0, Math.min(max, this._scrollHost.scrollLeft));
|
|
189
|
+
this._wrapper.classList.toggle('is-vg-table-fixed-scrolled-start', position > 0.5);
|
|
190
|
+
this._wrapper.classList.toggle('is-vg-table-fixed-scrolled-end', position < max - 0.5);
|
|
191
|
+
|
|
192
|
+
if (this._params.mode !== 'stack') {
|
|
193
|
+
this._setEdge('left', this._columns.left.at(-1) ?? null, force);
|
|
194
|
+
this._setEdge('right', this._columns.right[0] ?? null, force);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const leftActive = this._columns.left.filter((index) => {
|
|
199
|
+
const naturalStart = this._widths.slice(0, index).reduce((sum, width) => sum + width, 0);
|
|
200
|
+
const offset = this._stackOffset('left', index);
|
|
201
|
+
return position >= naturalStart - offset - 0.5;
|
|
202
|
+
});
|
|
203
|
+
const totalWidth = this._widths.reduce((sum, width) => sum + width, 0);
|
|
204
|
+
const remaining = max - position;
|
|
205
|
+
const rightActive = this._columns.right.filter((index) => {
|
|
206
|
+
const naturalEnd = this._widths.slice(0, index + 1).reduce((sum, width) => sum + width, 0);
|
|
207
|
+
const distance = totalWidth - naturalEnd;
|
|
208
|
+
return remaining >= distance - this._stackOffset('right', index) - 0.5;
|
|
209
|
+
});
|
|
210
|
+
this._setEdge('left', leftActive.at(-1) ?? null, force);
|
|
211
|
+
this._setEdge('right', rightActive[0] ?? null, force);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_stackOffset(side, index) {
|
|
215
|
+
const ordered = side === 'left' ? this._columns.left : [...this._columns.right].reverse();
|
|
216
|
+
let offset = 0;
|
|
217
|
+
for (const current of ordered) {
|
|
218
|
+
if (current === index) return offset;
|
|
219
|
+
offset += (this._widths[current] || 0) + this._params.stackGap;
|
|
220
|
+
}
|
|
221
|
+
return offset;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
_setEdge(side, index, force = false) {
|
|
225
|
+
if (!force && this._activeEdges[side] === index) return;
|
|
226
|
+
this._allManagedCells().forEach((cell) => {
|
|
227
|
+
if (cell.getAttribute(EDGE_ATTRIBUTE) === side) cell.removeAttribute(EDGE_ATTRIBUTE);
|
|
228
|
+
});
|
|
229
|
+
if (index !== null) {
|
|
230
|
+
this._cellsAt(index).forEach((cell) => cell.setAttribute(EDGE_ATTRIBUTE, side));
|
|
231
|
+
}
|
|
232
|
+
this._activeEdges[side] = index;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_allManagedCells() {
|
|
236
|
+
return Array.from(this._wrapper.querySelectorAll(`[${CELL_ATTRIBUTE}], [${EDGE_ATTRIBUTE}]`));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
_clearCells() {
|
|
240
|
+
this._allManagedCells().forEach((cell) => {
|
|
241
|
+
cell.removeAttribute(CELL_ATTRIBUTE);
|
|
242
|
+
cell.removeAttribute(EDGE_ATTRIBUTE);
|
|
243
|
+
cell.style.removeProperty(OFFSET_PROPERTY);
|
|
244
|
+
});
|
|
245
|
+
this._activeEdges = {left: null, right: null};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default _fixedColumns;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Описание: словари и разрешение локализованных подписей VGTable.
|
|
3
|
+
* Возможности: встроенные ru/en локали, региональные коды, пользовательские словари и подстановка переменных.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_LOCALE = 'ru';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_I18N = {
|
|
9
|
+
ru: {
|
|
10
|
+
pagination: {
|
|
11
|
+
size: {label: 'Строк на странице', suffix: 'на страницу'},
|
|
12
|
+
quick: {label: 'Перейти к странице', button: 'Перейти'},
|
|
13
|
+
labels: {
|
|
14
|
+
aria: 'Пагинация', prev: 'Назад', next: 'Вперёд',
|
|
15
|
+
ellipsisPrev: 'Перейти к предыдущим страницам', ellipsisNext: 'Перейти к следующим страницам',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
expandable: {labels: {expand: 'Развернуть дочерние строки', collapse: 'Свернуть дочерние строки'}},
|
|
19
|
+
state: {labels: {
|
|
20
|
+
empty: 'Нет данных', 'filtered-empty': 'По вашему запросу ничего не найдено',
|
|
21
|
+
error: 'Не удалось загрузить данные', retry: 'Повторить', reset: 'Сбросить фильтры',
|
|
22
|
+
}},
|
|
23
|
+
columnResize: {label: 'Изменить ширину колонки {column}'},
|
|
24
|
+
rowReorder: {labels: {
|
|
25
|
+
handle: 'Изменить позицию строки {row}',
|
|
26
|
+
}},
|
|
27
|
+
remote: {loading: 'Загрузка…', empty: 'Ничего не найдено', error: 'Не удалось загрузить данные', retry: 'Повторить'},
|
|
28
|
+
},
|
|
29
|
+
en: {
|
|
30
|
+
pagination: {
|
|
31
|
+
size: {label: 'Rows per page', suffix: 'per page'},
|
|
32
|
+
quick: {label: 'Go to page', button: 'Go'},
|
|
33
|
+
labels: {
|
|
34
|
+
aria: 'Pagination', prev: 'Previous', next: 'Next',
|
|
35
|
+
ellipsisPrev: 'Jump to previous pages', ellipsisNext: 'Jump to next pages',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
expandable: {labels: {expand: 'Expand child rows', collapse: 'Collapse child rows'}},
|
|
39
|
+
state: {labels: {
|
|
40
|
+
empty: 'No data', 'filtered-empty': 'No results found', error: 'Failed to load data', retry: 'Retry', reset: 'Reset filters',
|
|
41
|
+
}},
|
|
42
|
+
columnResize: {label: 'Resize column {column}'},
|
|
43
|
+
rowReorder: {labels: {
|
|
44
|
+
handle: 'Change position of row {row}',
|
|
45
|
+
}},
|
|
46
|
+
remote: {loading: 'Loading…', empty: 'Nothing found', error: 'Failed to load data', retry: 'Retry'},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const isObject = value => value && typeof value === 'object' && !Array.isArray(value);
|
|
51
|
+
|
|
52
|
+
const merge = (...sources) => sources.reduce((target, source) => {
|
|
53
|
+
if (!isObject(source)) return target;
|
|
54
|
+
Object.entries(source).forEach(([key, value]) => {
|
|
55
|
+
target[key] = isObject(value) ? merge(isObject(target[key]) ? target[key] : {}, value) : value;
|
|
56
|
+
});
|
|
57
|
+
return target;
|
|
58
|
+
}, {});
|
|
59
|
+
|
|
60
|
+
const normalizeLocale = locale => String(locale || DEFAULT_LOCALE).trim().toLowerCase().replace('_', '-') || DEFAULT_LOCALE;
|
|
61
|
+
|
|
62
|
+
const localeCandidates = locale => {
|
|
63
|
+
const normalized = normalizeLocale(locale);
|
|
64
|
+
const base = normalized.split('-')[0];
|
|
65
|
+
return normalized === base ? [normalized] : [normalized, base];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const resolveDictionary = (locale, dictionaries = {}) => {
|
|
69
|
+
const all = merge({}, DEFAULT_I18N, dictionaries);
|
|
70
|
+
const fallback = all[DEFAULT_LOCALE] || {};
|
|
71
|
+
const localized = localeCandidates(locale).map(key => all[key]).find(Boolean) || fallback;
|
|
72
|
+
return merge({}, fallback, localized);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export {normalizeLocale, resolveDictionary};
|