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:
+1
-1
@@ -92,7 +92,7 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
Select matching field
|
||||
{t`Select matching field`}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
|
||||
+3
-2
@@ -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
-4
@@ -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
-1
@@ -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({
|
||||
|
||||
+4
-3
@@ -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>
|
||||
|
||||
+3
-1
@@ -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 {
|
||||
|
||||
+2
-1
@@ -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,
|
||||
},
|
||||
|
||||
+2
-1
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user