Update user friendly errors for translations (#15000)

Force msg typing instead of string for user friendly errors
This commit is contained in:
Félix Malfait
2025-10-09 18:17:32 +02:00
committed by GitHub
parent 660cd38f35
commit e577c2d746
124 changed files with 796 additions and 563 deletions
@@ -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<any> {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
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),
),
);
}
}
@@ -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<IndexMetadataDTO[]> {
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 [];
}
@@ -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.`,
},
);
}
@@ -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) {
@@ -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`,
},
);
}