@gitlab/duo-ui 8.6.0 → 8.7.1
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/CHANGELOG.md +19 -0
- package/dist/components/chat/components/duo_chat_threads/duo_chat_threads.js +8 -4
- package/dist/components/chat/duo_chat.js +26 -2
- package/dist/utils/date.js +32 -9
- package/package.json +1 -1
- package/src/components/chat/components/duo_chat_threads/duo_chat_threads.vue +8 -4
- package/src/components/chat/duo_chat.vue +27 -0
- package/src/utils/date.js +41 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## [8.7.1](https://gitlab.com/gitlab-org/duo-ui/compare/v8.7.0...v8.7.1) (2025-03-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* Duo Chat overflow on header ([90b37c7](https://gitlab.com/gitlab-org/duo-ui/commit/90b37c70ccb25f93dfdb1f3709b59c8125c1b428))
|
|
7
|
+
|
|
8
|
+
# [8.7.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.6.0...v8.7.0) (2025-03-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* adapt default to BCP 47 ([7786272](https://gitlab.com/gitlab-org/duo-ui/commit/7786272f35b6447bda958ac01fc6922d0fb4c102))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* improve date formatting with localization support ([24c2548](https://gitlab.com/gitlab-org/duo-ui/commit/24c2548b2576fd70e5e93511f7107d965dd6361d))
|
|
19
|
+
|
|
1
20
|
# [8.6.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.5.0...v8.6.0) (2025-03-11)
|
|
2
21
|
|
|
3
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GlButton, GlIcon } from '@gitlab/ui';
|
|
2
2
|
import { translate } from '../../../../utils/i18n';
|
|
3
|
-
import {
|
|
3
|
+
import { formatLocalizedDate } from '../../../../utils/date';
|
|
4
4
|
import DuoChatThreadsEmpty from './duo_chat_threads_empty';
|
|
5
5
|
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
|
|
6
6
|
|
|
@@ -19,11 +19,15 @@ var script = {
|
|
|
19
19
|
threads: {
|
|
20
20
|
type: Array,
|
|
21
21
|
required: true
|
|
22
|
+
},
|
|
23
|
+
preferredLocale: {
|
|
24
|
+
type: Array,
|
|
25
|
+
required: true
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
28
|
computed: {
|
|
25
|
-
|
|
26
|
-
return
|
|
29
|
+
formattedLocalDate() {
|
|
30
|
+
return date => formatLocalizedDate(date, this.preferredLocale);
|
|
27
31
|
},
|
|
28
32
|
groupedThreads() {
|
|
29
33
|
return this.threads.reduce((threadsGroupedByDate, thread) => {
|
|
@@ -61,7 +65,7 @@ var script = {
|
|
|
61
65
|
const __vue_script__ = script;
|
|
62
66
|
|
|
63
67
|
/* template */
|
|
64
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-h-full gl-p-5"},[_c('div',{staticClass:"gl-bg-gray-50 gl-text-gray-500 gl-p-4 gl-mb-5 gl-rounded-base",attrs:{"data-testid":"chat-threads-info-banner"}},[_c('p',{staticClass:"gl-m-0 gl-flex"},[_c('gl-icon',{staticClass:"gl-mr-4",attrs:{"name":"bulb"}}),_vm._v(_vm._s(_vm.$options.i18n.CHAT_HISTORY_INFO)+"\n ")],1)]),_vm._v(" "),(_vm.hasThreads)?_vm._l((_vm.groupedThreads),function(threadsForDate,date){return _c('div',{key:date},[_c('div',{staticClass:"gl-font-bold gl-neutral-900 gl-mb-4",attrs:{"data-testid":"chat-threads-date-header"}},[_vm._v("\n "+_vm._s(_vm.
|
|
68
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-h-full gl-p-5"},[_c('div',{staticClass:"gl-bg-gray-50 gl-text-gray-500 gl-p-4 gl-mb-5 gl-rounded-base",attrs:{"data-testid":"chat-threads-info-banner"}},[_c('p',{staticClass:"gl-m-0 gl-flex"},[_c('gl-icon',{staticClass:"gl-mr-4",attrs:{"name":"bulb"}}),_vm._v(_vm._s(_vm.$options.i18n.CHAT_HISTORY_INFO)+"\n ")],1)]),_vm._v(" "),(_vm.hasThreads)?_vm._l((_vm.groupedThreads),function(threadsForDate,date){return _c('div',{key:date},[_c('div',{staticClass:"gl-font-bold gl-neutral-900 gl-mb-4",attrs:{"data-testid":"chat-threads-date-header"}},[_vm._v("\n "+_vm._s(_vm.formattedLocalDate(date))+"\n ")]),_vm._v(" "),_c('div',_vm._l((threadsForDate),function(thread){return _c('div',{key:thread.id,staticClass:"gl-flex gl-align-center gl-mb-4"},[_c('div',{staticClass:"thread-box hover:gl-bg-gray-50 focus:gl-bg-gray-50 gl-text-ellipsis gl-overflow-hidden gl-rounded-base gl-cursor-pointer gl-rounded-base gl-p-4 gl-w-full gl-whitespace-nowrap",attrs:{"tabindex":"0","data-testid":"chat-threads-thread-box"},on:{"click":function($event){return _vm.onSelectThread(thread)}}},[_vm._v("\n "+_vm._s(thread.title || 'Untitled Chat')+"\n ")]),_vm._v(" "),_c('gl-button',{staticClass:"gl-neutral-900 !gl-p-4",attrs:{"data-testid":"chat-threads-delete-thread-button","icon":"remove","category":"tertiary","size":"small","aria-label":_vm.$options.i18n.THREAD_DELETE_LABEL},on:{"click":function($event){return _vm.$emit('delete-thread', thread.id)}}})],1)}),0)])}):_c('duo-chat-threads-empty')],2)};
|
|
65
69
|
var __vue_staticRenderFns__ = [];
|
|
66
70
|
|
|
67
71
|
/* style */
|
|
@@ -33,6 +33,14 @@ const isThread = thread => typeof thread === 'object' && typeof thread.id === 's
|
|
|
33
33
|
|
|
34
34
|
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
35
35
|
const threadListValidator = threads => threads.every(isThread);
|
|
36
|
+
const localeValidator = value => {
|
|
37
|
+
try {
|
|
38
|
+
Intl.getCanonicalLocales(value);
|
|
39
|
+
return true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
36
44
|
var script = {
|
|
37
45
|
name: 'DuoChat',
|
|
38
46
|
components: {
|
|
@@ -232,6 +240,16 @@ var script = {
|
|
|
232
240
|
type: Boolean,
|
|
233
241
|
required: false,
|
|
234
242
|
default: false
|
|
243
|
+
},
|
|
244
|
+
/**
|
|
245
|
+
* The preferred locale for the chat interface.
|
|
246
|
+
* Follows BCP 47 language tag format (e.g., 'en-US', 'fr-FR', 'es-ES').
|
|
247
|
+
*/
|
|
248
|
+
preferredLocale: {
|
|
249
|
+
type: Array,
|
|
250
|
+
required: false,
|
|
251
|
+
default: () => ['en-US', 'en'],
|
|
252
|
+
validator: localeValidator
|
|
235
253
|
}
|
|
236
254
|
},
|
|
237
255
|
data() {
|
|
@@ -244,7 +262,8 @@ var script = {
|
|
|
244
262
|
compositionJustEnded: false,
|
|
245
263
|
contextItemsMenuIsOpen: false,
|
|
246
264
|
contextItemMenuRef: null,
|
|
247
|
-
currentView: this.multiThreadedView
|
|
265
|
+
currentView: this.multiThreadedView,
|
|
266
|
+
headerHeight: 0
|
|
248
267
|
};
|
|
249
268
|
},
|
|
250
269
|
computed: {
|
|
@@ -351,8 +370,13 @@ var script = {
|
|
|
351
370
|
},
|
|
352
371
|
mounted() {
|
|
353
372
|
this.scrollToBottom();
|
|
373
|
+
this.positionContent();
|
|
354
374
|
},
|
|
355
375
|
methods: {
|
|
376
|
+
positionContent() {
|
|
377
|
+
var _this$$refs, _this$$refs$header, _this$$refs$header$$e, _this$$refs$header$$e2;
|
|
378
|
+
this.headerHeight = ((_this$$refs = this.$refs) === null || _this$$refs === void 0 ? void 0 : (_this$$refs$header = _this$$refs.header) === null || _this$$refs$header === void 0 ? void 0 : (_this$$refs$header$$e = _this$$refs$header.$el) === null || _this$$refs$header$$e === void 0 ? void 0 : (_this$$refs$header$$e2 = _this$$refs$header$$e.getBoundingClientRect()) === null || _this$$refs$header$$e2 === void 0 ? void 0 : _this$$refs$header$$e2.height) || 0;
|
|
379
|
+
},
|
|
356
380
|
onGoBack() {
|
|
357
381
|
this.$emit('back-to-list');
|
|
358
382
|
},
|
|
@@ -574,7 +598,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
|
|
|
574
598
|
},attrs:{"width":_vm.shouldRenderResizable ? _vm.dimensions.width : null,"height":_vm.shouldRenderResizable ? _vm.dimensions.height : null,"max-width":_vm.shouldRenderResizable ? _vm.dimensions.maxWidth : null,"max-height":_vm.shouldRenderResizable ? _vm.dimensions.maxHeight : null,"min-width":_vm.shouldRenderResizable ? _vm.dimensions.minWidth : null,"left":_vm.shouldRenderResizable ? _vm.dimensions.left : null,"top":_vm.shouldRenderResizable ? _vm.dimensions.top : null,"fit-parent":true,"min-height":_vm.shouldRenderResizable ? _vm.dimensions.minHeight : null,"active":_vm.shouldRenderResizable ? ['l', 't', 'lt'] : null},on:{"resize:end":_vm.updateSize}},[(!_vm.isHidden)?_c('aside',{staticClass:"markdown-code-block duo-chat gl-bottom-0 gl-max-h-full",class:{
|
|
575
599
|
'resizable-content': _vm.shouldRenderResizable,
|
|
576
600
|
'duo-chat-drawer': !_vm.shouldRenderResizable,
|
|
577
|
-
},attrs:{"id":"chat-component","role":"complementary","data-testid":"chat-component"}},[(_vm.showHeader)?_c('duo-chat-header',{attrs:{"active-thread-id":_vm.activeThreadId,"title":_vm.isMultithreaded && _vm.currentView === 'list' ? _vm.$options.i18n.CHAT_HISTORY_TITLE : _vm.title,"error":_vm.error,"is-multithreaded":_vm.isMultithreaded,"current-view":_vm.currentView,"should-render-resizable":_vm.shouldRenderResizable,"badge-type":_vm.isMultithreaded ? null : _vm.badgeType},on:{"go-back":_vm.onGoBack,"new-chat":_vm.onNewChat,"close":_vm.hideChat},scopedSlots:_vm._u([{key:"subheader",fn:function(){return [_vm._t("subheader")]},proxy:true}],null,true)}):_vm._e(),_vm._v(" "),(_vm.shouldShowThreadList)?_c('div',{staticClass:"gl-h-full"},[_c('duo-chat-threads',{attrs:{"threads":_vm.threadList},on:{"new-chat":_vm.onNewChat,"select-thread":_vm.onSelectThread,"delete-thread":_vm.onDeleteThread,"close":_vm.hideChat}})],1):_c('span',{staticClass:"gl-h-full gl-flex gl-flex-col gl-justify-end"},[_c('div',{staticClass:"duo-chat-drawer-body gl-bg-default",attrs:{"data-testid":"chat-history"},on:{"scroll":_vm.handleScrollingTrottled}},[_c('transition-group',{staticClass:"duo-chat-history gl-flex gl-flex-col gl-justify-end",class:[
|
|
601
|
+
},attrs:{"id":"chat-component","role":"complementary","data-testid":"chat-component"}},[(_vm.showHeader)?_c('duo-chat-header',{ref:"header",attrs:{"active-thread-id":_vm.activeThreadId,"title":_vm.isMultithreaded && _vm.currentView === 'list' ? _vm.$options.i18n.CHAT_HISTORY_TITLE : _vm.title,"error":_vm.error,"is-multithreaded":_vm.isMultithreaded,"current-view":_vm.currentView,"should-render-resizable":_vm.shouldRenderResizable,"badge-type":_vm.isMultithreaded ? null : _vm.badgeType},on:{"go-back":_vm.onGoBack,"new-chat":_vm.onNewChat,"close":_vm.hideChat},scopedSlots:_vm._u([{key:"subheader",fn:function(){return [_vm._t("subheader")]},proxy:true}],null,true)}):_vm._e(),_vm._v(" "),(_vm.shouldShowThreadList)?_c('div',{staticClass:"gl-h-full"},[_c('duo-chat-threads',{attrs:{"threads":_vm.threadList,"preferred-locale":_vm.preferredLocale},on:{"new-chat":_vm.onNewChat,"select-thread":_vm.onSelectThread,"delete-thread":_vm.onDeleteThread,"close":_vm.hideChat}})],1):_c('span',{staticClass:"gl-h-full gl-flex gl-flex-col gl-justify-end"},[_c('div',{staticClass:"duo-chat-drawer-body gl-bg-default",style:({ marginTop: (_vm.headerHeight + "px") }),attrs:{"data-testid":"chat-history"},on:{"scroll":_vm.handleScrollingTrottled}},[_c('transition-group',{staticClass:"duo-chat-history gl-flex gl-flex-col gl-justify-end",class:[
|
|
578
602
|
{
|
|
579
603
|
'gl-h-full': !_vm.hasMessages,
|
|
580
604
|
'force-scroll-bar': _vm.hasMessages,
|
package/dist/utils/date.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const DEFAULT_LOCALE = ['en-US'];
|
|
1
2
|
function getOrdinalSuffix(day) {
|
|
2
3
|
if (day > 3 && day < 21) return 'th';
|
|
3
4
|
switch (day % 10) {
|
|
@@ -11,17 +12,39 @@ function getOrdinalSuffix(day) {
|
|
|
11
12
|
return 'th';
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
const isValidDateString = dateStr => {
|
|
16
|
+
if (!dateStr) return false;
|
|
15
17
|
const date = new Date(dateStr);
|
|
18
|
+
return date.toString() !== 'Invalid Date';
|
|
19
|
+
};
|
|
20
|
+
const getValidLocale = preferredLocale => {
|
|
21
|
+
const locale = preferredLocale !== null && preferredLocale !== void 0 && preferredLocale.length ? preferredLocale : DEFAULT_LOCALE;
|
|
22
|
+
const supportedLocales = Intl.DateTimeFormat.supportedLocalesOf(locale);
|
|
23
|
+
return supportedLocales.length ? supportedLocales : DEFAULT_LOCALE;
|
|
24
|
+
};
|
|
25
|
+
const createDateFormatter = locale => {
|
|
26
|
+
return new Intl.DateTimeFormat(locale, {
|
|
27
|
+
month: 'long',
|
|
28
|
+
day: 'numeric',
|
|
29
|
+
year: 'numeric'
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const formatEnglishDate = (date, formatter) => {
|
|
16
33
|
const day = date.getDate();
|
|
17
34
|
const suffix = getOrdinalSuffix(day);
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
const parts = formatter.formatToParts(date);
|
|
36
|
+
const formattedParts = parts.map(part => part.type === 'day' ? `${part.value}${suffix}` : part.value);
|
|
37
|
+
return formattedParts.join('');
|
|
38
|
+
};
|
|
39
|
+
function formatLocalizedDate(dateStr, preferredLocale) {
|
|
40
|
+
var _validLocale$;
|
|
41
|
+
if (!isValidDateString(dateStr)) {
|
|
42
|
+
return 'Invalid Date';
|
|
43
|
+
}
|
|
44
|
+
const date = new Date(dateStr);
|
|
45
|
+
const validLocale = getValidLocale(preferredLocale);
|
|
46
|
+
const formatter = createDateFormatter(validLocale);
|
|
47
|
+
return (_validLocale$ = validLocale[0]) !== null && _validLocale$ !== void 0 && _validLocale$.startsWith('en') ? formatEnglishDate(date, formatter) : formatter.format(date);
|
|
25
48
|
}
|
|
26
49
|
|
|
27
|
-
export {
|
|
50
|
+
export { formatLocalizedDate, getOrdinalSuffix };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { GlButton, GlIcon } from '@gitlab/ui';
|
|
3
3
|
import { translate } from '../../../../utils/i18n';
|
|
4
|
-
import {
|
|
4
|
+
import { formatLocalizedDate } from '../../../../utils/date';
|
|
5
5
|
import DuoChatThreadsEmpty from './duo_chat_threads_empty.vue';
|
|
6
6
|
|
|
7
7
|
const i18n = {
|
|
@@ -26,11 +26,15 @@ export default {
|
|
|
26
26
|
type: Array,
|
|
27
27
|
required: true,
|
|
28
28
|
},
|
|
29
|
+
preferredLocale: {
|
|
30
|
+
type: Array,
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
29
33
|
},
|
|
30
34
|
|
|
31
35
|
computed: {
|
|
32
|
-
|
|
33
|
-
return
|
|
36
|
+
formattedLocalDate() {
|
|
37
|
+
return (date) => formatLocalizedDate(date, this.preferredLocale);
|
|
34
38
|
},
|
|
35
39
|
|
|
36
40
|
groupedThreads() {
|
|
@@ -86,7 +90,7 @@ export default {
|
|
|
86
90
|
<template v-if="hasThreads">
|
|
87
91
|
<div v-for="(threadsForDate, date) in groupedThreads" :key="date">
|
|
88
92
|
<div data-testid="chat-threads-date-header" class="gl-font-bold gl-neutral-900 gl-mb-4">
|
|
89
|
-
{{
|
|
93
|
+
{{ formattedLocalDate(date) }}
|
|
90
94
|
</div>
|
|
91
95
|
|
|
92
96
|
<div>
|
|
@@ -86,6 +86,15 @@ const isThread = (thread) =>
|
|
|
86
86
|
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
87
87
|
const threadListValidator = (threads) => threads.every(isThread);
|
|
88
88
|
|
|
89
|
+
const localeValidator = (value) => {
|
|
90
|
+
try {
|
|
91
|
+
Intl.getCanonicalLocales(value);
|
|
92
|
+
return true;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
89
98
|
export default {
|
|
90
99
|
name: 'DuoChat',
|
|
91
100
|
components: {
|
|
@@ -287,6 +296,16 @@ export default {
|
|
|
287
296
|
required: false,
|
|
288
297
|
default: false,
|
|
289
298
|
},
|
|
299
|
+
/**
|
|
300
|
+
* The preferred locale for the chat interface.
|
|
301
|
+
* Follows BCP 47 language tag format (e.g., 'en-US', 'fr-FR', 'es-ES').
|
|
302
|
+
*/
|
|
303
|
+
preferredLocale: {
|
|
304
|
+
type: Array,
|
|
305
|
+
required: false,
|
|
306
|
+
default: () => ['en-US', 'en'],
|
|
307
|
+
validator: localeValidator,
|
|
308
|
+
},
|
|
290
309
|
},
|
|
291
310
|
data() {
|
|
292
311
|
return {
|
|
@@ -299,6 +318,7 @@ export default {
|
|
|
299
318
|
contextItemsMenuIsOpen: false,
|
|
300
319
|
contextItemMenuRef: null,
|
|
301
320
|
currentView: this.multiThreadedView,
|
|
321
|
+
headerHeight: 0,
|
|
302
322
|
};
|
|
303
323
|
},
|
|
304
324
|
computed: {
|
|
@@ -418,9 +438,13 @@ export default {
|
|
|
418
438
|
},
|
|
419
439
|
mounted() {
|
|
420
440
|
this.scrollToBottom();
|
|
441
|
+
this.positionContent();
|
|
421
442
|
},
|
|
422
443
|
|
|
423
444
|
methods: {
|
|
445
|
+
positionContent() {
|
|
446
|
+
this.headerHeight = this.$refs?.header?.$el?.getBoundingClientRect()?.height || 0;
|
|
447
|
+
},
|
|
424
448
|
onGoBack() {
|
|
425
449
|
this.$emit('back-to-list');
|
|
426
450
|
},
|
|
@@ -665,6 +689,7 @@ export default {
|
|
|
665
689
|
>
|
|
666
690
|
<duo-chat-header
|
|
667
691
|
v-if="showHeader"
|
|
692
|
+
ref="header"
|
|
668
693
|
:active-thread-id="activeThreadId"
|
|
669
694
|
:title="
|
|
670
695
|
isMultithreaded && currentView === 'list' ? $options.i18n.CHAT_HISTORY_TITLE : title
|
|
@@ -686,6 +711,7 @@ export default {
|
|
|
686
711
|
<div v-if="shouldShowThreadList" class="gl-h-full">
|
|
687
712
|
<duo-chat-threads
|
|
688
713
|
:threads="threadList"
|
|
714
|
+
:preferred-locale="preferredLocale"
|
|
689
715
|
@new-chat="onNewChat"
|
|
690
716
|
@select-thread="onSelectThread"
|
|
691
717
|
@delete-thread="onDeleteThread"
|
|
@@ -696,6 +722,7 @@ export default {
|
|
|
696
722
|
<div
|
|
697
723
|
class="duo-chat-drawer-body gl-bg-default"
|
|
698
724
|
data-testid="chat-history"
|
|
725
|
+
:style="{ marginTop: `${headerHeight}px` }"
|
|
699
726
|
@scroll="handleScrollingTrottled"
|
|
700
727
|
>
|
|
701
728
|
<transition-group
|
package/src/utils/date.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const DEFAULT_LOCALE = ['en-US'];
|
|
2
|
+
|
|
1
3
|
export function getOrdinalSuffix(day) {
|
|
2
4
|
if (day > 3 && day < 21) return 'th';
|
|
3
5
|
switch (day % 10) {
|
|
@@ -12,13 +14,48 @@ export function getOrdinalSuffix(day) {
|
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
const isValidDateString = (dateStr) => {
|
|
18
|
+
if (!dateStr) return false;
|
|
16
19
|
const date = new Date(dateStr);
|
|
20
|
+
return date.toString() !== 'Invalid Date';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getValidLocale = (preferredLocale) => {
|
|
24
|
+
const locale = preferredLocale?.length ? preferredLocale : DEFAULT_LOCALE;
|
|
25
|
+
const supportedLocales = Intl.DateTimeFormat.supportedLocalesOf(locale);
|
|
26
|
+
return supportedLocales.length ? supportedLocales : DEFAULT_LOCALE;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const createDateFormatter = (locale) => {
|
|
30
|
+
return new Intl.DateTimeFormat(locale, {
|
|
31
|
+
month: 'long',
|
|
32
|
+
day: 'numeric',
|
|
33
|
+
year: 'numeric',
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const formatEnglishDate = (date, formatter) => {
|
|
17
38
|
const day = date.getDate();
|
|
18
39
|
const suffix = getOrdinalSuffix(day);
|
|
19
40
|
|
|
20
|
-
const
|
|
21
|
-
const
|
|
41
|
+
const parts = formatter.formatToParts(date);
|
|
42
|
+
const formattedParts = parts.map((part) =>
|
|
43
|
+
part.type === 'day' ? `${part.value}${suffix}` : part.value
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return formattedParts.join('');
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function formatLocalizedDate(dateStr, preferredLocale) {
|
|
50
|
+
if (!isValidDateString(dateStr)) {
|
|
51
|
+
return 'Invalid Date';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const date = new Date(dateStr);
|
|
55
|
+
const validLocale = getValidLocale(preferredLocale);
|
|
56
|
+
const formatter = createDateFormatter(validLocale);
|
|
22
57
|
|
|
23
|
-
return
|
|
58
|
+
return validLocale[0]?.startsWith('en')
|
|
59
|
+
? formatEnglishDate(date, formatter)
|
|
60
|
+
: formatter.format(date);
|
|
24
61
|
}
|