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
@@ -1,4 +1,5 @@
import { type ReactNode, useContext } from 'react';
import { t } from '@lingui/core/macro';
import { ActionDisplay } from '@/action-menu/actions/display/components/ActionDisplay';
import { ActionConfigContext } from '@/action-menu/contexts/ActionConfigContext';
@@ -25,7 +26,7 @@ export const ActionModal = ({
title,
subtitle,
onConfirmClick,
confirmButtonText = 'Confirm',
confirmButtonText = t`Confirm`,
confirmButtonAccent = 'danger',
isLoading = false,
closeSidePanelOnShowPageOptionsActionExecution,
@@ -46,8 +46,5 @@ export const ActionDisplay = ({
return <ActionDropdownItem action={action} onClick={onClick} to={to} />;
}
return assertUnreachable(
displayType,
`Unsupported display type: ${displayType}`,
);
return assertUnreachable(displayType, 'Unsupported display type');
};
@@ -17,6 +17,7 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import { IconLayoutSidebarRightExpand } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
@@ -32,6 +33,7 @@ const StyledDropdownMenuContainer = styled.div`
`;
export const RecordIndexActionMenuDropdown = () => {
const { t } = useLingui();
const { actions } = useContext(ActionMenuContext);
const recordIndexActions = actions.filter(
@@ -103,7 +105,7 @@ export const RecordIndexActionMenuDropdown = () => {
openCommandMenu();
}}
focused={selectedItemId === 'more-actions'}
text="More actions"
text={t`More actions`}
/>
</SelectableListItem>
</SelectableList>
@@ -1,5 +1,6 @@
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { CalendarEventParticipantsResponseStatus } from '@/activities/calendar/components/CalendarEventParticipantsResponseStatus';
import { type CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
@@ -77,6 +78,7 @@ const StyledPropertyBox = styled(PropertyBox)`
export const CalendarEventDetails = ({
calendarEvent,
}: CalendarEventDetailsProps) => {
const { t } = useLingui();
const theme = useTheme();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.CalendarEvent,
@@ -155,14 +157,14 @@ export const CalendarEventDetails = ({
variant={ChipVariant.Highlighted}
clickable={false}
leftComponent={<IconCalendarEvent size={theme.icon.size.md} />}
label="Event"
label={t`Event`}
/>
<StyledHeader>
<StyledTitle canceled={calendarEvent.isCanceled}>
{calendarEvent.title}
</StyledTitle>
<StyledCreatedAt>
Created{' '}
{t`Created`}{' '}
{beautifyPastDateRelativeToNow(
new Date(calendarEvent.externalCreatedAt),
)}
@@ -22,7 +22,7 @@ export const CalendarEventParticipantsResponseStatus = ({
}
});
const responseStatusOrder: ('Yes' | 'Maybe' | 'No')[] = [
const responseStatusOrder: Array<'Yes' | 'Maybe' | 'No'> = [
'Yes',
'Maybe',
'No',
@@ -1,5 +1,6 @@
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { format } from 'date-fns';
import { useRecoilValue } from 'recoil';
@@ -93,7 +94,7 @@ export const CalendarEventRow = ({
const hasEnded = hasCalendarEventEnded(calendarEvent);
const startTimeLabel = calendarEvent.isFullDay
? 'All day'
? t`All day`
: format(startsAt, 'HH:mm');
const endTimeLabel = calendarEvent.isFullDay ? '' : format(endsAt, 'HH:mm');
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { format, getYear } from 'date-fns';
import { useRecoilValue } from 'recoil';
@@ -46,6 +47,7 @@ const StyledTitleContainer = styled.div`
`;
export const CalendarEventsCard = () => {
const { t } = useLingui();
const targetRecord = useTargetRecord();
const { localeCatalog } = useRecoilValue(dateLocaleState);
@@ -95,6 +97,8 @@ export const CalendarEventsCard = () => {
}
};
const objectName = targetRecord.targetObjectNameSingular;
if (firstQueryLoading) {
return <SkeletonLoader />;
}
@@ -109,11 +113,10 @@ export const CalendarEventsCard = () => {
<AnimatedPlaceholder type="noMatchRecord" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
No Events
{t`No Events`}
</AnimatedPlaceholderEmptyTitle>
<AnimatedPlaceholderEmptySubTitle>
No events have been scheduled with this{' '}
{targetRecord.targetObjectNameSingular} yet.
{t`No events have been scheduled with this ${objectName} yet.`}
</AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer>
</AnimatedPlaceholderEmptyContainer>
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useInView } from 'react-intersection-observer';
type CustomResolverFetchMoreLoaderProps = {
@@ -34,7 +35,7 @@ export const CustomResolverFetchMoreLoader = ({
return (
<StyledContainer ref={tbodyRef}>
{loading && <StyledText>Loading more...</StyledText>}
{loading && <StyledText>{t`Loading more...`}</StyledText>}
</StyledContainer>
);
};
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
@@ -11,7 +12,7 @@ export const EmailLoader = ({ loadingText }: { loadingText?: string }) => (
<AnimatedPlaceholder type="loadingMessages" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
{loadingText || 'Loading emails'}
{loadingText || t`Loading emails`}
</AnimatedPlaceholderEmptyTitle>
<Loader />
</AnimatedPlaceholderEmptyTextContainer>
@@ -1,5 +1,6 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { AppTooltip, IconLock, TooltipDelay } from 'twenty-ui/display';
import { MessageChannelVisibility } from '~/generated/graphql';
@@ -31,6 +32,7 @@ type EmailThreadNotSharedProps = {
export const EmailThreadNotShared = ({
visibility,
}: EmailThreadNotSharedProps) => {
const { t } = useLingui();
const theme = useTheme();
const containerId = 'email-thread-not-shared';
const isCompact = visibility === MessageChannelVisibility.SUBJECT;
@@ -39,12 +41,12 @@ export const EmailThreadNotShared = ({
<>
<StyledContainer id={containerId} isCompact={isCompact}>
<IconLock size={theme.icon.size.sm} />
{'Not shared'}
{t`Not shared`}
</StyledContainer>
{visibility === MessageChannelVisibility.SUBJECT && (
<AppTooltip
anchorSelect={`#${containerId}`}
content="Only the subject is shared"
content={t`Only the subject is shared`}
delay={TooltipDelay.mediumDelay}
noArrow
place="bottom"
@@ -3,6 +3,7 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useLingui } from '@lingui/react/macro';
import {
IconDotsVertical,
IconDownload,
@@ -27,6 +28,7 @@ export const AttachmentDropdown = ({
attachmentId,
hasDownloadPermission,
}: AttachmentDropdownProps) => {
const { t } = useLingui();
const dropdownId = `${attachmentId}-attachment-dropdown`;
const { closeDropdown } = useCloseDropdown();
@@ -57,18 +59,18 @@ export const AttachmentDropdown = ({
<DropdownMenuItemsContainer>
{hasDownloadPermission && (
<MenuItem
text="Download"
text={t`Download`}
LeftIcon={IconDownload}
onClick={handleDownload}
/>
)}
<MenuItem
text="Rename"
text={t`Rename`}
LeftIcon={IconPencil}
onClick={handleRename}
/>
<MenuItem
text="Delete"
text={t`Delete`}
accent="danger"
LeftIcon={IconTrash}
onClick={handleDelete}
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { lazy, type ReactElement, Suspense, useState } from 'react';
import { createPortal } from 'react-dom';
@@ -240,7 +241,7 @@ export const AttachmentList = ({
fallback={
<StyledLoadingContainer>
<StyledLoadingText>
Loading document viewer...
{t`Loading document viewer...`}
</StyledLoadingText>
</StyledLoadingContainer>
}
@@ -6,7 +6,7 @@ import DocViewer, { DocViewerRenderers } from '@cyntler/react-doc-viewer';
import '@cyntler/react-doc-viewer/dist/index.css';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { IconDownload } from 'twenty-ui/display';
@@ -99,6 +99,7 @@ export const DocumentViewer = ({
documentName,
documentUrl,
}: DocumentViewerProps) => {
const { t } = useLingui();
const theme = useTheme();
const [csvPreview, setCsvPreview] = useState<string | undefined>(undefined);
@@ -141,7 +142,7 @@ export const DocumentViewer = ({
</StyledMessage>
<Button
Icon={IconDownload}
title="Download File"
title={t`Download File`}
onClick={() => downloadFile(documentUrl, documentName)}
variant="secondary"
/>
@@ -1,5 +1,6 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useDropzone } from 'react-dropzone';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
@@ -47,6 +48,7 @@ export const DropZone = ({
setIsDraggingFile,
onUploadFiles,
}: DropZoneProps) => {
const { t } = useLingui();
const theme = useTheme();
const { maxFileSize } = useSpreadsheetImportInternal();
@@ -85,9 +87,9 @@ export const DropZone = ({
stroke={theme.icon.stroke.sm}
size={theme.icon.size.lg}
/>
<StyledUploadDragTitle>Upload files</StyledUploadDragTitle>
<StyledUploadDragTitle>{t`Upload files`}</StyledUploadDragTitle>
<StyledUploadDragSubTitle>
Drag and Drop Here
{t`Drag and Drop Here`}
</StyledUploadDragSubTitle>
</>
)}
@@ -1,4 +1,5 @@
import { useContext } from 'react';
import { t } from '@lingui/core/macro';
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
@@ -95,7 +96,7 @@ export const ActivityTargetsInlineCell = ({
}}
/>
),
label: 'Relations',
label: t`Relations`,
displayModeContent: (
<ActivityTargetChips
activityTargetObjectRecords={activityTargetObjectRecords}
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { useActivityTargetsComponentInstanceId } from '@/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId';
@@ -88,7 +89,7 @@ export const NoteTile = ({
})
}
>
<StyledNoteTitle>{note.title ?? 'Task Title'}</StyledNoteTitle>
<StyledNoteTitle>{note.title ?? t`Task Title`}</StyledNoteTitle>
<StyledCardContent>{body}</StyledCardContent>
</StyledCardDetailsContainer>
<StyledFooter>
@@ -68,10 +68,10 @@ export const NotesCard = () => {
<AnimatedPlaceholder type="noNote" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
No notes
{t`No notes`}
</AnimatedPlaceholderEmptyTitle>
<AnimatedPlaceholderEmptySubTitle>
There are no associated notes with this record.
{t`There are no associated notes with this record.`}
</AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer>
{hasObjectUpdatePermissions && (
@@ -78,10 +78,10 @@ export const TaskGroups = ({ targetableObject }: TaskGroupsProps) => {
<AnimatedPlaceholder type="noTask" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
Mission accomplished!
{t`Mission accomplished!`}
</AnimatedPlaceholderEmptyTitle>
<AnimatedPlaceholderEmptySubTitle>
All tasks addressed. Maintain the momentum.
{t`All tasks addressed. Maintain the momentum.`}
</AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer>
{hasObjectUpdatePermissions && (
@@ -1,5 +1,6 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
@@ -114,7 +115,7 @@ export const TaskRow = ({ task }: { task: Task }) => {
/>
</StyledCheckboxContainer>
<StyledTaskTitle completed={task.status === 'DONE'}>
{task.title || <StyledPlaceholder>Task title</StyledPlaceholder>}
{task.title || <StyledPlaceholder>{t`Task title`}</StyledPlaceholder>}
</StyledTaskTitle>
<StyledTaskBody>
<OverflowingTextWithTooltip text={body} />
@@ -72,10 +72,10 @@ export const TimelineCard = () => {
<AnimatedPlaceholder type="emptyTimeline" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
No activity yet
{t`No activity yet`}
</AnimatedPlaceholderEmptyTitle>
<AnimatedPlaceholderEmptySubTitle>
There is no activity associated with this record.
{t`There is no activity associated with this record.`}
</AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer>
</EmptyContainer>
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import {
type EventRowDynamicComponentProps,
@@ -91,7 +92,7 @@ export const EventRowActivity = ({
return event.linkedRecordCachedName;
}
return 'Untitled';
return t`Untitled`;
};
const activityTitle = computeActivityTitle();
@@ -103,7 +104,7 @@ export const EventRowActivity = ({
<StyledRow>
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn>
<StyledEventRowItemAction>
{`${eventAction} a related ${eventObject}`}
{t`${eventAction} a related ${eventObject}`}
</StyledEventRowItemAction>
<StyledLinkedActivity
onClick={() =>
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { isUndefined } from '@sniptt/guards';
import { CalendarEventNotSharedContent } from '@/activities/calendar/components/CalendarEventNotSharedContent';
@@ -138,14 +139,14 @@ export const EventCardCalendarEvent = ({
);
if (shouldHandleNotFound) {
return <div>Calendar event not found</div>;
return <div>{t`Calendar event not found`}</div>;
}
return <div>Error loading calendar event</div>;
return <div>{t`Error loading calendar event`}</div>;
}
if (loading || isUndefined(calendarEvent)) {
return <div>Loading...</div>;
return <div>{t`Loading...`}</div>;
}
const startsAtDate = calendarEvent?.startsAt;
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { EventCardCalendarEvent } from '@/activities/timeline-activities/rows/calendar/components/EventCardCalendarEvent';
@@ -30,6 +31,7 @@ export const EventRowCalendarEvent = ({
authorFullName,
labelIdentifierValue,
}: EventRowCalendarEventProps) => {
const { t } = useLingui();
const [, eventAction] = event.name.split('.');
const [isOpen, setIsOpen] = useState(false);
@@ -42,7 +44,7 @@ export const EventRowCalendarEvent = ({
<StyledRowContainer>
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn>
<StyledEventRowItemAction>
linked a calendar event with {labelIdentifierValue}
{t`linked a calendar event with ${labelIdentifierValue}`}
</StyledEventRowItemAction>
<EventCardToggleButton isOpen={isOpen} setIsOpen={setIsOpen} />
</StyledRowContainer>
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { EventCard } from '@/activities/timeline-activities/rows/components/EventCard';
@@ -54,6 +55,7 @@ export const EventRowMainObjectUpdated = ({
mainObjectMetadataItem,
createdAt,
}: EventRowMainObjectUpdatedProps) => {
const { t } = useLingui();
const diff: Record<string, { before: any; after: any }> =
event.properties?.diff;
@@ -70,12 +72,15 @@ export const EventRowMainObjectUpdated = ({
throw new Error('Cannot render update description without changes');
}
const fieldCount = diffEntries.length;
const recordLabel = labelIdentifierValue;
return (
<StyledEventRowMainObjectUpdatedContainer>
<StyledRowContainer>
<StyledRow>
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn>
updated
{t`updated`}
{diffEntries.length === 1 && (
<EventFieldDiffContainer
mainObjectMetadataItem={mainObjectMetadataItem}
@@ -87,9 +92,7 @@ export const EventRowMainObjectUpdated = ({
)}
{diffEntries.length > 1 && (
<>
<span>
{diffEntries.length} fields on {labelIdentifierValue}
</span>
<span>{t`${fieldCount} fields on ${recordLabel}`}</span>
<EventCardToggleButton isOpen={isOpen} setIsOpen={setIsOpen} />
</>
)}
@@ -7,7 +7,7 @@ import { useOpenEmailThreadInCommandMenu } from '@/command-menu/hooks/useOpenEma
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { Trans } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
@@ -59,6 +59,7 @@ export const EventCardMessage = ({
messageId: string;
authorFullName: string;
}) => {
const { t } = useLingui();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { openEmailThreadInCommandMenu } = useOpenEmailThreadInCommandMenu();
@@ -142,7 +143,7 @@ export const EventCardMessage = ({
{message.subject !==
FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED
? message.subject
: `Subject not shared`}
: t`Subject not shared`}
</StyledEmailTitle>
<StyledEmailParticipants>
<OverflowingTextWithTooltip text={messageParticipantHandles} />
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { EventCard } from '@/activities/timeline-activities/rows/components/EventCard';
@@ -42,7 +43,7 @@ export const EventRowMessage = ({
<StyledRowContainer>
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn>
<StyledEventRowItemAction>
linked an email with
{t`linked an email with`}
</StyledEventRowItemAction>
<StyledEventRowItemColumn>
{labelIdentifierValue}
@@ -5,6 +5,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { useToggleDropdown } from '@/ui/layout/dropdown/hooks/useToggleDropdown';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { type Editor } from '@tiptap/react';
import { useId } from 'react';
import { IconPilcrow } from 'twenty-ui/display';
@@ -50,7 +51,7 @@ export const TurnIntoBlockDropdown = ({
const options = useTurnIntoBlockOptions(editor);
const activeItem = options.find((option) => option.isActive());
const { icon: ActiveIcon = IconPilcrow, title: activeTitle = 'Paragraph' } =
const { icon: ActiveIcon = IconPilcrow, title: activeTitle = t`Paragraph` } =
activeItem ?? {};
return (
@@ -1,3 +1,4 @@
import { useLingui } from '@lingui/react/macro';
import { type Editor, useEditorState } from '@tiptap/react';
import {
type IconComponent,
@@ -17,12 +18,14 @@ export type TurnIntoBlockOptions = {
};
export const useTurnIntoBlockOptions = (editor: Editor) => {
const { t } = useLingui();
return useEditorState({
editor,
selector: ({ editor }): TurnIntoBlockOptions[] => [
{
id: 'paragraph',
title: 'Paragraph',
title: t`Paragraph`,
icon: IconPilcrow,
onClick: () => {
return editor.chain().focus().setParagraph().run();
@@ -36,7 +39,7 @@ export const useTurnIntoBlockOptions = (editor: Editor) => {
},
{
id: 'heading1',
title: 'Heading 1',
title: t`Heading 1`,
icon: IconH1,
onClick: () => {
return editor.chain().focus().setHeading({ level: 1 }).run();
@@ -50,7 +53,7 @@ export const useTurnIntoBlockOptions = (editor: Editor) => {
},
{
id: 'heading2',
title: 'Heading 2',
title: t`Heading 2`,
icon: IconH2,
onClick: () => {
return editor.chain().focus().setHeading({ level: 2 }).run();
@@ -64,7 +67,7 @@ export const useTurnIntoBlockOptions = (editor: Editor) => {
},
{
id: 'heading3',
title: 'Heading 3',
title: t`Heading 3`,
icon: IconH3,
onClick: () => {
return editor.chain().focus().setHeading({ level: 3 }).run();
@@ -51,16 +51,18 @@ export const useUploadWorkflowFile = () => {
createdAt: uploadedFile.createdAt,
};
const fileName = file.name;
enqueueSuccessSnackBar({
message: `File "${file.name}" uploaded successfully`,
message: t`File "${fileName}" uploaded successfully`,
});
return workflowFile;
} catch (error) {
logError(`Failed to upload workflow file "${file.name}": ${error}`);
const fileNameForError = file.name;
enqueueErrorSnackBar({
message: `Failed to upload "${file.name}"`,
message: t`Failed to upload "${fileNameForError}"`,
});
return null;
@@ -1,5 +1,6 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { AvatarChip, ChipVariant, LinkChip } from 'twenty-ui/components';
@@ -29,6 +30,7 @@ export const RecordLink = ({
return (
<LinkChip
label={displayName}
emptyLabel={t`Untitled`}
to={linkToShowPage}
variant={ChipVariant.Highlighted}
leftComponent={
@@ -152,45 +152,51 @@ type TimingTabProps = {
};
const TimingTab = ({ debug }: TimingTabProps) => {
const { t } = useLingui();
const totalTime =
debug.agentExecutionStartTimeMs !== undefined
? `${debug.agentExecutionStartTimeMs + (debug.agentExecutionTimeMs || 0)}ms`
: undefined;
const totalCost =
debug.totalCostInCredits !== undefined
? formatNumber(debug.totalCostInCredits)
: undefined;
return (
<StyledTimingSection>
<TimingRow
label="Routing decision"
label={t`Routing decision`}
value={debug.routingTimeMs && `${debug.routingTimeMs}ms`}
/>
<TimingRow
label="Context building (routing)"
label={t`Context building (routing)`}
value={debug.contextBuildTimeMs && `${debug.contextBuildTimeMs}ms`}
/>
<TimingRow
label="Context building (agent)"
label={t`Context building (agent)`}
value={
debug.agentContextBuildTimeMs && `${debug.agentContextBuildTimeMs}ms`
}
/>
<TimingRow
label="Tool generation"
label={t`Tool generation`}
value={debug.toolGenerationTimeMs && `${debug.toolGenerationTimeMs}ms`}
/>
<TimingRow
label="AI request prep"
label={t`AI request prep`}
value={debug.aiRequestPrepTimeMs && `${debug.aiRequestPrepTimeMs}ms`}
/>
<TimingRow
label="Agent execution"
label={t`Agent execution`}
value={debug.agentExecutionTimeMs && `${debug.agentExecutionTimeMs}ms`}
/>
<TimingRow label="Total time" value={totalTime} />
<TimingRow label="Available tools" value={debug.toolCount} />
<TimingRow label="Tool calls made" value={debug.toolCallCount} />
<TimingRow label="Context records" value={debug.contextRecordCount} />
<TimingRow label={t`Total time`} value={totalTime} />
<TimingRow label={t`Available tools`} value={debug.toolCount} />
<TimingRow label={t`Tool calls made`} value={debug.toolCallCount} />
<TimingRow label={t`Context records`} value={debug.contextRecordCount} />
<TimingRow
label="Context size"
label={t`Context size`}
value={
debug.contextSizeBytes !== undefined
? formatBytes(debug.contextSizeBytes)
@@ -198,7 +204,7 @@ const TimingTab = ({ debug }: TimingTabProps) => {
}
/>
<TimingRow
label="Routing tokens"
label={t`Routing tokens`}
value={
debug.routingTotalTokens !== undefined
? formatTokenBreakdown(
@@ -210,7 +216,7 @@ const TimingTab = ({ debug }: TimingTabProps) => {
}
/>
<TimingRow
label="Agent tokens"
label={t`Agent tokens`}
value={
debug.agentTotalTokens !== undefined
? formatTokenBreakdown(
@@ -222,12 +228,8 @@ const TimingTab = ({ debug }: TimingTabProps) => {
}
/>
<TimingRow
label="Total cost"
value={
debug.totalCostInCredits !== undefined
? `${formatNumber(debug.totalCostInCredits)} credits`
: undefined
}
label={t`Total cost`}
value={totalCost !== undefined ? t`${totalCost} credits` : undefined}
/>
</StyledTimingSection>
);
@@ -279,7 +281,7 @@ const ContextTab = ({ debug, copyToClipboard }: ContextTabProps) => {
if (!debug.context) {
return (
<StyledTimingLabel>
No context was provided for this request
{t`No context was provided for this request`}
</StyledTimingLabel>
);
}
@@ -302,9 +304,10 @@ const ContextTab = ({ debug, copyToClipboard }: ContextTabProps) => {
</StyledJsonTreeContainer>
);
} catch {
const contextValue = debug.context;
return (
<StyledTimingLabel>
Failed to parse context: {debug.context}
{t`Failed to parse context: ${contextValue}`}
</StyledTimingLabel>
);
}
@@ -315,6 +318,7 @@ type RoutingDebugDisplayProps = {
};
export const RoutingDebugDisplay = ({ debug }: RoutingDebugDisplayProps) => {
const { t } = useLingui();
const theme = useTheme();
const { copyToClipboard } = useCopyToClipboard();
const [isExpanded, setIsExpanded] = useState(false);
@@ -323,7 +327,7 @@ export const RoutingDebugDisplay = ({ debug }: RoutingDebugDisplayProps) => {
return (
<StyledContainer>
<StyledToggleButton onClick={() => setIsExpanded(!isExpanded)}>
<StyledTimingLabel>Debug Info</StyledTimingLabel>
<StyledTimingLabel>{t`Debug Info`}</StyledTimingLabel>
{isExpanded ? (
<IconChevronUp size={theme.icon.size.sm} />
) : (
@@ -338,20 +342,20 @@ export const RoutingDebugDisplay = ({ debug }: RoutingDebugDisplayProps) => {
isActive={activeTab === 'timing'}
onClick={() => setActiveTab('timing')}
>
Timing
{t`Timing`}
</StyledTab>
<StyledTab
isActive={activeTab === 'details'}
onClick={() => setActiveTab('details')}
>
Details
{t`Details`}
</StyledTab>
{debug.context && (
<StyledTab
isActive={activeTab === 'context'}
onClick={() => setActiveTab('context')}
>
Context
{t`Context`}
</StyledTab>
)}
</StyledTabContainer>
@@ -190,7 +190,7 @@ export const ToolStepRenderer = ({ toolPart }: { toolPart: ToolUIPart }) => {
}
const displayMessage = hasError
? 'Tool execution failed'
? t`Tool execution failed`
: output &&
typeof output === 'object' &&
'message' in output &&
@@ -240,13 +240,13 @@ export const ToolStepRenderer = ({ toolPart }: { toolPart: ToolUIPart }) => {
isActive={activeTab === 'output'}
onClick={() => setActiveTab('output')}
>
Output
{t`Output`}
</StyledTab>
<StyledTab
isActive={activeTab === 'input'}
onClick={() => setActiveTab('input')}
>
Input
{t`Input`}
</StyledTab>
</StyledTabContainer>
@@ -1,5 +1,6 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { ProgressBar } from 'twenty-ui/feedback';
@@ -103,6 +104,7 @@ const formatTokenCount = (count: number): string => {
};
export const AIChatContextUsageButton = () => {
const { t } = useLingui();
const theme = useTheme();
const [isHovered, setIsHovered] = useState(false);
const agentChatUsage = useRecoilValue(agentChatUsageState);
@@ -125,6 +127,8 @@ export const AIChatContextUsageButton = () => {
const formattedPercentage = percentage.toFixed(1);
const totalCredits =
agentChatUsage.inputCredits + agentChatUsage.outputCredits;
const inputCredits = agentChatUsage.inputCredits.toLocaleString();
const outputCredits = agentChatUsage.outputCredits.toLocaleString();
return (
<StyledContainer
@@ -162,23 +166,23 @@ export const AIChatContextUsageButton = () => {
<StyledBody>
<StyledRow>
<StyledLabel>Input</StyledLabel>
<StyledLabel>{t`Input`}</StyledLabel>
<StyledValue>
{formatTokenCount(agentChatUsage.inputTokens)} {' '}
{agentChatUsage.inputCredits.toLocaleString()} credits
{t`${inputCredits} credits`}
</StyledValue>
</StyledRow>
<StyledRow>
<StyledLabel>Output</StyledLabel>
<StyledLabel>{t`Output`}</StyledLabel>
<StyledValue>
{formatTokenCount(agentChatUsage.outputTokens)} {' '}
{agentChatUsage.outputCredits.toLocaleString()} credits
{t`${outputCredits} credits`}
</StyledValue>
</StyledRow>
</StyledBody>
<StyledFooter>
<StyledLabel>Total credits</StyledLabel>
<StyledLabel>{t`Total credits`}</StyledLabel>
<StyledPercentage>{totalCredits.toLocaleString()}</StyledPercentage>
</StyledFooter>
</StyledHoverCard>
@@ -3,6 +3,7 @@ import { getFileType } from '@/activities/files/utils/getFileType';
import { useFileCategoryColors } from '@/file/hooks/useFileCategoryColors';
import { IconMapping } from '@/file/utils/fileIconMappings';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { type FileUIPart } from 'ai';
import { isDefined } from 'twenty-shared/utils';
import { AvatarChip, Chip, ChipVariant, LinkChip } from 'twenty-ui/components';
@@ -23,7 +24,7 @@ export const AgentChatFilePreview = ({
useFileCategoryColors();
const fileName =
file instanceof File ? file.name : (file.filename ?? 'Unknown file');
file instanceof File ? file.name : (file.filename ?? t`Unknown file`);
const fileUrl = file instanceof File ? undefined : file.url;
@@ -54,6 +55,7 @@ export const AgentChatFilePreview = ({
return (
<LinkChip
label={fileName}
emptyLabel={t`Untitled`}
variant={ChipVariant.Static}
to={fileUrl}
target="_blank"
@@ -66,6 +68,7 @@ export const AgentChatFilePreview = ({
return (
<Chip
label={fileName}
emptyLabel={t`Untitled`}
variant={ChipVariant.Static}
clickable={false}
leftComponent={leftComponent}
@@ -8,6 +8,7 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
export const useGetBrowsingContext = () => {
@@ -103,7 +104,7 @@ export const useGetBrowsingContext = () => {
const fieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.id === filter.fieldMetadataId,
);
const fieldLabel = fieldMetadataItem?.label ?? 'Unknown field';
const fieldLabel = fieldMetadataItem?.label ?? t`Unknown field`;
return `${fieldLabel} ${filter.operand} "${filter.displayValue}"`;
});
@@ -1,6 +1,7 @@
import { type Form } from '@/auth/sign-in-up/hooks/useSignInUpForm';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import { Controller, useFormContext } from 'react-hook-form';
import { isDefined } from 'twenty-shared/utils';
@@ -20,6 +21,7 @@ export const SignInUpEmailField = ({
showErrors: boolean;
onInputChange?: (value: string) => void;
}) => {
const { t } = useLingui();
const form = useFormContext<Form>();
return (
@@ -45,7 +47,7 @@ export const SignInUpEmailField = ({
autoComplete="email"
autoFocus
value={value}
placeholder="Email"
placeholder={t`Email`}
onBlur={onBlur}
onChange={(email: string) => {
if (isDefined(onInputChange)) onInputChange(email);
@@ -50,7 +50,7 @@ export const SignInUpPasswordField = ({
autoFocus
value={value}
type="password"
placeholder="Password"
placeholder={t`Password`}
onBlur={onBlur}
onChange={onChange}
error={showErrors ? error?.message : undefined}
@@ -1,4 +1,5 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from '@lingui/core/macro';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useSearchParams } from 'react-router-dom';
@@ -20,12 +21,12 @@ const makeValidationSchema = (signInUpStep: SignInUpStep) =>
email: z
.string()
.trim()
.pipe(z.email({ error: 'Email must be a valid email' })),
.pipe(z.email({ error: t`Email must be a valid email` })),
password:
signInUpStep === SignInUpStep.Password
? z
.string()
.regex(PASSWORD_REGEX, 'Password must be min. 8 characters')
.regex(PASSWORD_REGEX, t`Password must be min. 8 characters`)
: z.string().optional(),
captchaToken: z.string().default(''),
})
@@ -1,19 +1,26 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from '@lingui/core/macro';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const otpValidationSchema = z.object({
otp: z.string().trim().length(6, 'OTP must be exactly 6 digits'),
});
const createOtpValidationSchema = () =>
z.object({
otp: z
.string()
.trim()
.length(6, t`OTP must be exactly 6 digits`),
});
export type OTPFormValues = z.infer<typeof otpValidationSchema>;
export type OTPFormValues = z.infer<
ReturnType<typeof createOtpValidationSchema>
>;
export const useTwoFactorAuthenticationForm = () => {
const form = useForm<OTPFormValues>({
mode: 'onSubmit',
defaultValues: {
otp: '',
},
resolver: zodResolver(otpValidationSchema),
resolver: zodResolver(createOtpValidationSchema()),
});
return { form };
@@ -260,7 +260,7 @@ export const SettingsBillingSubscriptionInfo = ({
message:
oppositPlan === BillingPlanKey.ENTERPRISE
? t`Subscription has been switched to ${oppositPlan} Plan.`
: `Subscription will be switched to ${oppositPlan} Plan the ${beautifiedRenewDate}.`,
: t`Subscription will be switched to ${oppositPlan} Plan the ${beautifiedRenewDate}.`,
});
} catch {
enqueueErrorSnackBar({
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
type TrialCardProps = {
duration: number;
@@ -24,10 +25,13 @@ const StyledCreditCardRequirementContainer = styled.div`
`;
export const TrialCard = ({ duration, withCreditCard }: TrialCardProps) => {
const { t } = useLingui();
return (
<StyledTrialCardContainer>
<StyledTrialDurationContainer>{`${duration} days trial`}</StyledTrialDurationContainer>
<StyledCreditCardRequirementContainer>{`${withCreditCard ? 'With Credit Card' : 'Without Credit Card'}`}</StyledCreditCardRequirementContainer>
<StyledTrialDurationContainer>{t`${duration} days trial`}</StyledTrialDurationContainer>
<StyledCreditCardRequirementContainer>
{withCreditCard ? t`With Credit Card` : t`Without Credit Card`}
</StyledCreditCardRequirementContainer>
</StyledTrialCardContainer>
);
};
@@ -60,9 +60,13 @@ export const MeteredPriceSelector = ({
const toOption = (meteredBillingPrice: MeteredBillingPrice) => {
const price = formatNumber(meteredBillingPrice.tiers[0].flatAmount / 100);
const credits = formatNumber(meteredBillingPrice.tiers[0].upTo, {
abbreviate: true,
decimals: 2,
});
return {
label: `${formatNumber(meteredBillingPrice.tiers[0].upTo, { abbreviate: true, decimals: 2 })} Credits - $${price}`,
label: t`${credits} Credits - $${price}`,
value: meteredBillingPrice.stripePriceId,
};
};
@@ -1,5 +1,6 @@
import { type CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { Fragment } from 'react/jsx-runtime';
import { isDefined } from 'twenty-shared/utils';
@@ -84,7 +85,7 @@ export const CommandMenuContextChip = ({
{text?.trim?.() ? (
<OverflowingTextWithTooltip text={text} />
) : !forceEmptyText ? (
<StyledEmptyText>Untitled</StyledEmptyText>
<StyledEmptyText>{t`Untitled`}</StyledEmptyText>
) : (
''
)}
@@ -10,6 +10,7 @@ import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelect
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useSetRecoilState } from 'recoil';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
@@ -93,7 +94,7 @@ export const CommandMenuList = ({
) : null,
)}
{noResults && !loading && (
<StyledEmpty>No results found</StyledEmpty>
<StyledEmpty>{t`No results found`}</StyledEmpty>
)}
</SelectableList>
</StyledInnerList>
@@ -97,9 +97,9 @@ export const CommandMenuWorkflowStepInfo = ({
? isTrigger
? (stepDefinition.definition.name ??
(stepDefinition.definition.type === 'MANUAL'
? 'Launch manually'
: 'Trigger'))
: (stepDefinition.definition.name ?? 'Action')
? t`Launch manually`
: t`Trigger`))
: (stepDefinition.definition.name ?? t`Action`)
: '';
const [editedTitle, setEditedTitle] = useState<string | null>(null);
@@ -135,7 +135,7 @@ export const CommandMenuWorkflowStepInfo = ({
actionType: stepDefinition.definition.type,
});
const headerType = isTrigger ? 'Trigger' : 'Action';
const headerType = isTrigger ? t`Trigger` : t`Action`;
const Icon = getIcon(headerIcon ?? 'IconDefault');
@@ -104,7 +104,6 @@ const meta: Meta<typeof CommandMenu> = {
ObjectMetadataItemsDecorator,
SnackBarDecorator,
ComponentWithRouterDecorator,
I18nFrontDecorator,
],
parameters: {
msw: graphqlMocks,
@@ -11,6 +11,7 @@ import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyO
import { emitSidePanelOpenEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelOpenEvent';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
import { IconDotsVertical } from 'twenty-ui/display';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
@@ -50,7 +51,7 @@ export const useCommandMenu = () => {
closeAnyOpenDropdown();
navigateCommandMenu({
page: CommandMenuPages.Root,
pageTitle: 'Command Menu',
pageTitle: t`Command Menu`,
pageIcon: IconDotsVertical,
resetNavigationStack: true,
});
@@ -1,6 +1,7 @@
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid';
import { IconMail } from 'twenty-ui/display';
@@ -47,7 +48,7 @@ export const useOpenEmailThreadInCommandMenu = () => {
navigateCommandMenu({
page: CommandMenuPages.ViewEmailThread,
pageTitle: 'Email Thread',
pageTitle: t`Email Thread`,
pageIcon: IconMail,
pageId: pageComponentInstanceId,
});
@@ -1,6 +1,7 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { viewableRichTextComponentState } from '@/command-menu/pages/rich-text-page/states/viewableRichTextComponentState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
import { useRecoilCallback } from 'recoil';
import { IconPencil } from 'twenty-ui/display';
@@ -25,7 +26,7 @@ export const useRichTextCommandMenu = () => {
openCommandMenu();
navigateCommandMenu({
page: CommandMenuPages.EditRichText,
pageTitle: 'Rich Text',
pageTitle: t`Rich Text`,
pageIcon: IconPencil,
});
},
@@ -135,7 +135,7 @@ export const CommandMenuMessageThreadPage = () => {
<StyledWrapper>
<StyledContainer>
{threadLoading ? (
<EmailLoader loadingText="Loading thread" />
<EmailLoader loadingText={t`Loading thread`} />
) : (
<>
<EmailThreadHeader
@@ -77,7 +77,7 @@ export const CommandMenuPageLayoutIframeSettings = () => {
<StyledContainer>
<FormTextFieldInput
label={t`URL to Embed`}
placeholder="https://example.com/embed"
placeholder={t`https://example.com/embed`}
defaultValue={url}
onChange={handleUrlChange}
error={urlError}
@@ -64,7 +64,7 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => {
const handleNavigateToIframeSettings = () => {
if (!isDefined(pageLayoutEditingWidgetId)) {
const newWidget = createPageLayoutIframeWidget('Untitled iFrame', null);
const newWidget = createPageLayoutIframeWidget(t`Untitled iFrame`, null);
setPageLayoutEditingWidgetId(newWidget.id);
}
@@ -1,6 +1,7 @@
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { ColorSample } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { type ThemeColor } from 'twenty-ui/theme';
@@ -57,7 +58,7 @@ export const ChartColorPaletteOption = ({
}}
>
<MenuItemSelect
text={'Palette'}
text={t`Palette`}
selected={false}
focused={selectedItemId === 'auto' || currentColor === 'auto'}
contextualText={colorSamples}
@@ -24,6 +24,7 @@ import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/uti
import { WorkflowIteratorSubStepSwitcher } from '@/workflow/workflow-steps/workflow-actions/iterator-action/components/WorkflowIteratorSubStepSwitcher';
import styled from '@emotion/styled';
import { isNull } from '@sniptt/guards';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui/display';
@@ -96,18 +97,18 @@ export const CommandMenuWorkflowRunViewStepContent = () => {
const tabs: SingleTabProps<TabId>[] = [
{
id: WorkflowRunTabId.OUTPUT,
title: 'Output',
title: t`Output`,
Icon: IconLogout,
disabled: isOutputTabDisabled,
},
{
id: WorkflowRunTabId.NODE,
title: 'Node',
title: t`Node`,
Icon: IconStepInto,
},
{
id: WorkflowRunTabId.INPUT,
title: 'Input',
title: t`Input`,
Icon: IconLogin2,
disabled: isInputTabDisabled,
},
@@ -15,6 +15,7 @@ import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/Other
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { useIcons } from 'twenty-ui/display';
@@ -76,7 +77,7 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = () => {
return (
<RightDrawerStepListContainer>
<RightDrawerWorkflowSelectStepTitle>
Data
{t`Data`}
</RightDrawerWorkflowSelectStepTitle>
{DATABASE_TRIGGER_TYPES.map((action) => {
const Icon = getIcon(action.icon);
@@ -92,7 +93,7 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = () => {
})}
<RightDrawerWorkflowSelectStepTitle>
Others
{t`Others`}
</RightDrawerWorkflowSelectStepTitle>
{OTHER_TRIGGER_TYPES.map((action) => {
const Icon = getIcon(action.icon);
@@ -2,6 +2,7 @@ import { AppErrorDisplay } from '@/error-handler/components/internal/AppErrorDis
import { type AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
type AppFullScreenErrorFallbackProps = AppErrorDisplayProps;
@@ -18,7 +19,7 @@ const StyledContainer = styled.div`
export const AppFullScreenErrorFallback = ({
error,
resetErrorBoundary,
title = 'Sorry, something went wrong',
title = t`Sorry, something went wrong`,
}: AppFullScreenErrorFallbackProps) => {
return (
<StyledContainer>
@@ -3,13 +3,14 @@ import { type AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplay
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import { t } from '@lingui/core/macro';
type AppPageErrorFallbackProps = AppErrorDisplayProps;
export const AppPageErrorFallback = ({
error,
resetErrorBoundary,
title = 'Sorry, something went wrong',
title = t`Sorry, something went wrong`,
}: AppPageErrorFallbackProps) => {
return (
<PageContainer>
@@ -109,11 +109,11 @@ export const AppRootErrorFallback = ({
<StyledImageContainer>
<StyledBackgroundImage
src="/images/placeholders/background/error_index_bg.png"
alt="Background"
alt={t`Background`}
/>
<StyledInnerImage
src="/images/placeholders/moving-image/error_index.png"
alt="Inner"
alt={t`Error illustration`}
/>
</StyledImageContainer>
<StyledEmptyTextContainer>
@@ -124,7 +124,7 @@ export const AppRootErrorFallback = ({
</StyledEmptyTextContainer>
<StyledButton onClick={resetErrorBoundary}>
<StyledIcon size={16} />
Reload
{t`Reload`}
</StyledButton>
</StyledEmptyContainer>
</StyledPanel>
@@ -12,7 +12,7 @@ import {
export const AppErrorDisplay = ({
resetErrorBoundary,
title = 'Sorry, something went wrong',
title = t`Sorry, something went wrong`,
}: AppErrorDisplayProps) => {
return (
<AnimatedPlaceholderEmptyContainer>
@@ -25,6 +25,7 @@ import { currentFavoriteFolderIdState } from '@/ui/navigation/navigation-drawer/
import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { Droppable } from '@hello-pangea/dnd';
import { useLingui } from '@lingui/react/macro';
import { useContext, useState } from 'react';
import { createPortal } from 'react-dom';
import { useLocation } from 'react-router-dom';
@@ -47,6 +48,7 @@ export const CurrentWorkspaceMemberFavorites = ({
folder,
isGroup,
}: CurrentWorkspaceMemberFavoritesProps) => {
const { t } = useLingui();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const currentPath = useLocation().pathname;
const currentViewPath = useLocation().pathname + useLocation().search;
@@ -161,6 +163,8 @@ export const CurrentWorkspaceMemberFavorites = ({
modalId,
);
const favoriteCount = folder.favorites.length;
return (
<>
<NavigationDrawerItemsCollapsableContainer
@@ -252,10 +256,18 @@ export const CurrentWorkspaceMemberFavorites = ({
createPortal(
<ConfirmationModal
modalId={modalId}
title={`Remove ${folder.favorites.length} ${folder.favorites.length > 1 ? 'favorites' : 'favorite'}?`}
subtitle={`This action will delete this favorite folder ${folder.favorites.length > 1 ? `and all ${folder.favorites.length} favorites` : 'and the favorite'} inside. Do you want to continue?`}
title={
folder.favorites.length > 1
? t`Remove ${favoriteCount} favorites?`
: t`Remove ${favoriteCount} favorite?`
}
subtitle={
folder.favorites.length > 1
? t`This action will delete this favorite folder and all ${favoriteCount} favorites inside. Do you want to continue?`
: t`This action will delete this favorite folder and the favorite inside. Do you want to continue?`
}
onConfirmClick={handleConfirmDelete}
confirmButtonText="Delete Folder"
confirmButtonText={t`Delete Folder`}
/>,
document.body,
)}
@@ -2,6 +2,7 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useLingui } from '@lingui/react/macro';
import { IconDotsVertical, IconPencil, IconTrash } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
@@ -19,6 +20,7 @@ export const FavoriteFolderNavigationDrawerItemDropdown = ({
onDelete,
closeDropdown,
}: FavoriteFolderNavigationDrawerItemDropdownProps) => {
const { t } = useLingui();
const handleRename = () => {
closeDropdown();
onRename();
@@ -44,13 +46,13 @@ export const FavoriteFolderNavigationDrawerItemDropdown = ({
LeftIcon={IconPencil}
onClick={handleRename}
accent="default"
text="Rename"
text={t`Rename`}
/>
<MenuItem
LeftIcon={IconTrash}
onClick={handleDelete}
accent="danger"
text="Delete"
text={t`Delete`}
/>
</DropdownMenuItemsContainer>
</DropdownContent>
@@ -4,6 +4,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { IconPlus } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
@@ -13,6 +14,7 @@ export const FavoriteFolderPickerFooter = ({
}: {
dropdownId: string;
}) => {
const { t } = useLingui();
const [, setIsFavoriteFolderCreating] = useRecoilState(
isFavoriteFolderCreatingState,
);
@@ -33,7 +35,7 @@ export const FavoriteFolderPickerFooter = ({
setIsFavoriteFolderCreating(true);
closeDropdown(dropdownId);
}}
text="Add folder"
text={t`Add folder`}
LeftIcon={() => <IconPlus size={theme.icon.size.md} />}
/>
</DropdownMenuItemsContainer>
@@ -4,6 +4,7 @@ import { type FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { MenuItem, MenuItemMultiSelect } from 'twenty-ui/navigation';
const StyledItemsContainer = styled.div`
@@ -26,6 +27,7 @@ export const FavoriteFolderPickerList = ({
folders,
toggleFolderSelection,
}: FavoriteFolderPickerListProps) => {
const { t } = useLingui();
const [favoriteFoldersSearchFilter] = useRecoilComponentState(
favoriteFolderSearchFilterComponentState,
);
@@ -51,7 +53,7 @@ export const FavoriteFolderPickerList = ({
key={`menu-${NO_FOLDER_ID}`}
onSelectChange={() => toggleFolderSelection(NO_FOLDER_ID)}
selected={favoriteFolderPickerChecked.includes(NO_FOLDER_ID)}
text="No folder"
text={t`No folder`}
className="no-folder-menu-item-multi-select"
/>
)}
@@ -68,7 +70,7 @@ export const FavoriteFolderPickerList = ({
className="folder-menu-item-multi-select"
/>
))
: !showNoFolderOption && <MenuItem text="No folders found" />}
: !showNoFolderOption && <MenuItem text={t`No folders found`} />}
</StyledItemsContainer>
);
};
@@ -2,6 +2,7 @@ import { InformationBannerComponentInstanceContext } from '@/information-banner/
import { informationBannerIsOpenComponentState } from '@/information-banner/states/informationBannerIsOpenComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import {
Banner,
type BannerVariant,
@@ -83,7 +84,7 @@ export const InformationBanner = ({
size="small"
variant="tertiary"
onClick={onClose}
ariaLabel="Close banner"
ariaLabel={t`Close banner`}
/>
)}
</Banner>
@@ -1,6 +1,7 @@
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { IconRefresh } from 'twenty-ui/display';
const StyledInformationBannerDeletedRecord = styled.div`
@@ -28,8 +29,8 @@ export const InformationBannerDeletedRecord = ({
<InformationBanner
componentInstanceId="information-banner-deleted-record"
variant="danger"
message={`This record has been deleted`}
buttonTitle="Restore"
message={t`This record has been deleted`}
buttonTitle={t`Restore`}
buttonIcon={IconRefresh}
buttonOnClick={() => restoreManyRecords({ idsToRestore: [recordId] })}
/>
@@ -3,6 +3,7 @@ import { useAccountToReconnect } from '@/information-banner/hooks/useAccountToRe
import { useDismissReconnectAccountBanner } from '@/information-banner/hooks/useDismissReconnectAccountBanner';
import { InformationBannerKeys } from '@/information-banner/types/InformationBannerKeys';
import { useTriggerProviderReconnect } from '@/settings/accounts/hooks/useTriggerProviderReconnect';
import { t } from '@lingui/core/macro';
import { IconRefresh } from 'twenty-ui/display';
const COMPONENT_INSTANCE_ID =
@@ -26,11 +27,13 @@ export const InformationBannerReconnectAccountEmailAliases = () => {
await dismissReconnectAccountBanner(accountToReconnect.id);
};
const mailboxHandle = accountToReconnect.handle;
return (
<InformationBanner
componentInstanceId={COMPONENT_INSTANCE_ID}
message={`Please reconnect your mailbox ${accountToReconnect.handle} to update your email aliases:`}
buttonTitle="Reconnect"
message={t`Please reconnect your mailbox ${mailboxHandle} to update your email aliases:`}
buttonTitle={t`Reconnect`}
buttonIcon={IconRefresh}
buttonOnClick={() =>
triggerProviderReconnect(
@@ -3,6 +3,7 @@ import { useAccountToReconnect } from '@/information-banner/hooks/useAccountToRe
import { useDismissReconnectAccountBanner } from '@/information-banner/hooks/useDismissReconnectAccountBanner';
import { InformationBannerKeys } from '@/information-banner/types/InformationBannerKeys';
import { useTriggerProviderReconnect } from '@/settings/accounts/hooks/useTriggerProviderReconnect';
import { t } from '@lingui/core/macro';
import { IconRefresh } from 'twenty-ui/display';
const COMPONENT_INSTANCE_ID =
@@ -26,12 +27,13 @@ export const InformationBannerReconnectAccountInsufficientPermissions = () => {
await dismissReconnectAccountBanner(accountToReconnect.id);
};
const mailboxHandle = accountToReconnect.handle;
return (
<InformationBanner
componentInstanceId={COMPONENT_INSTANCE_ID}
message={`Sync lost with mailbox ${accountToReconnect.handle}. Please
reconnect for updates:`}
buttonTitle="Reconnect"
message={t`Sync lost with mailbox ${mailboxHandle}. Please reconnect for updates:`}
buttonTitle={t`Reconnect`}
buttonIcon={IconRefresh}
buttonOnClick={() =>
triggerProviderReconnect(
@@ -3,6 +3,7 @@ import {
StyledDialog,
StyledHeading,
} from './KeyboardShortcutMenuStyles';
import { t } from '@lingui/core/macro';
import { IconButton } from 'twenty-ui/input';
import { IconX } from 'twenty-ui/display';
@@ -18,7 +19,7 @@ export const KeyboardMenuDialog = ({
return (
<StyledDialog>
<StyledHeading>
Keyboard shortcuts
{t`Keyboard shortcuts`}
<IconButton variant="tertiary" Icon={IconX} onClick={onClose} />
</StyledHeading>
<StyledContainer>{children}</StyledContainer>
@@ -9,6 +9,7 @@ import {
} from '../hooks/useKeyboardShortcutMenu';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { t } from '@lingui/core/macro';
import { KeyboardMenuDialog } from './KeyboardShortcutMenuDialog';
import { KeyboardMenuGroup } from './KeyboardShortcutMenuGroup';
import { KeyboardMenuItem } from './KeyboardShortcutMenuItem';
@@ -29,12 +30,12 @@ export const KeyboardShortcutMenuOpenContent = () => {
return (
<>
<KeyboardMenuDialog onClose={toggleKeyboardShortcutMenu}>
<KeyboardMenuGroup heading="Table">
<KeyboardMenuGroup heading={t`Table`}>
{KEYBOARD_SHORTCUTS_TABLE.map((TableShortcut, index) => (
<KeyboardMenuItem shortcut={TableShortcut} key={index} />
))}
</KeyboardMenuGroup>
<KeyboardMenuGroup heading="General">
<KeyboardMenuGroup heading={t`General`}>
{KEYBOARD_SHORTCUTS_GENERAL.map((GeneralShortcut) => (
<KeyboardMenuItem shortcut={GeneralShortcut} />
))}
@@ -6,12 +6,14 @@ import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
import { useEffect } from 'react';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { KeyboardShortcutMenu } from '../KeyboardShortcutMenu';
const meta: Meta<typeof KeyboardShortcutMenu> = {
title: 'Modules/KeyboardShortcutMenu/KeyboardShortcutMenu',
component: KeyboardShortcutMenu,
decorators: [
I18nFrontDecorator,
(Story) => {
const { openKeyboardShortcutMenu } = useKeyboardShortcutMenu();
useEffect(() => {
@@ -7,6 +7,7 @@ import { Select } from '@/ui/input/components/Select';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { capitalize } from 'twenty-shared/utils';
@@ -38,7 +39,7 @@ export const AdvancedFilterCommandMenuLogicalOperatorCell = ({
return (
<StyledContainer>
{index === 0 ? (
<StyledText>Where</StyledText>
<StyledText>{t`Where`}</StyledText>
) : index === 1 ? (
readonly ? (
<Select
@@ -5,6 +5,7 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -41,7 +42,7 @@ export const AdvancedFilterCommandMenuRecordFilterOperandSelect = ({
selectedOption={{
label: filter?.operand
? getOperandLabel(filter.operand)
: 'Select operand',
: t`Select operand`,
value: null,
}}
isDisabled
@@ -17,6 +17,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useContext } from 'react';
import { RecordFilterGroupLogicalOperator } from 'twenty-shared/types';
import { t } from '@lingui/core/macro';
import { getFilterTypeFromFieldType, isDefined } from 'twenty-shared/utils';
import { IconLibraryPlus, IconPlus } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
@@ -149,8 +150,8 @@ export const AdvancedFilterAddFilterRuleSelect = ({
<ActionButton
action={{
Icon: IconPlus,
label: 'Add rule',
shortLabel: 'Add rule',
label: t`Add rule`,
shortLabel: t`Add rule`,
key: 'add-rule',
}}
onClick={handleAddFilter}
@@ -165,8 +166,8 @@ export const AdvancedFilterAddFilterRuleSelect = ({
<ActionButton
action={{
Icon: IconPlus,
label: 'Add filter rule',
shortLabel: 'Add filter rule',
label: t`Add filter rule`,
shortLabel: t`Add filter rule`,
key: 'add-filter-rule',
}}
/>
@@ -176,13 +177,13 @@ export const AdvancedFilterAddFilterRuleSelect = ({
<DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconPlus}
text="Add rule"
text={t`Add rule`}
onClick={handleAddFilter}
/>
{isFilterRuleGroupOptionVisible && (
<MenuItem
LeftIcon={IconLibraryPlus}
text="Add rule group"
text={t`Add rule group`}
onClick={handleAddFilterGroup}
/>
)}
@@ -4,6 +4,7 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { TextInput } from '@/ui/input/components/TextInput';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
export const AdvancedFilterDropdownNumberInput = () => {
const selectedOperandInDropdown = useRecoilComponentValue(
@@ -32,7 +33,7 @@ export const AdvancedFilterDropdownNumberInput = () => {
<TextInput
value={objectFilterDropdownFilterValue}
onChange={handleChange}
placeholder="Enter value"
placeholder={t`Enter value`}
fullWidth
type="number"
/>
@@ -1,6 +1,7 @@
import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { TextInput } from '@/ui/input/components/TextInput';
import { t } from '@lingui/core/macro';
type AdvancedFilterDropdownTextInputProps = {
recordFilter: RecordFilter;
@@ -20,7 +21,7 @@ export const AdvancedFilterDropdownTextInput = ({
<TextInput
value={recordFilter.value}
onChange={handleChange}
placeholder="Enter value"
placeholder={t`Enter value`}
fullWidth
/>
);
@@ -2,6 +2,7 @@ import { AdvancedFilterLogicalOperatorDropdown } from '@/object-record/advanced-
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { capitalize } from 'twenty-shared/utils';
const StyledText = styled.div`
@@ -31,7 +32,7 @@ export const AdvancedFilterLogicalOperatorCell = ({
return (
<StyledContainer>
{index === 0 ? (
<StyledText>Where</StyledText>
<StyledText>{t`Where`}</StyledText>
) : index === 1 ? (
<AdvancedFilterLogicalOperatorDropdown
recordFilterGroup={recordFilterGroup}
@@ -7,6 +7,7 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { t } from '@lingui/core/macro';
import { IconDotsVertical, IconTrash } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
@@ -48,7 +49,7 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
dropdownId={dropdownId}
clickableComponent={
<IconButton
aria-label="Filter group rule options"
aria-label={t`Filter group rule options`}
variant="tertiary"
Icon={IconDotsVertical}
/>
@@ -57,7 +58,7 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItem
text="Remove rule group"
text={t`Remove rule group`}
onClick={handleRemove}
LeftIcon={IconTrash}
accent="danger"
@@ -5,6 +5,7 @@ import { getRecordFilterOperands } from '@/object-record/record-filter/utils/get
import { SelectControl } from '@/ui/input/components/SelectControl';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div`
@@ -43,7 +44,7 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
selectedOption={{
label: filter?.operand
? getOperandLabel(filter.operand)
: 'Select operand',
: t`Select operand`,
value: null,
}}
isDisabled
@@ -14,6 +14,7 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { type ViewFilterOperand } from 'twenty-shared/types';
import { MenuItem } from 'twenty-ui/navigation';
@@ -54,7 +55,7 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
selectedOption={{
label: filter?.operand
? getOperandLabel(filter.operand)
: 'Select operand',
: t`Select operand`,
value: null,
}}
/>
@@ -11,6 +11,7 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { IconDotsVertical, IconTrash } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
@@ -68,7 +69,7 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
dropdownId={dropdownId}
clickableComponent={
<IconButton
aria-label="Record filter rule options"
aria-label={t`Record filter rule options`}
variant="tertiary"
Icon={IconDotsVertical}
/>
@@ -77,7 +78,7 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItem
text="Remove rule"
text={t`Remove rule`}
onClick={handleRemove}
LeftIcon={IconTrash}
accent="danger"
@@ -23,6 +23,7 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { t } from '@lingui/core/macro';
import { IconChevronLeft, useIcons } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
@@ -112,6 +113,8 @@ export const AdvancedFilterSubFieldSelectMenu = ({
...subFieldNames.map((subFieldName) => subFieldName),
];
const fieldLabel = fieldMetadataItemUsedInDropdown?.label;
return (
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<DropdownMenuHeader
@@ -146,7 +149,7 @@ export const AdvancedFilterSubFieldSelectMenu = ({
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown.icon)}
text={`Any ${fieldMetadataItemUsedInDropdown.label} field`}
text={t`Any ${fieldLabel} field`}
/>
</SelectableListItem>
)}
@@ -9,6 +9,7 @@ import styled from '@emotion/styled';
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { useGetRecordFilterDisplayValue } from '@/object-record/record-filter/hooks/useGetRecordFilterDisplayValue';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752
@@ -57,7 +58,7 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({
const placeholderText = isDefined(fieldMetadataItem)
? getAdvancedFilterInputPlaceholderText(fieldMetadataItem)
: 'Enter filter';
: t`Enter filter`;
const recordFilterDisplayValue = getRecordFilterDisplayValue(recordFilter);
@@ -6,6 +6,7 @@ import { recordIndexOpenRecordInState } from '@/object-record/record-index/state
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { canOpenObjectInSidePanel } from '@/object-record/utils/canOpenObjectInSidePanel';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { t } from '@lingui/core/macro';
import { type MouseEvent } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -81,6 +82,7 @@ export const RecordChip = ({
return (
<Chip
label={recordChipData.name}
emptyLabel={t`Untitled`}
size={size}
maxWidth={maxWidth}
className={className}
@@ -104,6 +106,7 @@ export const RecordChip = ({
size={size}
maxWidth={maxWidth}
label={recordChipData.name}
emptyLabel={t`Untitled`}
isLabelHidden={isLabelHidden}
leftComponent={
isIconHidden ? null : (
@@ -1,5 +1,6 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue';
import { useObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useObjectFilterDropdownFilterValue';
@@ -45,7 +46,7 @@ export const ObjectFilterDropdownBooleanSelect = () => {
const handleOptionSelect = (newValue: boolean) => {
applyObjectFilterDropdownFilterValue(
newValue.toString(),
newValue ? 'True' : 'False',
newValue ? t`True` : t`False`,
);
closeDropdown();
@@ -8,6 +8,7 @@ import { type SelectableItem } from '@/object-record/select/types/SelectableItem
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
@@ -64,9 +65,10 @@ export const ObjectFilterDropdownSourceSelect = ({
.filter((option) => newSelectedItemIds.includes(option.id))
.map((option) => option.name);
const selectedCount = selectedItemNames.length;
const filterDisplayValue =
selectedItemNames.length > MAX_ITEMS_TO_DISPLAY
? `${selectedItemNames.length} source types`
? t`${selectedCount} source types`
: selectedItemNames.join(', ');
const newFilterValue =
@@ -50,7 +50,7 @@ export const ObjectOptionsDropdownCustomView = ({
? {
...currentView,
key: ViewKey.Custom,
name: currentView.name || 'Custom View',
name: currentView.name || t`Custom View`,
}
: null;
@@ -76,6 +76,8 @@ export const ObjectOptionsDropdownCustomView = ({
viewBarId: recordIndexId,
});
const visibleFieldsCount = visibleBoardFields.length;
const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
const setViewPickerReferenceViewId = useSetRecoilComponentState(
viewPickerReferenceViewIdComponentState,
@@ -211,7 +213,7 @@ export const ObjectOptionsDropdownCustomView = ({
onClick={() => onContentChange('fields')}
LeftIcon={IconListDetails}
text={t`Fields`}
contextualText={`${visibleBoardFields.length} shown`}
contextualText={t`${visibleFieldsCount} shown`}
contextualTextPosition="right"
hasSubMenu
/>
@@ -35,6 +35,8 @@ export const ObjectOptionsDropdownDefaultView = () => {
viewBarId: recordIndexId,
});
const visibleFieldsCount = visibleBoardFields.length;
const selectableItemIdArray = [
'Fields',
'Copy link to view',
@@ -86,7 +88,7 @@ export const ObjectOptionsDropdownDefaultView = () => {
onClick={() => onContentChange('fields')}
LeftIcon={IconListDetails}
text={t`Fields`}
contextualText={`${visibleBoardFields.length} shown`}
contextualText={t`${visibleFieldsCount} shown`}
contextualTextPosition="right"
hasSubMenu
/>
@@ -75,10 +75,10 @@ export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
/>
}
>
Hidden {recordGroupFieldMetadata?.label}
{t`Hidden`} {recordGroupFieldMetadata?.label}
</DropdownMenuHeader>
<RecordGroupsVisibilityDropdownSection
title={`Hidden ${recordGroupFieldMetadata?.label}`}
title={`${t`Hidden`} ${recordGroupFieldMetadata?.label}`}
recordGroupIds={hiddenRecordGroupIds}
onVisibilityChange={handleRecordGroupVisibilityChange}
isDraggable={false}
@@ -14,6 +14,7 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import {
IconChevronLeft,
IconHandMove,
@@ -67,7 +68,7 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
/>
}
>
Sort
{t`Sort`}
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<SelectableList
@@ -106,7 +106,7 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
/>
}
>
Group
{t`Group`}
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<SelectableList
@@ -187,7 +187,7 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
<MenuItemNavigate
onClick={() => onContentChange('hiddenRecordGroups')}
LeftIcon={IconEyeOff}
text={`Hidden ${recordGroupFieldMetadata?.label ?? ''}`}
text={`${t`Hidden`} ${recordGroupFieldMetadata?.label ?? ''}`}
/>
</SelectableListItem>
</SelectableList>
@@ -6,6 +6,7 @@ import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useC
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { IconPlus } from 'twenty-ui/display';
@@ -66,7 +67,7 @@ export const RecordBoardColumnNewRecordButton = () => {
}}
>
<IconPlus size={theme.icon.size.md} />
New
{t`New`}
</StyledNewButton>
);
};
@@ -91,15 +91,15 @@ export const RecordCalendarTopBar = () => {
selectSizeVariant="small"
options={[
{
label: 'Month',
label: t`Month`,
value: ViewCalendarLayout.MONTH,
},
{
label: 'Week',
label: t`Week`,
value: ViewCalendarLayout.WEEK,
},
{
label: 'Timeline',
label: t`Timeline`,
value: ViewCalendarLayout.DAY,
},
]}
@@ -7,6 +7,7 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import {
combineFilters,
@@ -56,7 +57,7 @@ export const useRecordCalendarQueryDateRangeFilter = (selectedDate: Date) => {
value: `${firstDayOfFirstWeek.toISOString()}`,
operand: RecordFilterOperand.IS_AFTER,
type: 'DATE',
label: 'After',
label: t`After`,
displayValue: `${firstDayOfFirstWeek.toISOString()}`,
};
@@ -66,7 +67,7 @@ export const useRecordCalendarQueryDateRangeFilter = (selectedDate: Date) => {
value: `${lastDayOfLastWeek.toISOString()}`,
operand: RecordFilterOperand.IS_BEFORE,
type: 'DATE',
label: 'Before',
label: t`Before`,
displayValue: `${lastDayOfLastWeek.toISOString()}`,
};
@@ -35,6 +35,8 @@ import {
computeMorphRelationFieldName,
CustomError,
} from 'twenty-shared/utils';
import { Trans } from '@lingui/react/macro';
import { t } from '@lingui/core/macro';
import {
IconChevronDown,
IconDotsVertical,
@@ -263,14 +265,14 @@ export const RecordDetailRelationRecordsListItem = ({
<DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconUnlink}
text="Detach"
text={t`Detach`}
onClick={handleDetach}
/>
{!isAccountOwnerRelation &&
relationObjectPermissions.canSoftDeleteObjectRecords && (
<MenuItem
LeftIcon={IconTrash}
text="Delete"
text={t`Delete`}
accent="danger"
onClick={handleDelete}
/>
@@ -295,17 +297,17 @@ export const RecordDetailRelationRecordsListItem = ({
{createPortal(
<ConfirmationModal
modalId={getDeleteRelationModalId(relationRecord.id)}
title={`Delete Related ${relationObjectTypeName}`}
title={t`Delete Related ${relationObjectTypeName}`}
subtitle={
<>
<Trans>
Are you sure you want to delete this related{' '}
{relationObjectMetadataNameSingular}?
<br />
This action will break all its relationships with other objects.
</>
</Trans>
}
onConfirmClick={handleConfirmDelete}
confirmButtonText={`Delete ${relationObjectTypeName}`}
confirmButtonText={t`Delete ${relationObjectTypeName}`}
/>,
document.body,
)}
@@ -23,7 +23,7 @@ export const LightCopyIconButton = ({ copyText }: LightCopyIconButtonProps) => {
onClick={() => {
copyToClipboard(copyText, t`Text copied to clipboard`);
}}
aria-label="Copy to Clipboard"
aria-label={t`Copy to Clipboard`}
/>
</StyledButtonContainer>
);
@@ -6,6 +6,7 @@ import { type VariablePickerComponent } from '@/object-record/record-field/ui/fo
import { type FieldAddressDraftValue } from '@/object-record/record-field/ui/types/FieldInputDraftValue';
import { type FieldAddressValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { t } from '@lingui/core/macro';
type FormAddressFieldInputProps = {
label?: string;
@@ -43,47 +44,47 @@ export const FormAddressFieldInput = ({
{label ? <InputLabel>{label}</InputLabel> : null}
<FormNestedFieldInputContainer>
<FormTextFieldInput
label="Address 1"
label={t`Address 1`}
defaultValue={defaultValue?.addressStreet1 ?? ''}
onChange={handleChange('addressStreet1')}
readonly={readonly}
VariablePicker={VariablePicker}
placeholder="Street address"
placeholder={t`Street address`}
/>
<FormTextFieldInput
label="Address 2"
label={t`Address 2`}
defaultValue={defaultValue?.addressStreet2 ?? ''}
onChange={handleChange('addressStreet2')}
readonly={readonly}
VariablePicker={VariablePicker}
placeholder="Street address 2"
placeholder={t`Street address 2`}
/>
<FormTextFieldInput
label="City"
label={t`City`}
defaultValue={defaultValue?.addressCity ?? ''}
onChange={handleChange('addressCity')}
readonly={readonly}
VariablePicker={VariablePicker}
placeholder="City"
placeholder={t`City`}
/>
<FormTextFieldInput
label="State"
label={t`State`}
defaultValue={defaultValue?.addressState ?? ''}
onChange={handleChange('addressState')}
readonly={readonly}
VariablePicker={VariablePicker}
placeholder="State"
placeholder={t`State`}
/>
<FormTextFieldInput
label="Post Code"
label={t`Post Code`}
defaultValue={defaultValue?.addressPostcode ?? ''}
onChange={handleChange('addressPostcode')}
readonly={readonly}
VariablePicker={VariablePicker}
placeholder="Post Code"
placeholder={t`Post Code`}
/>
<FormCountrySelectInput
label="Country"
label={t`Country`}
selectedCountryName={defaultValue?.addressCountry ?? ''}
onChange={handleChange('addressCountry')}
readonly={readonly}
@@ -3,6 +3,7 @@ import { useMemo } from 'react';
import { FormSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormSelectFieldInput';
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
import { t } from '@lingui/core/macro';
import type { CountryCode } from 'libphonenumber-js';
import { IconCircleOff, type IconComponentProps } from 'twenty-ui/display';
import { type SelectOption } from 'twenty-ui/input';
@@ -35,7 +36,7 @@ export const FormCountryCodeSelectInput = ({
);
return [
{
label: 'No country',
label: t`No country`,
value: '',
Icon: IconCircleOff,
},
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { useMemo } from 'react';
import { FormSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormSelectFieldInput';
@@ -32,7 +33,7 @@ export const FormCountrySelectInput = ({
);
return [
{
label: 'No country',
label: t`No country`,
value: '',
Icon: IconCircleOff,
},
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { FormFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputContainer';
import { FormNestedFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormNestedFieldInputContainer';
import { FormNumberFieldInput } from '@/object-record/record-field/ui/form-types/components/FormNumberFieldInput';
@@ -56,7 +57,7 @@ export const FormCurrencyFieldInput = ({
const currencies = useMemo(() => {
return [
{
label: 'No currency',
label: t`No currency`,
value: '',
Icon: IconCircleOff,
},
@@ -85,7 +86,7 @@ export const FormCurrencyFieldInput = ({
{label ? <InputLabel>{label}</InputLabel> : null}
<FormNestedFieldInputContainer>
<FormSelectFieldInput
label="Currency Code"
label={t`Currency Code`}
defaultValue={defaultValue?.currencyCode ?? ''}
onChange={handleCurrencyCodeChange}
options={currencies}
@@ -93,11 +94,11 @@ export const FormCurrencyFieldInput = ({
readonly={readonly}
/>
<FormNumberFieldInput
label="Amount"
label={t`Amount`}
defaultValue={formatMicrosToDisplayAmount(defaultValue?.amountMicros)}
onChange={handleAmountMicrosChange}
VariablePicker={VariablePicker}
placeholder="Set 3.21 for $3.21"
placeholder={t`Set 3.21 for $3.21`}
readonly={readonly}
/>
</FormNestedFieldInputContainer>
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { FormFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputContainer';
import { FormNestedFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormNestedFieldInputContainer';
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
@@ -41,7 +42,7 @@ export const FormFullNameFieldInput = ({
{label ? <InputLabel>{label}</InputLabel> : null}
<FormNestedFieldInputContainer>
<FormTextFieldInput
label="First Name"
label={t`First Name`}
defaultValue={defaultValue?.firstName}
onChange={handleFirstNameChange}
placeholder={
@@ -51,7 +52,7 @@ export const FormFullNameFieldInput = ({
VariablePicker={VariablePicker}
/>
<FormTextFieldInput
label="Last Name"
label={t`Last Name`}
defaultValue={defaultValue?.lastName}
onChange={handleLastNameChange}
placeholder={

Some files were not shown because too many files have changed in this diff Show More