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

@ -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>