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
@@ -96,7 +96,7 @@ export const SettingsAccountsBlocklistInput = ({
render={({ field: { value, onChange }, fieldState: { error } }) => (
<SettingsTextInput
instanceId="settings-accounts-blocklist-input"
placeholder="eddy@gmail.com, @apple.com"
placeholder={t`eddy@gmail.com, @apple.com`}
value={value}
onChange={onChange}
error={error?.message}
@@ -58,7 +58,7 @@ export const SettingsAccountsCalendarChannelsGeneral = () => {
description: '',
isCanceled: false,
location: '',
title: 'Onboarding call',
title: t`Onboarding call`,
visibility: CalendarChannelVisibility.SHARE_EVERYTHING,
};
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { type MessageFolder } from '@/accounts/types/MessageFolder';
import { SettingsAccountsMessageFolderIcon } from '@/settings/accounts/components/message-folders/SettingsAccountsMessageFolderIcon';
@@ -127,7 +128,7 @@ export const SettingsMessageFoldersTreeItem = ({
<StyledExpandButton
isExpanded={isExpanded}
onClick={handleExpandToggle}
aria-label={isExpanded ? 'Collapse folder' : 'Expand folder'}
aria-label={isExpanded ? t`Collapse folder` : t`Expand folder`}
>
<IconChevronRight size={16} />
</StyledExpandButton>
@@ -98,8 +98,9 @@ export const SettingsAdminWorkspaceContent = ({
);
},
onError: (error) => {
const errorMessage = error.message;
enqueueErrorSnackBar({
message: `Failed to impersonate user. ${error.message}`,
message: t`Failed to impersonate user. ${errorMessage}`,
});
},
}).finally(() => {
@@ -128,8 +129,9 @@ export const SettingsAdminWorkspaceContent = ({
if (isDefined(previousValue)) {
updateFeatureFlagState(workspaceId, featureFlag, previousValue);
}
const errorMessage = error.message;
enqueueErrorSnackBar({
message: `Failed to update feature flag. ${error.message}`,
message: t`Failed to update feature flag. ${errorMessage}`,
});
},
});
@@ -146,6 +148,7 @@ export const SettingsAdminWorkspaceContent = ({
value: (
<Chip
label={activeWorkspace?.name ?? ''}
emptyLabel={t`Untitled`}
leftComponent={
<AvatarChip
avatarUrl={
@@ -5,6 +5,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
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 { t } from '@lingui/core/macro';
import { type ConfigVariableValue } from 'twenty-shared/types';
import { CustomError } from 'twenty-shared/utils';
import { MenuItemMultiSelect } from 'twenty-ui/navigation';
@@ -109,7 +110,7 @@ export const ConfigVariableDatabaseInput = ({
label:
Array.isArray(value) && value.length > 0
? value.join(', ')
: 'Select options',
: t`Select options`,
}}
isDisabled={disabled}
hasRightElement={false}
@@ -152,7 +153,7 @@ export const ConfigVariableDatabaseInput = ({
}
}}
disabled={disabled}
placeholder={placeholder || 'Enter JSON array'}
placeholder={placeholder || t`Enter JSON array`}
/>
)}
</>
@@ -184,7 +185,7 @@ export const ConfigVariableDatabaseInput = ({
}
onChange={(text) => onChange(text)}
disabled={disabled}
placeholder={placeholder || 'Enter value'}
placeholder={placeholder || t`Enter value`}
fullWidth
/>
);
@@ -41,7 +41,7 @@ export const ConfigVariableValueInput = ({
options={variable.options}
disabled={disabled}
placeholder={
disabled ? 'Undefined' : t`Enter a value to store in database`
disabled ? t`Undefined` : t`Enter a value to store in database`
}
/>
) : (
@@ -50,7 +50,7 @@ export const SettingsAdminConfigVariables = () => {
const groupOptions = useMemo(
() => [
{ value: 'all', label: 'All Groups' },
{ value: 'all', label: t`All Groups` },
...allGroups.map((group) => ({
value: group.name,
label: group.name,
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { SettingsAdminConfigVariablesRow } from '@/settings/admin-panel/config-variables/components/SettingsAdminConfigVariablesRow';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
@@ -20,8 +21,8 @@ export const SettingsAdminConfigVariablesTable = ({
return (
<Table>
<TableRow gridAutoColumns="5fr 3fr 1fr">
<TableHeader>Name</TableHeader>
<TableHeader align="right">Value</TableHeader>
<TableHeader>{t`Name`}</TableHeader>
<TableHeader align="right">{t`Value`}</TableHeader>
<TableHeader align="right"></TableHeader>
</TableRow>
<StyledTableBody>
@@ -1,5 +1,6 @@
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
@@ -17,25 +18,27 @@ export const SettingsAdminHealthAccountSyncCountersTable = ({
title: string;
description: string;
}) => {
const { t } = useLingui();
if (!details) {
return null;
}
const items = [
{
label: 'Active Sync',
label: t`Active Sync`,
value: details.counters.ACTIVE,
},
{
label: 'Total Jobs',
label: t`Total Jobs`,
value: details.totalJobs,
},
{
label: 'Failed Jobs',
label: t`Failed Jobs`,
value: details.failedJobs,
},
{
label: 'Failure Rate',
label: t`Failure Rate`,
value: `${details.failureRate}%`,
},
];
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { Status } from 'twenty-ui/display';
import { AdminPanelHealthServiceStatus } from '~/generated-metadata/graphql';
@@ -9,10 +10,10 @@ export const SettingsAdminHealthStatusRightContainer = ({
return (
<>
{status === AdminPanelHealthServiceStatus.OPERATIONAL && (
<Status color="green" text="Operational" weight="medium" />
<Status color="green" text={t`Operational`} weight="medium" />
)}
{status === AdminPanelHealthServiceStatus.OUTAGE && (
<Status color="red" text="Outage" weight="medium" />
<Status color="red" text={t`Outage`} weight="medium" />
)}
</>
);
@@ -38,12 +38,14 @@ export const SettingsAdminJsonDataIndicatorHealthStatus = () => {
const isAnyNode = () => true;
const serviceLabel = indicatorHealth.label;
return (
<Section>
{isDown && (
<StyledErrorMessage>
{indicatorHealth.errorMessage ||
`${indicatorHealth.label} service is unreachable`}
t`${serviceLabel} service is unreachable`}
</StyledErrorMessage>
)}
{parsedDetails && (
@@ -40,7 +40,7 @@ export const SettingsAdminQueueJobRowDropdownMenu = ({
dropdownPlacement="right-start"
clickableComponent={
<LightIconButton
aria-label="Job Actions"
aria-label={t`Job Actions`}
Icon={IconDotsVertical}
accent="tertiary"
/>
@@ -53,8 +53,9 @@ export const SettingsAdminWorkerMetricsGraph = ({
},
fetchPolicy: 'no-cache',
onError: (error) => {
const errorMessage = error.message;
enqueueErrorSnackBar({
message: `Error fetching worker metrics: ${error.message}`,
message: t`Error fetching worker metrics: ${errorMessage}`,
});
},
});
@@ -2,6 +2,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { type ReactNode } from 'react';
import { t } from '@lingui/core/macro';
import { Card, CardContent } from 'twenty-ui/layout';
import { IconChevronRight } from 'twenty-ui/display';
import { Pill } from 'twenty-ui/components';
@@ -97,7 +98,7 @@ export const SettingsCard = ({
<StyledIconContainer>{Icon}</StyledIconContainer>
<StyledTitle disabled={disabled}>
{title}
{soon && <Pill label="Soon" />}
{soon && <Pill label={t`Soon`} />}
</StyledTitle>
{Status && Status}
<StyledIconChevronRight size={theme.icon.size.sm} />
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { Select } from '@/ui/input/components/Select';
import { isDefined } from 'twenty-shared/utils';
import { IconButton, type SelectOption } from 'twenty-ui/input';
@@ -55,7 +56,7 @@ export const SettingsDatabaseEventsForm = ({
const { getIcon } = useIcons();
const objectOptions: SelectOption<string>[] = [
{ label: 'All Objects', value: '*', Icon: IconNorthStar },
{ label: t`All Objects`, value: '*', Icon: IconNorthStar },
...objectMetadataItems.map((item) => ({
label: item.labelPlural,
value: item.nameSingular,
@@ -64,10 +65,10 @@ export const SettingsDatabaseEventsForm = ({
];
const actionOptions: SelectOption<string>[] = [
{ label: 'All', value: '*', Icon: IconNorthStar },
{ label: 'Created', value: 'created', Icon: IconPlus },
{ label: 'Updated', value: 'updated', Icon: IconBox },
{ label: 'Deleted', value: 'deleted', Icon: IconTrash },
{ label: t`All`, value: '*', Icon: IconNorthStar },
{ label: t`Created`, value: 'created', Icon: IconPlus },
{ label: t`Updated`, value: 'updated', Icon: IconBox },
{ label: t`Deleted`, value: 'deleted', Icon: IconTrash },
];
return (
@@ -82,7 +83,7 @@ export const SettingsDatabaseEventsForm = ({
updateOperation?.(index, 'object', newValue)
}
fullWidth
emptyOption={{ label: 'Object', value: null }}
emptyOption={{ label: t`Object`, value: null }}
disabled={disabled}
/>
<Select
@@ -95,7 +95,7 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
return (
<StyledContainer>
New Field <StyledSpan>-</StyledSpan>
{t`New Field`} <StyledSpan>-</StyledSpan>
<Dropdown
dropdownPlacement="bottom-start"
dropdownId={dropdownId}
@@ -9,6 +9,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { t } from '@lingui/core/macro';
import { type MouseEvent, useMemo, useState } from 'react';
import { type IconComponent } from 'twenty-ui/display';
import { type SelectOption } from 'twenty-ui/input';
@@ -72,7 +73,7 @@ export const MultiSelectAddressFields = <Value extends SelectValue>({
selectedOption={{
label:
values?.length === options.length
? 'Default'
? t`Default`
: values?.length.toString(),
value: values?.length,
}}
@@ -49,7 +49,7 @@ export const SettingsDataModelFieldAddressForm = ({
const { control } = useFormContext<SettingsDataModelFieldTextFormValues>();
const countries = [
{
label: 'No country',
label: t`No country`,
value: '',
Icon: IconCircleOff,
},
@@ -140,7 +140,7 @@ export const SettingsObjectNewFieldSelector = ({
[
key,
key === FieldMetadataType.MORPH_RELATION
? { ...config, label: 'Relation' }
? { ...config, label: t`Relation` }
: config,
] as [string, SettingsFieldTypeConfig<any>],
)
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { Separator } from '@/settings/components/Separator';
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
import { SettingsDataModelFieldIsUniqueForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm';
@@ -25,7 +26,7 @@ export const SettingsDataModelFieldNumberSettingsFormCard = ({
<SettingsDataModelFieldPreviewWidget
fieldMetadataItem={{
icon: watch('icon'),
label: watch('label') || 'New Field',
label: watch('label') || t`New Field`,
settings: watch('settings') || null,
type: FieldMetadataType.NUMBER,
}}
@@ -18,10 +18,42 @@ import {
IconX,
} from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { MenuItem, MenuItemSelectColor } from 'twenty-ui/navigation';
import {
type ColorLabels,
MenuItem,
MenuItemSelectColor,
} from 'twenty-ui/navigation';
import { MAIN_COLOR_NAMES } from 'twenty-ui/theme';
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
const useColorLabels = (): ColorLabels => ({
gray: t`Gray`,
tomato: t`Tomato`,
red: t`Red`,
ruby: t`Ruby`,
crimson: t`Crimson`,
pink: t`Pink`,
plum: t`Plum`,
purple: t`Purple`,
violet: t`Violet`,
iris: t`Iris`,
cyan: t`Cyan`,
turquoise: t`Turquoise`,
sky: t`Sky`,
blue: t`Blue`,
jade: t`Jade`,
green: t`Green`,
grass: t`Grass`,
mint: t`Mint`,
lime: t`Lime`,
bronze: t`Bronze`,
gold: t`Gold`,
brown: t`Brown`,
orange: t`Orange`,
amber: t`Amber`,
yellow: t`Yellow`,
});
type SettingsDataModelFieldSelectFormOptionRowProps = {
className?: string;
isDefault?: boolean;
@@ -51,7 +83,7 @@ const StyledColorSample = styled(ColorSample)`
margin-left: ${({ theme }) => theme.spacing(3.5)};
`;
const StyledOptionInput = styled(SettingsTextInput)`
const StyledOptionInput = styled(SettingsTextInput)`Chip
flex-grow: 1;
width: 100%;
& input {
@@ -80,6 +112,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
fieldIsNullable,
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
const theme = useTheme();
const colorLabels = useColorLabels();
const SELECT_COLOR_DROPDOWN_ID = `select-color-dropdown-${option.id}`;
const SELECT_ACTIONS_DROPDOWN_ID = `select-actions-dropdown-${option.id}`;
@@ -130,6 +163,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
}}
color={colorName}
selected={colorName === option.color}
colorLabels={colorLabels}
/>
))}
</DropdownMenuItemsContainer>
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { v4 } from 'uuid';
@@ -7,12 +8,15 @@ import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetad
import { type SettingsDataModelFieldSelectFormValues } from '@/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm';
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
const DEFAULT_OPTION: FieldMetadataItemOption = {
color: 'green',
id: v4(),
label: 'Option 1',
position: 0,
value: computeOptionValueFromLabel('Option 1'),
const getDefaultOption = (): FieldMetadataItemOption => {
const label = t`Option 1`;
return {
color: 'green',
id: v4(),
label,
position: 0,
value: computeOptionValueFromLabel(label),
};
};
type UseSelectSettingsFormInitialValuesProps = {
@@ -33,7 +37,7 @@ export const useSelectSettingsFormInitialValues = ({
? [...fieldMetadataItem.options].sort(
(optionA, optionB) => optionA.position - optionB.position,
)
: [DEFAULT_OPTION],
: [getDefaultOption()],
[fieldMetadataItem?.options],
);
@@ -30,9 +30,9 @@ export const SettingsAvailableStandardObjectsSection = ({
<Table>
<StyledAvailableStandardObjectTableRow>
<TableHeader></TableHeader>
<TableHeader>Name</TableHeader>
<TableHeader>Description</TableHeader>
<TableHeader align="right">Fields</TableHeader>
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Description`}</TableHeader>
<TableHeader align="right">{t`Fields`}</TableHeader>
</StyledAvailableStandardObjectTableRow>
<TableBody>
{objectItems.map((objectItem) => (
@@ -59,7 +59,7 @@ export const SettingsObjectFieldInactiveActionDropdown = ({
dropdownId={dropdownId}
clickableComponent={
<LightIconButton
aria-label="Inactive Field Options"
aria-label={t`Inactive Field Options`}
Icon={IconDotsVertical}
accent="tertiary"
/>
@@ -163,15 +163,15 @@ export const SettingsObjectFieldItemTableRow = ({
const typeLabel =
variant === 'field-type'
? isRemoteObjectField
? 'Remote'
? t`Remote`
: fieldMetadataItem.isCustom
? 'Custom'
: 'Standard'
? t`Custom`
: t`Standard`
: variant === 'identifier'
? isDefined(identifierType)
? identifierType === 'label'
? 'Record text'
: 'Record image'
? t`Record text`
: t`Record image`
: ''
: '';
@@ -181,9 +181,10 @@ export const SettingsObjectFieldItemTableRow = ({
isDefined(relationObjectMetadataItem?.namePlural) &&
!relationObjectMetadataItem.isSystem;
const morphRelationCount = fieldMetadataItem.morphRelations?.length;
const morphRelationLabel =
fieldMetadataItem.type === FieldMetadataType.MORPH_RELATION
? `${fieldMetadataItem.morphRelations?.length} Objects`
? t`${morphRelationCount} Objects`
: undefined;
const label = morphRelationLabel
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@@ -113,7 +114,7 @@ const SettingsDataModelObjectPreviewOtherObjects = ({
/>
</StyledIconContainer>
<StyledOverflowingTextWithTooltip>
<OverflowingTextWithTooltip text={`Other objects`} />
<OverflowingTextWithTooltip text={t`Other objects`} />
</StyledOverflowingTextWithTooltip>
</StyledObjectName>
<StyledNumber>
@@ -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 { t } from '@lingui/core/macro';
import { IconArchiveOff, IconDotsVertical, IconTrash } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
@@ -39,7 +40,7 @@ export const SettingsObjectInactiveMenuDropDown = ({
dropdownId={dropdownId}
clickableComponent={
<LightIconButton
aria-label="Inactive Object Options"
aria-label={t`Inactive Object Options`}
Icon={IconDotsVertical}
accent="tertiary"
/>
@@ -48,13 +49,13 @@ export const SettingsObjectInactiveMenuDropDown = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
<DropdownMenuItemsContainer>
<MenuItem
text="Activate"
text={t`Activate`}
LeftIcon={IconArchiveOff}
onClick={handleActivate}
/>
{isCustomObject && (
<MenuItem
text="Delete"
text={t`Delete`}
LeftIcon={IconTrash}
accent="danger"
onClick={handleDelete}
@@ -2,6 +2,7 @@ import { type Decorator, type Meta, type StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { SettingsObjectInactiveMenuDropDown } from '../SettingsObjectInactiveMenuDropDown';
const handleActivateMockFunction = fn();
@@ -23,7 +24,7 @@ const meta: Meta<typeof SettingsObjectInactiveMenuDropDown> = {
onActivate: handleActivateMockFunction,
onDelete: handleDeleteMockFunction,
},
decorators: [ComponentDecorator, ClearMocksDecorator],
decorators: [I18nFrontDecorator, ComponentDecorator, ClearMocksDecorator],
parameters: {
clearMocks: true,
},
@@ -93,7 +93,7 @@ export const SettingsDataModelObjectIdentifiersForm = ({
const emptyOption: SelectOption<string | null> = {
Icon: IconCircleOff,
label: 'None',
label: t`None`,
value: null,
};
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import { useCallback, useEffect } from 'react';
import { useDebouncedCallback } from 'use-debounce';
@@ -66,7 +67,7 @@ export const ApiKeyNameInput = ({
<StyledComboInputContainer>
<SettingsTextInput
instanceId={nameTextInputId}
placeholder="E.g. backoffice integration"
placeholder={t`E.g. backoffice integration`}
onChange={onNameUpdate}
fullWidth
value={apiKeyName}
@@ -52,7 +52,7 @@ export const SettingsDevelopersWebhookForm = ({
const getTitle = () => {
if (isCreationMode) {
return 'New Webhook';
return t`New Webhook`;
}
const targetUrl = formConfig.watch('targetUrl');
@@ -54,13 +54,13 @@ export const SettingsPublicDomainsListCard = () => {
RowRightComponent={({ item: publicDomain }) => (
<>
{!publicDomain.isValidated && (
<Status color="orange" text="Pending" />
<Status color="orange" text={t`Pending`} />
)}
<SettingPublicDomainRowDropdownMenu publicDomain={publicDomain} />
</>
)}
hasFooter
footerButtonLabel="Add Public Domain"
footerButtonLabel={t`Add Public Domain`}
onFooterButtonClick={() => {
setSelectedPublicDomain(undefined);
navigate(SettingsPath.PublicDomain);
@@ -86,9 +86,9 @@ export const SettingsIntegrationComponent = ({
{integration.text}
</StyledSection>
{integration.type === 'Soon' ? (
<StyledSoonPill label="Soon" />
<StyledSoonPill label={t`Soon`} />
) : integration.type === 'Active' ? (
<Status color="green" text="Active" />
<Status color="green" text={t`Active`} />
) : integration.type === 'Add' ? (
<Button
to={integration.link}
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import PreviewBackgroundImage from '../assets/preview-background.svg';
@@ -69,7 +70,7 @@ export const SettingsIntegrationPreview = ({
<StyledSyncImage />
<StyledTwentyLogo alt="" src="/images/integrations/twenty-logo.svg" />
</StyledLogosContainer>
<StyledLabel>Import your tables as remote objects</StyledLabel>
<StyledLabel>{t`Import your tables as remote objects`}</StyledLabel>
</StyledCardContent>
</StyledCard>
);
@@ -1,5 +1,6 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -43,7 +44,7 @@ export const useLabPublicFeatureFlags = () => {
value: boolean,
) => {
if (!isDefined(currentWorkspace)) {
setError('No workspace selected');
setError(t`No workspace selected`);
return false;
}
@@ -23,7 +23,7 @@ export const MemberNameFields = ({
instanceId={firstNameInstanceId}
label={t`First Name`}
value={firstName}
placeholder="Tim"
placeholder={t`Tim`}
onChange={(value) => {
onChange('firstName', value);
}}
@@ -33,7 +33,7 @@ export const MemberNameFields = ({
instanceId={lastNameInstanceId}
label={t`Last name`}
value={lastName}
placeholder="Cook"
placeholder={t`Cook`}
onChange={(value) => {
onChange('lastName', value);
}}
@@ -119,7 +119,7 @@ export const PlaygroundSetupForm = () => {
<SettingsTextInput
instanceId="playground-api-key"
label={t`API Key`}
placeholder="Enter your API key"
placeholder={t`Enter your API key`}
value={value}
onChange={(newValue) => {
onChange(newValue);
@@ -115,7 +115,7 @@ export const NameFields = ({ autoSave = true }: NameFieldsProps) => {
label={t`First Name`}
value={firstName}
onChange={setFirstName}
placeholder="Tim"
placeholder={t`Tim`}
fullWidth
disabled={!canEditFirstName}
/>
@@ -124,7 +124,7 @@ export const NameFields = ({ autoSave = true }: NameFieldsProps) => {
label={t`Last Name`}
value={lastName}
onChange={setLastName}
placeholder="Cook"
placeholder={t`Cook`}
fullWidth
disabled={!canEditLastName}
/>
@@ -1,5 +1,6 @@
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
@@ -26,7 +27,7 @@ export const useUpdateEmail = () => {
});
enqueueInfoSnackBar({
message: 'Check your inbox to verify your new email address.',
message: t`Check your inbox to verify your new email address.`,
});
} catch (error) {
if (error instanceof ApolloError) {
@@ -3,6 +3,7 @@ import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { UserContext } from '@/users/contexts/UserContext';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
@@ -111,7 +112,7 @@ export const SettingsRoleAssignmentTableRow = ({
dateFormat,
localeCatalog: dateLocale.localeCatalog,
})
: 'Never expires';
: t`Never expires`;
}
};
@@ -133,11 +133,11 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
title={<SettingsRoleLabelContainer roleId={roleId} />}
links={[
{
children: 'Workspace',
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: 'Roles',
children: t`Roles`,
href: getSettingsPath(SettingsPath.Roles),
},
{
@@ -94,7 +94,7 @@ export const SettingsSSOIdentitiesProvidersForm = () => {
value={value}
onChange={onChange}
fullWidth
placeholder="Google OIDC"
placeholder={t`Google OIDC`}
/>
)}
/>
@@ -1,5 +1,6 @@
/* @license Enterprise */
import { t } from '@lingui/core/macro';
import { SettingsListCard } from '@/settings/components/SettingsListCard';
import { SettingsSSOIdentityProviderRowRightContainer } from '@/settings/security/components/SSO/SettingsSSOIdentityProviderRowRightContainer';
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
@@ -26,7 +27,7 @@ export const SettingsSSOIdentitiesProvidersListCardWrapper = () => {
<SettingsSSOIdentityProviderRowRightContainer SSOIdp={SSOIdp} />
)}
hasFooter
footerButtonLabel="Add SSO Identity Provider"
footerButtonLabel={t`Add SSO Identity Provider`}
onFooterButtonClick={() => navigate(SettingsPath.NewSSOIdentityProvider)}
/>
);
@@ -173,7 +173,7 @@ export const SettingsSSOSAMLForm = () => {
<SettingsTextInput
instanceId="sso-saml-acs-url"
disabled={true}
label="ACS Url"
label={t`ACS Url`}
value={acsUrl}
fullWidth
/>
@@ -194,7 +194,7 @@ export const SettingsSSOSAMLForm = () => {
<SettingsTextInput
instanceId="sso-saml-entity-id"
disabled={true}
label="Entity ID"
label={t`Entity ID`}
value={entityID}
fullWidth
/>
@@ -70,7 +70,7 @@ export const SettingsApprovedAccessDomainsListCard = () => {
RowRightComponent={({ item: approvedAccessDomain }) => (
<>
{!approvedAccessDomain.isValidated && (
<Status color="orange" text="Pending" />
<Status color="orange" text={t`Pending`} />
)}
<SettingsSecurityApprovedAccessDomainRowDropdownMenu
approvedAccessDomain={approvedAccessDomain}
@@ -78,7 +78,7 @@ export const SettingsApprovedAccessDomainsListCard = () => {
</>
)}
hasFooter
footerButtonLabel="Add Approved Access Domain"
footerButtonLabel={t`Add Approved Access Domain`}
onFooterButtonClick={() =>
navigate(getSettingsPath(SettingsPath.NewApprovedAccessDomain))
}
@@ -69,15 +69,17 @@ export const SettingsServerlessFunctionCodeEditor = ({
const environmentVariables = {};
if (isDefined(environmentVariables)) {
const envTypeDefinitions = Object.keys(environmentVariables)
// eslint-disable-next-line lingui/no-unlocalized-strings
.map((key) => `${key}: string;`)
.join('\n');
const environmentDefinition = `
declare namespace NodeJS {
interface ProcessEnv {
${Object.keys(environmentVariables)
.map((key) => `${key}: string;`)
.join('\n')}
${envTypeDefinitions}
}
}
declare const process: {
env: NodeJS.ProcessEnv;
};
@@ -33,7 +33,7 @@ export const SettingsServerlessFunctionNewForm = ({
<StyledInputsContainer>
<SettingsTextInput
instanceId={nameTextInputId}
placeholder="Name"
placeholder={t`Name`}
fullWidth
autoFocusOnMount
value={formValues.name}
@@ -42,7 +42,7 @@ export const SettingsServerlessFunctionNewForm = ({
/>
<TextArea
textAreaId={descriptionTextAreaId}
placeholder="Description"
placeholder={t`Description`}
minRows={4}
value={formValues.description}
onChange={onChange('description')}
@@ -5,6 +5,7 @@ import { LinkChip } from 'twenty-ui/components';
import { getSettingsPath } from 'twenty-shared/utils';
import { useParams } from 'react-router-dom';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
export const SettingsServerlessFunctionTabEnvironmentVariablesSection = () => {
const { applicationId = '' } = useParams<{ applicationId: string }>();
@@ -12,22 +13,24 @@ export const SettingsServerlessFunctionTabEnvironmentVariablesSection = () => {
<Section>
<H2Title
title={t`Environment Variables`}
description="Accessible in your function via process.env.KEY"
description={t`Accessible in your function via process.env.KEY`}
/>
Environment variables are defined at application level for all functions.
Please check{' '}
<LinkChip
label={'application detail page'}
to={getSettingsPath(
SettingsPath.ApplicationDetail,
{
applicationId,
},
undefined,
'settings',
)}
/>
.
<Trans>
Environment variables are defined at application level for all
functions. Please check{' '}
<LinkChip
label={t`application detail page`}
to={getSettingsPath(
SettingsPath.ApplicationDetail,
{
applicationId,
},
undefined,
'settings',
)}
/>
.
</Trans>
</Section>
);
};
@@ -35,7 +35,7 @@ export const SettingsServerlessFunctionsTable = ({
<Table>
<StyledTableRow>
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>Runtime</TableHeader>
<TableHeader>{t`Runtime`}</TableHeader>
<TableHeader></TableHeader>
</StyledTableRow>
<StyledTableBody>
@@ -67,7 +67,7 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
options={{
readOnly: true,
readOnlyMessage: {
value: 'Managed serverless functions are not editable',
value: t`Managed serverless functions are not editable`,
},
}}
/>
@@ -53,7 +53,7 @@ export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
onChange={(newKey) =>
setEditedEnvVariable({ ...editedEnvVariable, key: newKey })
}
placeholder="Name"
placeholder={t`Name`}
fullWidth
/>
</TableCell>
@@ -105,7 +105,7 @@ export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
dropdownId={dropDownId}
clickableComponent={
<LightIconButton
aria-label="Env Variable Options"
aria-label={t`Env Variable Options`}
Icon={IconDotsVertical}
accent="tertiary"
/>
@@ -81,7 +81,7 @@ export const SettingsServerlessFunctionTestTab = ({
/>
{serverlessFunctionTestData.output.logs.length > 0 && (
<StyledCodeEditorContainer>
<InputLabel>Logs</InputLabel>
<InputLabel>{t`Logs`}</InputLabel>
<TextArea
textAreaId={testLogsTextAreaId}
value={isTesting ? '' : serverlessFunctionTestData.output.logs}
@@ -98,7 +98,7 @@ export const SettingsServerlessFunctionTriggersTab = ({
<StyledTableCell>{routeTrigger.httpMethod}</StyledTableCell>
<StyledTableCell>
<Tag
text={routeTrigger.isAuthRequired ? 'True' : 'False'}
text={routeTrigger.isAuthRequired ? t`True` : t`False`}
color={routeTrigger.isAuthRequired ? 'green' : 'orange'}
weight="medium"
/>
@@ -6,7 +6,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser';
import { useLingui } from '@lingui/react/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { useParams } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -103,7 +103,7 @@ export const DeleteTwoFactorAuthentication = () => {
title={t`2FA Method Reset`}
subtitle={
isTwoFactorAuthenticationEnforced ? (
<>
<Trans>
This will permanently delete your two factor authentication
method.
<br />
@@ -111,13 +111,13 @@ export const DeleteTwoFactorAuthentication = () => {
after deletion and will be asked to configure it again upon login.{' '}
<br />
Please type in your email to confirm.
</>
</Trans>
) : (
<>
<Trans>
This action cannot be undone. This will permanently reset your two
factor authentication method. <br /> Please type in your email to
confirm.
</>
</Trans>
)
}
onConfirmClick={reset2FA}
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
@@ -94,8 +95,8 @@ export const WorkspaceMemberPictureUploader = ({
setErrorMessage(null);
} catch (error) {
const message =
error instanceof Error ? error.message : 'Failed to upload picture';
setErrorMessage('An error occurred while uploading the picture.');
error instanceof Error ? error.message : t`Failed to upload picture`;
setErrorMessage(t`An error occurred while uploading the picture.`);
enqueueErrorSnackBar({ message });
} finally {
setIsUploading(false);
@@ -130,8 +131,8 @@ export const WorkspaceMemberPictureUploader = ({
setErrorMessage(null);
} catch (error) {
const message =
error instanceof Error ? error.message : 'Failed to remove picture';
setErrorMessage('An error occurred while removing the picture.');
error instanceof Error ? error.message : t`Failed to remove picture`;
setErrorMessage(t`An error occurred while removing the picture.`);
enqueueErrorSnackBar({ message });
} finally {
setIsUploading(false);
@@ -92,7 +92,7 @@ export const NameField = ({
label={t`Name`}
value={displayName}
onChange={setDisplayName}
placeholder="Apple"
placeholder={t`Apple`}
fullWidth
/>
</StyledComboInputContainer>