feat: add lingui/no-unlocalized-strings ESLint rule and fix translations (#16610)

## Summary
This PR adds the `lingui/no-unlocalized-strings` ESLint rule to detect
untranslated strings and fixes translation issues across multiple
components.

## Changes

### ESLint Configuration (`eslint.config.react.mjs`)
- Added comprehensive `ignore` patterns for non-translatable strings
(CSS values, HTML attributes, technical identifiers)
- Added `ignoreNames` for props that don't need translation (className,
data-*, aria-*, etc.)
- Added `ignoreFunctions` for console methods, URL APIs, and other
non-user-facing functions
- Disabled rule for debug files, storybook, and test files

### Components Fixed (~19 files)
- Object record components (field inputs, pickers, merge dialogs)
- Settings components (accounts, admin panel)
- Serverless function components
- Record table and title cell components

## Status
🚧 **Work in Progress** - ~124 files remaining to fix

This PR is being submitted as draft to allow progressive fixing of
remaining translation issues.

## Testing
- Run `npx eslint "src/**/*.tsx"` in `packages/twenty-front` to check
remaining issues
This commit is contained in:
Félix Malfait
2025-12-17 22:08:33 +01:00
committed by GitHub
parent c13b955a36
commit 1088f7bbab
368 changed files with 56823 additions and 836 deletions
@@ -91,7 +91,7 @@ export const WorkflowEditTriggerCronForm = ({
<>
<FormTextFieldInput
label={t`Expression`}
placeholder="0 */1 * * *"
placeholder={t`0 */1 * * *`}
error={errorMessagesVisible ? errorMessages.CUSTOM : undefined}
onBlur={onBlur}
hint={customDescription ?? ''}
@@ -107,8 +107,11 @@ export const WorkflowEditTriggerCronForm = ({
try {
CronExpressionParser.parse(newPattern);
} catch (error) {
const unknownError = t`Unknown error`;
const errorMessage =
error instanceof Error ? error.message : unknownError;
setErrorMessages({
CUSTOM: `Invalid cron pattern: ${error instanceof Error ? error.message : 'Unknown error'}`,
CUSTOM: t`Invalid cron pattern: ${errorMessage}`,
});
return;
}
@@ -154,7 +157,7 @@ export const WorkflowEditTriggerCronForm = ({
if (!isNumber(newDay) || newDay <= 0) {
setErrorMessages((prev) => ({
...prev,
DAYS_day: `Invalid day value '${newDay}'. Should be integer greater than 1`,
DAYS_day: t`Invalid day value '${newDay}'. Should be integer greater than 1`,
}));
return;
}
@@ -17,7 +17,7 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { useCallback, useMemo, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
@@ -36,8 +36,6 @@ const StyledRecordTypeSelectContainer = styled.div<{ fullWidth?: boolean }>`
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
`;
const DEFAULT_SELECTED_OPTION = { label: 'Select an option', value: '' };
const filterOptionsBySearch = <T extends { label: string }>(
options: T[],
searchValue: string,
@@ -67,6 +65,7 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
}: WorkflowEditTriggerDatabaseEventFormProps) => {
const theme = useTheme();
const { getIcon } = useIcons();
const { t } = useLingui();
const [searchInputValue, setSearchInputValue] = useState('');
const [isSystemObjectsOpen, setIsSystemObjectsOpen] = useState(false);
const dropdownId = 'workflow-edit-trigger-record-type';
@@ -82,6 +81,11 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
const isUpsertEvent = triggerEvent.event === 'upserted';
const isFieldFilteringSupported = isUpdateEvent || isUpsertEvent;
const defaultSelectedOption = useMemo(
() => ({ label: t`Select an option`, value: '' }),
[t],
);
const regularObjects = objectMetadataItems
.filter((item) => item.isActive && !item.isSystem)
.map((item) => ({
@@ -101,7 +105,7 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
const selectedOption =
[...regularObjects, ...systemObjects].find(
(option) => option.value === triggerEvent?.objectType,
) || DEFAULT_SELECTED_OPTION;
) || defaultSelectedOption;
const selectedObjectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === selectedOption.value,
@@ -167,7 +171,7 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
<>
<WorkflowStepBody>
<StyledRecordTypeSelectContainer fullWidth>
<StyledLabel>Record Type</StyledLabel>
<StyledLabel>{t`Record Type`}</StyledLabel>
<Dropdown
dropdownId="workflow-edit-trigger-record-type"
dropdownPlacement="bottom-start"
@@ -235,7 +239,7 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
searchInputValue.toLowerCase(),
)) && (
<MenuItem
text="Advanced"
text={t`Advanced`}
LeftIcon={IconSettings}
onClick={handleSystemObjectsClick}
hasSubMenu
@@ -251,8 +255,8 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
</StyledRecordTypeSelectContainer>
{isDefined(selectedObjectMetadataItem) && isFieldFilteringSupported && (
<WorkflowFieldsMultiSelect
label="Fields (Optional)"
placeholder="Select specific fields to listen to"
label={t`Fields (Optional)`}
placeholder={t`Select specific fields to listen to`}
objectMetadataItem={selectedObjectMetadataItem}
handleFieldsChange={handleFieldsChange}
readonly={triggerOptions.readonly ?? false}
@@ -21,6 +21,7 @@ import {
buildOutputSchemaFromValue,
TRIGGER_STEP_ID,
} from 'twenty-shared/workflow';
import { t } from '@lingui/core/macro';
import { IconCopy } from 'twenty-ui/display';
import { useDebouncedCallback } from 'use-debounce';
@@ -80,7 +81,7 @@ export const WorkflowEditTriggerWebhookForm = ({
<>
<WorkflowStepBody>
<TextInput
label="Live URL"
label={t`Live URL`}
value={displayWebhookUrl}
RightIcon={() => (
<IconCopy
@@ -93,7 +94,7 @@ export const WorkflowEditTriggerWebhookForm = ({
/>
<Select
dropdownId="workflow-edit-webhook-trigger-http-method"
label="HTTP method"
label={t`HTTP method`}
fullWidth
disabled={triggerOptions.readonly}
value={trigger.settings.httpMethod}
@@ -116,8 +117,8 @@ export const WorkflowEditTriggerWebhookForm = ({
/>
{trigger.settings.httpMethod === 'POST' && (
<FormRawJsonFieldInput
label="Expected Body"
placeholder="Enter a JSON object"
label={t`Expected Body`}
placeholder={t`Enter a JSON object`}
error={
errorMessagesVisible ? errorMessages.expectedBody : undefined
}
@@ -173,7 +174,7 @@ export const WorkflowEditTriggerWebhookForm = ({
)}
<Select
dropdownId="workflow-edit-webhook-trigger-auth"
label="Auth"
label={t`Auth`}
fullWidth
disabled
value={trigger.settings.authentication}