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,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { WorkflowAiAgentPermissionsObjectRow } from './WorkflowAiAgentPermissionsObjectRow';
import { StyledLabel, StyledList } from './WorkflowAiAgentPermissionsStyles';
@@ -18,7 +19,7 @@ export const WorkflowAiAgentPermissionsObjectsList = ({
}: WorkflowAiAgentPermissionsObjectsListProps) => {
return (
<div>
<StyledLabel>Objects</StyledLabel>
<StyledLabel>{t`Objects`}</StyledLabel>
<StyledList>
{objects.map((objectMetadata) => (
<WorkflowAiAgentPermissionsObjectRow
@@ -85,7 +85,8 @@ export const useWorkflowAiAgentPermissionActions = ({
workflowAiAgentActionAgent.label ??
workflowAiAgentActionAgent.name ??
t`Agent`;
const roleName = `${agentDisplayName} role (${workflowAiAgentActionAgent.id.substring(0, 8)})`;
const agentIdPrefix = workflowAiAgentActionAgent.id.substring(0, 8);
const roleName = t`${agentDisplayName} role (${agentIdPrefix})`;
const generatedRoleId = v4();
await createRole({
@@ -208,7 +208,7 @@ export const WorkflowEditActionServerlessFunction = ({
isLeaf: true,
icon: 'IconVariable',
tab: 'test',
label: 'Generate Function Output',
label: t`Generate Function Output`,
},
_outputSchemaType: 'LINK',
},
@@ -301,10 +301,14 @@ export const WorkflowEditActionServerlessFunction = ({
};
const tabs = [
{ id: WorkflowServerlessFunctionTabId.CODE, title: 'Code', Icon: IconCode },
{
id: WorkflowServerlessFunctionTabId.CODE,
title: t`Code`,
Icon: IconCode,
},
{
id: WorkflowServerlessFunctionTabId.TEST,
title: 'Test',
title: t`Test`,
Icon: IconPlayerPlay,
},
];
@@ -448,7 +452,7 @@ export const WorkflowEditActionServerlessFunction = ({
readonly={actionOptions.readonly}
/>
<StyledCodeEditorContainer>
<InputLabel>Result</InputLabel>
<InputLabel>{t`Result`}</InputLabel>
<ServerlessFunctionExecutionResult
serverlessFunctionTestData={serverlessFunctionTestData}
isTesting={isTesting}
@@ -456,7 +460,7 @@ export const WorkflowEditActionServerlessFunction = ({
</StyledCodeEditorContainer>
{serverlessFunctionTestData.output.logs.length > 0 && (
<StyledCodeEditorContainer>
<InputLabel>Logs</InputLabel>
<InputLabel>{t`Logs`}</InputLabel>
<TextArea
textAreaId={testLogsTextAreaId}
value={
@@ -3,6 +3,7 @@ import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/c
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { type FunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/types/FunctionInput';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import { isObject } from '@sniptt/guards';
@@ -59,7 +60,7 @@ export const WorkflowEditActionServerlessFunctionFields = ({
<FormTextFieldInput
key={pathKey}
label={inputKey}
placeholder="Enter value"
placeholder={t`Enter value`}
defaultValue={inputValue ? `${inputValue}` : ''}
readonly={readonly}
onChange={(value) => onInputChange?.(value, currentPath)}
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
@@ -201,11 +202,11 @@ export const WorkflowEditActionCreateRecord = ({
<WorkflowStepBody>
<Select
dropdownId="workflow-create-record-object-name"
label="Object"
label={t`Object`}
fullWidth
disabled={isFormDisabled}
value={formData.objectName}
emptyOption={{ label: 'Select an option', value: '' }}
emptyOption={{ label: t`Select an option`, value: '' }}
options={availableMetadata}
onChange={(updatedObjectName) => {
const newFormData: CreateRecordFormData = {
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { FormSingleRecordPicker } from '@/object-record/record-field/ui/form-types/components/FormSingleRecordPicker';
import { Select } from '@/ui/input/components/Select';
@@ -118,11 +119,11 @@ export const WorkflowEditActionDeleteRecord = ({
<WorkflowStepBody>
<Select
dropdownId="workflow-edit-action-record-delete-object-name"
label="Object"
label={t`Object`}
fullWidth
disabled={isFormDisabled}
value={formData.objectNameSingular}
emptyOption={{ label: 'Select an option', value: '' }}
emptyOption={{ label: t`Select an option`, value: '' }}
options={availableMetadata}
onChange={(objectNameSingular) => {
const newFormData: DeleteRecordFormData = {
@@ -143,7 +144,7 @@ export const WorkflowEditActionDeleteRecord = ({
{isDefined(objectNameSingular) && (
<FormSingleRecordPicker
label="Record"
label={t`Record`}
onChange={(objectRecordId) =>
handleFieldChange('objectRecordId', objectRecordId)
}
@@ -229,7 +229,10 @@ export const WorkflowEditActionSendEmail = ({
},
});
let emptyOption: SelectOption<string | null> = { label: 'None', value: null };
let emptyOption: SelectOption<string | null> = {
label: t`None`,
value: null,
};
const connectedAccountOptions: SelectOption<string | null>[] = [];
accounts.forEach((account) => {
@@ -263,7 +266,7 @@ export const WorkflowEditActionSendEmail = ({
<WorkflowStepBody>
<Select
dropdownId="select-connected-account-id"
label="Account"
label={t`Account`}
fullWidth
emptyOption={emptyOption}
value={formData.connectedAccountId}
@@ -274,7 +277,7 @@ export const WorkflowEditActionSendEmail = ({
navigate(SettingsPath.NewAccount);
},
Icon: IconPlus,
text: 'Add account',
text: t`Add account`,
}}
onChange={(connectedAccountId) => {
handleFieldChange('connectedAccountId', connectedAccountId);
@@ -284,8 +287,8 @@ export const WorkflowEditActionSendEmail = ({
dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
/>
<FormTextFieldInput
label="Email"
placeholder="Enter receiver email"
label={t`Email`}
placeholder={t`Enter receiver email`}
readonly={actionOptions.readonly}
defaultValue={formData.email}
onChange={(email) => {
@@ -294,8 +297,8 @@ export const WorkflowEditActionSendEmail = ({
VariablePicker={WorkflowVariablePicker}
/>
<FormTextFieldInput
label="Subject"
placeholder="Enter email subject"
label={t`Subject`}
placeholder={t`Enter email subject`}
readonly={actionOptions.readonly}
defaultValue={formData.subject}
onChange={(subject) => {
@@ -304,7 +307,7 @@ export const WorkflowEditActionSendEmail = ({
VariablePicker={WorkflowVariablePicker}
/>
<FormAdvancedTextFieldInput
label="Body"
label={t`Body`}
readonly={actionOptions.readonly}
defaultValue={formData.body}
onChange={(body: string) => {
@@ -331,7 +334,7 @@ export const WorkflowEditActionSendEmail = ({
maxWidth={EMAIL_EDITOR_MAX_WIDTH}
/>
<WorkflowSendEmailAttachments
label="Attachments"
label={t`Attachments`}
files={formData.files}
onChange={(files) => {
handleFieldChange('files', files);
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { FormFieldInput } from '@/object-record/record-field/ui/components/FormFieldInput';
@@ -150,11 +151,11 @@ export const WorkflowEditActionUpdateRecord = ({
<WorkflowStepBody>
<Select
dropdownId="workflow-update-record-object-name"
label="Object"
label={t`Object`}
fullWidth
disabled={isFormDisabled}
value={formData.objectNameSingular}
emptyOption={{ label: 'Select an option', value: '' }}
emptyOption={{ label: t`Select an option`, value: '' }}
options={availableMetadata}
onChange={(updatedObjectName) => {
const newFormData: UpdateRecordFormData = {
@@ -177,7 +178,7 @@ export const WorkflowEditActionUpdateRecord = ({
{isDefined(objectNameSingular) && (
<FormSingleRecordPicker
testId="workflow-update-record-object-record-id"
label="Record"
label={t`Record`}
onChange={(objectRecordId) =>
handleFieldChange('objectRecordId', objectRecordId)
}
@@ -190,8 +191,8 @@ export const WorkflowEditActionUpdateRecord = ({
{isDefined(selectedObjectMetadataItem) && (
<WorkflowFieldsMultiSelect
label="Fields to update"
placeholder="Select fields to update"
label={t`Fields to update`}
placeholder={t`Select fields to update`}
objectMetadataItem={selectedObjectMetadataItem}
handleFieldsChange={(fieldsToUpdate) =>
handleFieldChange('fieldsToUpdate', fieldsToUpdate)
@@ -219,11 +219,11 @@ export const WorkflowEditActionUpsertRecord = ({
<WorkflowStepBody>
<Select
dropdownId="workflow-upsert-record-object-name"
label="Object"
label={t`Object`}
fullWidth
disabled={isFormDisabled}
value={formData.objectName}
emptyOption={{ label: 'Select an option', value: '' }}
emptyOption={{ label: t`Select an option`, value: '' }}
options={availableMetadata}
onChange={(updatedObjectName) => {
const newFormData: UpsertRecordFormData = {
@@ -151,7 +151,7 @@ export const WorkflowEditActionDelay = ({
onChange={handleDateTimeChange}
readonly={actionOptions.readonly}
VariablePicker={WorkflowVariablePicker}
placeholder="Select a date"
placeholder={t`Select a date`}
/>
)}
{action.settings.input.delayType === 'DURATION' && (
@@ -162,7 +162,7 @@ export const WorkflowEditActionDelay = ({
onChange={(value) => handleDurationChange('days', value)}
readonly={actionOptions.readonly}
VariablePicker={WorkflowVariablePicker}
placeholder="0"
placeholder={t`0`}
/>
<FormNumberFieldInput
label={t`Hours`}
@@ -170,7 +170,7 @@ export const WorkflowEditActionDelay = ({
onChange={(value) => handleDurationChange('hours', value)}
readonly={actionOptions.readonly}
VariablePicker={WorkflowVariablePicker}
placeholder="0"
placeholder={t`0`}
/>
<FormNumberFieldInput
label={t`Minutes`}
@@ -178,7 +178,7 @@ export const WorkflowEditActionDelay = ({
onChange={(value) => handleDurationChange('minutes', value)}
readonly={actionOptions.readonly}
VariablePicker={WorkflowVariablePicker}
placeholder="0"
placeholder={t`0`}
/>
<FormNumberFieldInput
label={t`Seconds`}
@@ -186,7 +186,7 @@ export const WorkflowEditActionDelay = ({
onChange={(value) => handleDurationChange('seconds', value)}
readonly={actionOptions.readonly}
VariablePicker={WorkflowVariablePicker}
placeholder="0"
placeholder={t`0`}
/>
</>
)}
@@ -13,6 +13,7 @@ import {
type StepFilterGroup,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { t } from '@lingui/core/macro';
import { IconLibraryPlus, IconPlus } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { v4 } from 'uuid';
@@ -97,8 +98,8 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({
<ActionButton
action={{
Icon: IconPlus,
label: 'Add rule',
shortLabel: 'Add rule',
label: t`Add rule`,
shortLabel: t`Add rule`,
key: 'add-rule',
}}
onClick={handleAddFilter}
@@ -113,8 +114,8 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({
<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',
}}
/>
@@ -124,13 +125,13 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({
<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}
/>
)}
@@ -5,6 +5,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { useRemoveStepFilterGroup } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useRemoveStepFilterGroup';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { useContext } from 'react';
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';
@@ -25,7 +26,7 @@ export const WorkflowStepFilterGroupOptionsDropdown = ({
dropdownId={`step-filter-group-options-${stepFilterGroupId}`}
clickableComponent={
<IconButton
aria-label="Step filter group options"
aria-label={t`Step filter group options`}
variant="tertiary"
Icon={IconDotsVertical}
disabled={readonly}
@@ -36,7 +37,7 @@ export const WorkflowStepFilterGroupOptionsDropdown = ({
<DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconTrash}
text="Delete group"
text={t`Delete group`}
onClick={() => removeStepFilterGroup(stepFilterGroupId)}
accent="danger"
/>
@@ -4,8 +4,9 @@ import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/Gene
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { useLingui } from '@lingui/react/macro';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { useContext, useMemo } from 'react';
import { StepLogicalOperator, type StepFilterGroup } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
@@ -28,25 +29,29 @@ type WorkflowStepFilterLogicalOperatorCellProps = {
stepFilterGroup: StepFilterGroup;
};
const STEP_FILTER_LOGICAL_OPERATOR_OPTIONS = [
{
value: StepLogicalOperator.AND,
label: 'And',
},
{
value: StepLogicalOperator.OR,
label: 'Or',
},
];
export const WorkflowStepFilterLogicalOperatorCell = ({
index,
stepFilterGroup,
}: WorkflowStepFilterLogicalOperatorCellProps) => {
const { readonly } = useContext(WorkflowStepFilterContext);
const { t } = useLingui();
const { upsertStepFilterSettings } = useUpsertStepFilterSettings();
const stepFilterLogicalOperatorOptions = useMemo(
() => [
{
value: StepLogicalOperator.AND,
label: t`And`,
},
{
value: StepLogicalOperator.OR,
label: t`Or`,
},
],
[t],
);
const handleChange = (value: StepLogicalOperator) => {
upsertStepFilterSettings({
stepFilterGroupToUpsert: {
@@ -61,14 +66,14 @@ export const WorkflowStepFilterLogicalOperatorCell = ({
return (
<StyledContainer>
{index === 0 ? (
<StyledText>Where</StyledText>
<StyledText>{t`Where`}</StyledText>
) : index === 1 ? (
readonly ? (
<Select
dropdownWidth={GenericDropdownContentWidth.Narrow}
dropdownId={`advanced-filter-logical-operator-${stepFilterGroup.id}`}
value={stepFilterGroup.logicalOperator}
options={STEP_FILTER_LOGICAL_OPERATOR_OPTIONS}
options={stepFilterLogicalOperatorOptions}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
disabled
/>
@@ -78,7 +83,7 @@ export const WorkflowStepFilterLogicalOperatorCell = ({
dropdownId={`advanced-filter-logical-operator-${stepFilterGroup.id}`}
value={stepFilterGroup.logicalOperator}
onChange={handleChange}
options={STEP_FILTER_LOGICAL_OPERATOR_OPTIONS}
options={stepFilterLogicalOperatorOptions}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
/>
)
@@ -5,6 +5,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { useRemoveStepFilter } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useRemoveStepFilter';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { useContext } from 'react';
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';
@@ -25,7 +26,7 @@ export const WorkflowStepFilterOptionsDropdown = ({
dropdownId={`step-filter-options-${stepFilterId}`}
clickableComponent={
<IconButton
aria-label="Step filter options"
aria-label={t`Step filter options`}
variant="tertiary"
Icon={IconDotsVertical}
disabled={readonly}
@@ -36,7 +37,7 @@ export const WorkflowStepFilterOptionsDropdown = ({
<DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconTrash}
text="Delete"
text={t`Delete`}
onClick={() => removeStepFilter(stepFilterId)}
accent="danger"
/>
@@ -1,5 +1,7 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { msg } from '@lingui/core/macro';
import { i18n } from '@lingui/core';
import { useLingui } from '@lingui/react/macro';
import { isNumber } from '@sniptt/guards';
import { useEffect, useState } from 'react';
@@ -44,7 +46,7 @@ const StyledRecordTypeSelectContainer = styled.div<{ fullWidth?: boolean }>`
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
`;
const DEFAULT_SELECTED_OPTION = { label: 'Select an option', value: '' };
const defaultSelectedOptionMessage = msg`Select an option`;
type WorkflowEditActionFindRecordsProps = {
action: WorkflowFindRecordsAction;
@@ -115,7 +117,7 @@ export const WorkflowEditActionFindRecords = ({
label: selectedObjectMetadataItem?.labelPlural,
value: selectedObjectMetadataItem?.nameSingular,
}
: DEFAULT_SELECTED_OPTION;
: { label: i18n._(defaultSelectedOptionMessage), value: '' };
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
@@ -183,7 +185,7 @@ export const WorkflowEditActionFindRecords = ({
<>
<WorkflowStepBody>
<StyledRecordTypeSelectContainer fullWidth>
<StyledLabel>Object</StyledLabel>
<StyledLabel>{t`Object`}</StyledLabel>
<Dropdown
dropdownId={dropdownId}
dropdownPlacement="bottom-start"
@@ -86,7 +86,7 @@ export const WorkflowEditActionFormFieldSettings = ({
</StyledSettingsHeader>
<StyledSettingsContent>
<FormFieldInputContainer>
<InputLabel>Type</InputLabel>
<InputLabel>{t`Type`}</InputLabel>
<FormSelectFieldInput
options={FORM_SELECT_FIELD_TYPE_OPTIONS}
onChange={(newType: string | null) => {
@@ -3,6 +3,7 @@ import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/c
import { InputLabel } from '@/ui/input/components/InputLabel';
import { type WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { t } from '@lingui/core/macro';
import camelCase from 'lodash.camelcase';
import { FieldMetadataType } from 'twenty-shared/types';
@@ -17,7 +18,7 @@ export const WorkflowFormFieldSettingsDate = ({
}: WorkflowFormFieldSettingsDateProps) => {
return (
<FormFieldInputContainer>
<InputLabel>Label</InputLabel>
<InputLabel>{t`Label`}</InputLabel>
<FormTextFieldInput
onChange={(newLabel: string) => {
onChange({
@@ -3,6 +3,7 @@ import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/c
import { InputLabel } from '@/ui/input/components/InputLabel';
import { type WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import camelCase from 'lodash.camelcase';
import { FieldMetadataType } from 'twenty-shared/types';
@@ -25,7 +26,7 @@ export const WorkflowFormFieldSettingsNumber = ({
return (
<StyledContainer>
<FormFieldInputContainer>
<InputLabel>Label</InputLabel>
<InputLabel>{t`Label`}</InputLabel>
<FormTextFieldInput
onChange={(newLabel: string) => {
onChange({
@@ -41,7 +42,7 @@ export const WorkflowFormFieldSettingsNumber = ({
/>
</FormFieldInputContainer>
<FormFieldInputContainer>
<InputLabel>Placeholder</InputLabel>
<InputLabel>{t`Placeholder`}</InputLabel>
<FormTextFieldInput
onChange={(newPlaceholder: string) => {
onChange({
@@ -7,6 +7,7 @@ import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/Gene
import { type WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import camelCase from 'lodash.camelcase';
import { useIcons } from 'twenty-ui/display';
@@ -46,10 +47,10 @@ export const WorkflowFormFieldSettingsRecordPicker = ({
<FormFieldInputContainer>
<Select
dropdownId="workflow-form-field-settings-record-picker-object-name"
label="Object"
label={t`Object`}
fullWidth
value={field.settings?.objectName}
emptyOption={{ label: 'Select an option', value: '' }}
emptyOption={{ label: t`Select an option`, value: '' }}
options={availableMetadata}
onChange={(updatedObjectName) => {
onChange({
@@ -57,7 +58,7 @@ export const WorkflowFormFieldSettingsRecordPicker = ({
placeholder: `Select a ${
activeNonSystemObjectMetadataItems.find(
(item) => item.nameSingular === updatedObjectName,
)?.labelSingular || 'record'
)?.labelSingular || t`record`
}`,
settings: {
...field.settings,
@@ -71,7 +72,7 @@ export const WorkflowFormFieldSettingsRecordPicker = ({
/>
</FormFieldInputContainer>
<FormFieldInputContainer>
<InputLabel>Label</InputLabel>
<InputLabel>{t`Label`}</InputLabel>
<FormTextFieldInput
onChange={(newLabel: string) => {
onChange({
@@ -5,6 +5,7 @@ import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/c
import { InputLabel } from '@/ui/input/components/InputLabel';
import { type WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import camelCase from 'lodash.camelcase';
import { FieldMetadataType } from 'twenty-shared/types';
@@ -32,7 +33,7 @@ export const WorkflowFormFieldSettingsSelect = ({
}: WorkflowFormFieldSettingsSelectProps) => {
const selectTypeOptions = [
{
label: 'Existing Field',
label: t`Existing Field`,
value: 'EXISTING_FIELD',
},
];
@@ -65,7 +66,7 @@ export const WorkflowFormFieldSettingsSelect = ({
<StyledFormFieldSettingsSelect>
<StyledRowContainer>
<FormFieldInputContainer>
<InputLabel>Label</InputLabel>
<InputLabel>{t`Label`}</InputLabel>
<FormTextFieldInput
onChange={(newLabel: string) => {
onChange({
@@ -81,7 +82,7 @@ export const WorkflowFormFieldSettingsSelect = ({
/>
</FormFieldInputContainer>
<FormFieldInputContainer>
<InputLabel>Select Type</InputLabel>
<InputLabel>{t`Select Type`}</InputLabel>
<FormSelectFieldInput
onChange={(newSelectType: string | null) => {
if (newSelectType === null) {
@@ -102,7 +103,7 @@ export const WorkflowFormFieldSettingsSelect = ({
</FormFieldInputContainer>
</StyledRowContainer>
<FormFieldInputContainer>
<InputLabel>Field</InputLabel>
<InputLabel>{t`Field`}</InputLabel>
<FormSelectFieldInput
onChange={(newSelectFieldId: string | null) => {
if (newSelectFieldId === null) {
@@ -3,6 +3,7 @@ import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/c
import { InputLabel } from '@/ui/input/components/InputLabel';
import { type WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import camelCase from 'lodash.camelcase';
import { FieldMetadataType } from 'twenty-shared/types';
@@ -25,7 +26,7 @@ export const WorkflowFormFieldSettingsText = ({
return (
<StyledContainer>
<FormFieldInputContainer>
<InputLabel>Label</InputLabel>
<InputLabel>{t`Label`}</InputLabel>
<FormTextFieldInput
onChange={(newLabel: string) => {
onChange({
@@ -41,7 +42,7 @@ export const WorkflowFormFieldSettingsText = ({
/>
</FormFieldInputContainer>
<FormFieldInputContainer>
<InputLabel>Placeholder</InputLabel>
<InputLabel>{t`Placeholder`}</InputLabel>
<FormTextFieldInput
onChange={(newPlaceholder: string) => {
onChange({
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { FormFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputContainer';
import { FormRawJsonFieldInput } from '@/object-record/record-field/ui/form-types/components/FormRawJsonFieldInput';
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
@@ -160,14 +161,18 @@ export const BodyInput = ({
return (
<FormFieldInputContainer>
<InputLabel>Body Input</InputLabel>
<InputLabel>{t`Body Input`}</InputLabel>
<StyledSelectDropdown
options={[
{ label: 'Key/Value', value: BODY_TYPES.KEY_VALUE, Icon: IconKey },
{ label: 'Raw JSON', value: BODY_TYPES.RAW_JSON, Icon: IconFileText },
{ label: 'Form Data', value: BODY_TYPES.FORM_DATA, Icon: IconKey },
{ label: 'Text', value: BODY_TYPES.TEXT, Icon: IconFileText },
{ label: 'None', value: BODY_TYPES.NONE, Icon: IconFileText },
{ label: t`Key/Value`, value: BODY_TYPES.KEY_VALUE, Icon: IconKey },
{
label: t`Raw JSON`,
value: BODY_TYPES.RAW_JSON,
Icon: IconFileText,
},
{ label: t`Form Data`, value: BODY_TYPES.FORM_DATA, Icon: IconKey },
{ label: t`Text`, value: BODY_TYPES.TEXT, Icon: IconFileText },
{ label: t`None`, value: BODY_TYPES.NONE, Icon: IconFileText },
]}
dropdownId="body-input-mode"
value={getBodyTypeFromHeaders(headers) || BODY_TYPES.NONE}
@@ -192,8 +197,8 @@ export const BodyInput = ({
defaultValue={defaultValueParsed as Record<string, string>}
onChange={handleKeyValueChange}
readonly={readonly}
keyPlaceholder="Property name"
valuePlaceholder="Property value"
keyPlaceholder={t`Property name`}
valuePlaceholder={t`Property value`}
/>
) : getBodyTypeFromHeaders(headers) === BODY_TYPES.FORM_DATA ? (
<KeyValuePairInput
@@ -201,19 +206,19 @@ export const BodyInput = ({
defaultValue={defaultValueParsed as Record<string, string>}
onChange={handleKeyValueChange}
readonly={readonly}
keyPlaceholder="Property name"
valuePlaceholder="Property value"
keyPlaceholder={t`Property name`}
valuePlaceholder={t`Property value`}
/>
) : getBodyTypeFromHeaders(headers) === BODY_TYPES.TEXT ? (
<FormTextFieldInput
placeholder={'Enter text'}
placeholder={t`Enter text`}
readonly={readonly}
defaultValue={textValue}
onChange={(value: string) => handleChangeTextValue(value)}
VariablePicker={WorkflowVariablePicker}
/>
) : (
<StyledNoBodyMessage>No body</StyledNoBodyMessage>
<StyledNoBodyMessage>{t`No body`}</StyledNoBodyMessage>
)}
</StyledContainer>
</FormFieldInputContainer>
@@ -25,6 +25,10 @@ export const HttpRequestExecutionResult = ({
(httpRequestTestData.output.status !== undefined &&
httpRequestTestData.output.status >= 400);
const headersCount = Object.keys(
httpRequestTestData.output.headers || {},
).length;
const status: ExecutionStatus = {
isSuccess,
isError,
@@ -41,11 +45,10 @@ export const HttpRequestExecutionResult = ({
? ` - ${httpRequestTestData.output.duration}ms`
: ''
}`
: 'Request Failed',
: t`Request Failed`,
additionalInfo:
isSuccess &&
Object.keys(httpRequestTestData.output.headers || {}).length > 0
? `${Object.keys(httpRequestTestData.output.headers || {}).length} headers received`
isSuccess && headersCount > 0
? t`${headersCount} headers received`
: isError
? t`An error occurred`
: undefined,
@@ -58,8 +61,8 @@ export const HttpRequestExecutionResult = ({
height="100%"
status={status}
isTesting={isTesting}
loadingMessage="Sending request..."
idleMessage="Response"
loadingMessage={t`Sending request...`}
idleMessage={t`Response`}
/>
);
};
@@ -4,6 +4,7 @@ import { type WorkflowStep } from '@/workflow/types/Workflow';
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
import { type HttpRequestFormData } from '@/workflow/workflow-steps/workflow-actions/http-request-action/constants/HttpRequest';
import { httpRequestTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/http-request-action/states/httpRequestTestDataFamilyState';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
@@ -66,7 +67,7 @@ export const HttpRequestTestVariableInput = ({
<FormTextFieldInput
key={variablePath}
label={`${variablePath}`}
placeholder="Enter test value"
placeholder={t`Enter test value`}
readonly={readonly}
defaultValue={
httpRequestTestData.variableValues[variablePath] || ''
@@ -5,6 +5,7 @@ import { AUTO_SET_HEADER_KEYS } from '@/workflow/workflow-steps/workflow-actions
import { isAutoSetHeaderKey } from '@/workflow/workflow-steps/workflow-actions/http-request-action/utils/isReadOnlyHeaderKey';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { css } from '@emotion/react';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import { useState } from 'react';
import { IconTrash } from 'twenty-ui/display';
@@ -50,8 +51,8 @@ export const KeyValuePairInput = ({
label,
onChange,
readonly,
keyPlaceholder = 'Key',
valuePlaceholder = 'Value',
keyPlaceholder = t`Key`,
valuePlaceholder = t`Value`,
defaultValue,
}: KeyValuePairInputProps) => {
const [pairs, setPairs] = useState<KeyValuePair[]>(() => {
@@ -72,7 +73,7 @@ export const KeyValuePairInput = ({
const trimmedKey = pair.key.trim();
if (!pair.isAutoSet && isAutoSetHeaderKey(trimmedKey)) {
return 'This key should be set through body input';
return t`This key should be set through body input`;
}
return undefined;
@@ -119,10 +119,10 @@ export const WorkflowEditActionHttpRequest = ({
const tabs = [
{
id: WorkflowHttpRequestTabId.CONFIGURATION,
title: 'Configuration',
title: t`Configuration`,
Icon: IconSettings,
},
{ id: WorkflowHttpRequestTabId.TEST, title: 'Test', Icon: IconPlayerPlay },
{ id: WorkflowHttpRequestTabId.TEST, title: t`Test`, Icon: IconPlayerPlay },
];
useEffect(() => () => saveAction.flush(), [saveAction]);
@@ -138,15 +138,15 @@ export const WorkflowEditActionHttpRequest = ({
{activeTabId === WorkflowHttpRequestTabId.CONFIGURATION && (
<StyledConfigurationTabContent>
<FormTextFieldInput
label="URL"
placeholder="https://api.example.com/endpoint"
label={t`URL`}
placeholder={t`https://api.example.com/endpoint`}
readonly={actionOptions.readonly}
defaultValue={formData.url}
onChange={(value) => handleFieldChange('url', value)}
VariablePicker={WorkflowVariablePicker}
/>
<Select
label="HTTP Method"
label={t`HTTP Method`}
dropdownId="http-method"
options={[...HTTP_METHODS]}
value={formData.method}
@@ -158,12 +158,12 @@ export const WorkflowEditActionHttpRequest = ({
<KeyValuePairInput
key={getBodyTypeFromHeaders(formData.headers) || 'none'}
label="Headers Input"
label={t`Headers Input`}
defaultValue={formData.headers}
onChange={(value) => handleFieldChange('headers', value)}
readonly={actionOptions.readonly}
keyPlaceholder="Header name"
valuePlaceholder="Header value"
keyPlaceholder={t`Header name`}
valuePlaceholder={t`Header value`}
/>
{isMethodWithBody(formData.method) && (
@@ -178,7 +178,7 @@ export const WorkflowEditActionHttpRequest = ({
)}
<StyledFullHeightFormRawJsonFieldInput
label="Expected Response Body"
label={t`Expected Response Body`}
placeholder={JSON_RESPONSE_PLACEHOLDER}
defaultValue={outputSchema}
onChange={handleOutputSchemaChange}
@@ -6,6 +6,7 @@ import {
import { TEST_HTTP_REQUEST } from '@/workflow/workflow-steps/workflow-actions/http-request-action/graphql/mutations/testHttpRequest';
import { httpRequestTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/http-request-action/states/httpRequestTestDataFamilyState';
import { useMutation } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { isObject, isString } from '@sniptt/guards';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
@@ -127,7 +128,7 @@ export const useTestHttpRequest = (actionId: string) => {
const duration = Date.now() - startTime;
const rawErrorMessage =
error instanceof Error ? error.message : 'HTTP request failed';
error instanceof Error ? error.message : t`HTTP request failed`;
const jsonParsedErrorMessage = parseJson(rawErrorMessage);