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:
+6
-3
@@ -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;
|
||||
}
|
||||
|
||||
+12
-8
@@ -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}
|
||||
|
||||
+6
-5
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user