diff --git a/packages/twenty-front/src/localization/states/dateLocaleState.ts b/packages/twenty-front/src/localization/states/dateLocaleState.ts new file mode 100644 index 000000000..9580ef68c --- /dev/null +++ b/packages/twenty-front/src/localization/states/dateLocaleState.ts @@ -0,0 +1,17 @@ +import { Locale } from 'date-fns'; +import { enUS } from 'date-fns/locale'; +import { APP_LOCALES } from 'twenty-shared/translations'; +import { createState } from 'twenty-ui/utilities'; + +type DateLocaleState = { + locale?: keyof typeof APP_LOCALES; + localeCatalog: Locale; +}; + +export const dateLocaleState = createState({ + key: 'dateLocaleState', + defaultValue: { + locale: undefined, + localeCatalog: enUS, + }, +}); diff --git a/packages/twenty-front/src/modules/localization/utils/__tests__/formatDateISOStringToCustomUnicodeFormat.test.js b/packages/twenty-front/src/modules/localization/utils/__tests__/formatDateISOStringToCustomUnicodeFormat.test.js index 11c27143e..48f6d8b98 100644 --- a/packages/twenty-front/src/modules/localization/utils/__tests__/formatDateISOStringToCustomUnicodeFormat.test.js +++ b/packages/twenty-front/src/modules/localization/utils/__tests__/formatDateISOStringToCustomUnicodeFormat.test.js @@ -1,5 +1,6 @@ import { formatDateISOStringToCustomUnicodeFormat } from '@/localization/utils/formatDateISOStringToCustomUnicodeFormat'; import { formatInTimeZone } from 'date-fns-tz'; +import { enUS } from 'date-fns/locale'; jest.mock('date-fns-tz'); @@ -15,16 +16,18 @@ describe('formatDateISOStringToCustomUnicodeFormat', () => { it('should use provided timezone', () => { formatInTimeZone.mockReturnValue('06:30'); - const result = formatDateISOStringToCustomUnicodeFormat( - mockDate, - mockTimeZone, - mockTimeFormat, - ); + const result = formatDateISOStringToCustomUnicodeFormat({ + date: mockDate, + timeZone: mockTimeZone, + dateFormat: mockTimeFormat, + localeCatalog: enUS, + }); expect(formatInTimeZone).toHaveBeenCalledWith( new Date(mockDate), mockTimeZone, mockTimeFormat, + { locale: enUS }, ); expect(result).toBe('06:30'); }); @@ -34,16 +37,18 @@ describe('formatDateISOStringToCustomUnicodeFormat', () => { throw new Error(); }); - const result = formatDateISOStringToCustomUnicodeFormat( - mockDate, - mockTimeZone, - 'f', - ); + const result = formatDateISOStringToCustomUnicodeFormat({ + date: mockDate, + timeZone: mockTimeZone, + dateFormat: 'f', + localeCatalog: enUS, + }); expect(formatInTimeZone).toHaveBeenCalledWith( new Date(mockDate), mockTimeZone, 'f', + { locale: enUS }, ); expect(result).toBe('Invalid format string'); }); diff --git a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToCustomUnicodeFormat.ts b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToCustomUnicodeFormat.ts index 18ff0b81e..d604742f9 100644 --- a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToCustomUnicodeFormat.ts +++ b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToCustomUnicodeFormat.ts @@ -1,12 +1,20 @@ import { formatInTimeZone } from 'date-fns-tz'; -export const formatDateISOStringToCustomUnicodeFormat = ( - date: string, - timeZone: string, - dateFormat: string, -) => { +export const formatDateISOStringToCustomUnicodeFormat = ({ + date, + timeZone, + dateFormat, + localeCatalog, +}: { + date: string; + timeZone: string; + dateFormat: string; + localeCatalog: Locale; +}) => { try { - return formatInTimeZone(new Date(date), timeZone, dateFormat); + return formatInTimeZone(new Date(date), timeZone, dateFormat, { + locale: localeCatalog, + }); } catch (e) { return 'Invalid format string'; } diff --git a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDate.ts b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDate.ts index 9ae3efda1..e90a25358 100644 --- a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDate.ts +++ b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDate.ts @@ -1,10 +1,18 @@ import { DateFormat } from '@/localization/constants/DateFormat'; import { formatInTimeZone } from 'date-fns-tz'; -export const formatDateISOStringToDate = ( - date: string, - timeZone: string, - dateFormat: DateFormat, -) => { - return formatInTimeZone(new Date(date), timeZone, dateFormat); +export const formatDateISOStringToDate = ({ + date, + timeZone, + dateFormat, + localeCatalog, +}: { + date: string; + timeZone: string; + dateFormat: DateFormat; + localeCatalog?: Locale; +}) => { + return formatInTimeZone(new Date(date), timeZone, dateFormat, { + locale: localeCatalog, + }); }; diff --git a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDateTime.ts b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDateTime.ts index ad8389251..45487484d 100644 --- a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDateTime.ts +++ b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDateTime.ts @@ -2,15 +2,25 @@ import { DateFormat } from '@/localization/constants/DateFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat'; import { formatInTimeZone } from 'date-fns-tz'; -export const formatDateISOStringToDateTime = ( - date: string, - timeZone: string, - dateFormat: DateFormat, - timeFormat: TimeFormat, -) => { +export const formatDateISOStringToDateTime = ({ + date, + timeZone, + dateFormat, + timeFormat, + localeCatalog, +}: { + date: string; + timeZone: string; + dateFormat: DateFormat; + timeFormat: TimeFormat; + localeCatalog: Locale; +}) => { return formatInTimeZone( new Date(date), timeZone, `${dateFormat} ${timeFormat}`, + { + locale: localeCatalog, + }, ); }; diff --git a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToRelativeDate.ts b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToRelativeDate.ts index 2bcc93a30..99ffdade6 100644 --- a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToRelativeDate.ts +++ b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToRelativeDate.ts @@ -1,25 +1,40 @@ +import { t } from '@lingui/core/macro'; import { differenceInDays, formatDistance, isToday, + isTomorrow, + isYesterday, + Locale, startOfDay, } from 'date-fns'; -export const formatDateISOStringToRelativeDate = ( - isoDate: string, +export const formatDateISOStringToRelativeDate = ({ + isoDate, isDayMaximumPrecision = false, -) => { + localeCatalog, +}: { + isoDate: string; + isDayMaximumPrecision?: boolean; + localeCatalog: Locale; +}) => { const now = new Date(); const targetDate = new Date(isoDate); - if (isDayMaximumPrecision && isToday(targetDate)) return 'Today'; + if (isDayMaximumPrecision && isToday(targetDate)) return t`Today`; + if (isDayMaximumPrecision && isYesterday(targetDate)) return t`Yesterday`; + if (isDayMaximumPrecision && isTomorrow(targetDate)) return t`Tomorrow`; const isWithin24h = Math.abs(differenceInDays(targetDate, now)) < 1; if (isDayMaximumPrecision || !isWithin24h) return formatDistance(startOfDay(targetDate), startOfDay(now), { addSuffix: true, + locale: localeCatalog, }); - return formatDistance(targetDate, now, { addSuffix: true }); + return formatDistance(targetDate, now, { + addSuffix: true, + locale: localeCatalog, + }); }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts index 463d22ddd..dd3871906 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts @@ -6,6 +6,7 @@ import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords' import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; +import { enUS } from 'date-fns/locale'; import { FieldMetadataType } from '~/generated/graphql'; const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a'; @@ -35,6 +36,7 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadata, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.sum, + localeCatalog: enUS, ...defaultParams, }); @@ -53,6 +55,7 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadata, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.sum, + localeCatalog: enUS, ...defaultParams, }); @@ -90,6 +93,7 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadataWithPercentageField, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.avg, + localeCatalog: enUS, ...defaultParams, }); @@ -127,6 +131,7 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadataWithDecimalsField, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.sum, + localeCatalog: enUS, ...defaultParams, }); @@ -161,6 +166,7 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadataWithDatetimeField, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: DATE_AGGREGATE_OPERATIONS.earliest, + localeCatalog: enUS, ...defaultParams, }); @@ -195,6 +201,7 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadataWithDatetimeField, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: DATE_AGGREGATE_OPERATIONS.latest, + localeCatalog: enUS, ...defaultParams, }); @@ -215,6 +222,7 @@ describe('computeAggregateValueAndLabel', () => { const result = computeAggregateValueAndLabel({ data: mockData, objectMetadataItem: mockObjectMetadata, + localeCatalog: enUS, ...defaultParams, }); @@ -237,6 +245,7 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadata, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.sum, + localeCatalog: enUS, ...defaultParams, }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts index ccbb1c71a..f8aeccc88 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts @@ -26,6 +26,7 @@ export const computeAggregateValueAndLabel = ({ dateFormat, timeFormat, timeZone, + localeCatalog, }: { data: AggregateRecordsData; objectMetadataItem: ObjectMetadataItem; @@ -34,6 +35,7 @@ export const computeAggregateValueAndLabel = ({ dateFormat: DateFormat; timeFormat: TimeFormat; timeZone: string; + localeCatalog: Locale; }) => { if (isEmpty(data)) { return {}; @@ -105,6 +107,7 @@ export const computeAggregateValueAndLabel = ({ dateFormat, timeFormat, dateFieldSettings, + localeCatalog, }); break; } @@ -116,6 +119,7 @@ export const computeAggregateValueAndLabel = ({ timeZone, dateFormat, dateFieldSettings, + localeCatalog, }); break; } diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateFieldDisplay.perf.stories.tsx index b7c2d5849..cc9b1ccc2 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateFieldDisplay.perf.stories.tsx @@ -4,10 +4,10 @@ import { DateFormat } from '@/localization/constants/DateFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat'; import { DateFieldDisplay } from '@/object-record/record-field/meta-types/display/components/DateFieldDisplay'; import { UserContext } from '@/users/contexts/UserContext'; +import { ComponentDecorator } from 'twenty-ui/testing'; import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; -import { ComponentDecorator } from 'twenty-ui/testing'; const meta: Meta = { title: 'UI/Data/Field/Display/DateFieldDisplay', diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx index 4b4c96aac..83d18bcf6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx @@ -4,10 +4,10 @@ import { DateFormat } from '@/localization/constants/DateFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat'; import { DateTimeFieldDisplay } from '@/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay'; import { UserContext } from '@/users/contexts/UserContext'; +import { ComponentDecorator } from 'twenty-ui/testing'; import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; -import { ComponentDecorator } from 'twenty-ui/testing'; const meta: Meta = { title: 'UI/Data/Field/Display/DateTimeFieldDisplay', diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx index e5bad9c33..7d4f3165c 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx @@ -1,6 +1,7 @@ /* eslint-disable @nx/workspace-no-navigate-prefer-link */ import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; import { SettingsPath } from '@/types/SettingsPath'; +import { t } from '@lingui/core/macro'; import { IconSettings } from 'twenty-ui/display'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; @@ -13,9 +14,9 @@ export const RecordTableEmptyStateRemote = () => { return ( { - const { dateFormat, timeZone } = useContext(UserContext); + const { dateFormat } = useContext(UserContext); + const dateLocale = useRecoilValue(dateLocaleState); const formattedDate = formatDateString({ value, - timeZone, + timeZone: 'UTC', // Needed because we have db-stored date (yyyy-mm-dd) is converted to UTC dateTime by TypeORM dateFormat, dateFieldSettings, + localeCatalog: dateLocale.localeCatalog, }); return {formattedDate}; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx index eadb5185c..b6233702a 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx @@ -1,6 +1,8 @@ import { FieldDateMetadataSettings } from '@/object-record/record-field/types/FieldMetadata'; import { UserContext } from '@/users/contexts/UserContext'; import { useContext } from 'react'; +import { useRecoilValue } from 'recoil'; +import { dateLocaleState } from '~/localization/states/dateLocaleState'; import { formatDateTimeString } from '~/utils/string/formatDateTimeString'; import { EllipsisDisplay } from './EllipsisDisplay'; @@ -14,6 +16,7 @@ export const DateTimeDisplay = ({ dateFieldSettings, }: DateTimeDisplayProps) => { const { dateFormat, timeFormat, timeZone } = useContext(UserContext); + const dateLocale = useRecoilValue(dateLocaleState); const formattedDate = formatDateTimeString({ value, @@ -21,6 +24,7 @@ export const DateTimeDisplay = ({ dateFormat, timeFormat, dateFieldSettings, + localeCatalog: dateLocale.localeCatalog, }); return {formattedDate}; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/__stories__/DateDisplay.stories.tsx b/packages/twenty-front/src/modules/ui/field/display/components/__stories__/DateDisplay.stories.tsx new file mode 100644 index 000000000..1b2a7513c --- /dev/null +++ b/packages/twenty-front/src/modules/ui/field/display/components/__stories__/DateDisplay.stories.tsx @@ -0,0 +1,44 @@ +import { DateFormat } from '@/localization/constants/DateFormat'; +import { TimeFormat } from '@/localization/constants/TimeFormat'; +import { FieldDateDisplayFormat } from '@/object-record/record-field/types/FieldMetadata'; +import { UserContext } from '@/users/contexts/UserContext'; +import { expect } from '@storybook/jest'; +import type { Meta, StoryObj } from '@storybook/react'; +import { within } from '@storybook/testing-library'; +import { DateDisplay } from '../DateDisplay'; + +const meta: Meta = { + title: 'UI/Field/Display/DateDisplay', + component: DateDisplay, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + value: '2025-05-01T00:00:00.000Z', + dateFieldSettings: { + displayFormat: FieldDateDisplayFormat.USER_SETTINGS, + }, + }, + play: async ({ canvasElement }) => { + // Test that date is rightfully displayed and not converted to timeZone date which would be on April 30th + const canvas = within(canvasElement); + const dateElement = await canvas.findByText('1 May, 2025'); + expect(dateElement).toBeInTheDocument(); + }, +}; diff --git a/packages/twenty-front/src/modules/ui/field/display/utils/getDateFnsLocale.util.ts b/packages/twenty-front/src/modules/ui/field/display/utils/getDateFnsLocale.util.ts new file mode 100644 index 000000000..a9b1362f9 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/field/display/utils/getDateFnsLocale.util.ts @@ -0,0 +1,76 @@ +import { APP_LOCALES } from 'twenty-shared/translations'; + +type AppLocale = keyof typeof APP_LOCALES; +export const getDateFnsLocaleImport = (locale: AppLocale) => { + switch (locale) { + case 'af-ZA': + return import('date-fns/locale/af'); + case 'ar-SA': + return import('date-fns/locale/ar'); + case 'ca-ES': + return import('date-fns/locale/ca'); + case 'cs-CZ': + return import('date-fns/locale/cs'); + case 'da-DK': + return import('date-fns/locale/da'); + case 'de-DE': + return import('date-fns/locale/de'); + case 'el-GR': + return import('date-fns/locale/el'); + case 'en': + case 'pseudo-en': + return import('date-fns/locale/en-US'); + case 'es-ES': + return import('date-fns/locale/es'); + case 'fi-FI': + return import('date-fns/locale/fi'); + case 'fr-FR': + return import('date-fns/locale/fr'); + case 'he-IL': + return import('date-fns/locale/he'); + case 'hu-HU': + return import('date-fns/locale/hu'); + case 'it-IT': + return import('date-fns/locale/it'); + case 'ja-JP': + return import('date-fns/locale/ja'); + case 'ko-KR': + return import('date-fns/locale/ko'); + case 'nl-NL': + return import('date-fns/locale/nl'); + case 'no-NO': + return import('date-fns/locale/nb'); + case 'pl-PL': + return import('date-fns/locale/pl'); + case 'pt-BR': + case 'pt-PT': + return import('date-fns/locale/pt'); + case 'ro-RO': + return import('date-fns/locale/ro'); + case 'ru-RU': + return import('date-fns/locale/ru'); + case 'sr-Cyrl': + return import('date-fns/locale/sr'); + case 'sv-SE': + return import('date-fns/locale/sv'); + case 'tr-TR': + return import('date-fns/locale/tr'); + case 'uk-UA': + return import('date-fns/locale/uk'); + case 'vi-VN': + return import('date-fns/locale/vi'); + case 'zh-CN': + return import('date-fns/locale/zh-CN'); + case 'zh-TW': + return import('date-fns/locale/zh-TW'); + default: { + return import('date-fns/locale/en-US'); + } + } +}; + +export const getDateFnsLocale = async (localeString?: string | null) => { + return getDateFnsLocaleImport(localeString as AppLocale) + .then((m) => m.default as unknown as Locale) + .catch((_e) => undefined); +}; diff --git a/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx b/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx index 1e55953ba..7adbc8356 100644 --- a/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx +++ b/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useRecoilState, useSetRecoilState } from 'recoil'; +import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; @@ -18,12 +18,15 @@ import { detectTimeZone } from '@/localization/utils/detectTimeZone'; import { getDateFormatFromWorkspaceDateFormat } from '@/localization/utils/getDateFormatFromWorkspaceDateFormat'; import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat'; import { AppPath } from '@/types/AppPath'; +import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; +import { enUS } from 'date-fns/locale'; import { useLocation } from 'react-router-dom'; import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; import { isDefined } from 'twenty-shared/utils'; import { WorkspaceMember } from '~/generated-metadata/graphql'; import { useGetCurrentUserQuery } from '~/generated/graphql'; +import { dateLocaleState } from '~/localization/states/dateLocaleState'; import { dynamicActivate } from '~/utils/i18n/dynamicActivate'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; @@ -38,8 +41,22 @@ export const UserProviderEffect = () => { const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState); const setWorkspaces = useSetRecoilState(workspacesState); - const setDateTimeFormat = useSetRecoilState(dateTimeFormatState); + const updateLocaleCatalog = useRecoilCallback( + ({ snapshot, set }) => + async (newLocale: keyof typeof APP_LOCALES) => { + const localeValue = snapshot.getLoadable(dateLocaleState).getValue(); + if (localeValue.locale !== newLocale) { + getDateFnsLocale(newLocale).then((localeCatalog) => { + set(dateLocaleState, { + locale: newLocale, + localeCatalog: localeCatalog || enUS, + }); + }); + } + }, + [], + ); const setCurrentWorkspaceMember = useSetRecoilState( currentWorkspaceMemberState, @@ -98,9 +115,11 @@ export const UserProviderEffect = () => { }; if (isDefined(workspaceMember)) { - setCurrentWorkspaceMember( - affectDefaultValuesOnEmptyWorkspaceMemberFields(workspaceMember), - ); + const updatedWorkspaceMember = + affectDefaultValuesOnEmptyWorkspaceMemberFields(workspaceMember); + setCurrentWorkspaceMember(updatedWorkspaceMember); + + updateLocaleCatalog(updatedWorkspaceMember.locale); // TODO: factorize setDateTimeFormat({ @@ -152,6 +171,7 @@ export const UserProviderEffect = () => { setIsCurrentUserLoaded, setDateTimeFormat, setCurrentWorkspaceMembersWithDeleted, + updateLocaleCatalog, ]); return <>; diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerEditButton.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerEditButton.tsx index 90baaed48..aeee5f20c 100644 --- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerEditButton.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerEditButton.tsx @@ -7,6 +7,7 @@ import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode'; import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState'; import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState'; import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState'; +import { t } from '@lingui/core/macro'; import { Button } from 'twenty-ui/input'; export const ViewPickerEditButton = () => { @@ -49,7 +50,7 @@ export const ViewPickerEditButton = () => { ) { return (