fix: restore isCustom gate in metadata label resolvers (#21432)

## Context

#21228 removed the stored `isCustom` column and, with it, the `isCustom`
early-return in `resolveObjectMetadataStandardOverride` /
`resolveFieldMetadataStandardOverride`, on the assumption that falling
through the `standardOverrides` checks was equivalent.

It isn't: custom object/field labels now reach the Lingui lookup. A
custom label that collides with a standard catalog string (e.g. a custom
field labeled "Status") gets translated for non-English locales against
the user's intent, and every other custom label pays a hash + catalog
miss — and, in production, an "Uncompiled message detected" warning
(#21415) — on each metadata resolution.

## Fix

Restore the gate. `isCustom` is no longer stored, so call sites that
build the resolver input from flat entities (dataloader,
minimal-metadata, view controller, command-menu-item navigation context)
derive it via `belongsToTwentyStandardApp`; GraphQL resolvers keep
passing DTOs, which already carry the derived value.

## Testing

- Unit tests for both resolvers, including a new regression test: a
custom label matching a standard catalog entry is returned verbatim,
Lingui never called.

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Félix Malfait
2026-06-11 18:10:53 +02:00
committed by GitHub
parent cfb9772179
commit 69a7e614ff
11 changed files with 126 additions and 6 deletions
@@ -26,6 +26,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
label: 'Custom Label',
description: 'Custom Description',
icon: 'custom-icon',
isCustom: true,
standardOverrides: undefined,
};
@@ -39,11 +40,36 @@ describe('resolveFieldMetadataStandardOverride', () => {
expect(result).toBe('Custom Label');
});
it('should never translate a custom label even when it matches a standard catalog entry', () => {
const fieldMetadata = {
label: 'Status',
description: 'Custom Description',
icon: 'custom-icon',
isCustom: true,
standardOverrides: undefined,
};
mockGenerateMessageId.mockReturnValue('status.message.id');
mockI18n._.mockReturnValue('Statut');
const result = resolveFieldMetadataStandardOverride(
fieldMetadata,
'label',
'fr-FR',
mockI18n,
);
expect(result).toBe('Status');
expect(mockGenerateMessageId).not.toHaveBeenCalled();
expect(mockI18n._).not.toHaveBeenCalled();
});
it('should return the field value for custom description field', () => {
const fieldMetadata = {
label: 'Custom Label',
description: 'Custom Description',
icon: 'custom-icon',
isCustom: true,
standardOverrides: undefined,
};
@@ -62,6 +88,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
label: 'Custom Label',
description: 'Custom Description',
icon: 'custom-icon',
isCustom: true,
standardOverrides: undefined,
};
@@ -10,7 +10,7 @@ import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadat
export const resolveFieldMetadataStandardOverride = (
fieldMetadata: Pick<
FieldMetadataDTO,
'label' | 'description' | 'icon' | 'standardOverrides'
'label' | 'description' | 'icon' | 'isCustom' | 'standardOverrides'
>,
labelKey: 'label' | 'description' | 'icon',
locale: keyof typeof APP_LOCALES | undefined,
@@ -18,6 +18,13 @@ export const resolveFieldMetadataStandardOverride = (
): string => {
const safeLocale = locale ?? SOURCE_LOCALE;
// Custom field labels are user-authored: never overridden nor translated.
// Without this gate, a label colliding with a standard catalog string
// (e.g. "Status") would get translated against the user's intent.
if (fieldMetadata.isCustom) {
return fieldMetadata[labelKey] ?? '';
}
if (labelKey === 'icon' && isDefined(fieldMetadata.standardOverrides?.icon)) {
return fieldMetadata.standardOverrides.icon;
}