CSV Import translations and dark mode fixes (#11184)

Fixes #11182 and adds missing translations
This commit is contained in:
Félix Malfait
2025-03-27 08:53:00 +01:00
committed by GitHub
parent fad02fa26b
commit a545b3adf3
83 changed files with 5105 additions and 89 deletions

View File

@ -5,6 +5,7 @@ import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useLingui } from '@lingui/react/macro';
import { ChangeEvent, useState } from 'react';
import { SearchRecord } from '~/generated-metadata/graphql';
@ -34,13 +35,14 @@ export const RoleAssignmentWorkspaceMemberPickerDropdown = ({
const handleSearchFilterChange = (event: ChangeEvent<HTMLInputElement>) => {
setSearchFilter(event.target.value);
};
const { t } = useLingui();
return (
<DropdownMenu>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleSearchFilterChange}
placeholder="Search"
placeholder={t`Search`}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>

View File

@ -20,6 +20,7 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useLingui } from '@lingui/react/macro';
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
const StyledFloatingDropdown = styled.div`
@ -109,6 +110,8 @@ export const MatchColumnSelect = ({
setOptions(initialOptions);
}, [initialOptions]);
const { t } = useLingui();
return (
<>
<div ref={refs.setReference}>
@ -152,7 +155,7 @@ export const MatchColumnSelect = ({
<AppTooltip
key={option.value}
anchorSelect={`#${option.value}`}
content="You are already importing this column."
content={t`You are already importing this column.`}
place="right"
offset={-20}
/>,
@ -161,7 +164,7 @@ export const MatchColumnSelect = ({
</React.Fragment>
))}
{options?.length === 0 && (
<MenuItem key="No results" text="No results" />
<MenuItem key="No results" text={t`No results`} />
)}
</DropdownMenuItemsContainer>
</DropdownMenu>

View File

@ -5,6 +5,7 @@ import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useS
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
import { useLingui } from '@lingui/react/macro';
const StyledCloseButtonContainer = styled.div`
align-items: center;
@ -34,17 +35,24 @@ export const ModalCloseButton = ({ onClose }: ModalCloseButtonProps) => {
const { enqueueDialog } = useDialogManager();
const { t } = useLingui();
const handleClose = () => {
if (activeStep === -1) {
onClose();
return;
}
enqueueDialog({
title: 'Exit import flow',
message: 'Are you sure? Your current information will not be saved.',
title: t`Exit import flow`,
message: t`Are you sure? Your current information will not be saved.`,
buttons: [
{ title: 'Cancel' },
{ title: 'Exit', onClick: onClose, accent: 'danger', role: 'confirm' },
{ title: t`Cancel` },
{
title: t`Exit`,
onClick: onClose,
accent: 'danger',
role: 'confirm',
},
],
});
};

View File

@ -1,3 +1,4 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
// @ts-expect-error // Todo: remove usage of react-data-grid
import DataGrid, { DataGridProps } from 'react-data-grid';
@ -125,6 +126,8 @@ export const SpreadsheetImportTable = <Data,>({
selectedRows,
}: SpreadsheetImportTableProps<Data>) => {
const { rtl } = useSpreadsheetImportInternal();
const theme = useTheme();
const themeClassName = theme.name === 'dark' ? 'rdg-dark' : 'rdg-light';
if (!rows?.length || !columns?.length) return null;
@ -133,7 +136,7 @@ export const SpreadsheetImportTable = <Data,>({
direction={rtl ? 'rtl' : 'ltr'}
rowHeight={52}
{...{
className,
className: `${className || ''} ${themeClassName}`,
columns,
components,
headerRowHeight,

View File

@ -26,6 +26,7 @@ import { UnmatchColumn } from '@/spreadsheet-import/steps/components/MatchColumn
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { Trans, useLingui } from '@lingui/react/macro';
import { useRecoilState } from 'recoil';
import { ColumnGrid } from './components/ColumnGrid';
import { TemplateColumn } from './components/TemplateColumn';
@ -151,6 +152,8 @@ export const MatchColumnsStep = <T extends string>({
const { matchColumnsStepHook } = useSpreadsheetImportInternal();
const { t } = useLingui();
const onIgnore = useCallback(
(columnIndex: number) => {
setColumns(
@ -275,21 +278,22 @@ export const MatchColumnsStep = <T extends string>({
const handleOnContinue = useCallback(async () => {
if (unmatchedRequiredFields.length > 0) {
enqueueDialog({
title: 'Not all columns matched',
message:
'There are required columns that are not matched or ignored. Do you want to continue?',
title: t`Not all columns matched`,
message: t`There are required columns that are not matched or ignored. Do you want to continue?`,
children: (
<StyledColumnsContainer>
<StyledColumns>Columns not matched:</StyledColumns>
<StyledColumns>
<Trans>Columns not matched:</Trans>
</StyledColumns>
{unmatchedRequiredFields.map((field) => (
<StyledColumn key={field}>{field}</StyledColumn>
))}
</StyledColumnsContainer>
),
buttons: [
{ title: 'Cancel' },
{ title: t`Cancel` },
{
title: 'Continue',
title: t`Continue`,
onClick: handleAlertOnContinue,
variant: 'primary',
role: 'confirm',
@ -313,6 +317,7 @@ export const MatchColumnsStep = <T extends string>({
columns,
data,
fields,
t,
]);
useEffect(() => {
@ -334,8 +339,8 @@ export const MatchColumnsStep = <T extends string>({
>
<StyledContent>
<Heading
title="Match Columns"
description="Select the correct field for each column you'd like to import."
title={t`Match Columns`}
description={t`Select the correct field for each column you'd like to import.`}
/>
<ColumnGrid
columns={columns}
@ -367,7 +372,7 @@ export const MatchColumnsStep = <T extends string>({
<StepNavigationButton
onClick={handleOnContinue}
isLoading={isLoading}
title="Next Step"
title={t`Next Step`}
onBack={() => {
onBack?.();
setColumns([]);

View File

@ -3,8 +3,9 @@ import { IconForbid } from 'twenty-ui';
import { MatchColumnSelect } from '@/spreadsheet-import/components/MatchColumnSelect';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { Columns, ColumnType } from '../MatchColumnsStep';
import { useLingui } from '@lingui/react/macro';
import { FieldMetadataType } from 'twenty-shared/types';
import { Columns, ColumnType } from '../MatchColumnsStep';
const StyledContainer = styled.div`
display: flex;
@ -28,6 +29,8 @@ export const TemplateColumn = <T extends string>({
const column = columns[columnIndex];
const isIgnored = column.type === ColumnType.ignored;
const { t } = useLingui();
const fieldOptions = fields
.filter((field) => field.fieldMetadataType !== FieldMetadataType.RICH_TEXT)
.map(({ icon, label, key }) => {
@ -51,7 +54,7 @@ export const TemplateColumn = <T extends string>({
{
icon: IconForbid,
value: 'do-not-import',
label: 'Do not import',
label: t`Do not import`,
},
...fieldOptions,
];
@ -67,7 +70,7 @@ export const TemplateColumn = <T extends string>({
return (
<StyledContainer>
<MatchColumnSelect
placeholder="Select column..."
placeholder={t`Select column...`}
value={isIgnored ? ignoreValue : selectValue}
onChange={(value) => onChange(value?.value as T, column.index)}
options={selectOptions}

View File

@ -4,9 +4,10 @@ import { UnmatchColumnBanner } from '@/spreadsheet-import/steps/components/Match
import { Column } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { Fields } from '@/spreadsheet-import/types';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { AnimatedExpandableContainer } from 'twenty-ui';
import { isDefined } from 'twenty-shared/utils';
import { AnimatedExpandableContainer } from 'twenty-ui';
const getExpandableContainerTitle = <T extends string>(
fields: Fields<T>,
@ -50,6 +51,7 @@ export const UnmatchColumn = <T extends string>({
const [isExpanded, setIsExpanded] = useState(false);
const column = columns[columnIndex];
const isSelect = 'matchedOptions' in column;
const { t } = useLingui();
if (!isSelect) return null;
@ -73,7 +75,7 @@ export const UnmatchColumn = <T extends string>({
column={column}
onSubChange={onSubChange}
key={option.entry}
placeholder="Select an option"
placeholder={t`Select an option`}
/>
))}
</StyledContentWrapper>

View File

@ -10,6 +10,7 @@ import { Modal } from '@/ui/layout/modal/components/Modal';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
import { useLingui } from '@lingui/react/macro';
import { SelectHeaderTable } from './components/SelectHeaderTable';
const StyledHeading = styled(Heading)`
@ -87,10 +88,12 @@ export const SelectHeaderStep = ({
setIsLoading(false);
}, [handleContinue, importedRows, selectedRowIndexes]);
const { t } = useLingui();
return (
<>
<Modal.Content>
<StyledHeading title="Select header row" />
<StyledHeading title={t`Select header row`} />
<StyledTableContainer>
<SelectHeaderTable
importedRows={importedRows}
@ -102,7 +105,7 @@ export const SelectHeaderStep = ({
<StepNavigationButton
onClick={handleOnContinue}
onBack={onBack}
title="Continue"
title={t`Continue`}
isLoading={isLoading}
/>
</>

View File

@ -11,6 +11,7 @@ import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
import { Radio, RadioGroup } from 'twenty-ui';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { useLingui } from '@lingui/react/macro';
import { WorkBook } from 'xlsx-ugnis';
const StyledContent = styled(Modal.Content)`
@ -100,10 +101,12 @@ export const SelectSheetStep = ({
[handleContinue],
);
const { t } = useLingui();
return (
<>
<StyledContent>
<StyledHeading title="Select the sheet to use" />
<StyledHeading title={t`Select the sheet to use`} />
<StyledRadioContainer>
<RadioGroup onValueChange={(value) => setValue(value)} value={value}>
{sheetNames.map((sheetName) => (
@ -116,7 +119,7 @@ export const SelectSheetStep = ({
onClick={() => handleOnContinue(value)}
onBack={onBack}
isLoading={isLoading}
title="Next Step"
title={t`Next Step`}
/>
</>
);

View File

@ -8,6 +8,7 @@ import { StepBar } from '@/ui/navigation/step-bar/components/StepBar';
import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { useLingui } from '@lingui/react/macro';
import { SpreadsheetImportStepper } from './SpreadsheetImportStepper';
const StyledHeader = styled(Modal.Header)`
@ -23,13 +24,15 @@ const StyledHeader = styled(Modal.Header)`
}
`;
const stepTitles = {
uploadStep: 'Upload file',
matchColumnsStep: 'Match columns',
validationStep: 'Validate data',
} as const;
export const SpreadsheetImportStepperContainer = () => {
const { t } = useLingui();
const stepTitles = {
uploadStep: t`Upload file`,
matchColumnsStep: t`Match columns`,
validationStep: t`Validate data`,
};
const { initialStepState } = useSpreadsheetImportInternal();
const { steps, initialStep } = useSpreadsheetImportInitialStep(

View File

@ -7,6 +7,7 @@ import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpre
import { readFileAsync } from '@/spreadsheet-import/utils/readFilesAsync';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Trans, useLingui } from '@lingui/react/macro';
import { MainButton } from 'twenty-ui';
const StyledContainer = styled.div`
@ -130,6 +131,8 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
},
});
const { t } = useLingui();
return (
<StyledContainer
// eslint-disable-next-line react/jsx-props-no-spreading
@ -141,13 +144,19 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
{...getInputProps()}
/>
{isDragActive ? (
<StyledText>Drop file here...</StyledText>
<StyledText>
<Trans>Drop file here...</Trans>
</StyledText>
) : loading || isLoading ? (
<StyledText>Processing...</StyledText>
<StyledText>
<Trans>Processing...</Trans>
</StyledText>
) : (
<>
<StyledText>Upload .xlsx, .xls or .csv file</StyledText>
<MainButton onClick={open} title="Select file" />
<StyledText>
<Trans>Upload .xlsx, .xls or .csv file</Trans>
</StyledText>
<MainButton onClick={open} title={t`Select file`} />
</>
)}
</StyledContainer>

View File

@ -16,6 +16,7 @@ import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
import { Modal } from '@/ui/layout/modal/components/Modal';
import styled from '@emotion/styled';
import { Trans, useLingui } from '@lingui/react/macro';
import {
Dispatch,
SetStateAction,
@ -25,10 +26,10 @@ import {
} from 'react';
// @ts-expect-error Todo: remove usage of react-data-grid`
import { RowsChangeData } from 'react-data-grid';
import { isDefined } from 'twenty-shared/utils';
import { Button, IconTrash, Toggle } from 'twenty-ui';
import { generateColumns } from './components/columns';
import { ImportedStructuredRowMetadata } from './types';
import { isDefined } from 'twenty-shared/utils';
const StyledContent = styled(Modal.Content)`
padding-left: ${({ theme }) => theme.spacing(6)};
@ -120,6 +121,8 @@ export const ValidationStep = <T extends string>({
}
};
const { t } = useLingui();
const updateRow = useCallback(
(
rows: typeof data,
@ -234,13 +237,12 @@ export const ValidationStep = <T extends string>({
submitData();
} else {
enqueueDialog({
title: 'Finish flow with errors',
message:
'There are still some rows that contain errors. Rows with errors will be ignored when submitting.',
title: t`Finish flow with errors`,
message: t`There are still some rows that contain errors. Rows with errors will be ignored when submitting.`,
buttons: [
{ title: 'Cancel' },
{ title: t`Cancel` },
{
title: 'Submit',
title: t`Submit`,
variant: 'primary',
onClick: submitData,
role: 'confirm',
@ -254,8 +256,8 @@ export const ValidationStep = <T extends string>({
<>
<StyledContent>
<Heading
title="Review your import"
description="Correct the issues and fill the missing data."
title={t`Review your import`}
description={t`Correct the issues and fill the missing data.`}
/>
<StyledToolbar>
<StyledErrorToggle>
@ -264,7 +266,7 @@ export const ValidationStep = <T extends string>({
onChange={() => setFilterByErrors(!filterByErrors)}
/>
<StyledErrorToggleDescription>
Show only rows with errors
<Trans>Show only rows with errors</Trans>
</StyledErrorToggleDescription>
</StyledErrorToggle>
<StyledErrorToggle>
@ -273,12 +275,12 @@ export const ValidationStep = <T extends string>({
onChange={() => setShowUnmatchedColumns(!showUnmatchedColumns)}
/>
<StyledErrorToggleDescription>
Show unmatched columns
<Trans>Show unmatched columns</Trans>
</StyledErrorToggleDescription>
</StyledErrorToggle>
<Button
Icon={IconTrash}
title="Remove"
title={t`Remove`}
accent="danger"
onClick={deleteSelectedRows}
disabled={selectedRows.size === 0}
@ -296,8 +298,8 @@ export const ValidationStep = <T extends string>({
noRowsFallback: (
<StyledNoRowsContainer>
{filterByErrors
? 'No data containing errors'
: 'No data found'}
? t`No data containing errors`
: t`No data found`}
</StyledNoRowsContainer>
),
}}
@ -307,7 +309,7 @@ export const ValidationStep = <T extends string>({
<StepNavigationButton
onClick={onContinue}
onBack={onBack}
title="Confirm"
title={t`Confirm`}
/>
</>
);

View File

@ -6,6 +6,7 @@ import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/comp
import { MatchColumnsStep } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
const meta: Meta<typeof MatchColumnsStep> = {
@ -14,7 +15,7 @@ const meta: Meta<typeof MatchColumnsStep> = {
parameters: {
layout: 'fullscreen',
},
decorators: [SnackBarDecorator],
decorators: [SnackBarDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -9,6 +9,7 @@ import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/comp
import { SelectHeaderStep } from '@/spreadsheet-import/steps/components/SelectHeaderStep/SelectHeaderStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const meta: Meta<typeof SelectHeaderStep> = {
title: 'Modules/SpreadsheetImport/SelectHeaderStep',
@ -16,6 +17,7 @@ const meta: Meta<typeof SelectHeaderStep> = {
parameters: {
layout: 'fullscreen',
},
decorators: [I18nFrontDecorator],
};
export default meta;

View File

@ -6,6 +6,7 @@ import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/comp
import { SelectSheetStep } from '@/spreadsheet-import/steps/components/SelectSheetStep/SelectSheetStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const meta: Meta<typeof SelectSheetStep> = {
title: 'Modules/SpreadsheetImport/SelectSheetStep',
@ -13,6 +14,7 @@ const meta: Meta<typeof SelectSheetStep> = {
parameters: {
layout: 'fullscreen',
},
decorators: [I18nFrontDecorator],
};
export default meta;

View File

@ -5,12 +5,23 @@ import { within } from '@storybook/test';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { stepBarInternalState } from '@/ui/navigation/step-bar/states/stepBarInternalState';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { SpreadsheetImportStepperContainer } from '../SpreadsheetImportStepperContainer';
const meta: Meta<typeof SpreadsheetImportStepperContainer> = {
title: 'Modules/SpreadsheetImport/Steps',
component: SpreadsheetImportStepperContainer,
decorators: [ComponentWithRecoilScopeDecorator, SnackBarDecorator],
decorators: [
ComponentWithRecoilScopeDecorator,
SnackBarDecorator,
I18nFrontDecorator,
],
parameters: {
initialRecoilState: {
[stepBarInternalState.key]: { activeStep: 0 },
},
},
};
export default meta;

View File

@ -6,6 +6,7 @@ import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/comp
import { UploadStep } from '@/spreadsheet-import/steps/components/UploadStep/UploadStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
const meta: Meta<typeof UploadStep> = {
@ -14,7 +15,7 @@ const meta: Meta<typeof UploadStep> = {
parameters: {
layout: 'fullscreen',
},
decorators: [SnackBarDecorator],
decorators: [SnackBarDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -9,13 +9,14 @@ import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/components/ReactSpreadsheetImportContextProvider';
import { ValidationStep } from '@/spreadsheet-import/steps/components/ValidationStep/ValidationStep';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const meta: Meta<typeof ValidationStep> = {
title: 'Modules/SpreadsheetImport/ValidationStep',
component: ValidationStep,
parameters: {
layout: 'fullscreen',
},
decorators: [I18nFrontDecorator],
};
export default meta;