From e264d7f32be81534f3e594e635269eefcdaa1608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 28 Aug 2025 15:12:38 +0200 Subject: [PATCH] fix: Enable Lingui recommended rules and fix all translation violations (#14133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enable lingui/no-single-variables-to-translate and all other recommended Lingui rules - Fix single variable translation patterns (t`${variable}` → variable) - Fix expression-in-message violations by extracting variables - Fix t-call-in-function violations by moving translations inside functions - Update ESLint configs to use linguiPlugin.configs['flat/recommended'] - Clean up unused imports and improve translation patterns --- eslint.config.mjs | 6 ++-- eslint.config.react.mjs | 8 ++--- packages/twenty-emails/eslint.config.mjs | 2 +- .../hooks/useRunWorkflowRecordActions.tsx | 5 ++-- .../useRunWorkflowRecordAgnosticActions.tsx | 5 ++-- .../components/ClientConfigProvider.tsx | 4 ++- .../components/AppRootErrorFallback.tsx | 4 +-- ...tyApprovedAccessDomainValidationEffect.tsx | 11 ++++--- .../input/components/MultiSelectControl.tsx | 3 +- ...MultiWorkspaceDropdownThemesComponents.tsx | 8 ++++- packages/twenty-server/eslint.config.mjs | 5 ++-- .../view/exceptions/view-sort.exception.ts | 16 ++++++---- ...t-field-metadata-type-validator.service.ts | 8 +++-- ...field-input-to-flat-field-metadata.util.ts | 6 ++-- ...run-flat-field-metadata-validators.util.ts | 4 ++- .../validate-enum-flat-field-metadata.util.ts | 30 +++++++++---------- .../validate-flat-field-metadata-name.util.ts | 6 ++-- ...un-flat-object-metadata-validators.util.ts | 4 ++- ...alidate-flat-object-metadata-label.util.ts | 10 +++---- .../types/flat-metadata-validator.type.ts | 4 ++- ...name-flat-metadata-validators.constants.ts | 12 ++++---- ...igration-builder-exception-v2-formatter.ts | 7 +++-- packages/twenty-shared/eslint.config.mjs | 6 ++-- packages/twenty-ui/eslint.config.mjs | 9 ------ 24 files changed, 99 insertions(+), 84 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 2a7f21147d3..3e6d6894b52 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -14,6 +14,9 @@ export default [ // Base JavaScript configuration js.configs.recommended, + // Lingui recommended rules + linguiPlugin.configs['flat/recommended'], + // Global ignores { ignores: [ @@ -34,9 +37,6 @@ export default [ 'unicorn': unicornPlugin, }, rules: { - // Lingui rules - 'lingui/no-single-variables-to-translate': 'off', - // General rules 'func-style': ['error', 'declaration', { allowArrowFunctions: true }], 'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }], diff --git a/eslint.config.react.mjs b/eslint.config.react.mjs index 725f5477a28..cee94159fdb 100644 --- a/eslint.config.react.mjs +++ b/eslint.config.react.mjs @@ -17,6 +17,9 @@ export default [ // Base JavaScript configuration js.configs.recommended, + // Lingui recommended rules + linguiPlugin.configs['flat/recommended'], + // Base configuration for all files { files: ['**/*.{js,jsx,ts,tsx}'], @@ -38,9 +41,6 @@ export default [ }, }, rules: { - // Lingui rules - 'lingui/no-single-variables-to-translate': 'off', - // General rules 'func-style': ['error', 'declaration', { allowArrowFunctions: true }], 'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }], @@ -294,7 +294,7 @@ export default [ // JSON files { - files: ['*.json'], + files: ['**/*.json'], languageOptions: { parser: jsoncParser, }, diff --git a/packages/twenty-emails/eslint.config.mjs b/packages/twenty-emails/eslint.config.mjs index 427bfa9d054..eaa736a87d3 100644 --- a/packages/twenty-emails/eslint.config.mjs +++ b/packages/twenty-emails/eslint.config.mjs @@ -34,4 +34,4 @@ export default [ '@nx/dependency-checks': 'error', }, }, -]; +]; \ No newline at end of file diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx index 71b35ebb190..85c22e4b793 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx @@ -7,7 +7,6 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger'; import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; -import { msg } from '@lingui/core/macro'; import { type WorkflowVersion } from '@/workflow/types/Workflow'; import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon'; @@ -81,8 +80,8 @@ export const useRunWorkflowRecordActions = ({ type: ActionType.WorkflowRun, key: `workflow-run-${activeWorkflowVersion.id}`, scope: ActionScope.RecordSelection, - label: msg`${name}`, - shortLabel: msg`${name}`, + label: name, + shortLabel: name, position: index, Icon, isPinned: activeWorkflowVersion.trigger?.settings?.isPinned, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions.tsx index 64ccdc7825b..87c78bf7c18 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions.tsx @@ -4,11 +4,10 @@ import { ActionType } from '@/action-menu/actions/types/ActionType'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger'; import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; -import { msg } from '@lingui/core/macro'; +import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon'; import { useContext } from 'react'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; -import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon'; export const useRunWorkflowRecordAgnosticActions = () => { const { getIcon } = useIcons(); @@ -41,7 +40,7 @@ export const useRunWorkflowRecordAgnosticActions = () => { type: ActionType.WorkflowRun, key: `workflow-run-${activeWorkflowVersion.id}`, scope: ActionScope.Global, - label: msg`${name}`, + label: name, position: index, Icon, shouldBeRegistered: () => true, diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx index 774bb2361a8..3b5d0ea57e5 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx @@ -2,11 +2,13 @@ import { useRecoilValue } from 'recoil'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; import { AppFullScreenErrorFallback } from '@/error-handler/components/AppFullScreenErrorFallback'; +import { useLingui } from '@lingui/react/macro'; export const ClientConfigProvider: React.FC = ({ children, }) => { const { isErrored, error } = useRecoilValue(clientConfigApiStatusState); + const { t } = useLingui(); return isErrored && error instanceof Error ? ( = ({ resetErrorBoundary={() => { window.location.reload(); }} - title="Unable to Reach Back-end" + title={t`Unable to Reach Back-end`} /> ) : ( children diff --git a/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx b/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx index 913bd2ef53f..7e289785792 100644 --- a/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx @@ -100,7 +100,7 @@ const StyledIcon = styled(IconReload)` export const AppRootErrorFallback = ({ resetErrorBoundary, - title = 'Sorry, something went wrong', + title = t`Sorry, something went wrong`, }: AppRootErrorFallbackProps) => { return ( @@ -117,7 +117,7 @@ export const AppRootErrorFallback = ({ /> - {t`${title}`} + {title} {t`Please refresh the page.`} diff --git a/packages/twenty-front/src/modules/settings/security/components/approvedAccessDomains/SettingsSecurityApprovedAccessDomainValidationEffect.tsx b/packages/twenty-front/src/modules/settings/security/components/approvedAccessDomains/SettingsSecurityApprovedAccessDomainValidationEffect.tsx index e2f9022abf1..03af50ab978 100644 --- a/packages/twenty-front/src/modules/settings/security/components/approvedAccessDomains/SettingsSecurityApprovedAccessDomainValidationEffect.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/approvedAccessDomains/SettingsSecurityApprovedAccessDomainValidationEffect.tsx @@ -1,11 +1,11 @@ +import { approvedAccessDomainsState } from '@/settings/security/states/ApprovedAccessDomainsState'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { t } from '@lingui/core/macro'; import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { isDefined } from 'twenty-shared/utils'; import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; import { useValidateApprovedAccessDomainMutation } from '~/generated-metadata/graphql'; -import { approvedAccessDomainsState } from '@/settings/security/states/ApprovedAccessDomainsState'; export const SettingsSecurityApprovedAccessDomainValidationEffect = () => { const [validateApprovedAccessDomainMutation] = @@ -45,11 +45,10 @@ export const SettingsSecurityApprovedAccessDomainValidationEffect = () => { }); }, onError: (error) => { - const message = error?.message - ? error.message - : 'Error validating approved access domain'; enqueueErrorSnackBar({ - message: t`${message}`, + message: error?.message + ? error.message + : t`Error validating approved access domain`, options: { dedupeKey: 'approved-access-domain-validation-error-dedupe-key', }, diff --git a/packages/twenty-front/src/modules/ui/input/components/MultiSelectControl.tsx b/packages/twenty-front/src/modules/ui/input/components/MultiSelectControl.tsx index 905c67934cb..2442374b80f 100644 --- a/packages/twenty-front/src/modules/ui/input/components/MultiSelectControl.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/MultiSelectControl.tsx @@ -4,7 +4,6 @@ import { StyledSelectControlIconChevronDown, } from '@/ui/input/components/SelectControl'; import { useTheme } from '@emotion/react'; -import { t } from '@lingui/core/macro'; import pluralize from 'pluralize'; import React from 'react'; import { isDefined } from 'twenty-shared/utils'; @@ -64,7 +63,7 @@ export const MultiSelectControl = ({ ) : null} {isDefined(fixedText) ? ( diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownThemesComponents.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownThemesComponents.tsx index d0db6616b04..dc7b5acf7e5 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownThemesComponents.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownThemesComponents.tsx @@ -35,7 +35,13 @@ export const MultiWorkspaceDropdownThemesComponents = () => { setColorScheme(theme.id)} RightIcon={theme.id === colorScheme ? IconCheck : undefined} /> diff --git a/packages/twenty-server/eslint.config.mjs b/packages/twenty-server/eslint.config.mjs index d57a8c47f8a..9fba3b8e2ee 100644 --- a/packages/twenty-server/eslint.config.mjs +++ b/packages/twenty-server/eslint.config.mjs @@ -20,6 +20,9 @@ export default [ // Base JavaScript configuration js.configs.recommended, + // Lingui recommended rules + linguiPlugin.configs['flat/recommended'], + // Global ignores { ignores: [ @@ -49,8 +52,6 @@ export default [ '@stylistic': stylisticPlugin, }, rules: { - // Lingui rules - 'lingui/no-single-variables-to-translate': 'off', 'prettier/prettier': 'error', // General rules diff --git a/packages/twenty-server/src/engine/core-modules/view/exceptions/view-sort.exception.ts b/packages/twenty-server/src/engine/core-modules/view/exceptions/view-sort.exception.ts index 4805ff38e23..04cc6e279af 100644 --- a/packages/twenty-server/src/engine/core-modules/view/exceptions/view-sort.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/view/exceptions/view-sort.exception.ts @@ -35,25 +35,29 @@ export const generateViewSortExceptionMessage = ( switch (key) { case ViewSortExceptionMessageKey.WORKSPACE_ID_REQUIRED: - message = `WorkspaceId is required`; + message = t`WorkspaceId is required`; break; case ViewSortExceptionMessageKey.VIEW_ID_REQUIRED: - message = `ViewId is required`; + message = t`ViewId is required`; break; case ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND: - message = `View sort${id ? ` (id: ${id})` : ''} not found`; + message = id + ? t`View sort (id: ${id}) not found` + : t`View sort not found`; break; case ViewSortExceptionMessageKey.INVALID_VIEW_SORT_DATA: - message = `Invalid view sort data${id ? ` for view sort id: ${id}` : ''}`; + message = id + ? t`Invalid view sort data for view sort id: ${id}` + : t`Invalid view sort data`; break; case ViewSortExceptionMessageKey.FIELD_METADATA_ID_REQUIRED: - message = `FieldMetadataId is required`; + message = t`FieldMetadataId is required`; break; default: assertUnreachable(key); } - return t`${message}`; + return message; }; export const generateViewSortUserFriendlyExceptionMessage = ( 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 5867cddc86f..62335eca695 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 @@ -150,12 +150,14 @@ export class FlatFieldMetadataTypeValidatorService { ]; if (!isDefined(fieldMetadataTypeValidator)) { + const fieldType = flatFieldMetadataToValidate.type; + return [ { code: FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION, - message: `Unsupported field metadata type ${flatFieldMetadataToValidate.type}`, - value: flatFieldMetadataToValidate.type, - userFriendlyMessage: t`Unsupported field metadata type ${flatFieldMetadataToValidate.type}`, + message: `Unsupported field metadata type ${fieldType}`, + value: fieldType, + userFriendlyMessage: t`Unsupported field metadata type ${fieldType}`, }, ]; } 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 5c47c198a59..dbf103e110d 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 @@ -99,12 +99,14 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({ ); if (invalidUpdatedProperties.length > 0) { + const invalidProperties = invalidUpdatedProperties.join(', '); + return { status: 'fail', error: { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - message: `Cannot update standard field metadata properties: ${invalidUpdatedProperties.join(', ')}`, - userFriendlyMessage: t`Cannot update standard field properties: ${invalidUpdatedProperties.join(', ')}`, + message: `Cannot update standard field metadata properties: ${invalidProperties}`, + userFriendlyMessage: t`Cannot update standard field properties: ${invalidProperties}`, }, }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/run-flat-field-metadata-validators.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/run-flat-field-metadata-validators.util.ts index ef270845725..7887b347125 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/run-flat-field-metadata-validators.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/run-flat-field-metadata-validators.util.ts @@ -1,3 +1,5 @@ +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 { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type'; @@ -15,7 +17,7 @@ export const runFlatFieldMetadataValidators = ({ if (isInvalid) { return { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - message, + message: t(message), value: elementToValidate, }; } 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 92b4255aaa1..fb3f9883ffe 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 @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg, t } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { QUOTED_STRING_REGEX } from 'twenty-shared/constants'; import { @@ -31,11 +31,11 @@ const validateMetadataOptionId = (sanitizedId?: string) => { const validators: FlatMetadataValidator[] = [ { validator: (id) => !isDefined(id), - message: t`Option id is required`, + message: msg`Option id is required`, }, { validator: (id) => !z.string().uuid().safeParse(id).success, - message: t`Option id is invalid`, + message: msg`Option id is invalid`, }, ]; @@ -72,19 +72,19 @@ const validateMetadataOptionLabel = ( const validators: FlatMetadataValidator[] = [ { 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`, }, ]; @@ -120,15 +120,15 @@ const validateMetadataOptionValue = ( const validators: FlatMetadataValidator[] = [ { 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: t`Value must be in UPPER_CASE and follow snake_case "${sanitizedValue}"`, + message: msg`Value must be in UPPER_CASE and follow snake_case "${sanitizedValue}"`, }, ]; @@ -154,7 +154,7 @@ const validateDuplicates = ( FieldMetadataDefaultOption[] | FieldMetadataComplexOption[] > >((field) => ({ - message: t`Duplicated option ${field}`, + message: msg`Duplicated option ${field}`, validator: () => new Set(options.map((option) => option[field])).size !== options.length, })); @@ -215,14 +215,14 @@ const validateSelectDefaultValue = ({ const validators: FlatMetadataValidator[] = [ { 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) => !options.some( (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`, }, ]; @@ -253,11 +253,11 @@ const validateMultiSelectDefaultValue = ({ const validators: FlatMetadataValidator[] = [ { 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/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 ebc576acb97..f03a5e17c8a 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,3 +1,5 @@ +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'; @@ -11,8 +13,8 @@ export const validateFlatFieldMetadataName = ( if (isInvalid) { return { code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - message, - userFriendlyMessage: message, + message: t(message), + userFriendlyMessage: t(message), value: name, }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validators.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validators.util.ts index 6b3d4fa378e..c3fc5fa9ede 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validators.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validators.util.ts @@ -1,3 +1,5 @@ +import { t } from '@lingui/core/macro'; + import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type'; import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; import { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type'; @@ -15,7 +17,7 @@ export const runFlatObjectMetadataValidators = ({ if (isInvalid) { return { code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, - message, + message: t(message), value: elementToValidate, }; } 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 2027d25f8f3..a750eb921e8 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 { t } from '@lingui/core/macro'; +import { msg, t } 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'; @@ -20,11 +20,11 @@ export const validateFlatObjectMetadataLabel = ({ const validators: FlatMetadataValidator[] = [ { validator: (label) => beneathDatabaseIdentifierMinimumLength(label), - message: t`Object label is too short`, + message: msg`Object label is too short`, }, { validator: (label) => exceedsDatabaseIdentifierMaximumLength(label), - message: t`Object label is too long`, + message: msg`Object label is too long`, }, ]; @@ -43,8 +43,8 @@ export const validateFlatObjectMetadataLabel = ({ if (labelsAreIdentical) { errors.push({ code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, - userFriendlyMessage: `The singular and plural labels cannot be the same for an object`, - message: t`The singular and plural labels cannot be the same for an object`, + 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`, value: labelSingular, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/types/flat-metadata-validator.type.ts b/packages/twenty-server/src/engine/metadata-modules/types/flat-metadata-validator.type.ts index 494e211d6ca..7c5c3ad1877 100644 --- a/packages/twenty-server/src/engine/metadata-modules/types/flat-metadata-validator.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/types/flat-metadata-validator.type.ts @@ -1,4 +1,6 @@ +import { type MessageDescriptor } from '@lingui/core'; + export type FlatMetadataValidator = { validator: (value: T) => boolean; - message: string; + message: MessageDescriptor; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/constants/metadata-name-flat-metadata-validators.constants.ts b/packages/twenty-server/src/engine/metadata-modules/utils/constants/metadata-name-flat-metadata-validators.constants.ts index 1d5e12fc0b2..0e020b591b8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/constants/metadata-name-flat-metadata-validators.constants.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/constants/metadata-name-flat-metadata-validators.constants.ts @@ -1,4 +1,4 @@ -import { t } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import camelCase from 'lodash.camelcase'; import { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type'; @@ -11,26 +11,26 @@ import { STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUM export const METADATA_NAME_VALIDATORS: FlatMetadataValidator[] = [ { - message: t`Name is too long`, + message: msg`Name is too long`, validator: (name) => exceedsDatabaseIdentifierMaximumLength(name), }, { - message: t`Name is too short`, + message: msg`Name is too short`, validator: (name) => beneathDatabaseIdentifierMinimumLength(name), }, { - message: t`Name should be in camelCase`, + message: msg`Name should be in camelCase`, validator: (name) => name !== camelCase(name), }, { - message: t`Name is not valid: it must start with lowercase letter and contain only alphanumeric letters`, + message: msg`Name is not valid: it must start with lowercase letter and contain only alphanumeric letters`, validator: (name) => !name.match( STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX, ), }, { - message: t`The name is not available`, + message: msg`The name is not available`, validator: (name) => RESERVED_METADATA_NAME_KEYWORDS.includes(name), }, ]; 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 0006d3a2ddf..2662401512d 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 @@ -13,11 +13,14 @@ export const workspaceMigrationBuilderExceptionV2Formatter = ( const { errors, summary } = fromWorkspaceMigrationBuilderExceptionToValidationResponseError(error); + const invalidObjects = summary.invalidObjects; + const invalidFields = summary.invalidFields; + throw new BaseGraphQLError(error.message, ErrorCode.BAD_USER_INPUT, { code: 'METADATA_VALIDATION_ERROR', errors, summary, - message: `Validation failed for ${summary.invalidObjects} object(s) and ${summary.invalidFields} field(s)`, - userFriendlyMessage: t`Validation failed for ${summary.invalidObjects} object(s) and ${summary.invalidFields} field(s)`, + message: `Validation failed for ${invalidObjects} object(s) and ${invalidFields} field(s)`, + userFriendlyMessage: t`Validation failed for ${invalidObjects} object(s) and ${invalidFields} field(s)`, }); }; diff --git a/packages/twenty-shared/eslint.config.mjs b/packages/twenty-shared/eslint.config.mjs index 743bd6bb1dc..1ac9dfca4fd 100644 --- a/packages/twenty-shared/eslint.config.mjs +++ b/packages/twenty-shared/eslint.config.mjs @@ -19,6 +19,9 @@ export default [ // Base JavaScript configuration js.configs.recommended, + // Lingui recommended rules + linguiPlugin.configs['flat/recommended'], + // Global ignores { ignores: [ @@ -39,9 +42,6 @@ export default [ 'unicorn': unicornPlugin, }, rules: { - // Lingui rules - 'lingui/no-single-variables-to-translate': 'off', - // General rules 'func-style': ['error', 'declaration', { allowArrowFunctions: true }], 'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }], diff --git a/packages/twenty-ui/eslint.config.mjs b/packages/twenty-ui/eslint.config.mjs index 1a2b4105066..a6ab6a9965a 100644 --- a/packages/twenty-ui/eslint.config.mjs +++ b/packages/twenty-ui/eslint.config.mjs @@ -1,5 +1,4 @@ import typescriptParser from '@typescript-eslint/parser'; -import jsoncParser from 'jsonc-eslint-parser'; import path from 'path'; import { fileURLToPath } from 'url'; import reactConfig from '../../eslint.config.react.mjs'; @@ -18,14 +17,6 @@ export default [ ], }, - // JSON files configuration - { - files: ['**/*.json'], - languageOptions: { - parser: jsoncParser, - }, - }, - // TypeScript project-specific configuration { files: ['**/*.{ts,tsx}'],