Continue Frontend localization (#9909)

Translation more content on the frontend
This commit is contained in:
Félix Malfait
2025-01-29 17:36:28 +01:00
committed by GitHub
parent ce0a6c1b17
commit 9d32e63111
56 changed files with 3187 additions and 1366 deletions

View File

@ -6,6 +6,7 @@ import { EventFieldDiffValueEffect } from '@/activities/timeline-activities/rows
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { Trans } from '@lingui/react/macro';
type EventFieldDiffProps = {
diffRecord: Record<string, any>;
@ -57,7 +58,9 @@ export const EventFieldDiff = ({
<StyledEventFieldDiffContainer>
<EventFieldDiffLabel fieldMetadataItem={fieldMetadataItem} />
{isUpdatedToEmpty ? (
<StyledEmptyValue>Empty</StyledEmptyValue>
<StyledEmptyValue>
<Trans>Empty</Trans>
</StyledEmptyValue>
) : (
<>
<EventFieldDiffValueEffect

View File

@ -11,8 +11,9 @@ import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-sto
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useRecoilState, useRecoilValue } from 'recoil';
import { IconX, LightIconButton, isDefined, useIsMobile } from 'twenty-ui';
import { IconX, isDefined, LightIconButton, useIsMobile } from 'twenty-ui';
const StyledInputContainer = styled.div`
align-items: center;
@ -71,6 +72,8 @@ export const CommandMenuTopBar = () => {
commandMenuSearchState,
);
const { t } = useLingui();
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setCommandMenuSearch(event.target.value);
};
@ -108,7 +111,7 @@ export const CommandMenuTopBar = () => {
<StyledInput
autoFocus
value={commandMenuSearch}
placeholder="Type anything"
placeholder={t`Type anything`}
onChange={handleSearchChange}
/>
)}

View File

@ -11,6 +11,7 @@ import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigat
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { useLingui } from '@lingui/react/macro';
import { useRecoilState, useRecoilValue } from 'recoil';
import { IconFolderPlus, LightIconButton, isDefined } from 'twenty-ui';
@ -24,6 +25,8 @@ export const CurrentWorkspaceMemberFavoritesFolders = () => {
const loading = useIsPrefetchLoading();
const { t } = useLingui();
const {
toggleNavigationSection,
isNavigationSectionOpenState,
@ -52,7 +55,7 @@ export const CurrentWorkspaceMemberFavoritesFolders = () => {
<NavigationDrawerSection>
<NavigationDrawerAnimatedCollapseWrapper>
<NavigationDrawerSectionTitle
label="Favorites"
label={t`Favorites`}
onClick={toggleNavigationSection}
rightIcon={
<LightIconButton

View File

@ -2,11 +2,13 @@ import { useWorkspaceFavorites } from '@/favorites/hooks/useWorkspaceFavorites';
import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems';
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { useLingui } from '@lingui/react/macro';
export const WorkspaceFavorites = () => {
const { workspaceFavoritesObjectMetadataItems } = useWorkspaceFavorites();
const loading = useIsPrefetchLoading();
const { t } = useLingui();
if (loading) {
return <NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader />;
@ -14,7 +16,7 @@ export const WorkspaceFavorites = () => {
return (
<NavigationDrawerSectionForObjectMetadataItems
sectionTitle={'Workspace'}
sectionTitle={t`Workspace`}
objectMetadataItems={workspaceFavoritesObjectMetadataItems}
isRemote={false}
/>

View File

@ -5,8 +5,11 @@ import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { useLingui } from '@lingui/react/macro';
export const NavigationDrawerOpenedSection = () => {
const { t } = useLingui();
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const filteredActiveObjectMetadataItems = activeObjectMetadataItems.filter(
(item) => !item.isRemote,
@ -47,7 +50,7 @@ export const NavigationDrawerOpenedSection = () => {
return (
shouldDisplayObjectInOpenedSection && (
<NavigationDrawerSectionForObjectMetadataItems
sectionTitle={'Opened'}
sectionTitle={t`Opened`}
objectMetadataItems={[objectMetadataItem]}
isRemote={false}
/>

View File

@ -2,6 +2,7 @@ import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdow
import { useResetFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useResetFilterDropdown';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { Trans } from '@lingui/react/macro';
export const MultipleFiltersButton = () => {
const { resetFilterDropdown } = useResetFilterDropdown();
@ -20,7 +21,7 @@ export const MultipleFiltersButton = () => {
onClick={handleClick}
isUnfolded={isDropdownOpen}
>
Filter
<Trans>Filter</Trans>
</StyledHeaderDropdownButton>
);
};

View File

@ -26,6 +26,8 @@ import { FeatureFlagKey } from '~/generated/graphql';
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { useLingui } from '@lingui/react/macro';
export const StyledInput = styled.input`
background: transparent;
border: none;
@ -161,12 +163,14 @@ export const ObjectFilterDropdownFilterSelect = ({
isAdvancedFilterButtonVisible &&
isAdvancedFiltersEnabled;
const { t } = useLingui();
return (
<>
<StyledInput
value={objectFilterDropdownSearchInput}
autoFocus
placeholder="Search fields"
placeholder={t`Search fields`}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setObjectFilterDropdownSearchInput(event.target.value)
}

View File

@ -15,6 +15,7 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object-
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { useLingui } from '@lingui/react/macro';
import { getRecordFilterOperandsForRecordFilterDefinition } from '../../record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition';
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
@ -58,6 +59,7 @@ export const SingleEntityObjectFilterDropdownButton = ({
]);
const theme = useTheme();
const { t } = useLingui();
return (
<Dropdown
@ -76,7 +78,7 @@ export const SingleEntityObjectFilterDropdownButton = ({
}
/>
) : (
'Filter'
t`Filter`
)}
<IconChevronDown size={theme.icon.size.md} />
</StyledHeaderDropdownButton>

View File

@ -19,6 +19,7 @@ import {
getCanvasElementForDropdownTesting,
} from 'twenty-ui';
import { FieldMetadataType } from '~/generated/graphql';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@ -130,6 +131,7 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
SnackBarDecorator,
ComponentDecorator,
IconsProviderDecorator,
I18nFrontDecorator,
],
args: {
hotkeyScope: {

View File

@ -11,6 +11,7 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ViewType } from '@/views/types/ViewType';
import { Trans } from '@lingui/react/macro';
type ObjectOptionsDropdownProps = {
viewType: ViewType;
@ -36,7 +37,7 @@ export const ObjectOptionsDropdown = ({
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
clickableComponent={
<StyledHeaderDropdownButton isUnfolded={isDropdownOpen}>
Options
<Trans>Options</Trans>
</StyledHeaderDropdownButton>
}
onClose={handleResetContent}

View File

@ -25,6 +25,7 @@ import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
import { Trans, useLingui } from '@lingui/react/macro';
import { SORT_DIRECTIONS, SortDirection } from '../types/SortDirection';
export const StyledInput = styled.input`
@ -172,6 +173,8 @@ export const ObjectSortDropdownButton = ({
const { isDropdownOpen } = useDropdown(OBJECT_SORT_DROPDOWN_ID);
const { t } = useLingui();
return (
<Dropdown
dropdownId={OBJECT_SORT_DROPDOWN_ID}
@ -182,7 +185,7 @@ export const ObjectSortDropdownButton = ({
onClick={handleButtonClick}
isUnfolded={isDropdownOpen}
>
Sort
<Trans>Sort</Trans>
</StyledHeaderDropdownButton>
}
dropdownComponents={
@ -194,7 +197,9 @@ export const ObjectSortDropdownButton = ({
<MenuItem
key={index}
onClick={() => handleSortDirectionClick(sortDirection)}
text={sortDirection === 'asc' ? 'Ascending' : 'Descending'}
text={
sortDirection === 'asc' ? t`Ascending` : t`Descending`
}
/>
))}
</DropdownMenuItemsContainer>
@ -206,12 +211,12 @@ export const ObjectSortDropdownButton = ({
setIsSortDirectionMenuUnfolded(!isSortDirectionMenuUnfolded)
}
>
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
{selectedSortDirection === 'asc' ? t`Ascending` : t`Descending`}
</DropdownMenuHeader>
<StyledInput
autoFocus
value={objectSortDropdownSearchInput}
placeholder="Search fields"
placeholder={t`Search fields`}
onChange={(event) =>
setObjectSortDropdownSearchInput(event.target.value)
}

View File

@ -10,9 +10,11 @@ import {
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { t } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
const { t } = useLingui();
const { onContentChange, closeDropdown } =
useDropdown<RecordBoardColumnHeaderAggregateDropdownContextValue>({
context: RecordBoardColumnHeaderAggregateDropdownContext,
@ -33,14 +35,14 @@ export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
onClick={() => {
onContentChange('countAggregateOperationsOptions');
}}
text={'Count'}
text={t`Count`}
hasSubMenu
/>
<MenuItem
onClick={() => {
onContentChange('percentAggregateOperationsOptions');
}}
text={'Percent'}
text={t`Percent`}
hasSubMenu
/>
<MenuItem
@ -54,7 +56,7 @@ export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
onClick={() => {
onContentChange('moreAggregateOperationOptions');
}}
text={'More options'}
text={t`More options`}
hasSubMenu
/>
</DropdownMenuItemsContainer>

View File

@ -166,7 +166,7 @@ describe('computeAggregateValueAndLabel', () => {
expect(result).toEqual({
label: 'Earliest',
labelWithFieldName: 'Earliest date of Created At',
labelWithFieldName: 'Earliest of Created At',
value: '1 Jan, 2023 12:00',
});
});
@ -201,7 +201,7 @@ describe('computeAggregateValueAndLabel', () => {
expect(result).toEqual({
value: '31 Dec, 2023 23:59',
label: 'Latest',
labelWithFieldName: 'Latest date of Updated At',
labelWithFieldName: 'Latest of Updated At',
});
});

View File

@ -8,6 +8,7 @@ import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/Agg
import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions';
import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { t } from '@lingui/core/macro';
import isEmpty from 'lodash.isempty';
import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -47,8 +48,10 @@ export const computeAggregateValueAndLabel = ({
data?.[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]?.[
AGGREGATE_OPERATIONS.count
],
label: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`,
labelWithFieldName: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`,
label: getAggregateOperationLabel(AGGREGATE_OPERATIONS.count),
labelWithFieldName: getAggregateOperationLabel(
AGGREGATE_OPERATIONS.count,
),
};
}
@ -118,15 +121,16 @@ export const computeAggregateValueAndLabel = ({
}
}
}
const label = getAggregateOperationShortLabel(aggregateOperation);
const aggregateLabel = t(getAggregateOperationShortLabel(aggregateOperation));
const fieldLabel = field.label;
const labelWithFieldName =
aggregateOperation === AGGREGATE_OPERATIONS.count
? `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`
: `${getAggregateOperationLabel(aggregateOperation)} of ${field.label}`;
: t`${aggregateLabel} of ${fieldLabel}`;
return {
value,
label,
label: aggregateLabel,
labelWithFieldName,
};
};

View File

@ -1,35 +1,36 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { t } from '@lingui/core/macro';
export const getAggregateOperationLabel = (
operation: ExtendedAggregateOperations,
) => {
switch (operation) {
case AGGREGATE_OPERATIONS.min:
return 'Min';
return t`Min`;
case AGGREGATE_OPERATIONS.max:
return 'Max';
return t`Max`;
case AGGREGATE_OPERATIONS.avg:
return 'Average';
return t`Average`;
case AGGREGATE_OPERATIONS.sum:
return 'Sum';
return t`Sum`;
case AGGREGATE_OPERATIONS.count:
return 'Count all';
return t`Count all`;
case AGGREGATE_OPERATIONS.countEmpty:
return 'Count empty';
return t`Count empty`;
case AGGREGATE_OPERATIONS.countNotEmpty:
return 'Count not empty';
return t`Count not empty`;
case AGGREGATE_OPERATIONS.countUniqueValues:
return 'Count unique values';
return t`Count unique values`;
case AGGREGATE_OPERATIONS.percentageEmpty:
return 'Percent empty';
return t`Percent empty`;
case AGGREGATE_OPERATIONS.percentageNotEmpty:
return 'Percent not empty';
return t`Percent not empty`;
case DATE_AGGREGATE_OPERATIONS.earliest:
return 'Earliest date';
return t`Earliest date`;
case DATE_AGGREGATE_OPERATIONS.latest:
return 'Latest date';
return t`Latest date`;
default:
throw new Error(`Unknown aggregate operation: ${operation}`);
}

View File

@ -1,33 +1,34 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { msg } from '@lingui/core/macro';
export const getAggregateOperationShortLabel = (
operation: ExtendedAggregateOperations,
) => {
switch (operation) {
case AGGREGATE_OPERATIONS.min:
return 'Min';
return msg`Min`;
case AGGREGATE_OPERATIONS.max:
return 'Max';
return msg`Max`;
case AGGREGATE_OPERATIONS.avg:
return 'Average';
return msg`Average`;
case AGGREGATE_OPERATIONS.sum:
return 'Sum';
return msg`Sum`;
case AGGREGATE_OPERATIONS.count:
return 'All';
return msg`All`;
case AGGREGATE_OPERATIONS.countEmpty:
case AGGREGATE_OPERATIONS.percentageEmpty:
return 'Empty';
return msg`Empty`;
case AGGREGATE_OPERATIONS.countNotEmpty:
case AGGREGATE_OPERATIONS.percentageNotEmpty:
return 'Not empty';
return msg`Not empty`;
case AGGREGATE_OPERATIONS.countUniqueValues:
return 'Unique';
return msg`Unique`;
case DATE_AGGREGATE_OPERATIONS.earliest:
return 'Earliest';
return msg`Earliest`;
case DATE_AGGREGATE_OPERATIONS.latest:
return 'Latest';
return msg`Latest`;
default:
throw new Error(`Unknown aggregate operation: ${operation}`);
}

View File

@ -9,6 +9,7 @@ import {
useRecordInlineCellContext,
} from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
import { RecordInlineCellButton } from '@/object-record/record-inline-cell/components/RecordInlineCellEditButton';
import { useLingui } from '@lingui/react/macro';
const StyledRecordInlineCellNormalModeOuterContainer = styled.div<
Pick<
@ -61,6 +62,8 @@ export const RecordInlineCellDisplayMode = ({
}: React.PropsWithChildren<unknown>) => {
const { isFocused } = useFieldFocus();
const { t } = useLingui();
const {
editModeContentOnly,
@ -80,7 +83,7 @@ export const RecordInlineCellDisplayMode = ({
const shouldDisplayEditModeOnFocus = isFocused && isFieldInputOnly;
const emptyPlaceHolder = showLabel ? 'Empty' : label;
const emptyPlaceHolder = showLabel ? t`Empty` : label;
return (
<>

View File

@ -32,6 +32,7 @@ import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { View } from '@/views/types/View';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { useLingui } from '@lingui/react/macro';
import { RelationDefinitionType } from '~/generated-metadata/graphql';
import { getAppPath } from '~/utils/navigation/getAppPath';
type RecordDetailRelationSectionProps = {
@ -45,6 +46,7 @@ const StyledAddDropdown = styled(Dropdown)`
export const RecordDetailRelationSection = ({
loading,
}: RecordDetailRelationSectionProps) => {
const { t } = useLingui();
const { recordId, fieldDefinition } = useContext(FieldContext);
const {
fieldName,
@ -168,6 +170,8 @@ export const RecordDetailRelationSection = ({
if (loading) return null;
const relationRecordsCount = relationRecords.length;
return (
<RecordDetailSection>
<RecordDetailSectionHeader
@ -177,8 +181,8 @@ export const RecordDetailRelationSection = ({
? {
to: filterLinkHref,
label:
relationRecords.length > 0
? `All (${relationRecords.length})`
relationRecordsCount > 0
? t`All (${relationRecordsCount})`
: '',
}
: undefined

View File

@ -9,9 +9,11 @@ import { DATE_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/r
import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
import { STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/standardAggregateOperationOptions';
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
import { t } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
export const RecordTableColumnAggregateFooterDropdownContent = () => {
const { t } = useLingui();
const { currentContentId, fieldMetadataType } = useDropdown({
context: RecordTableColumnAggregateFooterDropdownContext,
});
@ -33,7 +35,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
return (
<RecordTableColumnAggregateFooterDropdownSubmenuContent
aggregateOperations={aggregateOperations}
title="More options"
title={t`More options`}
/>
);
}
@ -47,7 +49,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
return (
<RecordTableColumnAggregateFooterDropdownSubmenuContent
aggregateOperations={aggregateOperations}
title="Count"
title={t`Count`}
/>
);
}
@ -61,7 +63,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
return (
<RecordTableColumnAggregateFooterDropdownSubmenuContent
aggregateOperations={aggregateOperations}
title="Percent"
title={t`Percent`}
/>
);
}

View File

@ -16,6 +16,7 @@ import { onToggleColumnFilterComponentState } from '@/object-record/record-table
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useLingui } from '@lingui/react/macro';
import { useTableColumns } from '../../hooks/useTableColumns';
import { ColumnDefinition } from '../../types/ColumnDefinition';
@ -26,6 +27,8 @@ export type RecordTableColumnHeadDropdownMenuProps = {
export const RecordTableColumnHeadDropdownMenu = ({
column,
}: RecordTableColumnHeadDropdownMenuProps) => {
const { t } = useLingui();
const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector,
);
@ -96,14 +99,14 @@ export const RecordTableColumnHeadDropdownMenu = ({
<MenuItem
LeftIcon={IconFilter}
onClick={handleFilterClick}
text="Filter"
text={t`Filter`}
/>
)}
{isSortable && (
<MenuItem
LeftIcon={IconSortDescending}
onClick={handleSortClick}
text="Sort"
text={t`Sort`}
/>
)}
{showSeparator && <DropdownMenuSeparator />}
@ -111,21 +114,21 @@ export const RecordTableColumnHeadDropdownMenu = ({
<MenuItem
LeftIcon={IconArrowLeft}
onClick={handleColumnMoveLeft}
text="Move left"
text={t`Move left`}
/>
)}
{canMoveRight && (
<MenuItem
LeftIcon={IconArrowRight}
onClick={handleColumnMoveRight}
text="Move right"
text={t`Move right`}
/>
)}
{canHide && (
<MenuItem
LeftIcon={IconEyeOff}
onClick={handleColumnVisibility}
text="Hide"
text={t`Hide`}
/>
)}
</DropdownMenuItemsContainer>

View File

@ -31,13 +31,6 @@ const StyledImage = styled.img<{ isFirstCard: boolean }>`
width: 100%;
`;
const StyledFallbackDiv = styled.div<{ isFirstCard: boolean }>`
background-color: ${({ theme }) => theme.background.tertiary};
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
height: ${({ isFirstCard }) => (isFirstCard ? '240px' : '120px')};
width: 100%;
`;
export const SettingsLabContent = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { labPublicFeatureFlags, handleLabPublicFeatureFlagUpdate } =
@ -57,27 +50,36 @@ export const SettingsLabContent = () => {
return (
currentWorkspace?.id && (
<StyledCardGrid>
{labPublicFeatureFlags.map((flag, index) => (
<Card key={flag.key} rounded>
{flag.metadata.imagePath && !hasImageLoadingError[flag.key] ? (
<StyledImage
src={flag.metadata.imagePath}
alt={flag.metadata.label}
isFirstCard={index === 0}
onError={() => handleImageError(flag.key)}
{[...labPublicFeatureFlags]
.sort((a, b) => {
// Sort flags with images first
if (a.metadata.imagePath !== '' && b.metadata.imagePath === '')
return -1;
if (a.metadata.imagePath === '' && b.metadata.imagePath !== '')
return 1;
return 0;
})
.map((flag, index) => (
<Card key={flag.key} rounded>
{flag.metadata.imagePath && !hasImageLoadingError[flag.key] ? (
<StyledImage
src={flag.metadata.imagePath}
alt={flag.metadata.label}
isFirstCard={index === 0}
onError={() => handleImageError(flag.key)}
/>
) : (
<></>
)}
<SettingsOptionCardContentToggle
title={flag.metadata.label}
description={flag.metadata.description}
checked={flag.value}
onChange={(value) => handleToggle(flag.key, value)}
toggleCentered={false}
/>
) : (
<StyledFallbackDiv isFirstCard={index === 0} />
)}
<SettingsOptionCardContentToggle
title={flag.metadata.label}
description={flag.metadata.description}
checked={flag.value}
onChange={(value) => handleToggle(flag.key, value)}
toggleCentered={false}
/>
</Card>
))}
</Card>
))}
</StyledCardGrid>
)
);

View File

@ -1,6 +1,7 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Trans } from '@lingui/react/macro';
import { ChangeEvent, ReactNode, useRef } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { AppTooltip, Avatar, AvatarType, IconComponent } from 'twenty-ui';
@ -153,7 +154,7 @@ export const ShowPageSummaryCard = ({
<StyledTitle isMobile={isMobile}>{title}</StyledTitle>
{beautifiedCreatedAt && (
<StyledDate isMobile={isMobile} id={dateElementId}>
Added {beautifiedCreatedAt}
<Trans>Added {beautifiedCreatedAt}</Trans>
</StyledDate>
)}
<AppTooltip

View File

@ -1,7 +1,8 @@
import { MessageDescriptor } from '@lingui/core';
import { IconComponent } from 'twenty-ui';
export type TableFieldMetadata<ItemType> = {
fieldLabel: string;
fieldLabel: MessageDescriptor;
fieldName: keyof ItemType;
fieldType: 'string' | 'number';
align: 'left' | 'right';

View File

@ -35,6 +35,7 @@ import { mockedWorkspaceMemberData } from '~/testing/mock-data/users';
import { CurrentWorkspaceMemberFavoritesFolders } from '@/favorites/components/CurrentWorkspaceMemberFavoritesFolders';
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import jsonPage from '../../../../../../../package.json';
import { NavigationDrawer } from '../NavigationDrawer';
@ -50,6 +51,7 @@ const meta: Meta<typeof NavigationDrawer> = {
SnackBarDecorator,
ObjectMetadataItemsDecorator,
PrefetchLoadedDecorator,
I18nFrontDecorator,
(Story) => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,