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
@@ -92,7 +92,7 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
/>
}
>
Select matching field
{t`Select matching field`}
</DropdownMenuHeader>
<DropdownMenuSearchInput
value={searchFilter}
@@ -1,4 +1,5 @@
import { type SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import React from 'react';
@@ -109,8 +110,8 @@ export const ColumnGrid = ({
<StyledGridContainer>
<StyledGrid>
<StyledGridRow height="32px">
<StyledGridHeader position="left">Imported data</StyledGridHeader>
<StyledGridHeader position="right">Twenty fields</StyledGridHeader>
<StyledGridHeader position="left">{t`Imported data`}</StyledGridHeader>
<StyledGridHeader position="right">{t`Twenty fields`}</StyledGridHeader>
</StyledGridRow>
{columns.map((column, index) => {
const userColumn = renderUserColumn(columns, index);
@@ -6,6 +6,7 @@ import { type SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetCo
import { type SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -14,15 +15,16 @@ import { AnimatedExpandableContainer } from 'twenty-ui/layout';
const getExpandableContainerTitle = (
fields: SpreadsheetImportFields,
column: SpreadsheetColumn,
) => {
): string => {
const fieldLabel = fields.find(
(field) => 'value' in column && field.key === column.value,
)?.label;
return `Match ${fieldLabel} (${
const unmatchedCount =
'matchedOptions' in column &&
column.matchedOptions?.filter((option) => !isDefined(option.value)).length
} Unmatched)`;
column.matchedOptions?.filter((option) => !isDefined(option.value)).length;
return t`Match ${fieldLabel} (${unmatchedCount} Unmatched)`;
};
type UnmatchColumnProps = {
@@ -2,6 +2,7 @@
// prettier-ignore
import { useRowSelection, type Column, type FormatterProps } from 'react-data-grid';
import { t } from '@lingui/core/macro';
import { type ImportedRow } from '@/spreadsheet-import/types';
import { Radio } from 'twenty-ui/input';
@@ -14,7 +15,7 @@ const SelectFormatter = (props: SelectFormatterProps) => {
return (
<Radio
aria-label="Select"
aria-label={t`Select`}
checked={isRowSelected}
onChange={(event) => {
onRowSelectionChange({
@@ -55,6 +55,7 @@ export const SelectSheetStep = ({
onBack,
currentStepState,
}: SelectSheetStepProps) => {
const { t } = useLingui();
const [isLoading, setIsLoading] = useState(false);
const [value, setValue] = useState(sheetNames[0]);
@@ -70,7 +71,8 @@ export const SelectSheetStep = ({
maxRecords,
)
) {
onError(`Too many records. Up to ${maxRecords.toString()} allowed`);
const maxRecordsString = maxRecords.toString();
onError(t`Too many records. Up to ${maxRecordsString} allowed`);
return;
}
try {
@@ -93,6 +95,7 @@ export const SelectSheetStep = ({
setPreviousStepState,
setCurrentStepState,
uploadStepHook,
t,
],
);
@@ -105,8 +108,6 @@ export const SelectSheetStep = ({
[handleContinue],
);
const { t } = useLingui();
return (
<>
<StyledContent>
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import { useCallback, useState } from 'react';
import { type WorkBook } from 'xlsx-ugnis';
@@ -49,7 +50,8 @@ export const UploadStep = ({
maxRecords > 0 &&
exceedsMaxRecords(workbook.Sheets[workbook.SheetNames[0]], maxRecords)
) {
onError(`Too many records. Up to ${maxRecords.toString()} allowed`);
const maxRecordsString = maxRecords.toString();
onError(t`Too many records. Up to ${maxRecordsString} allowed`);
return;
}
try {
@@ -136,8 +136,9 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
onDropRejected: (fileRejections) => {
setLoading(false);
fileRejections.forEach((fileRejection) => {
const fileName = fileRejection.file.name;
enqueueErrorSnackBar({
message: `${fileRejection.file.name} upload rejected`,
message: t`${fileName} upload rejected`,
options: {
detailedMessage: fileRejection.errors[0].message,
},
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
// @ts-expect-error // Todo: remove usage of react-data-grid
import { type Column, useRowSelection } from 'react-data-grid';
@@ -90,7 +91,7 @@ export const generateColumns = (
return (
<StyledCheckboxContainer>
<Checkbox
aria-label="Select"
aria-label={t`Select`}
checked={isRowSelected}
variant={CheckboxVariant.Tertiary}
onChange={(event) => {