From e577c2d74624bd7e147f7b5c047c962ad9d88819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 9 Oct 2025 18:17:32 +0200 Subject: [PATCH] Update user friendly errors for translations (#15000) Force msg typing instead of string for user friendly errors --- .../modules/apollo/services/apollo.factory.ts | 7 +-- .../graphql-config/graphql-config.service.ts | 3 ++ ...phql-query-create-many-resolver.service.ts | 6 +-- .../graphql/metadata-graphql-api.module.ts | 10 +++- .../api/graphql/metadata.module-factory.ts | 3 ++ .../utils/handle-duplicate-key-error.util.ts | 13 +++-- .../admin-panel/admin-panel.service.ts | 3 +- .../core-modules/api-key/api-key.service.ts | 7 ++- .../approved-access-domain.service.ts | 10 ++-- .../auth/services/auth.service.ts | 8 ++-- .../auth/services/sign-in-up.service.ts | 22 ++++----- .../auth/strategies/jwt.auth.strategy.ts | 4 +- ...auth-graphql-api-exception-handler.util.ts | 8 ++-- .../core-modules/billing/billing.validate.ts | 4 +- .../core-modules/captcha/captcha.guard.ts | 6 ++- .../services/dns-manager.service.ts | 15 +++--- .../validator/dns-manager.validate.ts | 4 +- ...mail-verification-exception-filter.util.ts | 8 ++-- .../hooks/use-graphql-error-handler.hook.ts | 21 +++++--- .../generate-graphql-error-from-error.util.ts | 13 +++-- .../graphql/utils/graphql-errors.util.ts | 3 +- ...mtp-caldav-connection-validator.service.ts | 10 ++-- .../imap-smtp-caldav-connection.service.ts | 22 ++++----- .../public-domain/public-domain.service.ts | 6 +-- .../utils/transform-phones-value.util.ts | 14 +++--- ...or-authentication-exception.filter.spec.ts | 7 ++- ...-factor-authentication-exception.filter.ts | 4 +- .../user/services/user.service.ts | 4 +- .../core-modules/webhook/webhook.service.ts | 7 ++- .../workspace/services/workspace.service.ts | 10 ++-- .../impersonate-permission-guard.spec.ts | 12 +++-- .../guards/impersonate-permission.guard.ts | 6 +-- .../guards/settings-permissions.guard.ts | 4 +- .../field-metadata/field-metadata.resolver.ts | 38 +++++++++++---- ...adata-graphql-api-exception.interceptor.ts | 18 ++++++- .../field-metadata-enum-validation.service.ts | 39 +++++++-------- .../field-metadata-relation.service.ts | 4 +- .../field-metadata-validation.service.ts | 4 +- .../services/field-metadata.service.ts | 8 ++-- ...data-graphql-api-exception-handler.util.ts | 8 +++- ...lat-field-metadatas-to-create.spec.ts.snap | 30 +++++++++--- ...t-field-metadata-type-validator.service.ts | 6 +-- ...at-field-metadata-validation-error.type.ts | 4 +- ...-to-flat-field-metadatas-to-create.util.ts | 4 +- ...ield-input-to-flat-field-metadatas.util.ts | 4 +- ...ield-input-to-flat-field-metadatas.util.ts | 4 +- ...field-input-to-flat-field-metadata.util.ts | 6 +-- .../validate-enum-flat-field-metadata.util.ts | 14 +++--- ...t-field-metadata-name-availability.util.ts | 6 +-- .../validate-flat-field-metadata-name.util.ts | 6 +-- ...ph-or-relation-flat-field-metadata.util.ts | 8 ++-- ...te-morph-relation-creation-payload.util.ts | 12 ++--- ...validate-relation-creation-payload.util.ts | 6 +-- ...t-object-metadata-validation-error.type.ts | 4 +- ...e-flat-object-metadata-identifiers.util.ts | 8 ++-- ...alidate-flat-object-metadata-label.util.ts | 4 +- ...at-object-metadata-name-and-labels.util.ts | 6 +-- ...validate-flat-object-metadata-name.util.ts | 4 +- .../index-field-metadata.exception.ts | 4 +- .../index-metadata/index-metadata.resolver.ts | 11 ++++- .../validate-can-create-unique-index.util.ts | 4 +- ...adata-graphql-api-exception.interceptor.ts | 20 +++++++- .../object-metadata.resolver.ts | 28 ++++++++--- .../object-metadata-field-relation.service.ts | 3 +- ...data-graphql-api-exception-handler.util.ts | 8 +++- .../validate-object-metadata-input.util.ts | 3 +- .../field-permission.service.ts | 26 +++++----- .../object-permission.service.ts | 22 ++++----- .../permission-flag.service.ts | 10 ++-- .../permissions/permissions.service.ts | 13 ++--- ...sion-graphql-api-exception-handler.util.ts | 5 +- .../metadata-modules/role/role.resolver.ts | 5 +- .../metadata-modules/role/role.service.ts | 22 ++++----- .../user-role/user-role.service.ts | 16 +++---- .../compute-metadata-name-from-label.util.ts | 6 +-- .../validate-field-name-availability.utils.ts | 6 +-- ...e-metadata-name-is-not-reserved-keyword.ts | 4 +- ...ect-with-same-name-exists-or-throw.util.ts | 4 +- .../exceptions/view-field.exception.ts | 15 +++--- .../view-field/services/view-field.service.ts | 9 ++-- .../exceptions/view-filter-group.exception.ts | 13 ++--- .../exceptions/view-filter.exception.ts | 13 ++--- .../exceptions/view-group.exception.ts | 13 ++--- .../exceptions/view-sort.exception.ts | 13 ++--- .../view/exceptions/view.exception.ts | 11 +++-- ...view-graphql-api-exception-handler.util.ts | 5 +- .../view-graphql-api-exception.filter.ts | 22 ++++++++- .../compute-twenty-orm-exception.ts | 4 +- ...ectRecordNotFoundErrorMessage.util.spec.ts | 11 ++++- ...tConnectRecordNotFoundErrorMessage.util.ts | 4 +- ...ute-relation-connect-query-configs.util.ts | 12 ++--- .../workspace-datasource.service.ts | 4 +- .../workspace-migration-runner.service.ts | 4 +- .../types/validate-error-response.type.ts | 7 ++- ...ption-to-validation-response-error.util.ts | 15 ++++-- .../utils/translate-validation-errors.util.ts | 48 +++++++++++++++++++ ...igration-builder-exception-v2-formatter.ts | 11 +++-- .../failed-flat-entity-validation.type.ts | 4 +- .../flat-cron-trigger-validator.service.ts | 12 ++--- ...atabase-event-trigger-validator.service.ts | 12 ++--- .../flat-field-metadata-validator.service.ts | 28 +++++------ .../flat-index-metadata-validator.service.ts | 18 +++---- .../flat-object-metadata-validator.service.ts | 18 +++---- .../flat-route-trigger-validator.service.ts | 14 +++--- ...t-serverless-function-validator.service.ts | 8 ++-- .../flat-view-field-validator.service.ts | 24 +++++----- .../services/flat-view-validator.service.ts | 12 ++--- ...-field-metadata-id-flat-view-field.util.ts | 8 ++-- .../microsoft-import-driver.exception.ts | 8 +++- .../utils/assert-workflow-statuses-not-set.ts | 4 +- .../assert-workflow-version-has-steps.ts | 4 +- .../assert-workflow-version-is-draft.util.ts | 4 +- ...orkflow-version-trigger-is-defined.util.ts | 4 +- ...ow-version-validation.workspace-service.ts | 10 ++-- .../workflow-runner.workspace-service.ts | 4 +- .../utils/assert-form-step-is-valid.util.ts | 10 ++-- .../assert-version-can-be-activated.util.ts | 38 +++++++-------- .../compute-cron-pattern-from-schedule.ts | 6 +-- .../workflow-trigger.workspace-service.ts | 6 +-- .../utils/__test__/custom-exception.spec.ts | 10 +++- .../src/utils/custom-exception.ts | 8 ++-- ...eld-metadata-enum.integration-spec.ts.snap | 22 ++++----- ...eld-metadata-enum.integration-spec.ts.snap | 22 ++++----- ...um-field-metadata.integration-spec.ts.snap | 48 +++++++++---------- 124 files changed, 796 insertions(+), 563 deletions(-) create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/translate-validation-errors.util.ts diff --git a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts index 665ee4a2a77..fe242b29029 100644 --- a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts +++ b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts @@ -96,11 +96,14 @@ export class ApolloFactory implements ApolloManager { const authLink = setContext(async (_, { headers }) => { const tokenPair = getTokenPair(); + const locale = this.currentWorkspaceMember?.locale ?? i18n.locale; + if (isUndefinedOrNull(tokenPair)) { return { headers: { ...headers, ...options.headers, + 'x-locale': locale, }, }; } @@ -112,9 +115,7 @@ export class ApolloFactory implements ApolloManager { ...headers, ...options.headers, authorization: token ? `Bearer ${token}` : '', - ...(this.currentWorkspaceMember?.locale - ? { 'x-locale': this.currentWorkspaceMember.locale } - : { 'x-locale': i18n.locale }), + 'x-locale': locale, ...(this.currentWorkspace?.metadataVersion && { 'X-Schema-Version': `${this.currentWorkspace.metadataVersion}`, }), diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts index d0dad56cc14..a2f455011f5 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts @@ -24,6 +24,7 @@ import { CoreEngineModule } from 'src/engine/core-modules/core-engine.module'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { useSentryTracing } from 'src/engine/core-modules/exception-handler/hooks/use-sentry-tracing'; import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { type User } from 'src/engine/core-modules/user/user.entity'; @@ -47,6 +48,7 @@ export class GraphQLConfigService private readonly moduleRef: ModuleRef, private readonly metricsService: MetricsService, private readonly dataloaderService: DataloaderService, + private readonly i18nService: I18nService, ) {} createGqlOptions(): YogaDriverConfig { @@ -63,6 +65,7 @@ export class GraphQLConfigService useGraphQLErrorHandlerHook({ metricsService: this.metricsService, exceptionHandlerService: this.exceptionHandlerService, + i18nService: this.i18nService, twentyConfigService: this.twentyConfigService, }), ]; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index 4760591166f..3d18cc0704e 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { @@ -47,7 +47,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol `Maximum number of records to upsert is ${QUERY_MAX_RECORDS}.`, GraphqlQueryRunnerExceptionCode.UPSERT_MAX_RECORDS_EXCEEDED, { - userFriendlyMessage: t`Maximum number of records to upsert is ${QUERY_MAX_RECORDS}.`, + userFriendlyMessage: msg`Maximum number of records to upsert is ${QUERY_MAX_RECORDS}.`, }, ); } @@ -359,7 +359,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol `Multiple records found with the same unique field values for ${conflictingFieldsValues}. Cannot determine which record to update.`, GraphqlQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT, { - userFriendlyMessage: t`Multiple records found with the same unique field values for ${conflictingFieldsValues}. Cannot determine which record to update.`, + userFriendlyMessage: msg`Multiple records found with the same unique field values for ${conflictingFieldsValues}. Cannot determine which record to update.`, }, ); } diff --git a/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts b/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts index f4233ccbca5..d8d487cc054 100644 --- a/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts @@ -7,6 +7,8 @@ import { GraphQLConfigModule } from 'src/engine/api/graphql/graphql-config/graph import { metadataModuleFactory } from 'src/engine/api/graphql/metadata.module-factory'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { I18nModule } from 'src/engine/core-modules/i18n/i18n.module'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; @@ -21,13 +23,19 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor GraphQLModule.forRootAsync({ driver: YogaDriver, useFactory: metadataModuleFactory, - imports: [GraphQLConfigModule, DataloaderModule, MetricsModule], + imports: [ + GraphQLConfigModule, + DataloaderModule, + MetricsModule, + I18nModule, + ], inject: [ TwentyConfigService, ExceptionHandlerService, DataloaderService, CacheStorageNamespace.EngineWorkspace, MetricsService, + I18nService, ], }), MetadataEngineModule, diff --git a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts index 0e21b85fcef..a12da8ca33d 100644 --- a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts @@ -9,6 +9,7 @@ import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphq import { type CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { type ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook'; +import { type I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { type MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { type TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { type DataloaderService } from 'src/engine/dataloaders/dataloader.service'; @@ -20,6 +21,7 @@ export const metadataModuleFactory = async ( dataloaderService: DataloaderService, cacheStorageService: CacheStorageService, metricsService: MetricsService, + i18nService: I18nService, ): Promise => { const config: YogaDriverConfig = { autoSchemaFile: true, @@ -39,6 +41,7 @@ export const metadataModuleFactory = async ( useGraphQLErrorHandlerHook({ metricsService: metricsService, exceptionHandlerService, + i18nService, twentyConfigService, }), useCachedMetadata({ diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util.ts index 98cc842bc38..dae563cee80 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util.ts @@ -1,3 +1,4 @@ +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { type QueryFailedError } from 'typeorm'; @@ -42,7 +43,7 @@ export const handleDuplicateKeyError = ( `A duplicate entry was detected`, TwentyORMExceptionCode.DUPLICATE_ENTRY_DETECTED, { - userFriendlyMessage: `This record already exists. Please check your data and try again.`, + userFriendlyMessage: msg`This record already exists. Please check your data and try again.`, }, ); } @@ -50,20 +51,26 @@ export const handleDuplicateKeyError = ( const columnNames = affectedColumns.join(', '); if (affectedColumns?.length === 1) { + const fieldName = columnNames.toLowerCase(); + throw new UserInputError( `Duplicate ${columnNames} ${duplicatedValues ? `with value ${duplicatedValues}` : ''}. Please set a unique one.`, { - userFriendlyMessage: `This ${columnNames.toLowerCase()} ${duplicatedValues ? `with value ${duplicatedValues}` : ''} is already taken. Please choose a different value.`, + userFriendlyMessage: duplicatedValues + ? msg`This ${fieldName} with value ${duplicatedValues} is already taken. Please choose a different value.` + : msg`This ${fieldName} is already taken. Please choose a different value.`, isExpected: true, }, ); } + const fieldNames = columnNames.toLowerCase(); + throw new TwentyORMException( `A duplicate entry was detected. The combination of ${columnNames} must be unique.`, TwentyORMExceptionCode.DUPLICATE_ENTRY_DETECTED, { - userFriendlyMessage: `This combination of ${columnNames.toLowerCase()} already exists. Please use different values.`, + userFriendlyMessage: msg`This combination of ${fieldNames} already exists. Please use different values.`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.service.ts b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.service.ts index a6a64ff0938..ed3f41e8a4f 100644 --- a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import axios from 'axios'; import semver from 'semver'; import { Repository } from 'typeorm'; @@ -65,7 +66,7 @@ export class AdminPanelService { userValidator.assertIsDefinedOrThrow( targetUser, new AuthException('User not found', AuthExceptionCode.INVALID_INPUT, { - userFriendlyMessage: 'User not found. Please check the email or ID.', + userFriendlyMessage: msg`User not found. Please check the email or ID.`, }), ); diff --git a/packages/twenty-server/src/engine/core-modules/api-key/api-key.service.ts b/packages/twenty-server/src/engine/core-modules/api-key/api-key.service.ts index 7eec1a6b015..af0014db510 100644 --- a/packages/twenty-server/src/engine/core-modules/api-key/api-key.service.ts +++ b/packages/twenty-server/src/engine/core-modules/api-key/api-key.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { DataSource, IsNull, Repository } from 'typeorm'; import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; @@ -113,8 +114,7 @@ export class ApiKeyService { 'This API Key is revoked', ApiKeyExceptionCode.API_KEY_REVOKED, { - userFriendlyMessage: - 'This API Key has been revoked and can no longer be used.', + userFriendlyMessage: msg`This API Key has been revoked and can no longer be used.`, }, ); } @@ -124,8 +124,7 @@ export class ApiKeyService { 'This API Key has expired', ApiKeyExceptionCode.API_KEY_EXPIRED, { - userFriendlyMessage: - 'This API Key has expired. Please create a new one.', + userFriendlyMessage: msg`This API Key has expired. Please create a new one.`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts b/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts index 640d072c8f2..430863ff574 100644 --- a/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts +++ b/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts @@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import crypto from 'crypto'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { render } from '@react-email/render'; import { SendApprovedAccessDomainValidation } from 'twenty-emails'; import { SettingsPath } from 'twenty-shared/types'; @@ -46,7 +46,7 @@ export class ApprovedAccessDomainService { 'Approved access domain has already been validated', ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VERIFIED, { - userFriendlyMessage: t`Approved access domain has already been validated`, + userFriendlyMessage: msg`Approved access domain has already been validated`, }, ); } @@ -56,7 +56,7 @@ export class ApprovedAccessDomainService { 'Approved access domain does not match email domain', ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_DOES_NOT_MATCH_DOMAIN_EMAIL, { - userFriendlyMessage: t`Approved access domain does not match email domain`, + userFriendlyMessage: msg`Approved access domain does not match email domain`, }, ); } @@ -136,7 +136,7 @@ export class ApprovedAccessDomainService { 'Approved access domain has already been validated', ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED, { - userFriendlyMessage: t`Approved access domain has already been validated`, + userFriendlyMessage: msg`Approved access domain has already been validated`, }, ); } @@ -180,7 +180,7 @@ export class ApprovedAccessDomainService { 'Approved access domain already registered.', ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_REGISTERED, { - userFriendlyMessage: t`Approved access domain already registered.`, + userFriendlyMessage: msg`Approved access domain already registered.`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts index 9eef2e75751..d461fcf2af7 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts @@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import crypto, { randomUUID } from 'node:crypto'; -import { msg, t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { render } from '@react-email/render'; import { addMilliseconds } from 'date-fns'; import ms from 'ms'; @@ -163,7 +163,7 @@ export class AuthService { 'Incorrect login method', AuthExceptionCode.INVALID_INPUT, { - userFriendlyMessage: t`User was not created with email/password`, + userFriendlyMessage: msg`User was not created with email/password`, }, ); } @@ -175,7 +175,7 @@ export class AuthService { 'Wrong password', AuthExceptionCode.FORBIDDEN_EXCEPTION, { - userFriendlyMessage: t`Wrong password`, + userFriendlyMessage: msg`Wrong password`, }, ); } @@ -746,7 +746,7 @@ export class AuthService { 'User does not have access to this workspace', AuthExceptionCode.FORBIDDEN_EXCEPTION, { - userFriendlyMessage: t`User does not have access to this workspace`, + userFriendlyMessage: msg`User does not have access to this workspace`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts index 95d9c50a65c..8c31bcd96eb 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts @@ -2,7 +2,7 @@ import { HttpService } from '@nestjs/axios'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { TWENTY_ICONS_BASE_URL } from 'twenty-shared/constants'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { Repository } from 'typeorm'; @@ -30,6 +30,7 @@ import { DomainManagerService } from 'src/engine/core-modules/domain-manager/ser import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; +import { UserService } from 'src/engine/core-modules/user/services/user.service'; import { User } from 'src/engine/core-modules/user/user.entity'; import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type'; @@ -37,7 +38,6 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email'; import { isWorkEmail } from 'src/utils/is-work-email'; -import { UserService } from 'src/engine/core-modules/user/services/user.service'; @Injectable() // eslint-disable-next-line @nx/workspace-inject-workspace-repository @@ -66,7 +66,7 @@ export class SignInUpService { 'Email is required', AuthExceptionCode.INVALID_INPUT, { - userFriendlyMessage: t`Email is required`, + userFriendlyMessage: msg`Email is required`, }, ); } @@ -124,7 +124,7 @@ export class SignInUpService { 'Password too weak', AuthExceptionCode.INVALID_INPUT, { - userFriendlyMessage: t`Password too weak`, + userFriendlyMessage: msg`Password too weak`, }, ); } @@ -146,7 +146,7 @@ export class SignInUpService { 'Wrong password', AuthExceptionCode.FORBIDDEN_EXCEPTION, { - userFriendlyMessage: t`Wrong password`, + userFriendlyMessage: msg`Wrong password`, }, ); } @@ -172,7 +172,7 @@ export class SignInUpService { 'Email is required', AuthExceptionCode.INVALID_INPUT, { - userFriendlyMessage: t`Email is required`, + userFriendlyMessage: msg`Email is required`, }, ); } @@ -216,7 +216,7 @@ export class SignInUpService { 'Workspace is not ready to welcome new members', AuthExceptionCode.FORBIDDEN_EXCEPTION, { - userFriendlyMessage: t`Workspace is not ready to welcome new members`, + userFriendlyMessage: msg`Workspace is not ready to welcome new members`, }, ); } @@ -232,7 +232,7 @@ export class SignInUpService { 'User is not part of the workspace', AuthExceptionCode.FORBIDDEN_EXCEPTION, { - userFriendlyMessage: t`User is not part of the workspace`, + userFriendlyMessage: msg`User is not part of the workspace`, }, ); } @@ -379,7 +379,7 @@ export class SignInUpService { 'Workspace creation is restricted to admins', AuthExceptionCode.FORBIDDEN_EXCEPTION, { - userFriendlyMessage: t`Workspace creation is restricted to admins`, + userFriendlyMessage: msg`Workspace creation is restricted to admins`, }, ); } @@ -398,7 +398,7 @@ export class SignInUpService { 'Email is required', AuthExceptionCode.INVALID_INPUT, { - userFriendlyMessage: t`Email is required`, + userFriendlyMessage: msg`Email is required`, }, ); } @@ -470,7 +470,7 @@ export class SignInUpService { new AuthException( 'User already exist', AuthExceptionCode.USER_ALREADY_EXIST, - { userFriendlyMessage: t`User already exists` }, + { userFriendlyMessage: msg`User already exists` }, ), ); diff --git a/packages/twenty-server/src/engine/core-modules/auth/strategies/jwt.auth.strategy.ts b/packages/twenty-server/src/engine/core-modules/auth/strategies/jwt.auth.strategy.ts index f3e997ad015..907d794929f 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/strategies/jwt.auth.strategy.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/strategies/jwt.auth.strategy.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { InjectRepository } from '@nestjs/typeorm'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { Strategy } from 'passport-jwt'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; @@ -158,7 +158,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') { 'UserWorkspace not found', AuthExceptionCode.USER_WORKSPACE_NOT_FOUND, { - userFriendlyMessage: t`User does not have access to this workspace`, + userFriendlyMessage: msg`User does not have access to this workspace`, }, ), ); diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/auth-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/auth-graphql-api-exception-handler.util.ts index a7ce319ec1d..3ac89baf611 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/utils/auth-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/auth-graphql-api-exception-handler.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { assertUnreachable } from 'twenty-shared/utils'; import { @@ -31,14 +31,14 @@ export const authGraphqlApiExceptionHandler = (exception: AuthException) => { case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED: case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED: throw new ForbiddenError(exception.message, { - userFriendlyMessage: t`Authentication is not enabled with this provider.`, + userFriendlyMessage: msg`Authentication is not enabled with this provider.`, subCode: exception.code, }); case AuthExceptionCode.EMAIL_NOT_VERIFIED: case AuthExceptionCode.INVALID_DATA: throw new ForbiddenError(exception.message, { subCode: AuthExceptionCode.EMAIL_NOT_VERIFIED, - userFriendlyMessage: t`Email is not verified.`, + userFriendlyMessage: msg`Email is not verified.`, }); case AuthExceptionCode.TWO_FACTOR_AUTHENTICATION_PROVISION_REQUIRED: case AuthExceptionCode.TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED: @@ -47,7 +47,7 @@ export const authGraphqlApiExceptionHandler = (exception: AuthException) => { }); case AuthExceptionCode.UNAUTHENTICATED: throw new AuthenticationError(exception.message, { - userFriendlyMessage: t`You must be authenticated to perform this action.`, + userFriendlyMessage: msg`You must be authenticated to perform this action.`, subCode: exception.code, }); case AuthExceptionCode.USER_NOT_FOUND: diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.validate.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.validate.ts index 5553fb0b04b..50381b05071 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.validate.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.validate.ts @@ -1,4 +1,5 @@ import { isDefined } from 'twenty-shared/utils'; +import { msg } from '@lingui/core/macro'; import { type BillingPrice } from 'src/engine/core-modules/billing/entities/billing-price.entity'; import { type MeterBillingPriceTiers } from 'src/engine/core-modules/billing/types/meter-billing-price-tier.type'; @@ -137,8 +138,7 @@ const assertIsSubscription = ( 'Subscription must have exactly two subscription items. Check that stripe and database are in sync', BillingExceptionCode.BILLING_SUBSCRIPTION_INVALID, { - userFriendlyMessage: - 'Your billing subscription is corrupted. Please contact support.', + userFriendlyMessage: msg`Your billing subscription is corrupted. Please contact support.`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/captcha/captcha.guard.ts b/packages/twenty-server/src/engine/core-modules/captcha/captcha.guard.ts index 8a26eb06a38..0f4ab7817f2 100644 --- a/packages/twenty-server/src/engine/core-modules/captcha/captcha.guard.ts +++ b/packages/twenty-server/src/engine/core-modules/captcha/captcha.guard.ts @@ -5,7 +5,7 @@ import { } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { CaptchaException, @@ -41,7 +41,9 @@ export class CaptchaGuard implements CanActivate { throw new CaptchaException( 'Invalid Captcha, please try another device', CaptchaExceptionCode.INVALID_CAPTCHA, - { userFriendlyMessage: t`Invalid Captcha, please try another device` }, + { + userFriendlyMessage: msg`Invalid Captcha, please try another device`, + }, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/dns-manager/services/dns-manager.service.ts b/packages/twenty-server/src/engine/core-modules/dns-manager/services/dns-manager.service.ts index aea15eff86d..0b54fdce557 100644 --- a/packages/twenty-server/src/engine/core-modules/dns-manager/services/dns-manager.service.ts +++ b/packages/twenty-server/src/engine/core-modules/dns-manager/services/dns-manager.service.ts @@ -1,6 +1,7 @@ /* @license Enterprise */ import { Injectable } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import Cloudflare from 'cloudflare'; import { type CustomHostnameCreateParams, @@ -8,14 +9,14 @@ import { } from 'cloudflare/resources/custom-hostnames/custom-hostnames'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; +import { type DomainValidRecords } from 'src/engine/core-modules/dns-manager/dtos/domain-valid-records'; import { DnsManagerException, DnsManagerExceptionCode, } from 'src/engine/core-modules/dns-manager/exceptions/dns-manager.exception'; -import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service'; import { dnsManagerValidator } from 'src/engine/core-modules/dns-manager/validator/dns-manager.validate'; +import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { type DomainValidRecords } from 'src/engine/core-modules/dns-manager/dtos/domain-valid-records'; type DnsManagerOptions = { isPublicDomain?: boolean; @@ -43,7 +44,7 @@ export class DnsManagerService { throw new DnsManagerException( 'Hostname already registered', DnsManagerExceptionCode.HOSTNAME_ALREADY_REGISTERED, - { userFriendlyMessage: 'Domain is already registered' }, + { userFriendlyMessage: msg`Domain is already registered` }, ); } @@ -66,8 +67,7 @@ export class DnsManagerService { 'Missing public domain URL', DnsManagerExceptionCode.MISSING_PUBLIC_DOMAIN_URL, { - userFriendlyMessage: - 'Public domain URL is not defined. Please set the PUBLIC_DOMAIN_URL environment variable', + userFriendlyMessage: msg`Public domain URL is not defined. Please set the PUBLIC_DOMAIN_URL environment variable`, }, ); } @@ -219,11 +219,14 @@ export class DnsManagerService { } // should never happen. error 5xx + const hostnameCount = customHostnames.result.length; + const domainName = hostname; + throw new DnsManagerException( 'More than one custom hostname found in cloudflare', DnsManagerExceptionCode.MULTIPLE_HOSTNAMES_FOUND, { - userFriendlyMessage: `${customHostnames.result.length} hostnames found for domain '${hostname}'. Expect 1`, + userFriendlyMessage: msg`${hostnameCount} hostnames found for domain '${domainName}'. Expect 1`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/dns-manager/validator/dns-manager.validate.ts b/packages/twenty-server/src/engine/core-modules/dns-manager/validator/dns-manager.validate.ts index 575210dab2e..30e60d05427 100644 --- a/packages/twenty-server/src/engine/core-modules/dns-manager/validator/dns-manager.validate.ts +++ b/packages/twenty-server/src/engine/core-modules/dns-manager/validator/dns-manager.validate.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import type Cloudflare from 'cloudflare'; @@ -15,7 +15,7 @@ const isCloudflareInstanceDefined = ( 'Cloudflare instance is not defined', DnsManagerExceptionCode.CLOUDFLARE_CLIENT_NOT_INITIALIZED, { - userFriendlyMessage: t`Environment variable CLOUDFLARE_API_KEY must be defined to use this feature.`, + userFriendlyMessage: msg`Environment variable CLOUDFLARE_API_KEY must be defined to use this feature.`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/email-verification/email-verification-exception-filter.util.ts b/packages/twenty-server/src/engine/core-modules/email-verification/email-verification-exception-filter.util.ts index f3589a8b1c3..115bcaf2b92 100644 --- a/packages/twenty-server/src/engine/core-modules/email-verification/email-verification-exception-filter.util.ts +++ b/packages/twenty-server/src/engine/core-modules/email-verification/email-verification-exception-filter.util.ts @@ -1,7 +1,7 @@ import { Catch, type ExceptionFilter } from '@nestjs/common'; import { assertUnreachable } from 'twenty-shared/utils'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { EmailVerificationException, @@ -19,7 +19,7 @@ export class EmailVerificationExceptionFilter implements ExceptionFilter { case EmailVerificationExceptionCode.TOKEN_EXPIRED: throw new ForbiddenError(exception.message, { subCode: exception.code, - userFriendlyMessage: t`Request has expired, please try again.`, + userFriendlyMessage: msg`Request has expired, please try again.`, }); case EmailVerificationExceptionCode.INVALID_TOKEN: case EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE: @@ -30,12 +30,12 @@ export class EmailVerificationExceptionFilter implements ExceptionFilter { case EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED: throw new UserInputError(exception.message, { subCode: exception.code, - userFriendlyMessage: t`Email already verified.`, + userFriendlyMessage: msg`Email already verified.`, }); case EmailVerificationExceptionCode.EMAIL_VERIFICATION_NOT_REQUIRED: throw new UserInputError(exception.message, { subCode: exception.code, - userFriendlyMessage: t`Email verification not required.`, + userFriendlyMessage: msg`Email verification not required.`, }); case EmailVerificationExceptionCode.INVALID_EMAIL: throw new UserInputError(exception); diff --git a/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts b/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts index bd43061cbcc..70b17b076d3 100644 --- a/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts +++ b/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts @@ -4,7 +4,7 @@ import { type OnExecuteDoneHookResultOnNextHook, type Plugin, } from '@envelop/core'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { GraphQLError, Kind, @@ -12,6 +12,7 @@ import { print, } from 'graphql'; import semver from 'semver'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { isDefined } from 'twenty-shared/utils'; import { type GraphQLContext } from 'src/engine/api/graphql/graphql-config/interfaces/graphql-context.interface'; @@ -23,6 +24,7 @@ import { convertGraphQLErrorToBaseGraphQLError, ErrorCode, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { type I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { type MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; import { type TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; @@ -46,6 +48,8 @@ type GraphQLErrorHandlerHookOptions = { */ exceptionHandlerService: ExceptionHandlerService; + i18nService: I18nService; + twentyConfigService: TwentyConfigService; /** * The key of the event id in the error's extension. `null` to disable. @@ -201,6 +205,10 @@ export const useGraphQLErrorHandlerHook = < } // Step 3: Transform errors for GraphQL response (clean GraphQL errors) + const userLocale = args.contextValue.req.locale ?? SOURCE_LOCALE; + const i18n = options.i18nService.getI18nInstance(userLocale); + const defaultErrorMessage = msg`An error occurred.`; + const transformedErrors = processedErrors.map((error) => { const graphqlError = error instanceof BaseGraphQLError @@ -208,12 +216,13 @@ export const useGraphQLErrorHandlerHook = < ...error, extensions: { ...error.extensions, - userFriendlyMessage: + userFriendlyMessage: i18n._( error.extensions.userFriendlyMessage ?? - t`An error occurred.`, + defaultErrorMessage, + ), }, } - : generateGraphQLErrorFromError(error); + : generateGraphQLErrorFromError(error, i18n); if (error.eventId && eventIdKey) { graphqlError.extensions = { @@ -260,7 +269,7 @@ export const useGraphQLErrorHandlerHook = < }); throw new GraphQLError(SCHEMA_MISMATCH_ERROR, { extensions: { - userFriendlyMessage: t`Your workspace has been updated with a new data model. Please refresh the page.`, + userFriendlyMessage: msg`Your workspace has been updated with a new data model. Please refresh the page.`, }, }); } @@ -288,7 +297,7 @@ export const useGraphQLErrorHandlerHook = < throw new GraphQLError(APP_VERSION_MISMATCH_ERROR, { extensions: { code: APP_VERSION_MISMATCH_CODE, - userFriendlyMessage: t`Your app version is out of date. Please refresh the page to continue.`, + userFriendlyMessage: msg`Your app version is out of date. Please refresh the page to continue.`, }, }); } diff --git a/packages/twenty-server/src/engine/core-modules/graphql/utils/generate-graphql-error-from-error.util.ts b/packages/twenty-server/src/engine/core-modules/graphql/utils/generate-graphql-error-from-error.util.ts index 8f102d04849..9b417961d5e 100644 --- a/packages/twenty-server/src/engine/core-modules/graphql/utils/generate-graphql-error-from-error.util.ts +++ b/packages/twenty-server/src/engine/core-modules/graphql/utils/generate-graphql-error-from-error.util.ts @@ -1,4 +1,5 @@ -import { t } from '@lingui/core/macro'; +import { type I18n } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { BaseGraphQLError, @@ -8,17 +9,21 @@ import { CustomException } from 'src/utils/custom-exception'; export const generateGraphQLErrorFromError = ( error: Error | CustomException, + i18n: I18n, ) => { const graphqlError = new BaseGraphQLError( error.message, ErrorCode.INTERNAL_SERVER_ERROR, ); + const defaultErrorMessage = msg`An error occurred.`; + if (error instanceof CustomException) { - graphqlError.extensions.userFriendlyMessage = - error.userFriendlyMessage ?? t`An error occurred.`; + graphqlError.extensions.userFriendlyMessage = i18n._( + error.userFriendlyMessage ?? defaultErrorMessage, + ); } else { - graphqlError.extensions.userFriendlyMessage = t`An error occurred.`; + graphqlError.extensions.userFriendlyMessage = i18n._(defaultErrorMessage); } return graphqlError; diff --git a/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts b/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts index 20ccbac8690..e59d61f79f6 100644 --- a/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts +++ b/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts @@ -1,3 +1,4 @@ +import { type MessageDescriptor } from '@lingui/core'; import { type ASTNode, GraphQLError, @@ -33,7 +34,7 @@ export enum ErrorCode { } type RestrictedGraphQLErrorExtensions = { - userFriendlyMessage?: string; + userFriendlyMessage?: MessageDescriptor; subCode?: string; }; diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service.ts index 7408f5a8327..f60f448239a 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { z } from 'zod'; import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -20,8 +21,7 @@ export class ImapSmtpCaldavValidatorService { ): ConnectionParameters { if (!params) { throw new UserInputError('Protocol connection parameters are required', { - userFriendlyMessage: - 'Please provide connection details to configure your email account.', + userFriendlyMessage: msg`Please provide connection details to configure your email account.`, }); } @@ -36,15 +36,13 @@ export class ImapSmtpCaldavValidatorService { throw new UserInputError( `Protocol connection validation failed: ${errorMessages}`, { - userFriendlyMessage: - 'Please check your connection settings. Make sure the server host, port, and password are correct.', + userFriendlyMessage: msg`Please check your connection settings. Make sure the server host, port, and password are correct.`, }, ); } throw new UserInputError('Protocol connection validation failed', { - userFriendlyMessage: - 'There was an issue with your connection settings. Please try again.', + userFriendlyMessage: msg`There was an issue with your connection settings. Please try again.`, }); } } diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts index 3f0f883b435..f0e7b9c4681 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { ImapFlow } from 'imapflow'; import { createTransport } from 'nodemailer'; import { ConnectedAccountProvider } from 'twenty-shared/types'; @@ -59,8 +60,7 @@ export class ImapSmtpCaldavService { throw new UserInputError( 'IMAP authentication failed. Please check your credentials.', { - userFriendlyMessage: - "We couldn't log in to your email account. Please check your email address and password, then try again.", + userFriendlyMessage: msg`We couldn't log in to your email account. Please check your email address and password, then try again.`, }, ); } @@ -69,15 +69,13 @@ export class ImapSmtpCaldavService { throw new UserInputError( `IMAP connection refused. Please verify server and port.`, { - userFriendlyMessage: - "We couldn't connect to your email server. Please check your server settings and try again.", + userFriendlyMessage: msg`We couldn't connect to your email server. Please check your server settings and try again.`, }, ); } throw new UserInputError(`IMAP connection failed: ${error.message}`, { - userFriendlyMessage: - 'We encountered an issue connecting to your email account. Please check your settings and try again.', + userFriendlyMessage: msg`We encountered an issue connecting to your email account. Please check your settings and try again.`, }); } finally { if (client.authenticated) { @@ -110,8 +108,7 @@ export class ImapSmtpCaldavService { error.stack, ); throw new UserInputError(`SMTP connection failed: ${error.message}`, { - userFriendlyMessage: - "We couldn't connect to your outgoing email server. Please check your SMTP settings and try again.", + userFriendlyMessage: msg`We couldn't connect to your outgoing email server. Please check your SMTP settings and try again.`, }); } @@ -137,14 +134,12 @@ export class ImapSmtpCaldavService { ); if (error.code === 'FailedToOpenSocket') { throw new UserInputError(`CALDAV connection failed: ${error.message}`, { - userFriendlyMessage: - "We couldn't connect to your CalDAV server. Please check your server settings and try again.", + userFriendlyMessage: msg`We couldn't connect to your CalDAV server. Please check your server settings and try again.`, }); } throw new UserInputError(`CALDAV connection failed: ${error.message}`, { - userFriendlyMessage: - 'Invalid credentials. Please check your username and password.', + userFriendlyMessage: msg`Invalid credentials. Please check your username and password.`, }); } @@ -171,8 +166,7 @@ export class ImapSmtpCaldavService { throw new UserInputError( 'Invalid account type. Must be one of: IMAP, SMTP, CALDAV', { - userFriendlyMessage: - 'Please select a valid connection type (IMAP, SMTP, or CalDAV) and try again.', + userFriendlyMessage: msg`Please select a valid connection type (IMAP, SMTP, or CalDAV) and try again.`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/public-domain/public-domain.service.ts b/packages/twenty-server/src/engine/core-modules/public-domain/public-domain.service.ts index bc721ec1c1e..f23d0c54a44 100644 --- a/packages/twenty-server/src/engine/core-modules/public-domain/public-domain.service.ts +++ b/packages/twenty-server/src/engine/core-modules/public-domain/public-domain.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { Repository } from 'typeorm'; import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; @@ -62,7 +62,7 @@ export class PublicDomainService { 'Domain already used for workspace custom domain', PublicDomainExceptionCode.DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN, { - userFriendlyMessage: t`Domain already used for workspace custom domain`, + userFriendlyMessage: msg`Domain already used for workspace custom domain`, }, ); } @@ -77,7 +77,7 @@ export class PublicDomainService { 'Public domain already registered', PublicDomainExceptionCode.PUBLIC_DOMAIN_ALREADY_REGISTERED, { - userFriendlyMessage: t`Public domain already registered`, + userFriendlyMessage: msg`Public domain already registered`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-phones-value.util.ts b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-phones-value.util.ts index 37ecccf70fe..b1dcfbc9dab 100644 --- a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-phones-value.util.ts +++ b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-phones-value.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isArray, isNonEmptyString } from '@sniptt/guards'; import { type CountryCallingCode, @@ -44,7 +44,7 @@ const validatePrimaryPhoneCountryCodeAndCallingCode = ({ throw new RecordTransformerException( `Invalid country code ${countryCode}`, RecordTransformerExceptionCode.INVALID_PHONE_COUNTRY_CODE, - { userFriendlyMessage: t`Invalid country code ${countryCode}` }, + { userFriendlyMessage: msg`Invalid country code ${countryCode}` }, ); } @@ -58,7 +58,7 @@ const validatePrimaryPhoneCountryCodeAndCallingCode = ({ throw new RecordTransformerException( `Invalid calling code ${callingCode}`, RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE, - { userFriendlyMessage: t`Invalid calling code ${callingCode}` }, + { userFriendlyMessage: msg`Invalid calling code ${callingCode}` }, ); } @@ -72,7 +72,7 @@ const validatePrimaryPhoneCountryCodeAndCallingCode = ({ `Provided country code and calling code are conflicting`, RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE, { - userFriendlyMessage: t`Provided country code and calling code are conflicting`, + userFriendlyMessage: msg`Provided country code and calling code are conflicting`, }, ); } @@ -94,7 +94,7 @@ const parsePhoneNumberExceptionWrapper = ({ throw new RecordTransformerException( `Provided phone number is invalid ${number}`, RecordTransformerExceptionCode.INVALID_PHONE_NUMBER, - { userFriendlyMessage: t`Provided phone number is invalid ${number}` }, + { userFriendlyMessage: msg`Provided phone number is invalid ${number}` }, ); } }; @@ -119,7 +119,7 @@ const validateAndInferMetadataFromPrimaryPhoneNumber = ({ 'Provided and inferred country code are conflicting', RecordTransformerExceptionCode.CONFLICTING_PHONE_COUNTRY_CODE, { - userFriendlyMessage: t`Provided and inferred country code are conflicting`, + userFriendlyMessage: msg`Provided and inferred country code are conflicting`, }, ); } @@ -133,7 +133,7 @@ const validateAndInferMetadataFromPrimaryPhoneNumber = ({ 'Provided and inferred calling code are conflicting', RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE, { - userFriendlyMessage: t`Provided and inferred calling code are conflicting`, + userFriendlyMessage: msg`Provided and inferred calling code are conflicting`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter.spec.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter.spec.ts index c242510502e..60d70a2bda0 100644 --- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter.spec.ts @@ -37,8 +37,11 @@ describe('TwoFactorAuthenticationExceptionFilter', () => { expect(error.extensions.subCode).toBe( TwoFactorAuthenticationExceptionCode.INVALID_OTP, ); - expect(error.extensions.userFriendlyMessage).toBe( - 'Invalid verification code. Please try again.', + expect(error.extensions.userFriendlyMessage).toEqual( + expect.objectContaining({ + id: expect.any(String), + message: 'Invalid verification code. Please try again.', + }), ); } }); diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter.ts index ec4f107c712..c5b64966159 100644 --- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter.ts @@ -1,7 +1,7 @@ import { Catch, type ExceptionFilter } from '@nestjs/common'; import { assertUnreachable } from 'twenty-shared/utils'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { ForbiddenError, @@ -19,7 +19,7 @@ export class TwoFactorAuthenticationExceptionFilter implements ExceptionFilter { case TwoFactorAuthenticationExceptionCode.INVALID_OTP: throw new UserInputError(exception.message, { subCode: exception.code, - userFriendlyMessage: t`Invalid verification code. Please try again.`, + userFriendlyMessage: msg`Invalid verification code. Please try again.`, }); case TwoFactorAuthenticationExceptionCode.INVALID_CONFIGURATION: case TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_NOT_FOUND: diff --git a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts index 9b60bb847ed..1e87b2689ad 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts @@ -2,6 +2,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import assert from 'assert'; +import { msg } from '@lingui/core/macro'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; import { IsNull, Not, Repository } from 'typeorm'; @@ -124,8 +125,7 @@ export class UserService extends TypeOrmQueryService { PermissionsExceptionMessage.CANNOT_DELETE_LAST_ADMIN_USER, PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER, { - userFriendlyMessage: - 'Cannot delete account: you are the only admin. Assign another admin or delete the workspace(s) first.', + userFriendlyMessage: msg`Cannot delete account: you are the only admin. Assign another admin or delete the workspace(s) first.`, }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/webhook/webhook.service.ts b/packages/twenty-server/src/engine/core-modules/webhook/webhook.service.ts index 1b2e20c9d2d..65cfb02a78f 100644 --- a/packages/twenty-server/src/engine/core-modules/webhook/webhook.service.ts +++ b/packages/twenty-server/src/engine/core-modules/webhook/webhook.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { ArrayContains, IsNull, Repository } from 'typeorm'; import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; @@ -78,7 +79,7 @@ export class WebhookService { throw new WebhookException( 'Invalid target URL provided', WebhookExceptionCode.INVALID_TARGET_URL, - { userFriendlyMessage: 'Please provide a valid HTTP or HTTPS URL.' }, + { userFriendlyMessage: msg`Please provide a valid HTTP or HTTPS URL.` }, ); } @@ -111,7 +112,9 @@ export class WebhookService { throw new WebhookException( 'Invalid target URL provided', WebhookExceptionCode.INVALID_TARGET_URL, - { userFriendlyMessage: 'Please provide a valid HTTP or HTTPS URL.' }, + { + userFriendlyMessage: msg`Please provide a valid HTTP or HTTPS URL.`, + }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index 6d945a680f6..56593b7a6e2 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import assert from 'assert'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; @@ -136,7 +136,7 @@ export class WorkspaceService extends TypeOrmQueryService { 'Domain is already registered as public domain', WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN, { - userFriendlyMessage: t`Domain is already registered as public domain`, + userFriendlyMessage: msg`Domain is already registered as public domain`, }, ); } @@ -452,8 +452,7 @@ export class WorkspaceService extends TypeOrmQueryService { PermissionsExceptionMessage.PERMISSION_DENIED, PermissionsExceptionCode.PERMISSION_DENIED, { - userFriendlyMessage: - 'You do not have permission to manage security settings. Please contact your workspace administrator.', + userFriendlyMessage: msg`You do not have permission to manage security settings. Please contact your workspace administrator.`, }, ); } @@ -502,8 +501,7 @@ export class WorkspaceService extends TypeOrmQueryService { PermissionsExceptionMessage.PERMISSION_DENIED, PermissionsExceptionCode.PERMISSION_DENIED, { - userFriendlyMessage: - 'You do not have permission to manage workspace settings. Please contact your workspace administrator.', + userFriendlyMessage: msg`You do not have permission to manage workspace settings. Please contact your workspace administrator.`, }, ); } diff --git a/packages/twenty-server/src/engine/guards/__tests__/impersonate-permission-guard.spec.ts b/packages/twenty-server/src/engine/guards/__tests__/impersonate-permission-guard.spec.ts index b145c482550..00f0e7867ff 100644 --- a/packages/twenty-server/src/engine/guards/__tests__/impersonate-permission-guard.spec.ts +++ b/packages/twenty-server/src/engine/guards/__tests__/impersonate-permission-guard.spec.ts @@ -109,8 +109,11 @@ describe('ImpersonateGuard', () => { await expect(guard.canActivate(mockExecutionContext)).rejects.toMatchObject( { - userFriendlyMessage: - 'You do not have permission to impersonate users. Please contact your workspace administrator for access.', + userFriendlyMessage: expect.objectContaining({ + id: expect.any(String), + message: + 'You do not have permission to impersonate users. Please contact your workspace administrator for access.', + }), }, ); }); @@ -137,7 +140,10 @@ describe('ImpersonateGuard', () => { await expect(guard.canActivate(mockExecutionContext)).rejects.toMatchObject( { - userFriendlyMessage: "Can't impersonate user via api key", + userFriendlyMessage: expect.objectContaining({ + id: expect.any(String), + message: "Can't impersonate user via api key", + }), }, ); }); diff --git a/packages/twenty-server/src/engine/guards/impersonate-permission.guard.ts b/packages/twenty-server/src/engine/guards/impersonate-permission.guard.ts index 23dc3f11d3b..354a5eada31 100644 --- a/packages/twenty-server/src/engine/guards/impersonate-permission.guard.ts +++ b/packages/twenty-server/src/engine/guards/impersonate-permission.guard.ts @@ -5,6 +5,7 @@ import { } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'class-validator'; import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; @@ -30,7 +31,7 @@ export class ImpersonatePermissionGuard implements CanActivate { PermissionsExceptionMessage.PERMISSION_DENIED, PermissionsExceptionCode.PERMISSION_DENIED, { - userFriendlyMessage: "Can't impersonate user via api key", + userFriendlyMessage: msg`Can't impersonate user via api key`, }, ); } @@ -50,8 +51,7 @@ export class ImpersonatePermissionGuard implements CanActivate { PermissionsExceptionMessage.PERMISSION_DENIED, PermissionsExceptionCode.PERMISSION_DENIED, { - userFriendlyMessage: - 'You do not have permission to impersonate users. Please contact your workspace administrator for access.', + userFriendlyMessage: msg`You do not have permission to impersonate users. Please contact your workspace administrator for access.`, }, ); } diff --git a/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts b/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts index 366ed12b5a2..bdcc30f9d7f 100644 --- a/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts +++ b/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts @@ -7,6 +7,7 @@ import { } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; +import { msg } from '@lingui/core/macro'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { type PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; @@ -56,8 +57,7 @@ export const SettingsPermissionsGuard = ( PermissionsExceptionMessage.PERMISSION_DENIED, PermissionsExceptionCode.PERMISSION_DENIED, { - userFriendlyMessage: - 'You do not have permission to access this feature. Please contact your workspace administrator for access.', + userFriendlyMessage: msg`You do not have permission to access this feature. Please contact your workspace administrator for access.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts index 4cc5b935172..76f4a41f8e3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts @@ -18,6 +18,7 @@ import { ForbiddenError, ValidationError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; @@ -52,6 +53,7 @@ export class FieldMetadataResolver { private readonly beforeUpdateOneField: BeforeUpdateOneField, private readonly featureFlagService: FeatureFlagService, private readonly fieldMetadataServiceV2: FieldMetadataServiceV2, + private readonly i18nService: I18nService, ) {} @UseGuards(SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL)) @@ -59,6 +61,7 @@ export class FieldMetadataResolver { async createOneField( @Args('input') input: CreateOneFieldMetadataInput, @AuthWorkspace() { id: workspaceId }: Workspace, + @Context() context: I18nContext, ) { try { return await this.fieldMetadataService.createOne({ @@ -66,7 +69,10 @@ export class FieldMetadataResolver { workspaceId, }); } catch (error) { - return fieldMetadataGraphqlApiExceptionHandler(error); + return fieldMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } } @@ -101,7 +107,10 @@ export class FieldMetadataResolver { workspaceId, }); } catch (error) { - fieldMetadataGraphqlApiExceptionHandler(error); + fieldMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } } @@ -110,6 +119,7 @@ export class FieldMetadataResolver { async deleteOneField( @Args('input') input: DeleteOneFieldInput, @AuthWorkspace() { id: workspaceId }: Workspace, + @Context() context: I18nContext, ) { if (!isDefined(workspaceId)) { throw new ForbiddenError('Could not retrieve workspace ID'); @@ -129,7 +139,10 @@ export class FieldMetadataResolver { }); } } catch (error) { - fieldMetadataGraphqlApiExceptionHandler(error); + fieldMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } const fieldMetadata = @@ -154,7 +167,10 @@ export class FieldMetadataResolver { try { return await this.fieldMetadataService.deleteOneField(input, workspaceId); } catch (error) { - fieldMetadataGraphqlApiExceptionHandler(error); + fieldMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } } @@ -167,7 +183,7 @@ export class FieldMetadataResolver { id: fieldMetadataId, objectMetadataId, }: Pick, - @Context() context: { loaders: IDataloaders }, + @Context() context: { loaders: IDataloaders } & I18nContext, ): Promise { try { return await context.loaders.relationLoader.load({ @@ -176,7 +192,10 @@ export class FieldMetadataResolver { workspaceId: workspace.id, }); } catch (error) { - return fieldMetadataGraphqlApiExceptionHandler(error); + return fieldMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } } @@ -188,7 +207,7 @@ export class FieldMetadataResolver { id: fieldMetadataId, objectMetadataId, }: Pick, - @Context() context: { loaders: IDataloaders }, + @Context() context: { loaders: IDataloaders } & I18nContext, ): Promise { try { return await context.loaders.morphRelationLoader.load({ @@ -197,7 +216,10 @@ export class FieldMetadataResolver { workspaceId: workspace.id, }); } catch (error) { - return fieldMetadataGraphqlApiExceptionHandler(error); + return fieldMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor.ts index 3a0209693f5..f9155df16e9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor.ts @@ -1,20 +1,34 @@ import { type CallHandler, type ExecutionContext, + Injectable, type NestInterceptor, } from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; import { type Observable, catchError } from 'rxjs'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util'; +@Injectable() export class FieldMetadataGraphqlApiExceptionInterceptor implements NestInterceptor { + constructor(private readonly i18nService: I18nService) {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any - intercept(_: ExecutionContext, next: CallHandler): Observable { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const gqlContext = GqlExecutionContext.create(context); + const ctx = gqlContext.getContext(); + const locale = ctx.req?.locale ?? SOURCE_LOCALE; + const i18n = this.i18nService.getI18nInstance(locale); + return next .handle() - .pipe(catchError((err) => fieldMetadataGraphqlApiExceptionHandler(err))); + .pipe( + catchError((err) => fieldMetadataGraphqlApiExceptionHandler(err, i18n)), + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service.ts index 05eba2b241e..6307f2ac1db 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { type EnumFieldMetadataType, @@ -31,7 +32,7 @@ import { isSnakeCaseString } from 'src/utils/is-snake-case-string'; type Validator = { validator: (str: T) => boolean; - message: string; + message: MessageDescriptor; }; type FieldMetadataUpdateCreateInput = CreateFieldInput | UpdateFieldInput; @@ -59,7 +60,7 @@ export class FieldMetadataEnumValidationService { if (shouldThrow) { throw new FieldMetadataException( - message, + message.message ?? 'Invalid field input', FieldMetadataExceptionCode.INVALID_FIELD_INPUT, { userFriendlyMessage: message, @@ -72,11 +73,11 @@ export class FieldMetadataEnumValidationService { const validators: Validator[] = [ { validator: (id) => !isDefined(id), - message: 'Option id is required', + message: msg`Option id is required`, }, { validator: (id) => !z.string().uuid().safeParse(id).success, - message: 'Option id is invalid', + message: msg`Option id is invalid`, }, ]; @@ -89,23 +90,23 @@ export class FieldMetadataEnumValidationService { const validators: Validator[] = [ { validator: (label) => !isDefined(label), - message: t`Option label is required`, + message: msg`Option label is required`, }, { validator: exceedsDatabaseIdentifierMaximumLength, - message: t`Option label exceeds 63 characters`, + message: msg`Option label exceeds 63 characters`, }, { validator: beneathDatabaseIdentifierMinimumLength, - message: t`Option label "${sanitizedLabel}" is beneath 1 character`, + message: msg`Option label "${sanitizedLabel}" is beneath 1 character`, }, { validator: (label) => label.includes(','), - message: t`Label must not contain a comma`, + message: msg`Label must not contain a comma`, }, { validator: (label) => !isNonEmptyString(label) || label === ' ', - message: t`Label must not be empty`, + message: msg`Label must not be empty`, }, ]; @@ -118,19 +119,19 @@ export class FieldMetadataEnumValidationService { const validators: Validator[] = [ { validator: (value) => !isDefined(value), - message: t`Option value is required`, + message: msg`Option value is required`, }, { validator: exceedsDatabaseIdentifierMaximumLength, - message: t`Option value exceeds 63 characters`, + message: msg`Option value exceeds 63 characters`, }, { validator: beneathDatabaseIdentifierMinimumLength, - message: t`Option value "${sanitizedValue}" is beneath 1 character`, + message: msg`Option value "${sanitizedValue}" is beneath 1 character`, }, { validator: (value) => !isSnakeCaseString(value), - message: `Value must be in UPPER_CASE and follow snake_case "${sanitizedValue}"`, + message: msg`Value must be in UPPER_CASE and follow snake_case "${sanitizedValue}"`, }, ]; @@ -153,7 +154,7 @@ export class FieldMetadataEnumValidationService { const duplicatedValidators = fieldsToCheckForDuplicates.map< Validator >((field) => ({ - message: `Duplicated option ${field}`, + message: msg`Duplicated option ${field}`, validator: () => new Set(options.map((option) => option[field])).size !== options.length, })); @@ -198,7 +199,7 @@ export class FieldMetadataEnumValidationService { const validators: Validator[] = [ { validator: (value: string) => !QUOTED_STRING_REGEX.test(value), - message: 'Default value should be as quoted string', + message: msg`Default value should be as quoted string`, }, { validator: (value: string) => @@ -206,7 +207,7 @@ export class FieldMetadataEnumValidationService { (option) => option.value === value.replace(QUOTED_STRING_REGEX, '$1'), ), - message: `Default value "${defaultValue}" must be one of the option values`, + message: msg`Default value "${defaultValue}" must be one of the option values`, }, ]; @@ -229,11 +230,11 @@ export class FieldMetadataEnumValidationService { const validators: Validator[] = [ { validator: (values) => values.length === 0, - message: 'If defined default value must contain at least one value', + message: msg`If defined default value must contain at least one value`, }, { validator: (values) => new Set(values).size !== values.length, - message: 'Default values must be unique', + message: msg`Default values must be unique`, }, ]; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts index 26a25f0e88e..ea1d05845cc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { IsEnum, IsString, IsUUID } from 'class-validator'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -201,7 +201,7 @@ export class FieldMetadataRelationService { `Name "${computedMetadataNameFromLabel}" cannot be the same on both side of the relation`, FieldMetadataExceptionCode.INVALID_FIELD_INPUT, { - userFriendlyMessage: t`Name "${computedMetadataNameFromLabel}" cannot be the same on both side of the relation`, + userFriendlyMessage: msg`Name "${computedMetadataNameFromLabel}" cannot be the same on both side of the relation`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service.ts index 5dc17a09cfd..07c5834ad0b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type ClassConstructor, plainToInstance } from 'class-transformer'; import { IsArray, @@ -196,7 +196,7 @@ export class FieldMetadataValidationService { `Name "${fieldMetadataInput.name}" is not available, check that it is not duplicating another field's name.`, FieldMetadataExceptionCode.INVALID_FIELD_INPUT, { - userFriendlyMessage: t`Name is not available, it may be duplicating another field's name.`, + userFriendlyMessage: msg`Name is not available, it may be duplicating another field's name.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts index 398f7d93f67..64cd566a5ae 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -190,7 +190,7 @@ export class FieldMetadataService extends TypeOrmQueryService { +export const fieldMetadataGraphqlApiExceptionHandler = ( + error: Error, + i18n: I18n, +) => { if (error instanceof WorkspaceMigrationBuilderExceptionV2) { - workspaceMigrationBuilderExceptionV2Formatter(error); + workspaceMigrationBuilderExceptionV2Formatter(error, i18n); } if (error instanceof InvalidMetadataException) { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap index d7ec766caaf..fd365731faf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap @@ -5,7 +5,10 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "error": { "code": "FIELD_METADATA_RELATION_MALFORMED", "message": "Morph relation creation payloads must have the same relation type", - "userFriendlyMessage": "Morph relation creation payloads must have the same relation type", + "userFriendlyMessage": { + "id": Any, + "message": "Morph relation creation payloads must have the same relation type", + }, }, "status": "fail", } @@ -16,7 +19,10 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "error": { "code": "FIELD_METADATA_RELATION_MALFORMED", "message": "Morph relation input transpilation failed", - "userFriendlyMessage": "Invalid morph relation input", + "userFriendlyMessage": { + "id": Any, + "message": "Invalid morph relation input", + }, "value": [ { "targetObjectMetadataId": Any, @@ -33,7 +39,10 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "error": { "code": "FIELD_METADATA_RELATION_MALFORMED", "message": "Morph relation creation payloads must have only relation to the same object metadata", - "userFriendlyMessage": "Morph relation creation payloads must only contain relation to the same object metadata", + "userFriendlyMessage": { + "id": Any, + "message": "Morph relation creation payloads must only contain relation to the same object metadata", + }, }, "status": "fail", } @@ -44,7 +53,10 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "error": { "code": "FIELD_METADATA_RELATION_MALFORMED", "message": "Morph relation creation payloads are empty", - "userFriendlyMessage": "At least one relation is require", + "userFriendlyMessage": { + "id": Any, + "message": "At least one relation is require", + }, }, "status": "fail", } @@ -55,7 +67,10 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "error": { "code": "INVALID_FIELD_INPUT", "message": "Relation creation payload is required", - "userFriendlyMessage": "Relation creation payload is required", + "userFriendlyMessage": { + "id": Any, + "message": "Relation creation payload is required", + }, "value": undefined, }, "status": "fail", @@ -67,7 +82,10 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "error": { "code": "FIELD_METADATA_RELATION_MALFORMED", "message": "Morph relation input transpilation failed", - "userFriendlyMessage": "Invalid morph relation input", + "userFriendlyMessage": { + "id": Any, + "message": "Invalid morph relation input", + }, "value": [ { "targetFieldIcon": "IconPet", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service.ts index d5679b97759..98021fccc7d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'class-validator'; import { FieldMetadataType } from 'twenty-shared/types'; @@ -66,7 +66,7 @@ export class FlatFieldMetadataTypeValidatorService { { code: FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION, message: 'Morph relation feature flag is disabled', - userFriendlyMessage: t`Morph relation fields are disabled for your workspace`, + userFriendlyMessage: msg`Morph relation fields are disabled for your workspace`, }, ]; } @@ -192,7 +192,7 @@ export class FlatFieldMetadataTypeValidatorService { code: FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION, message: `Unsupported field metadata type ${fieldType}`, value: fieldType, - userFriendlyMessage: t`Unsupported field metadata type ${fieldType}`, + userFriendlyMessage: msg`Unsupported field metadata type ${fieldType}`, }, ]; } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type.ts index f090cc02bd7..9b95430f82f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type.ts @@ -1,8 +1,10 @@ +import { type MessageDescriptor } from '@lingui/core'; + import { type FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; export type FlatFieldMetadataValidationError = { code: FieldMetadataExceptionCode; message: string; - userFriendlyMessage?: string; + userFriendlyMessage?: MessageDescriptor; value?: unknown; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util.ts index 779c8b78224..a3b81ba080a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { assertUnreachable, @@ -59,7 +59,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({ error: { code: FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, message: 'Provided object metadata id does not exist', - userFriendlyMessage: t`Created field metadata, parent object metadata not found`, + userFriendlyMessage: msg`Created field metadata, parent object metadata not found`, }, }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts index c7dc619d07d..a29da6509ed 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type FieldMetadataType } from 'twenty-shared/types'; import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; @@ -45,7 +45,7 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({ error: { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: `Relation creation payload is required`, - userFriendlyMessage: t`Relation creation payload is required`, + userFriendlyMessage: msg`Relation creation payload is required`, value: rawMorphCreationPayload, }, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts index b78cf97b3e0..3f359de94eb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -38,7 +38,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({ error: { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: `Relation creation payload is required`, - userFriendlyMessage: t`Relation creation payload is required`, + userFriendlyMessage: msg`Relation creation payload is required`, value: rawCreationPayload, }, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts index 5e9970e339b..b2cf8a98f7b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { extractAndSanitizeObjectStringFields, isDefined, @@ -150,7 +150,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({ error: { code: FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND, message: 'Field metadata to update not found', - userFriendlyMessage: t`Field metadata to update not found`, + userFriendlyMessage: msg`Field metadata to update not found`, }, }; } @@ -172,7 +172,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({ error: { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: `Cannot update standard field metadata properties: ${invalidProperties}`, - userFriendlyMessage: t`Cannot update standard field properties: ${invalidProperties}`, + userFriendlyMessage: msg`Cannot update standard field properties: ${invalidProperties}`, }, }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-enum-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-enum-flat-field-metadata.util.ts index 57a84c0c20f..660175759f0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-enum-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-enum-flat-field-metadata.util.ts @@ -53,7 +53,7 @@ const validateMetadataOptionLabel = ( { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: t`Option label is required`, - userFriendlyMessage: t`Option label is required`, + userFriendlyMessage: msg`Option label is required`, }, ]; } @@ -63,7 +63,7 @@ const validateMetadataOptionLabel = ( { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: t`Option label must be a string of at least one character`, - userFriendlyMessage: t`Option label format not supported`, + userFriendlyMessage: msg`Option label format not supported`, value: sanitizedLabel, }, ]; @@ -102,7 +102,7 @@ const validateMetadataOptionValue = ( { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: t`Option value is required`, - userFriendlyMessage: t`Option value is required`, + userFriendlyMessage: msg`Option value is required`, }, ]; } @@ -112,7 +112,7 @@ const validateMetadataOptionValue = ( { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: t`Option value must be a string of at least one character`, - userFriendlyMessage: t`Option value format not supported`, + userFriendlyMessage: msg`Option value format not supported`, }, ]; } @@ -175,7 +175,7 @@ const validateFieldMetadataInputOptions = ( { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: 'Options are required for enum fields', - userFriendlyMessage: t`Options are required for enum fields`, + userFriendlyMessage: msg`Options are required for enum fields`, value: options, }, ]; @@ -206,7 +206,7 @@ const validateSelectDefaultValue = ({ { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: `Default value for select must be a string got ${defaultValue}`, - userFriendlyMessage: t`Default value must be a string`, + userFriendlyMessage: msg`Default value must be a string`, value: defaultValue, }, ]; @@ -243,7 +243,7 @@ const validateMultiSelectDefaultValue = ({ return [ { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - userFriendlyMessage: t`Multi-select field default value must be an array`, + userFriendlyMessage: msg`Multi-select field default value must be an array`, message: `Default value for multi-select must be an array got ${multiSelectDefaultValue}`, value: multiSelectDefaultValue, }, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name-availability.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name-availability.util.ts index 9c1dab7b76d..284d1aa37df 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name-availability.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name-availability.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -101,7 +101,7 @@ export const validateFlatFieldMetadataNameAvailability = ({ code: FieldMetadataExceptionCode.NOT_AVAILABLE, value: flatFieldMetadataName, message: `Name "${flatFieldMetadataName}" is not available as it is already used by another field`, - userFriendlyMessage: t`Name "${flatFieldMetadataName}" is not available as it is already used by another field`, + userFriendlyMessage: msg`Name "${flatFieldMetadataName}" is not available as it is already used by another field`, }); } @@ -110,7 +110,7 @@ export const validateFlatFieldMetadataNameAvailability = ({ code: FieldMetadataExceptionCode.RESERVED_KEYWORD, message: `Name "${flatFieldMetadataName}" is reserved composite field name`, value: flatFieldMetadataName, - userFriendlyMessage: t`Name "${flatFieldMetadataName}" is not available`, + userFriendlyMessage: msg`Name "${flatFieldMetadataName}" is not available`, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name.util.ts index f03a5e17c8a..8a169397e1c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name.util.ts @@ -1,5 +1,3 @@ -import { t } from '@lingui/core/macro'; - import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type'; import { METADATA_NAME_VALIDATORS } from 'src/engine/metadata-modules/utils/constants/metadata-name-flat-metadata-validators.constants'; @@ -13,8 +11,8 @@ export const validateFlatFieldMetadataName = ( if (isInvalid) { return { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - message: t(message), - userFriendlyMessage: t(message), + message: message.message ?? '', + userFriendlyMessage: message, value: name, }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util.ts index 9f346a3b5f7..f571be8c982 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined, isValidUuid } from 'twenty-shared/utils'; import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; @@ -26,7 +26,7 @@ export const validateMorphOrRelationFlatFieldMetadata = async ({ : { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: `Invalid uuid ${id}`, - userFriendlyMessage: t`Invalid uuid ${id}`, + userFriendlyMessage: msg`Invalid uuid ${id}`, value: id, }, ); @@ -44,7 +44,7 @@ export const validateMorphOrRelationFlatFieldMetadata = async ({ errors.push({ code: FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, message: 'Relation target object metadata not found', - userFriendlyMessage: t`Object targeted by the relation not found`, + userFriendlyMessage: msg`Object targeted by the relation not found`, }); } @@ -61,7 +61,7 @@ export const validateMorphOrRelationFlatFieldMetadata = async ({ message: isDefined(remainingFlatEntityMapsToValidate) ? 'Relation field target metadata not found in both existing and about to be created field metadatas' : 'Relation field target metadata not found', - userFriendlyMessage: t`Relation field target metadata not found`, + userFriendlyMessage: msg`Relation field target metadata not found`, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util.ts index d8df9a08dff..02da013088c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'class-validator'; import { type RelationCreationPayload } from 'twenty-shared/types'; @@ -35,7 +35,7 @@ export const validateMorphRelationCreationPayload = async ({ error: { code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, message: 'Morph relation creation payloads are empty', - userFriendlyMessage: t`At least one relation is require`, + userFriendlyMessage: msg`At least one relation is require`, }, }; } @@ -55,7 +55,7 @@ export const validateMorphRelationCreationPayload = async ({ code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, message: 'Morph relation creation payloads must have the same relation type', - userFriendlyMessage: t`Morph relation creation payloads must have the same relation type`, + userFriendlyMessage: msg`Morph relation creation payloads must have the same relation type`, }, }; } @@ -74,7 +74,7 @@ export const validateMorphRelationCreationPayload = async ({ code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, message: 'Morph relation creation payloads must not target source object metadata', - userFriendlyMessage: t`Morph relation creation payloads must only contain relation to other object metadata`, + userFriendlyMessage: msg`Morph relation creation payloads must only contain relation to other object metadata`, }, }; } @@ -88,7 +88,7 @@ export const validateMorphRelationCreationPayload = async ({ code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, message: 'Morph relation creation payloads must have only relation to the same object metadata', - userFriendlyMessage: t`Morph relation creation payloads must only contain relation to the same object metadata`, + userFriendlyMessage: msg`Morph relation creation payloads must only contain relation to the same object metadata`, }, }; } @@ -130,7 +130,7 @@ export const validateMorphRelationCreationPayload = async ({ error: { code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, message: 'Morph relation input transpilation failed', - userFriendlyMessage: t`Invalid morph relation input`, + userFriendlyMessage: msg`Invalid morph relation input`, value: relationCreationPayloadReport.failed .map((failedTranspilation) => failedTranspilation.error.value) .filter(isDefined), diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util.ts index 164bd3fba75..4790c4d79f4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type RelationCreationPayload } from 'twenty-shared/types'; import { isDefined, @@ -42,7 +42,7 @@ export const validateRelationCreationPayload = async ({ error: { code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, message: `Relation creation payload is invalid`, - userFriendlyMessage: t`Invalid relation creation payload`, + userFriendlyMessage: msg`Invalid relation creation payload`, value: relationCreationPayload, }, }; @@ -62,7 +62,7 @@ export const validateRelationCreationPayload = async ({ error: { code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, message: `Object metadata relation target not found for relation creation payload`, - userFriendlyMessage: t`Object targeted by field to create not found`, + userFriendlyMessage: msg`Object targeted by field to create not found`, value: relationCreationPayload, }, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type.ts index bdb876d2b98..e7a79fc561f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type.ts @@ -1,8 +1,10 @@ +import { type MessageDescriptor } from '@lingui/core'; + import { type ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; export type FlatObjectMetadataValidationError = { code: ObjectMetadataExceptionCode; message: string; - userFriendlyMessage?: string; + userFriendlyMessage?: MessageDescriptor; value?: unknown; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util.ts index 7a7cf5fad1b..879a6a6ccdd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined, isLabelIdentifierFieldMetadataTypes, @@ -38,14 +38,14 @@ export const validateFlatObjectMetadataIdentifiers = ({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: 'labelIdentifierFieldMetadataId validation failed: related field metadata not found', - userFriendlyMessage: t`Field declared as label identifier not found`, + userFriendlyMessage: msg`Field declared as label identifier not found`, }); } else if (!isLabelIdentifierFieldMetadataTypes(flatFieldMetadata.type)) { errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: 'labelIdentifierFieldMetadataId validation failed: field type not compatible', - userFriendlyMessage: t`Field cannot be used as label identifier`, + userFriendlyMessage: msg`Field cannot be used as label identifier`, }); } } @@ -61,7 +61,7 @@ export const validateFlatObjectMetadataIdentifiers = ({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: 'imageIdentifierFieldMetadataId validation failed: related field metadata not found', - userFriendlyMessage: t`Field declared as image identifier not found`, + userFriendlyMessage: msg`Field declared as image identifier not found`, }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-label.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-label.util.ts index a750eb921e8..e18f52b76a2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-label.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-label.util.ts @@ -1,4 +1,4 @@ -import { msg, t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; @@ -44,7 +44,7 @@ export const validateFlatObjectMetadataLabel = ({ errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: `The singular and plural labels cannot be the same for an object`, - userFriendlyMessage: t`The singular and plural labels cannot be the same for an object`, + userFriendlyMessage: msg`The singular and plural labels cannot be the same for an object`, value: labelSingular, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util.ts index fe576887ce1..8d22f27d851 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type'; import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type'; @@ -39,7 +39,7 @@ export const validateFlatObjectMetadataNameAndLabels = ({ errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: t`Names are not synced with labels`, - userFriendlyMessage: t`Names are not synced with labels`, + userFriendlyMessage: msg`Names are not synced with labels`, }); } @@ -54,7 +54,7 @@ export const validateFlatObjectMetadataNameAndLabels = ({ errors.push({ code: ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS, message: 'Object already exists', - userFriendlyMessage: t`Object already exists`, + userFriendlyMessage: msg`Object already exists`, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name.util.ts index 91c08dbf967..2decbc1245d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; @@ -30,7 +30,7 @@ export const validateFlatObjectMetadataNames = ({ errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: `The singular and plural names cannot be the same for an object`, - userFriendlyMessage: t`The singular and plural names cannot be the same for an object`, + userFriendlyMessage: msg`The singular and plural names cannot be the same for an object`, value: namePlural, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.exception.ts index fab7e7ef2b0..a1464f1f94d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.exception.ts @@ -1,3 +1,5 @@ +import { type MessageDescriptor } from '@lingui/core'; + import { CustomException } from 'src/utils/custom-exception'; export class IndexMetadataException extends CustomException { @@ -5,7 +7,7 @@ export class IndexMetadataException extends CustomException { constructor( message: string, code: IndexMetadataExceptionCode, - { userFriendlyMessage }: { userFriendlyMessage?: string } = {}, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { super(message, code, { userFriendlyMessage }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.resolver.ts index a56df021848..b6ffd902a15 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.resolver.ts @@ -3,6 +3,8 @@ import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; +import { type I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; @@ -20,11 +22,13 @@ import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-module PermissionsGraphqlApiExceptionFilter, ) export class IndexMetadataResolver { + constructor(private readonly i18nService: I18nService) {} + @ResolveField(() => [IndexFieldMetadataDTO], { nullable: false }) async indexFieldMetadataList( @AuthWorkspace() workspace: Workspace, @Parent() indexMetadata: IndexMetadataDTO, - @Context() context: { loaders: IDataloaders }, + @Context() context: { loaders: IDataloaders } & I18nContext, ): Promise { try { const indexFieldMetadataItems = @@ -36,7 +40,10 @@ export class IndexMetadataResolver { return indexFieldMetadataItems; } catch (error) { - objectMetadataGraphqlApiExceptionHandler(error); + objectMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); return []; } diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/utils/validate-can-create-unique-index.util.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/utils/validate-can-create-unique-index.util.ts index 01628373ce6..827de149f42 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/utils/validate-can-create-unique-index.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/utils/validate-can-create-unique-index.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; @@ -30,7 +30,7 @@ export const validateCanCreateUniqueIndex = ( `Unique index cannot be created for field ${field.name} of type ${fieldType}`, FieldMetadataExceptionCode.INVALID_FIELD_INPUT, { - userFriendlyMessage: t`${fieldType} fields cannot be unique.`, + userFriendlyMessage: msg`${fieldType} fields cannot be unique.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor.ts index a3a0809a841..88aafd83597 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor.ts @@ -1,20 +1,36 @@ import { type CallHandler, type ExecutionContext, + Injectable, type NestInterceptor, } from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; import { type Observable, catchError } from 'rxjs'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util'; +@Injectable() export class ObjectMetadataGraphqlApiExceptionInterceptor implements NestInterceptor { + constructor(private readonly i18nService: I18nService) {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any - intercept(_: ExecutionContext, next: CallHandler): Observable { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const gqlContext = GqlExecutionContext.create(context); + const ctx = gqlContext.getContext(); + const locale = ctx.req?.locale ?? SOURCE_LOCALE; + const i18n = this.i18nService.getI18nInstance(locale); + return next .handle() - .pipe(catchError((err) => objectMetadataGraphqlApiExceptionHandler(err))); + .pipe( + catchError((err) => + objectMetadataGraphqlApiExceptionHandler(err, i18n), + ), + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts index 7f7916ecded..62db3222213 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts @@ -117,6 +117,7 @@ export class ObjectMetadataResolver { async deleteOneObject( @Args('input') input: DeleteOneObjectInput, @AuthWorkspace() { id: workspaceId }: Workspace, + @Context() context: I18nContext, ) { try { return await this.objectMetadataService.deleteOneObject( @@ -124,7 +125,10 @@ export class ObjectMetadataResolver { workspaceId, ); } catch (error) { - objectMetadataGraphqlApiExceptionHandler(error); + objectMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } } @@ -148,7 +152,10 @@ export class ObjectMetadataResolver { workspaceId, }); } catch (error) { - objectMetadataGraphqlApiExceptionHandler(error); + objectMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } } @@ -166,7 +173,10 @@ export class ObjectMetadataResolver { workspaceId, ); } catch (error) { - objectMetadataGraphqlApiExceptionHandler(error); + objectMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); } } @@ -187,7 +197,10 @@ export class ObjectMetadataResolver { return fieldMetadataItems; } catch (error) { - objectMetadataGraphqlApiExceptionHandler(error); + objectMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); return []; } @@ -197,7 +210,7 @@ export class ObjectMetadataResolver { async indexMetadataList( @AuthWorkspace() workspace: Workspace, @Parent() objectMetadata: ObjectMetadataDTO, - @Context() context: { loaders: IDataloaders }, + @Context() context: { loaders: IDataloaders } & I18nContext, ): Promise { try { const indexMetadataItems = await context.loaders.indexMetadataLoader.load( @@ -209,7 +222,10 @@ export class ObjectMetadataResolver { return indexMetadataItems; } catch (error) { - objectMetadataGraphqlApiExceptionHandler(error); + objectMetadataGraphqlApiExceptionHandler( + error, + this.i18nService.getI18nInstance(context.req.locale), + ); return []; } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts index 794e079d814..9f5bfaa4d2c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { type QueryRunner, Repository } from 'typeorm'; @@ -607,7 +608,7 @@ export class ObjectMetadataFieldRelationService { `Name "${name}" is not available.`, ObjectMetadataExceptionCode.NAME_CONFLICT, { - userFriendlyMessage: `Name "${name}" is not available.`, + userFriendlyMessage: msg`Name "${name}" is not available.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts index 34fb41a9875..26b14cde04b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts @@ -1,3 +1,4 @@ +import { type I18n } from '@lingui/core'; import { assertUnreachable } from 'twenty-shared/utils'; import { @@ -15,9 +16,12 @@ import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exce import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; import { workspaceMigrationBuilderExceptionV2Formatter } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-exception-v2-formatter'; -export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => { +export const objectMetadataGraphqlApiExceptionHandler = ( + error: Error, + i18n: I18n, +) => { if (error instanceof WorkspaceMigrationBuilderExceptionV2) { - workspaceMigrationBuilderExceptionV2Formatter(error); + workspaceMigrationBuilderExceptionV2Formatter(error, i18n); } if (error instanceof InvalidMetadataException) { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts index 2a6a40fe81e..04e08b0ae97 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts @@ -1,3 +1,4 @@ +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { type CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; @@ -35,7 +36,7 @@ export const validateObjectMetadataInputNameOrThrow = (name: string): void => { errorMessage, ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, { - userFriendlyMessage: errorMessage, + userFriendlyMessage: msg`Invalid object metadata input`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts index c0b81ed928f..f1c8690ba4f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { type ObjectsPermissionsByRoleIdDeprecated } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { In, Repository } from 'typeorm'; @@ -207,8 +208,7 @@ export class FieldPermissionService { PermissionsExceptionMessage.ONLY_FIELD_RESTRICTION_ALLOWED, PermissionsExceptionCode.ONLY_FIELD_RESTRICTION_ALLOWED, { - userFriendlyMessage: - 'Field permissions can only be used to restrict access, not to grant additional permissions.', + userFriendlyMessage: msg`Field permissions can only be used to restrict access, not to grant additional permissions.`, }, ); } @@ -221,8 +221,7 @@ export class FieldPermissionService { PermissionsExceptionMessage.OBJECT_METADATA_NOT_FOUND, PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND, { - userFriendlyMessage: - 'The object you are trying to set permissions for could not be found. It may have been deleted.', + userFriendlyMessage: msg`The object you are trying to set permissions for could not be found. It may have been deleted.`, }, ); } @@ -232,8 +231,7 @@ export class FieldPermissionService { PermissionsExceptionMessage.CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT, PermissionsExceptionCode.CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT, { - userFriendlyMessage: - 'You cannot set field permissions on system objects as they are managed by the platform.', + userFriendlyMessage: msg`You cannot set field permissions on system objects as they are managed by the platform.`, }, ); } @@ -248,8 +246,7 @@ export class FieldPermissionService { PermissionsExceptionMessage.FIELD_METADATA_NOT_FOUND, PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND, { - userFriendlyMessage: - 'The field you are trying to set permissions for could not be found. It may have been deleted.', + userFriendlyMessage: msg`The field you are trying to set permissions for could not be found. It may have been deleted.`, }, ); } @@ -262,8 +259,7 @@ export class FieldPermissionService { PermissionsExceptionMessage.OBJECT_PERMISSION_NOT_FOUND, PermissionsExceptionCode.OBJECT_PERMISSION_NOT_FOUND, { - userFriendlyMessage: - 'No permissions are set for this role on the selected object. Please set object permissions first.', + userFriendlyMessage: msg`No permissions are set for this role on the selected object. Please set object permissions first.`, }, ); } @@ -289,8 +285,7 @@ export class FieldPermissionService { PermissionsExceptionMessage.ROLE_NOT_FOUND, PermissionsExceptionCode.ROLE_NOT_FOUND, { - userFriendlyMessage: - 'The role you are trying to modify could not be found. It may have been deleted or you may not have access to it.', + userFriendlyMessage: msg`The role you are trying to modify could not be found. It may have been deleted or you may not have access to it.`, }, ); } @@ -304,8 +299,7 @@ export class FieldPermissionService { PermissionsExceptionMessage.ROLE_NOT_EDITABLE, PermissionsExceptionCode.ROLE_NOT_EDITABLE, { - userFriendlyMessage: - 'This role cannot be modified because it is a system role. Only custom roles can be edited.', + userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, }, ); } @@ -436,10 +430,12 @@ export class FieldPermissionService { firstFieldPermission.canUpdateFieldValue; if (hasConflictingPermissions) { + const fieldName = fieldMetadata.name; + throw new UserInputError( 'Conflicting field permissions found for relation target field', { - userFriendlyMessage: `Contradicting field permissions have been detected on a relation field (${fieldMetadata.name}).`, + userFriendlyMessage: msg`Contradicting field permissions have been detected on a relation field (${fieldName}).`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-permission/object-permission.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-permission/object-permission.service.ts index 2a531911d7b..77e44ff61a4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-permission/object-permission.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-permission/object-permission.service.ts @@ -1,5 +1,6 @@ import { InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { In, Repository } from 'typeorm'; @@ -66,8 +67,7 @@ export class ObjectPermissionService { 'Object metadata id not found', PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND, { - userFriendlyMessage: - 'The object you are trying to set permissions for could not be found. It may have been deleted.', + userFriendlyMessage: msg`The object you are trying to set permissions for could not be found. It may have been deleted.`, }, ); } @@ -77,8 +77,7 @@ export class ObjectPermissionService { PermissionsExceptionMessage.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT, PermissionsExceptionCode.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT, { - userFriendlyMessage: - 'You cannot set permissions on system objects as they are managed by the platform.', + userFriendlyMessage: msg`You cannot set permissions on system objects as they are managed by the platform.`, }, ); } @@ -183,8 +182,7 @@ export class ObjectPermissionService { PermissionsExceptionMessage.CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT, PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT, { - userFriendlyMessage: - 'You cannot grant edit permissions without also granting read permissions. Please enable read access first.', + userFriendlyMessage: msg`You cannot grant edit permissions without also granting read permissions. Please enable read access first.`, }, ); } @@ -216,8 +214,7 @@ export class ObjectPermissionService { PermissionsExceptionMessage.ROLE_NOT_FOUND, PermissionsExceptionCode.ROLE_NOT_FOUND, { - userFriendlyMessage: - 'The role you are trying to modify could not be found. It may have been deleted or you may not have access to it.', + userFriendlyMessage: msg`The role you are trying to modify could not be found. It may have been deleted or you may not have access to it.`, }, ); } @@ -234,8 +231,7 @@ export class ObjectPermissionService { PermissionsExceptionMessage.OBJECT_METADATA_NOT_FOUND, PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND, { - userFriendlyMessage: - 'One or more objects you are trying to set permissions for could not be found. They may have been deleted.', + userFriendlyMessage: msg`One or more objects you are trying to set permissions for could not be found. They may have been deleted.`, }, ); } @@ -262,8 +258,7 @@ export class ObjectPermissionService { PermissionsExceptionMessage.ROLE_NOT_FOUND, PermissionsExceptionCode.ROLE_NOT_FOUND, { - userFriendlyMessage: - 'The role you are trying to modify could not be found. It may have been deleted or you may not have access to it.', + userFriendlyMessage: msg`The role you are trying to modify could not be found. It may have been deleted or you may not have access to it.`, }, ); } @@ -277,8 +272,7 @@ export class ObjectPermissionService { PermissionsExceptionMessage.ROLE_NOT_EDITABLE, PermissionsExceptionCode.ROLE_NOT_EDITABLE, { - userFriendlyMessage: - 'This role cannot be modified because it is a system role. Only custom roles can be edited.', + userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts index 965270c1016..b016cd0ba2a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts @@ -1,5 +1,6 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { DataSource, In, Repository } from 'typeorm'; @@ -44,8 +45,7 @@ export class PermissionFlagService { `${PermissionsExceptionMessage.INVALID_SETTING}: ${invalidFlags.join(', ')}`, PermissionsExceptionCode.INVALID_SETTING, { - userFriendlyMessage: - 'Some of the permissions you selected are not valid. Please try again with valid permission settings.', + userFriendlyMessage: msg`Some of the permissions you selected are not valid. Please try again with valid permission settings.`, }, ); } @@ -119,8 +119,7 @@ export class PermissionFlagService { PermissionsExceptionMessage.ROLE_NOT_FOUND, PermissionsExceptionCode.ROLE_NOT_FOUND, { - userFriendlyMessage: - 'The role you are trying to modify could not be found. It may have been deleted or you may not have access to it.', + userFriendlyMessage: msg`The role you are trying to modify could not be found. It may have been deleted or you may not have access to it.`, }, ); } @@ -157,8 +156,7 @@ export class PermissionFlagService { PermissionsExceptionMessage.ROLE_NOT_EDITABLE, PermissionsExceptionCode.ROLE_NOT_EDITABLE, { - userFriendlyMessage: - 'This role cannot be modified because it is a system role. Only custom roles can be edited.', + userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index b1be6d5aa0d..ee614720310 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; @@ -50,8 +51,7 @@ export class PermissionsService { PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE, PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE, { - userFriendlyMessage: - 'Your role in this workspace could not be found. Please contact your workspace administrator.', + userFriendlyMessage: msg`Your role in this workspace could not be found. Please contact your workspace administrator.`, }, ); } @@ -135,8 +135,7 @@ export class PermissionsService { PermissionsExceptionMessage.API_KEY_ROLE_NOT_FOUND, PermissionsExceptionCode.API_KEY_ROLE_NOT_FOUND, { - userFriendlyMessage: - 'The API key does not have a valid role assigned. Please check your API key configuration.', + userFriendlyMessage: msg`The API key does not have a valid role assigned. Please check your API key configuration.`, }, ); } @@ -157,8 +156,7 @@ export class PermissionsService { PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE, PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE, { - userFriendlyMessage: - 'Your role in this workspace could not be found. Please contact your workspace administrator.', + userFriendlyMessage: msg`Your role in this workspace could not be found. Please contact your workspace administrator.`, }, ); } @@ -170,8 +168,7 @@ export class PermissionsService { PermissionsExceptionMessage.NO_AUTHENTICATION_CONTEXT, PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT, { - userFriendlyMessage: - 'Authentication is required to access this feature. Please sign in and try again.', + userFriendlyMessage: msg`Authentication is required to access this feature. Please sign in and try again.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts index ce9ea4c919e..687d84bcd25 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts @@ -1,3 +1,4 @@ +import { msg } from '@lingui/core/macro'; import { assertUnreachable } from 'twenty-shared/utils'; import { @@ -16,12 +17,12 @@ export const permissionGraphqlApiExceptionHandler = ( switch (error.code) { case PermissionsExceptionCode.PERMISSION_DENIED: throw new ForbiddenError(error.message, { - userFriendlyMessage: 'User does not have permission.', + userFriendlyMessage: msg`User does not have permission.`, subCode: error.code, }); case PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT: throw new ForbiddenError(error.message, { - userFriendlyMessage: 'No valid authentication context found.', + userFriendlyMessage: msg`No valid authentication context found.`, subCode: error.code, }); case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS: diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts index ee0505865d7..ad9d8af5760 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts @@ -8,6 +8,8 @@ import { Resolver, } from '@nestjs/graphql'; +import { msg } from '@lingui/core/macro'; + import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/api-key-role.service'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -92,8 +94,7 @@ export class RoleResolver { PermissionsExceptionMessage.CANNOT_UPDATE_SELF_ROLE, PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE, { - userFriendlyMessage: - 'You cannot change your own role. Please ask another administrator to update your role.', + userFriendlyMessage: msg`You cannot change your own role. Please ask another administrator to update your role.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts index 4a9932c00ae..5ecac581209 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts @@ -1,6 +1,6 @@ import { InjectRepository } from '@nestjs/typeorm'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; @@ -122,8 +122,7 @@ export class RoleService { PermissionsExceptionMessage.ROLE_NOT_FOUND, PermissionsExceptionCode.ROLE_NOT_FOUND, { - userFriendlyMessage: - 'The role you are looking for could not be found. It may have been deleted or you may not have access to it.', + userFriendlyMessage: msg`The role you are looking for could not be found. It may have been deleted or you may not have access to it.`, }, ); } @@ -169,8 +168,7 @@ export class RoleService { PermissionsExceptionMessage.DEFAULT_ROLE_NOT_FOUND, PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND, { - userFriendlyMessage: - 'The default role for this workspace could not be found. Please contact support for assistance.', + userFriendlyMessage: msg`The default role for this workspace could not be found. Please contact support for assistance.`, }, ); } @@ -277,8 +275,7 @@ export class RoleService { error.message, PermissionsExceptionCode.INVALID_ARG, { - userFriendlyMessage: - 'Some of the information provided is invalid. Please check your input and try again.', + userFriendlyMessage: msg`Some of the information provided is invalid. Please check your input and try again.`, }, ); } @@ -299,7 +296,7 @@ export class RoleService { throw new PermissionsException( PermissionsExceptionMessage.ROLE_LABEL_ALREADY_EXISTS, PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS, - { userFriendlyMessage: t`A role with this label already exists.` }, + { userFriendlyMessage: msg`A role with this label already exists.` }, ); } } @@ -344,8 +341,7 @@ export class RoleService { PermissionsExceptionMessage.CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION, PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION, { - userFriendlyMessage: - 'You cannot grant edit permissions without also granting read permissions. Please enable read access first.', + userFriendlyMessage: msg`You cannot grant edit permissions without also granting read permissions. Please enable read access first.`, }, ); } @@ -403,8 +399,7 @@ export class RoleService { PermissionsExceptionMessage.ROLE_NOT_EDITABLE, PermissionsExceptionCode.ROLE_NOT_EDITABLE, { - userFriendlyMessage: - 'This role cannot be modified because it is a system role. Only custom roles can be edited.', + userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, }, ); } @@ -422,8 +417,7 @@ export class RoleService { PermissionsExceptionMessage.DEFAULT_ROLE_CANNOT_BE_DELETED, PermissionsExceptionCode.DEFAULT_ROLE_CANNOT_BE_DELETED, { - userFriendlyMessage: - 'The default role cannot be deleted as it is required for the workspace to function properly.', + userFriendlyMessage: msg`The default role cannot be deleted as it is required for the workspace to function properly.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts index f84aa8eacac..97cd3b4873b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts @@ -1,5 +1,6 @@ import { InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { In, Not, Repository } from 'typeorm'; @@ -196,8 +197,7 @@ export class UserRoleService { PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE, PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE, { - userFriendlyMessage: - 'Your role in this workspace could not be found. Please contact your workspace administrator.', + userFriendlyMessage: msg`Your role in this workspace could not be found. Please contact your workspace administrator.`, }, ); } @@ -235,8 +235,7 @@ export class UserRoleService { 'User workspace not found', PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND, { - userFriendlyMessage: - 'Your workspace membership could not be found. You may no longer have access to this workspace.', + userFriendlyMessage: msg`Your workspace membership could not be found. You may no longer have access to this workspace.`, }, ); } @@ -252,8 +251,7 @@ export class UserRoleService { 'Role not found', PermissionsExceptionCode.ROLE_NOT_FOUND, { - userFriendlyMessage: - 'The role you are trying to assign could not be found. It may have been deleted.', + userFriendlyMessage: msg`The role you are trying to assign could not be found. It may have been deleted.`, }, ); } @@ -263,8 +261,7 @@ export class UserRoleService { `Role "${role.label}" cannot be assigned to users`, PermissionsExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_USERS, { - userFriendlyMessage: - 'This role cannot be assigned to users. Please select a different role.', + userFriendlyMessage: msg`This role cannot be assigned to users. Please select a different role.`, }, ); } @@ -312,8 +309,7 @@ export class UserRoleService { PermissionsExceptionMessage.CANNOT_UNASSIGN_LAST_ADMIN, PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN, { - userFriendlyMessage: - 'You cannot remove the admin role from the last administrator. Please assign another administrator first.', + userFriendlyMessage: msg`You cannot remove the admin role from the last administrator. Please assign another administrator first.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts index f9708eda2e7..27c695e2b0e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import camelCase from 'lodash.camelcase'; import { slugify } from 'transliteration'; import { isDefined } from 'twenty-shared/utils'; @@ -14,7 +14,7 @@ export const computeMetadataNameFromLabel = (label: string): string => { 'Label is required', InvalidMetadataExceptionCode.LABEL_REQUIRED, { - userFriendlyMessage: t`Label is required`, + userFriendlyMessage: msg`Label is required`, }, ); } @@ -36,7 +36,7 @@ export const computeMetadataNameFromLabel = (label: string): string => { `Invalid label: "${label}"`, InvalidMetadataExceptionCode.INVALID_LABEL, { - userFriendlyMessage: t`Invalid label: "${label}"`, + userFriendlyMessage: msg`Invalid label: "${label}"`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts index 4ab69a95a17..81b4361133e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; @@ -54,7 +54,7 @@ export const validateFieldNameAvailabilityOrThrow = ({ `Name "${name}" is not available as it is already used by another field`, InvalidMetadataExceptionCode.NOT_AVAILABLE, { - userFriendlyMessage: t`This name is not available as it is already used by another field.`, + userFriendlyMessage: msg`This name is not available as it is already used by another field.`, }, ); } @@ -64,7 +64,7 @@ export const validateFieldNameAvailabilityOrThrow = ({ `Name "${name}" is not available`, InvalidMetadataExceptionCode.RESERVED_KEYWORD, { - userFriendlyMessage: t`This name is not available.`, + userFriendlyMessage: msg`This name is not available.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword.ts index 5527410edfe..e6a766bb7c9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { InvalidMetadataException, @@ -74,7 +74,7 @@ export const validateMetadataNameIsNotReservedKeywordOrThrow = ( `The name "${name}" is not available`, InvalidMetadataExceptionCode.RESERVED_KEYWORD, { - userFriendlyMessage: t`This name is not available.`, + userFriendlyMessage: msg`This name is not available.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util.ts index 978a0e5c33b..edbd3a52ce5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type'; @@ -43,7 +43,7 @@ export const validatesNoOtherObjectWithSameNameExistsOrThrows = ( 'Object already exists', ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS, { - userFriendlyMessage: t`Object already exists`, + userFriendlyMessage: msg`Object already exists`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/exceptions/view-field.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/exceptions/view-field.exception.ts index 43e996104d7..e363766a1d8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/exceptions/view-field.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/exceptions/view-field.exception.ts @@ -1,4 +1,5 @@ -import { t } from '@lingui/core/macro'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -8,7 +9,7 @@ export class ViewFieldException extends CustomException { constructor( message: string, code: ViewFieldExceptionCode, - { userFriendlyMessage }: { userFriendlyMessage?: string } = {}, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { super(message, code, { userFriendlyMessage }); } @@ -52,15 +53,15 @@ export const generateViewFieldExceptionMessage = ( export const generateViewFieldUserFriendlyExceptionMessage = ( key: ViewFieldExceptionMessageKey, -) => { +): MessageDescriptor | undefined => { switch (key) { case ViewFieldExceptionMessageKey.WORKSPACE_ID_REQUIRED: - return t`WorkspaceId is required to create a view field.`; + return msg`WorkspaceId is required to create a view field.`; case ViewFieldExceptionMessageKey.VIEW_ID_REQUIRED: - return t`ViewId is required to create a view field.`; + return msg`ViewId is required to create a view field.`; case ViewFieldExceptionMessageKey.FIELD_METADATA_ID_REQUIRED: - return t`FieldMetadataId is required to create a view field.`; + return msg`FieldMetadataId is required to create a view field.`; case ViewFieldExceptionMessageKey.VIEW_FIELD_ALREADY_EXISTS: - return t`View field already exists.`; + return msg`View field already exists.`; } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field.service.ts index 640e7bb5d1c..d1878aff3ff 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { IsNull, Repository } from 'typeorm'; @@ -319,7 +320,7 @@ export class ViewFieldService { if (newOrUpdatedViewField.isVisible === false) { throw new UserInputError('Label metadata identifier must stay visible.', { - userFriendlyMessage: 'Record text must stay visible.', + userFriendlyMessage: msg`Record text must stay visible.`, }); } } @@ -370,8 +371,7 @@ export class ViewFieldService { throw new UserInputError( 'Label metadata identifier must keep the minimal position in the view.', { - userFriendlyMessage: - 'Record text must be in first position of the view.', + userFriendlyMessage: msg`Record text must be in first position of the view.`, }, ); } @@ -389,8 +389,7 @@ export class ViewFieldService { throw new UserInputError( 'Label metadata identifier must keep the minimal position in the view.', { - userFriendlyMessage: - 'Record text must be in first position of the view.', + userFriendlyMessage: msg`Record text must be in first position of the view.`, }, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception.ts index 81c86655e60..46cbdda413a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception.ts @@ -1,4 +1,5 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; +import { type MessageDescriptor } from '@lingui/core'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -8,7 +9,7 @@ export class ViewFilterGroupException extends CustomException { constructor( message: string, code: ViewFilterGroupExceptionCode, - { userFriendlyMessage }: { userFriendlyMessage?: string } = {}, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { super(message, code, { userFriendlyMessage }); } @@ -49,13 +50,13 @@ export const generateViewFilterGroupExceptionMessage = ( export const generateViewFilterGroupUserFriendlyExceptionMessage = ( key: ViewFilterGroupExceptionMessageKey, -) => { +): MessageDescriptor | undefined => { switch (key) { case ViewFilterGroupExceptionMessageKey.WORKSPACE_ID_REQUIRED: - return t`WorkspaceId is required to create a view filter group.`; + return msg`WorkspaceId is required to create a view filter group.`; case ViewFilterGroupExceptionMessageKey.VIEW_ID_REQUIRED: - return t`ViewId is required to create a view filter group.`; + return msg`ViewId is required to create a view filter group.`; case ViewFilterGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED: - return t`FieldMetadataId is required to create a view filter group.`; + return msg`FieldMetadataId is required to create a view filter group.`; } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/exceptions/view-filter.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/exceptions/view-filter.exception.ts index c93b16c33c8..f1b73502b0d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/exceptions/view-filter.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/exceptions/view-filter.exception.ts @@ -1,4 +1,5 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; +import { type MessageDescriptor } from '@lingui/core'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -8,7 +9,7 @@ export class ViewFilterException extends CustomException { constructor( message: string, code: ViewFilterExceptionCode, - { userFriendlyMessage }: { userFriendlyMessage?: string } = {}, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { super(message, code, { userFriendlyMessage }); } @@ -49,13 +50,13 @@ export const generateViewFilterExceptionMessage = ( export const generateViewFilterUserFriendlyExceptionMessage = ( key: ViewFilterExceptionMessageKey, -) => { +): MessageDescriptor | undefined => { switch (key) { case ViewFilterExceptionMessageKey.WORKSPACE_ID_REQUIRED: - return t`WorkspaceId is required to create a view filter.`; + return msg`WorkspaceId is required to create a view filter.`; case ViewFilterExceptionMessageKey.VIEW_ID_REQUIRED: - return t`ViewId is required to create a view filter.`; + return msg`ViewId is required to create a view filter.`; case ViewFilterExceptionMessageKey.FIELD_METADATA_ID_REQUIRED: - return t`FieldMetadataId is required to create a view filter.`; + return msg`FieldMetadataId is required to create a view filter.`; } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts index 1a4ce0eecaa..0346a5db7fb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts @@ -1,4 +1,5 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; +import { type MessageDescriptor } from '@lingui/core'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -8,7 +9,7 @@ export class ViewGroupException extends CustomException { constructor( message: string, code: ViewGroupExceptionCode, - { userFriendlyMessage }: { userFriendlyMessage?: string } = {}, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { super(message, code, { userFriendlyMessage }); } @@ -49,13 +50,13 @@ export const generateViewGroupExceptionMessage = ( export const generateViewGroupUserFriendlyExceptionMessage = ( key: ViewGroupExceptionMessageKey, -) => { +): MessageDescriptor | undefined => { switch (key) { case ViewGroupExceptionMessageKey.WORKSPACE_ID_REQUIRED: - return t`WorkspaceId is required to create a view group.`; + return msg`WorkspaceId is required to create a view group.`; case ViewGroupExceptionMessageKey.VIEW_ID_REQUIRED: - return t`ViewId is required to create a view group.`; + return msg`ViewId is required to create a view group.`; case ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED: - return t`FieldMetadataId is required to create a view group.`; + return msg`FieldMetadataId is required to create a view group.`; } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/view-sort/exceptions/view-sort.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-sort/exceptions/view-sort.exception.ts index 04cc6e279af..d3942ba1b72 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-sort/exceptions/view-sort.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-sort/exceptions/view-sort.exception.ts @@ -1,4 +1,5 @@ -import { t } from '@lingui/core/macro'; +import { msg, t } from '@lingui/core/macro'; +import { type MessageDescriptor } from '@lingui/core'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -8,7 +9,7 @@ export class ViewSortException extends CustomException { constructor( message: string, code: ViewSortExceptionCode, - { userFriendlyMessage }: { userFriendlyMessage?: string } = {}, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { super(message, code, { userFriendlyMessage }); } @@ -62,13 +63,13 @@ export const generateViewSortExceptionMessage = ( export const generateViewSortUserFriendlyExceptionMessage = ( key: ViewSortExceptionMessageKey, -) => { +): MessageDescriptor | undefined => { switch (key) { case ViewSortExceptionMessageKey.WORKSPACE_ID_REQUIRED: - return t`WorkspaceId is required to create a view sort.`; + return msg`WorkspaceId is required to create a view sort.`; case ViewSortExceptionMessageKey.VIEW_ID_REQUIRED: - return t`ViewId is required to create a view sort.`; + return msg`ViewId is required to create a view sort.`; case ViewSortExceptionMessageKey.FIELD_METADATA_ID_REQUIRED: - return t`FieldMetadataId is required to create a view sort.`; + return msg`FieldMetadataId is required to create a view sort.`; } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/view/exceptions/view.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view/exceptions/view.exception.ts index 68049918b28..d901087fc09 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/exceptions/view.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/exceptions/view.exception.ts @@ -1,4 +1,5 @@ -import { t } from '@lingui/core/macro'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -8,7 +9,7 @@ export class ViewException extends CustomException { constructor( message: string, code: ViewExceptionCode, - { userFriendlyMessage }: { userFriendlyMessage?: string } = {}, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { super(message, code, { userFriendlyMessage }); } @@ -46,11 +47,11 @@ export const generateViewExceptionMessage = ( export const generateViewUserFriendlyExceptionMessage = ( key: ViewExceptionMessageKey, -) => { +): MessageDescriptor | undefined => { switch (key) { case ViewExceptionMessageKey.WORKSPACE_ID_REQUIRED: - return t`WorkspaceId is required to create a view.`; + return msg`WorkspaceId is required to create a view.`; case ViewExceptionMessageKey.OBJECT_METADATA_ID_REQUIRED: - return t`ObjectMetadataId is required to create a view.`; + return msg`ObjectMetadataId is required to create a view.`; } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/view/utils/view-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/view/utils/view-graphql-api-exception-handler.util.ts index 3e1ea6abd60..cb741e86756 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/utils/view-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/utils/view-graphql-api-exception-handler.util.ts @@ -1,3 +1,4 @@ +import { type I18n } from '@lingui/core'; import { assertUnreachable } from 'twenty-shared/utils'; import { @@ -31,9 +32,9 @@ import { import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; import { workspaceMigrationBuilderExceptionV2Formatter } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-exception-v2-formatter'; -export const viewGraphqlApiExceptionHandler = (error: Error) => { +export const viewGraphqlApiExceptionHandler = (error: Error, i18n: I18n) => { if (error instanceof WorkspaceMigrationBuilderExceptionV2) { - return workspaceMigrationBuilderExceptionV2Formatter(error); + return workspaceMigrationBuilderExceptionV2Formatter(error, i18n); } if (error instanceof ViewException) { diff --git a/packages/twenty-server/src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter.ts index a22f15264b5..5e9cdcceed4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter.ts @@ -1,5 +1,14 @@ -import { Catch, type ExceptionFilter } from '@nestjs/common'; +import { + Catch, + type ExecutionContext, + type ExceptionFilter, + Injectable, +} from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; + +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { ViewFieldException } from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception'; import { ViewFilterGroupException } from 'src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception'; import { ViewFilterException } from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception'; @@ -18,7 +27,10 @@ import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manag ViewSortException, WorkspaceMigrationBuilderExceptionV2, ) +@Injectable() export class ViewGraphqlApiExceptionFilter implements ExceptionFilter { + constructor(private readonly i18nService: I18nService) {} + catch( exception: | ViewException @@ -28,7 +40,13 @@ export class ViewGraphqlApiExceptionFilter implements ExceptionFilter { | ViewGroupException | ViewSortException | WorkspaceMigrationBuilderExceptionV2, + host: ExecutionContext, ) { - return viewGraphqlApiExceptionHandler(exception); + const gqlContext = GqlExecutionContext.create(host); + const ctx = gqlContext.getContext(); + const userLocale = ctx.req?.locale ?? SOURCE_LOCALE; + const i18n = this.i18nService.getI18nInstance(userLocale); + + return viewGraphqlApiExceptionHandler(exception, i18n); } } diff --git a/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts b/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts index ab7cfdfde22..02682bf91ca 100644 --- a/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts +++ b/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { QueryFailedError } from 'typeorm'; @@ -25,7 +25,7 @@ export const computeTwentyORMException = ( 'Query read timeout', TwentyORMExceptionCode.QUERY_READ_TIMEOUT, { - userFriendlyMessage: t`We are experiencing a temporary issue with our database. Please try again later.`, + userFriendlyMessage: msg`We are experiencing a temporary issue with our database. Please try again later.`, }, ); } diff --git a/packages/twenty-server/src/engine/twenty-orm/relation-nested-queries/utils/__tests__/formatConnectRecordNotFoundErrorMessage.util.spec.ts b/packages/twenty-server/src/engine/twenty-orm/relation-nested-queries/utils/__tests__/formatConnectRecordNotFoundErrorMessage.util.spec.ts index bf49117c180..12a7cdda819 100644 --- a/packages/twenty-server/src/engine/twenty-orm/relation-nested-queries/utils/__tests__/formatConnectRecordNotFoundErrorMessage.util.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/relation-nested-queries/utils/__tests__/formatConnectRecordNotFoundErrorMessage.util.spec.ts @@ -14,8 +14,15 @@ describe('formatConnectRecordNotFoundErrorMessage', () => { expect(result).toEqual({ errorMessage: 'Expected 1 record to connect to connectFieldName, but found 0 for field1 = value1 and field2 = value2', - userFriendlyMessage: - "Can't connect to connectFieldName. No unique record found with condition: field1 = value1 and field2 = value2", + userFriendlyMessage: expect.objectContaining({ + id: expect.any(String), + message: + "Can't connect to {connectFieldName}. No unique record found with condition: {formattedConnectCondition}", + values: { + connectFieldName: 'connectFieldName', + formattedConnectCondition: 'field1 = value1 and field2 = value2', + }, + }), }); }); }); diff --git a/packages/twenty-server/src/engine/twenty-orm/relation-nested-queries/utils/formatConnectRecordNotFoundErrorMessage.util.ts b/packages/twenty-server/src/engine/twenty-orm/relation-nested-queries/utils/formatConnectRecordNotFoundErrorMessage.util.ts index 0f0dd311b95..99e26f73792 100644 --- a/packages/twenty-server/src/engine/twenty-orm/relation-nested-queries/utils/formatConnectRecordNotFoundErrorMessage.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/relation-nested-queries/utils/formatConnectRecordNotFoundErrorMessage.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type UniqueConstraintCondition } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type'; @@ -13,6 +13,6 @@ export const formatConnectRecordNotFoundErrorMessage = ( return { errorMessage: `Expected 1 record to connect to ${connectFieldName}, but found ${recordToConnectTotal} for ${formattedConnectCondition}`, - userFriendlyMessage: t`Can't connect to ${connectFieldName}. No unique record found with condition: ${formattedConnectCondition}`, + userFriendlyMessage: msg`Can't connect to ${connectFieldName}. No unique record found with condition: ${formattedConnectCondition}`, }; }; diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util.ts index af09947a6fa..d2174789d42 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import deepEqual from 'deep-equal'; import { FieldMetadataType } from 'twenty-shared/types'; import { getUniqueConstraintsFields, isDefined } from 'twenty-shared/utils'; @@ -142,7 +142,7 @@ const computeRecordToConnectCondition = ( `Connect is not allowed for ${connectFieldName} on ${objectMetadata.nameSingular}`, TwentyORMExceptionCode.CONNECT_NOT_ALLOWED, { - userFriendlyMessage: t`Connect is not allowed for ${connectFieldName} on ${objectMetadataNameSingular}`, + userFriendlyMessage: msg`Connect is not allowed for ${connectFieldName} on ${objectMetadataNameSingular}`, }, ); } @@ -156,7 +156,7 @@ const computeRecordToConnectCondition = ( `Target object metadata not found for ${connectFieldName}`, TwentyORMExceptionCode.MALFORMED_METADATA, { - userFriendlyMessage: t`Target object metadata not found for ${connectFieldName}`, + userFriendlyMessage: msg`Target object metadata not found for ${connectFieldName}`, }, ); } @@ -202,7 +202,7 @@ const checkUniqueConstraintFullyPopulated = ( `Missing required fields: at least one unique constraint have to be fully populated for '${connectFieldName}'.`, TwentyORMExceptionCode.CONNECT_UNIQUE_CONSTRAINT_ERROR, { - userFriendlyMessage: t`Missing required fields: at least one unique constraint have to be fully populated for '${connectFieldName}'.`, + userFriendlyMessage: msg`Missing required fields: at least one unique constraint have to be fully populated for '${connectFieldName}'.`, }, ); } @@ -235,7 +235,7 @@ const checkNoRelationFieldConflictOrThrow = ( `${fieldName} and ${fieldName}Id cannot be both provided.`, TwentyORMExceptionCode.CONNECT_NOT_ALLOWED, { - userFriendlyMessage: t`${fieldName} and ${fieldName}Id cannot be both provided.`, + userFriendlyMessage: msg`${fieldName} and ${fieldName}Id cannot be both provided.`, }, ); } @@ -284,7 +284,7 @@ const checkUniqueConstraintsAreSameOrThrow = ( `Expected the same constraint fields to be used consistently across all operations for ${relationConnectQueryConfig.connectFieldName}.`, TwentyORMExceptionCode.CONNECT_UNIQUE_CONSTRAINT_ERROR, { - userFriendlyMessage: t`Expected the same constraint fields to be used consistently across all operations for ${connectFieldName}.`, + userFriendlyMessage: msg`Expected the same constraint fields to be used consistently across all operations for ${connectFieldName}.`, }, ); } diff --git a/packages/twenty-server/src/engine/workspace-datasource/workspace-datasource.service.ts b/packages/twenty-server/src/engine/workspace-datasource/workspace-datasource.service.ts index be1f1f2ab7e..a5526d9332d 100644 --- a/packages/twenty-server/src/engine/workspace-datasource/workspace-datasource.service.ts +++ b/packages/twenty-server/src/engine/workspace-datasource/workspace-datasource.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; +import { msg } from '@lingui/core/macro'; import { type DataSource, type EntityManager } from 'typeorm'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; @@ -75,8 +76,7 @@ export class WorkspaceDataSourceService { 'Method not allowed as permissions are not handled at datasource level.', PermissionsExceptionCode.METHOD_NOT_ALLOWED, { - userFriendlyMessage: - 'This operation is not allowed. Please try a different approach or contact support if you need assistance.', + userFriendlyMessage: msg`This operation is not allowed. Please try a different approach or contact support if you need assistance.`, }, ); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts index fa811d764ac..842854f11af 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { DataSource, type QueryRunner, Table, type TableColumn } from 'typeorm'; @@ -257,7 +257,7 @@ export class WorkspaceMigrationRunnerService { `Unique index creation failed because of unique constraint violation`, IndexMetadataExceptionCode.INDEX_CREATION_FAILED, { - userFriendlyMessage: t`Cannot enable uniqueness due to existing duplicate values. Please review and fix your data first (including soft deleted records).`, + userFriendlyMessage: msg`Cannot enable uniqueness due to existing duplicate values. Please review and fix your data first (including soft deleted records).`, }, ); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type.ts index b442bd413fa..36f49862210 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type.ts @@ -1,21 +1,20 @@ import { type AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type'; import { type FieldMetadataMinimalInformation } from 'src/engine/metadata-modules/flat-field-metadata/types/field-metadata-minimal-information.type'; -import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type'; -import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type'; import { type ObjectMetadataMinimalInformation } from 'src/engine/metadata-modules/flat-object-metadata/types/object-metadata-minimal-information.type'; import { type OrchestratorFailureReport } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type'; +import { type TranslatedFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/translate-validation-errors.util'; import { type WorkspaceMigrationFieldActionTypeV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; import { type WorkspaceMigrationObjectActionTypeV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; export type ValidationErrorFieldResponse = Partial & { operation: WorkspaceMigrationFieldActionTypeV2; - errors: FlatFieldMetadataValidationError[]; + errors: TranslatedFlatEntityValidationError[]; }; export type ValidationErrorObjectResponse = Partial & { operation: WorkspaceMigrationObjectActionTypeV2; - errors: FlatObjectMetadataValidationError[]; + errors: TranslatedFlatEntityValidationError[]; fields: ValidationErrorFieldResponse[]; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-validation-response-error.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-validation-response-error.util.ts index d5e188f83c2..eb6e646073b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-validation-response-error.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-validation-response-error.util.ts @@ -1,8 +1,12 @@ +import { type I18n } from '@lingui/core'; + import { type WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; import { type ValidationErrorResponse } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type'; +import { translateOrchestratorFailureReport } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/translate-validation-errors.util'; export const fromWorkspaceMigrationBuilderExceptionToValidationResponseError = ( workspaceMigrationBuilderException: WorkspaceMigrationBuilderExceptionV2, + i18n: I18n, ): ValidationErrorResponse => { const emptyResponseError: ValidationErrorResponse = { summary: { @@ -30,10 +34,15 @@ export const fromWorkspaceMigrationBuilderExceptionToValidationResponseError = ( }, }; + const report = + workspaceMigrationBuilderException.failedWorkspaceMigrationBuildResult + .report; + + // Translate all MessageDescriptors in the report structure + const translatedReport = translateOrchestratorFailureReport(report, i18n); + return { ...emptyResponseError, - errors: - workspaceMigrationBuilderException.failedWorkspaceMigrationBuildResult - .report, + errors: translatedReport, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/translate-validation-errors.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/translate-validation-errors.util.ts new file mode 100644 index 00000000000..d9dbf8aef29 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/translate-validation-errors.util.ts @@ -0,0 +1,48 @@ +import { type I18n, type MessageDescriptor } from '@lingui/core'; + +import { type OrchestratorFailureReport } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type'; + +export type TranslatedFlatEntityValidationError = + { + code: TCode; + message: string; + userFriendlyMessage?: string; + value?: unknown; + }; + +const translateMessageDescriptorsInObject = (obj: T, i18n: I18n): T => { + if (!obj || typeof obj !== 'object') { + return obj; + } + + if ( + typeof obj === 'object' && + 'id' in obj && + 'message' in obj && + typeof (obj as MessageDescriptor).id === 'string' + ) { + return i18n._(obj as MessageDescriptor) as T; + } + + if (Array.isArray(obj)) { + return obj.map((item) => + translateMessageDescriptorsInObject(item, i18n), + ) as T; + } + + const result = {} as T; + + for (const [key, value] of Object.entries(obj)) { + (result as Record)[key] = + translateMessageDescriptorsInObject(value, i18n); + } + + return result; +}; + +export const translateOrchestratorFailureReport = ( + report: OrchestratorFailureReport, + i18n: I18n, +): OrchestratorFailureReport => { + return translateMessageDescriptorsInObject(report, i18n); +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-exception-v2-formatter.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-exception-v2-formatter.ts index d6265f7c725..78a970f25a2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-exception-v2-formatter.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-exception-v2-formatter.ts @@ -1,4 +1,5 @@ -import { t } from '@lingui/core/macro'; +import { type I18n } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { BaseGraphQLError, @@ -9,15 +10,19 @@ import { fromWorkspaceMigrationBuilderExceptionToValidationResponseError } from export const workspaceMigrationBuilderExceptionV2Formatter = ( error: WorkspaceMigrationBuilderExceptionV2, + i18n: I18n, ) => { const { errors, summary } = - fromWorkspaceMigrationBuilderExceptionToValidationResponseError(error); + fromWorkspaceMigrationBuilderExceptionToValidationResponseError( + error, + i18n, + ); throw new BaseGraphQLError(error.message, ErrorCode.BAD_USER_INPUT, { code: 'METADATA_VALIDATION_ERROR', errors, summary, message: `Validation failed for 0 object(s) and 0 field(s)`, - userFriendlyMessage: t`Validation failed for 0 object(s) and 0 field(s)`, + userFriendlyMessage: msg`Validation failed for 0 object(s) and 0 field(s)`, }); }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type.ts index 92ed1efd98c..f31a72731f7 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type.ts @@ -1,10 +1,12 @@ +import { type MessageDescriptor } from '@lingui/core'; + import { type FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type'; import { type WorkspaceMigrationActionTypeV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; export type FlatEntityValidationError = { code: TCode; // should be a better extends message: string; - userFriendlyMessage?: string; + userFriendlyMessage?: MessageDescriptor; value?: unknown; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service.ts index cb9f1d8893d..3c22d842d1f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type'; @@ -33,7 +33,7 @@ export class FlatCronTriggerValidatorService { errors.push({ code: CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND, message: t`Cron trigger not found`, - userFriendlyMessage: t`Cron trigger not found`, + userFriendlyMessage: msg`Cron trigger not found`, }); } @@ -46,7 +46,7 @@ export class FlatCronTriggerValidatorService { errors.push({ code: CronTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, message: t`Serverless function not found`, - userFriendlyMessage: t`Serverless function not found`, + userFriendlyMessage: msg`Serverless function not found`, }); } @@ -72,7 +72,7 @@ export class FlatCronTriggerValidatorService { errors.push({ code: CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND, message: t`Cron trigger not found`, - userFriendlyMessage: t`Cron trigger not found`, + userFriendlyMessage: msg`Cron trigger not found`, }); } @@ -102,7 +102,7 @@ export class FlatCronTriggerValidatorService { errors.push({ code: CronTriggerExceptionCode.CRON_TRIGGER_ALREADY_EXIST, message: t`Cron trigger with same id already exists`, - userFriendlyMessage: t`Cron trigger already exists`, + userFriendlyMessage: msg`Cron trigger already exists`, }); } @@ -115,7 +115,7 @@ export class FlatCronTriggerValidatorService { errors.push({ code: CronTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, message: t`Serverless function not found`, - userFriendlyMessage: t`Serverless function not found`, + userFriendlyMessage: msg`Serverless function not found`, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service.ts index 7da013a6de6..de9141ea141 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type'; @@ -35,7 +35,7 @@ export class FlatDatabaseEventTriggerValidatorService { errors.push({ code: DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND, message: t`Database event trigger not found`, - userFriendlyMessage: t`Database event trigger not found`, + userFriendlyMessage: msg`Database event trigger not found`, }); } @@ -48,7 +48,7 @@ export class FlatDatabaseEventTriggerValidatorService { errors.push({ code: DatabaseEventTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, message: t`Serverless function not found`, - userFriendlyMessage: t`Serverless function not found`, + userFriendlyMessage: msg`Serverless function not found`, }); } @@ -76,7 +76,7 @@ export class FlatDatabaseEventTriggerValidatorService { errors.push({ code: DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND, message: t`Database event trigger not found`, - userFriendlyMessage: t`Database event trigger not found`, + userFriendlyMessage: msg`Database event trigger not found`, }); } @@ -108,7 +108,7 @@ export class FlatDatabaseEventTriggerValidatorService { errors.push({ code: DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_ALREADY_EXIST, message: t`Database event trigger with same id already exists`, - userFriendlyMessage: t`Database event trigger already exists`, + userFriendlyMessage: msg`Database event trigger already exists`, }); } @@ -121,7 +121,7 @@ export class FlatDatabaseEventTriggerValidatorService { errors.push({ code: DatabaseEventTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, message: t`Serverless function not found`, - userFriendlyMessage: t`Serverless function not found`, + userFriendlyMessage: msg`Serverless function not found`, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts index d9b98c43a93..c0bcdf2a2a6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -62,7 +62,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, message: 'field metadata to update object metadata not found', - userFriendlyMessage: t`Object related to field to update not found`, + userFriendlyMessage: msg`Object related to field to update not found`, }); return validationResult; @@ -72,7 +72,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND, message: 'Label identifier field metadata id does not exist', - userFriendlyMessage: t`Object related to updated field does not have a label identifier`, + userFriendlyMessage: msg`Object related to updated field does not have a label identifier`, }); } @@ -83,7 +83,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND, message: 'field metadata to update not found', - userFriendlyMessage: t`Field to update not found`, + userFriendlyMessage: msg`Field to update not found`, }); return validationResult; @@ -108,7 +108,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED, message: `Forbidden updated properties for relation field metadata: ${relationNonEditableUpdatedProperties.join(', ')}`, - userFriendlyMessage: t`Forbidden updated properties for relation field metadata`, + userFriendlyMessage: msg`Forbidden updated properties for relation field metadata`, }); } } @@ -131,7 +131,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED, message: `Name is not synced with label.`, - userFriendlyMessage: t`Updated field name is not synced with label`, + userFriendlyMessage: msg`Updated field name is not synced with label`, }); } @@ -172,7 +172,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND, message: 'Field metadata to delete not found', - userFriendlyMessage: t`Field metadata to delete not found`, + userFriendlyMessage: msg`Field metadata to delete not found`, }); return validationResult; @@ -195,7 +195,7 @@ export class FlatFieldMetadataValidatorService { code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED, message: 'Cannot delete, please update the label identifier field first', - userFriendlyMessage: t`Cannot delete, please update the label identifier field first`, + userFriendlyMessage: msg`Cannot delete, please update the label identifier field first`, }); } @@ -218,7 +218,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: "Standard Fields can't be deleted", - userFriendlyMessage: t`Standard fields cannot be deleted`, + userFriendlyMessage: msg`Standard fields cannot be deleted`, }); } @@ -230,7 +230,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, message: "Active fields can't be deleted", - userFriendlyMessage: t`Active fields cannot be deleted`, + userFriendlyMessage: msg`Active fields cannot be deleted`, }); } @@ -265,7 +265,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, message: 'Object metadata not found', - userFriendlyMessage: t`Field to create related object not found`, + userFriendlyMessage: msg`Field to create related object not found`, }); } else { if ( @@ -276,7 +276,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS, message: 'Field with same id already exists in object', - userFriendlyMessage: t`Field already exists`, + userFriendlyMessage: msg`Field already exists`, }); } @@ -284,7 +284,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED, message: 'Remote objects are read-only', - userFriendlyMessage: t`Remote objects are not production ready yet`, + userFriendlyMessage: msg`Remote objects are not production ready yet`, }); } @@ -305,7 +305,7 @@ export class FlatFieldMetadataValidatorService { validationResult.errors.push({ code: FieldMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL, message: `Name is not synced with label`, - userFriendlyMessage: t`Field name is not synced with field label`, + userFriendlyMessage: msg`Field name is not synced with field label`, value: flatFieldMetadataToValidate.label, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-index-metadata-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-index-metadata-validator.service.ts index 88168d10c48..8be52931e92 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-index-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-index-metadata-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { FlatEntityMapsExceptionCode } from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception'; @@ -75,7 +75,7 @@ export class FlatIndexValidatorService { validationResult.errors.push({ code: IndexExceptionCode.INDEX_ALREADY_EXISTS, message: t`Index with same id already exists`, - userFriendlyMessage: t`Index already exists`, + userFriendlyMessage: msg`Index already exists`, }); } @@ -88,7 +88,7 @@ export class FlatIndexValidatorService { validationResult.errors.push({ code: IndexExceptionCode.INDEX_OBJECT_NOT_FOUND, message: t`Could not find index related object metadata`, - userFriendlyMessage: t`Index related object not found`, + userFriendlyMessage: msg`Index related object not found`, }); } @@ -106,7 +106,7 @@ export class FlatIndexValidatorService { validationResult.errors.push({ code: IndexExceptionCode.INDEX_ALREADY_EXISTS, message: t`Index with same name already exists`, - userFriendlyMessage: t`Index with same name already exists`, + userFriendlyMessage: msg`Index with same name already exists`, }); } @@ -114,7 +114,7 @@ export class FlatIndexValidatorService { validationResult.errors.push({ code: IndexExceptionCode.INDEX_EMPTY_FIELDS, message: t`Index must have at least one field`, - userFriendlyMessage: t`An index must contain at least one field`, + userFriendlyMessage: msg`An index must contain at least one field`, }); } else { flatIndexToValidate.flatIndexFieldMetadatas.forEach((flatIndexField) => { @@ -127,7 +127,7 @@ export class FlatIndexValidatorService { validationResult.errors.push({ code: IndexExceptionCode.INDEX_FIELD_NOT_FOUND, message: t`Could not find index field related field metadata`, - userFriendlyMessage: t`Field referenced in index does not exist`, + userFriendlyMessage: msg`Field referenced in index does not exist`, }); } else { if ( @@ -137,7 +137,7 @@ export class FlatIndexValidatorService { validationResult.errors.push({ code: IndexExceptionCode.INDEX_FIELD_WRONG_OBJECT, message: t`Field does not belong to the indexed object`, - userFriendlyMessage: t`Field cannot be indexed as it belongs to a different object`, + userFriendlyMessage: msg`Field cannot be indexed as it belongs to a different object`, }); } } @@ -146,7 +146,7 @@ export class FlatIndexValidatorService { validationResult.errors.push({ code: IndexExceptionCode.INDEX_FIELD_INVALID_REFERENCE, message: t`Index field references incorrect index metadata`, - userFriendlyMessage: t`Index field has an invalid reference`, + userFriendlyMessage: msg`Index field has an invalid reference`, }); } @@ -158,7 +158,7 @@ export class FlatIndexValidatorService { validationResult.errors.push({ code: IndexExceptionCode.INDEX_FIELD_ID_DUPLICATE, message: t`Index field ID already exists in another index`, - userFriendlyMessage: t`Field ID is already used in another index`, + userFriendlyMessage: msg`Field ID is already used in another index`, }); } }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts index d9d46857a17..081ee68ec7f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type'; @@ -46,7 +46,7 @@ export class FlatObjectMetadataValidatorService { validationResult.errors.push({ code: ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, message: t`Object to update not found`, - userFriendlyMessage: t`Object to update not found`, + userFriendlyMessage: msg`Object to update not found`, }); return validationResult; @@ -75,7 +75,7 @@ export class FlatObjectMetadataValidatorService { validationResult.errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: 'labelIdentifierFieldMetadataId cannot be null', - userFriendlyMessage: t`Field label identifier is required`, + userFriendlyMessage: msg`Field label identifier is required`, }); } @@ -110,7 +110,7 @@ export class FlatObjectMetadataValidatorService { validationResult.errors.push({ code: ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, message: t`Object to delete not found`, - userFriendlyMessage: t`Object to delete not found`, + userFriendlyMessage: msg`Object to delete not found`, }); } else { validationResult.flatEntityMinimalInformation = { @@ -123,7 +123,7 @@ export class FlatObjectMetadataValidatorService { validationResult.errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: t`Remote objects are not supported yet`, - userFriendlyMessage: t`Remote objects are not supported yet`, + userFriendlyMessage: msg`Remote objects are not supported yet`, }); } @@ -134,7 +134,7 @@ export class FlatObjectMetadataValidatorService { validationResult.errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: t`Standard objects cannot be deleted`, - userFriendlyMessage: t`Standard objects cannot be deleted`, + userFriendlyMessage: msg`Standard objects cannot be deleted`, }); } @@ -142,7 +142,7 @@ export class FlatObjectMetadataValidatorService { validationResult.errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: t`Active objects cannot be deleted`, - userFriendlyMessage: t`Active objects cannot be deleted`, + userFriendlyMessage: msg`Active objects cannot be deleted`, }); } } @@ -176,7 +176,7 @@ export class FlatObjectMetadataValidatorService { objectValidationResult.errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: t`Object with same id already exists`, - userFriendlyMessage: t`Object with same id already exists`, + userFriendlyMessage: msg`Object with same id already exists`, }); } @@ -184,7 +184,7 @@ export class FlatObjectMetadataValidatorService { objectValidationResult.errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, message: t`Remote objects are not supported yet`, - userFriendlyMessage: t`Remote objects are not supported yet`, + userFriendlyMessage: msg`Remote objects are not supported yet`, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service.ts index c83a19bb4e7..ca18219f622 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type'; @@ -33,7 +33,7 @@ export class FlatRouteTriggerValidatorService { errors.push({ code: RouteTriggerExceptionCode.ROUTE_NOT_FOUND, message: t`Route not found`, - userFriendlyMessage: t`Route not found`, + userFriendlyMessage: msg`Route not found`, }); } @@ -46,7 +46,7 @@ export class FlatRouteTriggerValidatorService { errors.push({ code: RouteTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, message: t`Serverless function not found`, - userFriendlyMessage: t`Serverless function not found`, + userFriendlyMessage: msg`Serverless function not found`, }); } @@ -72,7 +72,7 @@ export class FlatRouteTriggerValidatorService { errors.push({ code: RouteTriggerExceptionCode.ROUTE_NOT_FOUND, message: t`Route not found`, - userFriendlyMessage: t`Route not found`, + userFriendlyMessage: msg`Route not found`, }); } @@ -102,7 +102,7 @@ export class FlatRouteTriggerValidatorService { errors.push({ code: RouteTriggerExceptionCode.ROUTE_ALREADY_EXIST, message: t`Route with same id already exists`, - userFriendlyMessage: t`Route already exists`, + userFriendlyMessage: msg`Route already exists`, }); } @@ -119,7 +119,7 @@ export class FlatRouteTriggerValidatorService { errors.push({ code: RouteTriggerExceptionCode.ROUTE_PATH_ALREADY_EXIST, message: t`Route with same path already exists`, - userFriendlyMessage: t`Route path already exists`, + userFriendlyMessage: msg`Route path already exists`, }); } @@ -132,7 +132,7 @@ export class FlatRouteTriggerValidatorService { errors.push({ code: RouteTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, message: t`Serverless function not found`, - userFriendlyMessage: t`Serverless function not found`, + userFriendlyMessage: msg`Serverless function not found`, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service.ts index 81172f6dff5..012ace5cc12 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type'; @@ -31,7 +31,7 @@ export class FlatServerlessFunctionValidatorService { errors.push({ code: ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, message: t`Serverless function not found`, - userFriendlyMessage: t`Serverless function not found`, + userFriendlyMessage: msg`Serverless function not found`, }); } @@ -57,7 +57,7 @@ export class FlatServerlessFunctionValidatorService { errors.push({ code: ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, message: t`Serverless function not found`, - userFriendlyMessage: t`Serverless function not found`, + userFriendlyMessage: msg`Serverless function not found`, }); } @@ -88,7 +88,7 @@ export class FlatServerlessFunctionValidatorService { errors.push({ code: ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_ALREADY_EXIST, message: t`Serverless function with same id already exists`, - userFriendlyMessage: t`Serverless function already exists`, + userFriendlyMessage: msg`Serverless function already exists`, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service.ts index f9ddb451e61..6e17bfd6de3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; @@ -45,7 +45,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field to update not found`, - userFriendlyMessage: t`View field to update not found`, + userFriendlyMessage: msg`View field to update not found`, }); return validationResult; @@ -66,7 +66,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field to update parent view not found`, - userFriendlyMessage: t`View field to update parent view not found`, + userFriendlyMessage: msg`View field to update parent view not found`, }); return validationResult; @@ -81,7 +81,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field to update parent view object not found`, - userFriendlyMessage: t`View field to update parent view object not found`, + userFriendlyMessage: msg`View field to update parent view object not found`, }); return validationResult; @@ -130,14 +130,14 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field to delete not found`, - userFriendlyMessage: t`View field to delete not found`, + userFriendlyMessage: msg`View field to delete not found`, }); } else { if (!isDefined(existingFlatViewField.deletedAt)) { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field to delete has not been soft deleted`, - userFriendlyMessage: t`View field to delete has not been soft deleted`, + userFriendlyMessage: msg`View field to delete has not been soft deleted`, }); } } @@ -177,7 +177,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field metadata with id ${flatViewFieldId} already exists`, - userFriendlyMessage: t`View field metadata already exists`, + userFriendlyMessage: msg`View field metadata already exists`, }); } @@ -190,7 +190,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`Field metadata not found`, - userFriendlyMessage: t`Field metadata not found`, + userFriendlyMessage: msg`Field metadata not found`, }); } @@ -200,7 +200,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View not found`, - userFriendlyMessage: t`View not found`, + userFriendlyMessage: msg`View not found`, }); return validationResult; @@ -221,7 +221,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field with same fieldmetadataId and viewId already exists`, - userFriendlyMessage: t`View field already exists`, + userFriendlyMessage: msg`View field already exists`, }); } @@ -234,7 +234,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field related view object metadata not found`, - userFriendlyMessage: t`View field related view object metadata not found`, + userFriendlyMessage: msg`View field related view object metadata not found`, }); return validationResult; @@ -264,7 +264,7 @@ export class FlatViewFieldValidatorService { validationResult.errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View field position cannot be lower than label identifier view field position`, - userFriendlyMessage: t`View field position cannot be lower than label identifier view field position`, + userFriendlyMessage: msg`View field position cannot be lower than label identifier view field position`, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service.ts index c272e1ee7ab..fc057f4f412 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type'; @@ -30,7 +30,7 @@ export class FlatViewValidatorService { errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View not found`, - userFriendlyMessage: t`View not found`, + userFriendlyMessage: msg`View not found`, }); } @@ -55,14 +55,14 @@ export class FlatViewValidatorService { errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View not found`, - userFriendlyMessage: t`View not found`, + userFriendlyMessage: msg`View not found`, }); } else { if (!isDefined(existingFlatView.deletedAt)) { errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View to delete has not been soft deleted`, - userFriendlyMessage: t`View to delete has not been soft deleted`, + userFriendlyMessage: msg`View to delete has not been soft deleted`, }); } } @@ -90,7 +90,7 @@ export class FlatViewValidatorService { errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`Object metadata not found`, - userFriendlyMessage: t`Object metadata not found`, + userFriendlyMessage: msg`Object metadata not found`, }); } @@ -98,7 +98,7 @@ export class FlatViewValidatorService { errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`View with same id is already exists`, - userFriendlyMessage: t`View already exists`, + userFriendlyMessage: msg`View already exists`, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-label-identifier-field-metadata-id-flat-view-field.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-label-identifier-field-metadata-id-flat-view-field.util.ts index 45daf21b3c4..4ab61a023d3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-label-identifier-field-metadata-id-flat-view-field.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-label-identifier-field-metadata-id-flat-view-field.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { t, msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type'; @@ -26,7 +26,7 @@ export const validateLabelIdentifierFieldMetadataIdFlatViewField = ({ errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`Label identifier view field has to be in the lowest position`, - userFriendlyMessage: t`Label identifier view field has to be in the lowest position`, + userFriendlyMessage: msg`Label identifier view field has to be in the lowest position`, }); } @@ -34,7 +34,7 @@ export const validateLabelIdentifierFieldMetadataIdFlatViewField = ({ errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`Label identifier view field has to be visible`, - userFriendlyMessage: t`Label identifier view field has to be visible`, + userFriendlyMessage: msg`Label identifier view field has to be visible`, }); } @@ -42,7 +42,7 @@ export const validateLabelIdentifierFieldMetadataIdFlatViewField = ({ errors.push({ code: ViewExceptionCode.INVALID_VIEW_DATA, message: t`Label identifier view field cannot be deleted`, - userFriendlyMessage: t`Label identifier view field cannot be deleted`, + userFriendlyMessage: msg`Label identifier view field cannot be deleted`, }); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception.ts index 1e0d6a965d7..c4e1bc82ec9 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception.ts @@ -1,3 +1,6 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { CustomException } from 'src/utils/custom-exception'; export class MicrosoftImportDriverException extends CustomException { @@ -6,10 +9,11 @@ export class MicrosoftImportDriverException extends CustomException { message: string, code: string, statusCode: number, - { userFriendlyMessage }: { userFriendlyMessage?: string } = {}, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { super(message, code, { - userFriendlyMessage: userFriendlyMessage ?? message, + userFriendlyMessage: + userFriendlyMessage ?? msg`An error occurred during message import`, }); this.statusCode = statusCode; } diff --git a/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-statuses-not-set.ts b/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-statuses-not-set.ts index 9e53b4ff15f..7fb63357142 100644 --- a/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-statuses-not-set.ts +++ b/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-statuses-not-set.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { WorkflowQueryValidationException, @@ -14,7 +14,7 @@ export const assertWorkflowStatusesNotSet = ( 'Statuses cannot be set manually.', WorkflowQueryValidationExceptionCode.FORBIDDEN, { - userFriendlyMessage: t`Statuses cannot be set manually.`, + userFriendlyMessage: msg`Statuses cannot be set manually.`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-has-steps.ts b/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-has-steps.ts index cfffad247ae..128eb53686c 100644 --- a/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-has-steps.ts +++ b/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-has-steps.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; @@ -17,7 +17,7 @@ export function assertWorkflowVersionHasSteps( 'Workflow version does not contain at least one step', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, { - userFriendlyMessage: t`Workflow version does not contain at least one step`, + userFriendlyMessage: msg`Workflow version does not contain at least one step`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-is-draft.util.ts b/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-is-draft.util.ts index af3ae62bf71..4e16f4383cd 100644 --- a/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-is-draft.util.ts +++ b/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-is-draft.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { WorkflowQueryValidationException, @@ -17,7 +17,7 @@ export const assertWorkflowVersionIsDraft = ( 'Workflow version is not in draft status', WorkflowQueryValidationExceptionCode.FORBIDDEN, { - userFriendlyMessage: t`Workflow version is not in draft status`, + userFriendlyMessage: msg`Workflow version is not in draft status`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-trigger-is-defined.util.ts b/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-trigger-is-defined.util.ts index a62e8e4eb33..871436794f1 100644 --- a/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-trigger-is-defined.util.ts +++ b/packages/twenty-server/src/modules/workflow/common/utils/assert-workflow-version-trigger-is-defined.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { @@ -20,7 +20,7 @@ export function assertWorkflowVersionTriggerIsDefined( 'Workflow version does not contain trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, { - userFriendlyMessage: t`Workflow version does not contain trigger`, + userFriendlyMessage: msg`Workflow version does not contain trigger`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts index daa20e5eb8b..15ab8394108 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { IsNull, Not } from 'typeorm'; import { @@ -40,7 +40,7 @@ export class WorkflowVersionValidationWorkspaceService { 'Cannot create workflow version with status other than draft', WorkflowQueryValidationExceptionCode.FORBIDDEN, { - userFriendlyMessage: t`Cannot create workflow version with status other than draft`, + userFriendlyMessage: msg`Cannot create workflow version with status other than draft`, }, ); } @@ -67,7 +67,7 @@ export class WorkflowVersionValidationWorkspaceService { 'Cannot create multiple draft versions for the same workflow', WorkflowQueryValidationExceptionCode.FORBIDDEN, { - userFriendlyMessage: t`Cannot create multiple draft versions for the same workflow`, + userFriendlyMessage: msg`Cannot create multiple draft versions for the same workflow`, }, ); } @@ -97,7 +97,7 @@ export class WorkflowVersionValidationWorkspaceService { 'Cannot update workflow version status manually', WorkflowQueryValidationExceptionCode.FORBIDDEN, { - userFriendlyMessage: t`Cannot update workflow version status manually`, + userFriendlyMessage: msg`Cannot update workflow version status manually`, }, ); } @@ -143,7 +143,7 @@ export class WorkflowVersionValidationWorkspaceService { 'The initial version of a workflow can not be deleted', WorkflowQueryValidationExceptionCode.FORBIDDEN, { - userFriendlyMessage: t`The initial version of a workflow can not be deleted`, + userFriendlyMessage: msg`The initial version of a workflow can not be deleted`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service.ts index baeb05a0437..b0629bae93d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { StepStatus } from 'twenty-shared/workflow'; @@ -145,7 +145,7 @@ export class WorkflowRunnerWorkspaceService { 'Step is not a form', WorkflowVersionStepExceptionCode.INVALID_REQUEST, { - userFriendlyMessage: t`Step is not a form`, + userFriendlyMessage: msg`Step is not a form`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util.ts index 0fe405b749b..8977cb247a5 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { type WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type'; @@ -13,7 +13,7 @@ export function assertFormStepIsValid(settings: WorkflowFormActionSettings) { 'No input provided in form step', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`No input provided in form step`, + userFriendlyMessage: msg`No input provided in form step`, }, ); } @@ -23,7 +23,7 @@ export function assertFormStepIsValid(settings: WorkflowFormActionSettings) { 'Form action must have at least one field', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, { - userFriendlyMessage: t`Form action must have at least one field`, + userFriendlyMessage: msg`Form action must have at least one field`, }, ); } @@ -37,7 +37,7 @@ export function assertFormStepIsValid(settings: WorkflowFormActionSettings) { 'Form action fields must have unique names', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, { - userFriendlyMessage: t`Form action fields must have unique names`, + userFriendlyMessage: msg`Form action fields must have unique names`, }, ); } @@ -52,7 +52,7 @@ export function assertFormStepIsValid(settings: WorkflowFormActionSettings) { 'Form action fields must have a defined label and type', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, { - userFriendlyMessage: t`Form action fields must have a defined label and type`, + userFriendlyMessage: msg`Form action fields must have a defined label and type`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts index 0fefa5924f3..9645e401eae 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { WorkflowVersionStatus, @@ -36,7 +36,7 @@ export function assertVersionCanBeActivated( 'Cannot activate non-draft or non-last-published version', WorkflowTriggerExceptionCode.INVALID_INPUT, { - userFriendlyMessage: t`Cannot activate non-draft or non-last-published version`, + userFriendlyMessage: msg`Cannot activate non-draft or non-last-published version`, }, ); } @@ -48,7 +48,7 @@ function assertVersionIsValid(workflowVersion: WorkflowVersionWorkspaceEntity) { 'Workflow version does not contain trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, { - userFriendlyMessage: t`Workflow version does not contain trigger`, + userFriendlyMessage: msg`Workflow version does not contain trigger`, }, ); } @@ -58,7 +58,7 @@ function assertVersionIsValid(workflowVersion: WorkflowVersionWorkspaceEntity) { 'No trigger type provided', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`No trigger type provided`, + userFriendlyMessage: msg`No trigger type provided`, }, ); } @@ -68,7 +68,7 @@ function assertVersionIsValid(workflowVersion: WorkflowVersionWorkspaceEntity) { 'No steps provided in workflow version', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`No steps provided in workflow version`, + userFriendlyMessage: msg`No steps provided in workflow version`, }, ); } @@ -103,7 +103,7 @@ function assertTriggerSettingsAreValid( 'Invalid trigger type for enabling workflow trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid trigger type for enabling workflow trigger`, + userFriendlyMessage: msg`Invalid trigger type for enabling workflow trigger`, }, ); } @@ -116,7 +116,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'No setting type provided in cron trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`No setting type provided in cron trigger`, + userFriendlyMessage: msg`No setting type provided in cron trigger`, }, ); } @@ -127,7 +127,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'No pattern provided in CUSTOM cron trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`No pattern provided in CUSTOM cron trigger`, + userFriendlyMessage: msg`No pattern provided in CUSTOM cron trigger`, }, ); } @@ -141,7 +141,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'No schedule provided in cron trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`No schedule provided in cron trigger`, + userFriendlyMessage: msg`No schedule provided in cron trigger`, }, ); } @@ -150,7 +150,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'Invalid day value. Should be integer greater than 1', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid day value. Should be integer greater than 1`, + userFriendlyMessage: msg`Invalid day value. Should be integer greater than 1`, }, ); } @@ -159,7 +159,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'Invalid hour value. Should be integer between 0 and 23', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid hour value. Should be integer between 0 and 23`, + userFriendlyMessage: msg`Invalid hour value. Should be integer between 0 and 23`, }, ); } @@ -168,7 +168,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'Invalid minute value. Should be integer between 0 and 59', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid minute value. Should be integer between 0 and 59`, + userFriendlyMessage: msg`Invalid minute value. Should be integer between 0 and 59`, }, ); } @@ -182,7 +182,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'No schedule provided in cron trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid hour value. Should be integer greater than 1`, + userFriendlyMessage: msg`Invalid hour value. Should be integer greater than 1`, }, ); } @@ -191,7 +191,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'Invalid hour value. Should be integer greater than 1', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid hour value. Should be integer greater than 1`, + userFriendlyMessage: msg`Invalid hour value. Should be integer greater than 1`, }, ); } @@ -201,7 +201,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'Invalid minute value. Should be integer between 0 and 59', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid minute value. Should be integer between 0 and 59`, + userFriendlyMessage: msg`Invalid minute value. Should be integer between 0 and 59`, }, ); } @@ -215,7 +215,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'No schedule provided in cron trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid minute value. Should be integer greater than 1`, + userFriendlyMessage: msg`Invalid minute value. Should be integer greater than 1`, }, ); } @@ -225,7 +225,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'Invalid minute value. Should be integer greater than 1', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid minute value. Should be integer greater than 1`, + userFriendlyMessage: msg`Invalid minute value. Should be integer greater than 1`, }, ); } @@ -238,7 +238,7 @@ function assertCronTriggerSettingsAreValid(settings: any) { 'Invalid setting type provided in cron trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Invalid setting type provided in cron trigger`, + userFriendlyMessage: msg`Invalid setting type provided in cron trigger`, }, ); } @@ -251,7 +251,7 @@ function assertDatabaseEventTriggerSettingsAreValid(settings: any) { 'No event name provided in database event trigger', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`No event name provided in database event trigger`, + userFriendlyMessage: msg`No event name provided in database event trigger`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule.ts index 3c9ea34a633..cbc8da3121e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { CronExpressionParser } from 'cron-parser'; import { @@ -15,7 +15,7 @@ const validatePattern = (pattern: string) => { `Cron pattern '${pattern}' is invalid: ${error.message}`, WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Cron pattern '${pattern}' is invalid`, + userFriendlyMessage: msg`Cron pattern '${pattern}' is invalid`, }, ); } @@ -56,7 +56,7 @@ export const computeCronPatternFromSchedule = ( 'Unsupported cron schedule type', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, { - userFriendlyMessage: t`Unsupported cron schedule type`, + userFriendlyMessage: msg`Unsupported cron schedule type`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts index 7373c79098a..73001070198 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; @@ -214,7 +214,7 @@ export class WorkflowTriggerWorkspaceService { 'Cannot have more than one active workflow version', WorkflowTriggerExceptionCode.FORBIDDEN, { - userFriendlyMessage: t`Cannot have more than one active workflow version`, + userFriendlyMessage: msg`Cannot have more than one active workflow version`, }, ); } @@ -240,7 +240,7 @@ export class WorkflowTriggerWorkspaceService { 'Cannot disable non-active workflow version', WorkflowTriggerExceptionCode.FORBIDDEN, { - userFriendlyMessage: t`Cannot disable non-active workflow version`, + userFriendlyMessage: msg`Cannot disable non-active workflow version`, }, ); } diff --git a/packages/twenty-server/src/utils/__test__/custom-exception.spec.ts b/packages/twenty-server/src/utils/__test__/custom-exception.spec.ts index a746dd306bf..71d0f9bf895 100644 --- a/packages/twenty-server/src/utils/__test__/custom-exception.spec.ts +++ b/packages/twenty-server/src/utils/__test__/custom-exception.spec.ts @@ -44,7 +44,10 @@ describe('CustomException', () => { it('should set userFriendlyMessage when provided', () => { const message = 'Test error message'; const code = 'TEST_ERROR'; - const userFriendlyMessage = 'User friendly error message'; + const userFriendlyMessage = { + id: 'test.message', + message: 'User friendly error message', + }; const exception = new TestException(message, code, { userFriendlyMessage, }); @@ -81,7 +84,10 @@ describe('UnknownException', () => { it('should set userFriendlyMessage when provided', () => { const message = 'Test error message'; const code = 'TEST_ERROR'; - const userFriendlyMessage = 'User friendly error message'; + const userFriendlyMessage = { + id: 'test.message', + message: 'User friendly error message', + }; const exception = new UnknownException(message, code, { userFriendlyMessage, }); diff --git a/packages/twenty-server/src/utils/custom-exception.ts b/packages/twenty-server/src/utils/custom-exception.ts index 34300cc1e24..f84634b1e98 100644 --- a/packages/twenty-server/src/utils/custom-exception.ts +++ b/packages/twenty-server/src/utils/custom-exception.ts @@ -1,3 +1,4 @@ +import { type MessageDescriptor } from '@lingui/core'; import { CustomError } from 'twenty-shared/utils'; const CommonExceptionCode = { @@ -19,17 +20,16 @@ export abstract class CustomException< ExceptionCode extends string = string, ForceFriendlyMessage = false, ExceptionMessage extends string = string, - ExceptionFriendlyMessage extends string = string, > extends CustomError { code: ExceptionCode; - userFriendlyMessage?: ExceptionFriendlyMessage; + userFriendlyMessage?: MessageDescriptor; constructor( message: ExceptionMessage, code: ExceptionCode, ...userFriendlyMessage: ForceFriendlyMessage extends true - ? [{ userFriendlyMessage: ExceptionFriendlyMessage }] - : [{ userFriendlyMessage?: ExceptionFriendlyMessage }?] + ? [{ userFriendlyMessage: MessageDescriptor }] + : [{ userFriendlyMessage?: MessageDescriptor }?] ) { super(message); this.code = code; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts.snap index 12a6b005acf..b92855602d2 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts.snap @@ -50,7 +50,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option id", }, - "message": "Duplicated option id", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -64,7 +64,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option position", }, - "message": "Duplicated option position", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -78,7 +78,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option value", }, - "message": "Duplicated option value", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -92,7 +92,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option value", }, - "message": "Duplicated option value", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -162,7 +162,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option label "" is beneath 1 character", }, - "message": "Option label "" is beneath 1 character", + "message": "Option label "{sanitizedLabel}" is beneath 1 character", "name": "UserInputError", }, ] @@ -176,7 +176,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option value "" is beneath 1 character", }, - "message": "Option value "" is beneath 1 character", + "message": "Option value "{sanitizedValue}" is beneath 1 character", "name": "UserInputError", }, ] @@ -218,7 +218,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", }, - "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + "message": "Value must be in UPPER_CASE and follow snake_case "{sanitizedValue}"", "name": "UserInputError", }, ] @@ -287,7 +287,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "22222"", }, - "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + "message": "Value must be in UPPER_CASE and follow snake_case "{sanitizedValue}"", "name": "UserInputError", }, ] @@ -385,7 +385,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option label "" is beneath 1 character", }, - "message": "Option label "" is beneath 1 character", + "message": "Option label "{sanitizedLabel}" is beneath 1 character", "name": "UserInputError", }, ] @@ -399,7 +399,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option value "" is beneath 1 character", }, - "message": "Option value "" is beneath 1 character", + "message": "Option value "{sanitizedValue}" is beneath 1 character", "name": "UserInputError", }, ] @@ -511,7 +511,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Default value "'UNKNOWN_OPTION'" must be one of the option values", }, - "message": "Default value "'UNKNOWN_OPTION'" must be one of the option values", + "message": "Default value "{defaultValue}" must be one of the option values", "name": "UserInputError", }, ] diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-select-field-metadata-enum.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-select-field-metadata-enum.integration-spec.ts.snap index 09ead2527ac..91adc73ff9b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-select-field-metadata-enum.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-select-field-metadata-enum.integration-spec.ts.snap @@ -22,7 +22,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Default value "'OPTION_424242'" must be one of the option values", }, - "message": "Default value "'OPTION_424242'" must be one of the option values", + "message": "Default value "{defaultValue}" must be one of the option values", "name": "UserInputError", }, ] @@ -50,7 +50,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option id", }, - "message": "Duplicated option id", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -64,7 +64,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option position", }, - "message": "Duplicated option position", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -78,7 +78,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option value", }, - "message": "Duplicated option value", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -92,7 +92,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option value", }, - "message": "Duplicated option value", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -148,7 +148,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option label "" is beneath 1 character", }, - "message": "Option label "" is beneath 1 character", + "message": "Option label "{sanitizedLabel}" is beneath 1 character", "name": "UserInputError", }, ] @@ -162,7 +162,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option value "" is beneath 1 character", }, - "message": "Option value "" is beneath 1 character", + "message": "Option value "{sanitizedValue}" is beneath 1 character", "name": "UserInputError", }, ] @@ -190,7 +190,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", }, - "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + "message": "Value must be in UPPER_CASE and follow snake_case "{sanitizedValue}"", "name": "UserInputError", }, ] @@ -245,7 +245,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "22222"", }, - "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + "message": "Value must be in UPPER_CASE and follow snake_case "{sanitizedValue}"", "name": "UserInputError", }, ] @@ -343,7 +343,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option label "" is beneath 1 character", }, - "message": "Option label "" is beneath 1 character", + "message": "Option label "{sanitizedLabel}" is beneath 1 character", "name": "UserInputError", }, ] @@ -357,7 +357,7 @@ exports[`Failing create field metadata RATING tests suite Create should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option value "" is beneath 1 character", }, - "message": "Option value "" is beneath 1 character", + "message": "Option value "{sanitizedValue}" is beneath 1 character", "name": "UserInputError", }, ] diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-update-one-enum-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-update-one-enum-field-metadata.integration-spec.ts.snap index 2fa77f8cf23..15bfdbdce23 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-update-one-enum-field-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-update-one-enum-field-metadata.integration-spec.ts.snap @@ -50,7 +50,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option id", }, - "message": "Duplicated option id", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -64,7 +64,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option position", }, - "message": "Duplicated option position", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -78,7 +78,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option value", }, - "message": "Duplicated option value", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -92,7 +92,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option value", }, - "message": "Duplicated option value", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -162,7 +162,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option label "" is beneath 1 character", }, - "message": "Option label "" is beneath 1 character", + "message": "Option label "{sanitizedLabel}" is beneath 1 character", "name": "UserInputError", }, ] @@ -176,7 +176,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option value "" is beneath 1 character", }, - "message": "Option value "" is beneath 1 character", + "message": "Option value "{sanitizedValue}" is beneath 1 character", "name": "UserInputError", }, ] @@ -218,7 +218,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", }, - "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + "message": "Value must be in UPPER_CASE and follow snake_case "{sanitizedValue}"", "name": "UserInputError", }, ] @@ -287,7 +287,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "22222"", }, - "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + "message": "Value must be in UPPER_CASE and follow snake_case "{sanitizedValue}"", "name": "UserInputError", }, ] @@ -371,7 +371,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option label "" is beneath 1 character", }, - "message": "Option label "" is beneath 1 character", + "message": "Option label "{sanitizedLabel}" is beneath 1 character", "name": "UserInputError", }, ] @@ -385,7 +385,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option value "" is beneath 1 character", }, - "message": "Option value "" is beneath 1 character", + "message": "Option value "{sanitizedValue}" is beneath 1 character", "name": "UserInputError", }, ] @@ -483,7 +483,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Default value "'OPTION_42'" must be one of the option values", }, - "message": "Default value "'OPTION_42'" must be one of the option values", + "message": "Default value "{defaultValue}" must be one of the option values", "name": "UserInputError", }, ] @@ -497,7 +497,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite Update should fa "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Default value "'UNKNOWN_OPTION'" must be one of the option values", }, - "message": "Default value "'UNKNOWN_OPTION'" must be one of the option values", + "message": "Default value "{defaultValue}" must be one of the option values", "name": "UserInputError", }, ] @@ -525,7 +525,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Default value "'OPTION_424242'" must be one of the option values", }, - "message": "Default value "'OPTION_424242'" must be one of the option values", + "message": "Default value "{defaultValue}" must be one of the option values", "name": "UserInputError", }, ] @@ -553,7 +553,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option id", }, - "message": "Duplicated option id", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -567,7 +567,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option position", }, - "message": "Duplicated option position", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -581,7 +581,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option value", }, - "message": "Duplicated option value", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -595,7 +595,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Duplicated option value", }, - "message": "Duplicated option value", + "message": "Duplicated option {field}", "name": "UserInputError", }, ] @@ -651,7 +651,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option label "" is beneath 1 character", }, - "message": "Option label "" is beneath 1 character", + "message": "Option label "{sanitizedLabel}" is beneath 1 character", "name": "UserInputError", }, ] @@ -665,7 +665,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option value "" is beneath 1 character", }, - "message": "Option value "" is beneath 1 character", + "message": "Option value "{sanitizedValue}" is beneath 1 character", "name": "UserInputError", }, ] @@ -693,7 +693,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", }, - "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + "message": "Value must be in UPPER_CASE and follow snake_case "{sanitizedValue}"", "name": "UserInputError", }, ] @@ -748,7 +748,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "22222"", }, - "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + "message": "Value must be in UPPER_CASE and follow snake_case "{sanitizedValue}"", "name": "UserInputError", }, ] @@ -832,7 +832,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option label "" is beneath 1 character", }, - "message": "Option label "" is beneath 1 character", + "message": "Option label "{sanitizedLabel}" is beneath 1 character", "name": "UserInputError", }, ] @@ -846,7 +846,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Option value "" is beneath 1 character", }, - "message": "Option value "" is beneath 1 character", + "message": "Option value "{sanitizedValue}" is beneath 1 character", "name": "UserInputError", }, ] @@ -944,7 +944,7 @@ exports[`Failing update field metadata SELECT tests suite Update should fail wit "subCode": "INVALID_FIELD_INPUT", "userFriendlyMessage": "Default value "'OPTION_42'" must be one of the option values", }, - "message": "Default value "'OPTION_42'" must be one of the option values", + "message": "Default value "{defaultValue}" must be one of the option values", "name": "UserInputError", }, ]