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
@@ -2,6 +2,7 @@ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRe
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
import { useVectorSearchFieldInRecordIndexContextOrThrow } from '@/views/hooks/useVectorSearchFieldInRecordIndexContextOrThrow';
import { t } from '@lingui/core/macro';
import { ViewFilterOperand } from 'twenty-shared/types';
import { getFilterTypeFromFieldType, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
@@ -28,7 +29,7 @@ export const useVectorSearchFilterActions = () => {
displayValue: value,
operand: ViewFilterOperand.VECTOR_SEARCH,
type: getFilterTypeFromFieldType(vectorSearchField.type),
label: 'Search',
label: t`Search`,
};
upsertRecordFilter(vectorSearchRecordFilter);
@@ -36,7 +36,7 @@ import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states
import { viewPickerMainGroupByFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerMainGroupByFieldMetadataIdComponentState';
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState';
import { useLingui } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { useMemo, useState } from 'react';
import { IconX } from 'twenty-ui/display';
@@ -149,6 +149,8 @@ export const ViewPickerContentCreateMode = () => {
setViewPickerMode('list');
};
const objectLabel = objectMetadataItem.labelPlural;
return (
<DropdownContent>
<DropdownMenuHeader
@@ -210,8 +212,9 @@ export const ViewPickerContentCreateMode = () => {
</ViewPickerSelectContainer>
{availableFieldsForKanban.length === 0 && (
<StyledFieldAvailableContainer>
Set up a Select field on {objectMetadataItem.labelPlural} to
create a Kanban
<Trans>
Set up a Select field on {objectLabel} to create a Kanban
</Trans>
</StyledFieldAvailableContainer>
)}
</>
@@ -240,8 +243,9 @@ export const ViewPickerContentCreateMode = () => {
</ViewPickerSelectContainer>
{availableFieldsForCalendar.length === 0 && (
<StyledFieldAvailableContainer>
Set up a Date field on {objectMetadataItem.labelPlural} to
create a Calendar
<Trans>
Set up a Date field on {objectLabel} to create a Calendar
</Trans>
</StyledFieldAvailableContainer>
)}
</>
@@ -21,6 +21,7 @@ import { viewPickerInputNameComponentState } from '@/views/view-picker/states/vi
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
import { t } from '@lingui/core/macro';
import { IconChevronLeft } from 'twenty-ui/display';
export const ViewPickerContentEditMode = () => {
@@ -75,7 +76,7 @@ export const ViewPickerContentEditMode = () => {
/>
}
>
Edit view
{t`Edit view`}
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<ViewPickerIconAndNameContainer>
@@ -94,7 +94,7 @@ export const ViewPickerCreateButton = () => {
<Button
title={t`Create`}
onClick={handleCreateButtonClick}
ariaLabel="Create new view"
ariaLabel={t`Create new view`}
accent="blue"
fullWidth
size="small"
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@@ -85,7 +86,7 @@ export const ViewPickerDropdown = () => {
<IconList size={theme.icon.size.md} />
)}
<StyledViewName>
<OverflowingTextWithTooltip text={currentView?.name ?? 'All'} />
<OverflowingTextWithTooltip text={currentView?.name ?? t`All`} />
</StyledViewName>
<StyledDropdownLabelAdornments>
{isDefined(totalCount) && <>· {totalCount} </>}