Translation followup (#9735)

Address PR comments and more progress on translation
This commit is contained in:
Félix Malfait
2025-01-19 13:29:19 +01:00
committed by GitHub
parent 052331685f
commit 056cb7c66d
97 changed files with 3981 additions and 402 deletions

View File

@ -6,6 +6,7 @@ import { Key } from 'ts-key-enum';
import { z } from 'zod';
import { TextInput } from '@/ui/input/components/TextInput';
import { useLingui } from '@lingui/react/macro';
import { Button } from 'twenty-ui';
import { isDomain } from '~/utils/is-domain';
@ -24,28 +25,6 @@ type SettingsAccountsBlocklistInputProps = {
blockedEmailOrDomainList: string[];
};
const validationSchema = (blockedEmailOrDomainList: string[]) =>
z
.object({
emailOrDomain: z
.string()
.trim()
.email('Invalid email or domain')
.or(
z
.string()
.refine(
(value) => value.startsWith('@') && isDomain(value.slice(1)),
'Invalid email or domain',
),
)
.refine(
(value) => !blockedEmailOrDomainList.includes(value),
'Email or domain is already in blocklist',
),
})
.required();
type FormInput = {
emailOrDomain: string;
};
@ -54,6 +33,30 @@ export const SettingsAccountsBlocklistInput = ({
updateBlockedEmailList,
blockedEmailOrDomainList,
}: SettingsAccountsBlocklistInputProps) => {
const { t } = useLingui();
const validationSchema = (blockedEmailOrDomainList: string[]) =>
z
.object({
emailOrDomain: z
.string()
.trim()
.email(t`Invalid email or domain`)
.or(
z
.string()
.refine(
(value) => value.startsWith('@') && isDomain(value.slice(1)),
t`Invalid email or domain`,
),
)
.refine(
(value) => !blockedEmailOrDomainList.includes(value),
t`Email or domain is already in blocklist`,
),
})
.required();
const { reset, handleSubmit, control, formState } = useForm<FormInput>({
mode: 'onSubmit',
resolver: zodResolver(validationSchema(blockedEmailOrDomainList)),
@ -99,7 +102,7 @@ export const SettingsAccountsBlocklistInput = ({
)}
/>
</StyledLinkContainer>
<Button title="Add to blocklist" type="submit" />
<Button title={t`Add to blocklist`} type="submit" />
</StyledContainer>
</form>
);

View File

@ -2,6 +2,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import {
Button,
@ -38,14 +39,16 @@ export const SettingsAccountsListEmptyStateCard = ({
FeatureFlagKey.IsMicrosoftSyncEnabled,
);
const { t } = useLingui();
return (
<Card>
<StyledHeader>{label || 'No connected account'}</StyledHeader>
<StyledHeader>{label || t`No connected account`}</StyledHeader>
<StyledBody>
{currentWorkspace?.isGoogleAuthEnabled && (
<Button
Icon={IconGoogle}
title="Connect with Google"
title={t`Connect with Google`}
variant="secondary"
onClick={() => triggerApisOAuth('google')}
/>
@ -53,7 +56,7 @@ export const SettingsAccountsListEmptyStateCard = ({
{isMicrosoftSyncEnabled && currentWorkspace?.isMicrosoftAuthEnabled && (
<Button
Icon={IconMicrosoft}
title="Connect with Microsoft"
title={t`Connect with Microsoft`}
variant="secondary"
onClick={() => triggerApisOAuth('microsoft')}
/>

View File

@ -3,6 +3,7 @@ import { expect, fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator } from 'twenty-ui';
import { SettingsAccountsBlocklistInput } from '@/settings/accounts/components/SettingsAccountsBlocklistInput';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const updateBlockedEmailListJestFn = fn();
@ -16,7 +17,7 @@ const ClearMocksDecorator: Decorator = (Story, context) => {
const meta: Meta<typeof SettingsAccountsBlocklistInput> = {
title: 'Modules/Settings/Accounts/Blocklist/SettingsAccountsBlocklistInput',
component: SettingsAccountsBlocklistInput,
decorators: [ComponentDecorator, ClearMocksDecorator],
decorators: [ComponentDecorator, ClearMocksDecorator, I18nFrontDecorator],
args: {
updateBlockedEmailList: updateBlockedEmailListJestFn,
blockedEmailOrDomainList: [],

View File

@ -3,11 +3,12 @@ import { ComponentDecorator } from 'twenty-ui';
import { SettingsAccountsBlocklistInput } from '@/settings/accounts/components/SettingsAccountsBlocklistInput';
import { SettingsAccountsBlocklistSection } from '@/settings/accounts/components/SettingsAccountsBlocklistSection';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const meta: Meta<typeof SettingsAccountsBlocklistSection> = {
title: 'Modules/Settings/Accounts/Blocklist/SettingsAccountsBlocklistSection',
component: SettingsAccountsBlocklistInput,
decorators: [ComponentDecorator],
decorators: [ComponentDecorator, I18nFrontDecorator],
};
export default meta;

View File

@ -1,3 +1,4 @@
import { useLingui } from '@lingui/react/macro';
import { LightButton } from 'twenty-ui';
type CancelButtonProps = {
@ -9,9 +10,10 @@ export const CancelButton = ({
onCancel,
disabled = false,
}: CancelButtonProps) => {
const { t } = useLingui();
return (
<LightButton
title="Cancel"
title={t`Cancel`}
accent="tertiary"
onClick={onCancel}
disabled={disabled}

View File

@ -34,6 +34,7 @@ import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/compo
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useLingui } from '@lingui/react/macro';
import { matchPath, resolvePath, useLocation } from 'react-router-dom';
import { FeatureFlagKey } from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
@ -49,6 +50,8 @@ type SettingsNavigationItem = {
export const SettingsNavigationDrawerItems = () => {
const { signOut } = useAuth();
const { t } = useLingui();
const billing = useRecoilValue(billingState);
const isFunctionSettingsEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsFunctionSettingsEnabled,
@ -66,13 +69,13 @@ export const SettingsNavigationDrawerItems = () => {
const accountSubSettings: SettingsNavigationItem[] = [
{
label: 'Emails',
label: t`Emails`,
path: SettingsPath.AccountsEmails,
Icon: IconMail,
indentationLevel: 2,
},
{
label: 'Calendars',
label: t`Calendars`,
path: SettingsPath.AccountsCalendars,
Icon: IconCalendarEvent,
indentationLevel: 2,
@ -95,20 +98,20 @@ export const SettingsNavigationDrawerItems = () => {
return (
<>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label="User" />
<NavigationDrawerSectionTitle label={t`User`} />
<SettingsNavigationDrawerItem
label="Profile"
label={t`Profile`}
path={SettingsPath.ProfilePage}
Icon={IconUserCircle}
/>
<SettingsNavigationDrawerItem
label="Experience"
label={t`Experience`}
path={SettingsPath.Experience}
Icon={IconColorSwatch}
/>
<NavigationDrawerItemGroup>
<SettingsNavigationDrawerItem
label="Accounts"
label={t`Accounts`}
path={SettingsPath.Accounts}
Icon={IconAt}
matchSubPages={false}
@ -130,37 +133,37 @@ export const SettingsNavigationDrawerItems = () => {
</NavigationDrawerItemGroup>
</NavigationDrawerSection>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label="Workspace" />
<NavigationDrawerSectionTitle label={t`Workspace`} />
<SettingsNavigationDrawerItem
label="General"
label={t`General`}
path={SettingsPath.Workspace}
Icon={IconSettings}
/>
<SettingsNavigationDrawerItem
label="Members"
label={t`Members`}
path={SettingsPath.WorkspaceMembersPage}
Icon={IconUsers}
/>
{isBillingPageEnabled && (
<SettingsNavigationDrawerItem
label="Billing"
label={t`Billing`}
path={SettingsPath.Billing}
Icon={IconCurrencyDollar}
/>
)}
<SettingsNavigationDrawerItem
label="Data model"
label={t`Data model`}
path={SettingsPath.Objects}
Icon={IconHierarchy2}
/>
<SettingsNavigationDrawerItem
label="Integrations"
label={t`Integrations`}
path={SettingsPath.Integrations}
Icon={IconApps}
/>
<AdvancedSettingsWrapper navigationDrawerItem={true}>
<SettingsNavigationDrawerItem
label="Security"
label={t`Security`}
path={SettingsPath.Security}
Icon={IconKey}
/>
@ -173,7 +176,7 @@ export const SettingsNavigationDrawerItems = () => {
</AdvancedSettingsWrapper>
<AdvancedSettingsWrapper navigationDrawerItem={true}>
<SettingsNavigationDrawerItem
label="API & Webhooks"
label={t`API & Webhooks`}
path={SettingsPath.Developers}
Icon={IconCode}
/>
@ -181,7 +184,7 @@ export const SettingsNavigationDrawerItems = () => {
{isFunctionSettingsEnabled && (
<AdvancedSettingsWrapper navigationDrawerItem={true}>
<SettingsNavigationDrawerItem
label="Functions"
label={t`Functions`}
path={SettingsPath.ServerlessFunctions}
Icon={IconFunction}
/>
@ -189,21 +192,21 @@ export const SettingsNavigationDrawerItems = () => {
)}
</NavigationDrawerSection>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label="Other" />
<NavigationDrawerSectionTitle label={t`Other`} />
{isAdminPageEnabled && (
<SettingsNavigationDrawerItem
label="Server Admin Panel"
label={t`Server Admin Panel`}
path={SettingsPath.AdminPanel}
Icon={IconServer}
/>
)}
<SettingsNavigationDrawerItem
label="Releases"
label={t`Releases`}
path={SettingsPath.Releases}
Icon={IconRocket}
/>
<NavigationDrawerItem
label="Logout"
label={t`Logout`}
onClick={signOut}
Icon={IconDoorEnter}
/>

View File

@ -1,10 +1,12 @@
import { Meta, StoryObj } from '@storybook/react';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { CancelButton } from '../SaveAndCancelButtons/CancelButton';
const meta: Meta<typeof CancelButton> = {
title: 'Modules/Settings/CancelButton',
component: CancelButton,
decorators: [I18nFrontDecorator],
};
export default meta;

View File

@ -126,9 +126,7 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
</DropdownMenuItemsContainer>
</DropdownMenu>
}
dropdownHotkeyScope={{
scope: dropdownId,
}}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
</StyledContainer>
);

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { ReactNode } from 'react';
import { StyledFormCardTitle } from '@/settings/data-model/fields/components/StyledFormCardTitle';
import { Trans } from '@lingui/react/macro';
import { Card, CardContent } from 'twenty-ui';
type SettingsDataModelPreviewFormCardProps = {
@ -25,7 +26,9 @@ export const SettingsDataModelPreviewFormCard = ({
}: SettingsDataModelPreviewFormCardProps) => (
<Card className={className} fullWidth rounded>
<StyledPreviewContainer divider={!!form}>
<StyledFormCardTitle>Preview</StyledFormCardTitle>
<StyledFormCardTitle>
<Trans>Preview</Trans>
</StyledFormCardTitle>
{preview}
</StyledPreviewContainer>
{!!form && <StyledFormContainer>{form}</StyledFormContainer>}

View File

@ -11,6 +11,7 @@ import { getErrorMessageFromError } from '@/settings/data-model/fields/forms/uti
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextInput } from '@/ui/input/components/TextInput';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import {
AppTooltip,
Card,
@ -91,6 +92,8 @@ export const SettingsDataModelFieldIconLabelForm = ({
const theme = useTheme();
const { t } = useLingui();
const isLabelSyncedWithName =
watch('isLabelSyncedWithName') ??
(isDefined(fieldMetadataItem)
@ -99,8 +102,8 @@ export const SettingsDataModelFieldIconLabelForm = ({
const label = watch('label');
const apiNameTooltipText = isLabelSyncedWithName
? 'Deactivate "Synchronize Objects Labels and API Names" to set a custom API name'
: 'Input must be in camel case and cannot start with a number';
? t`Deactivate "Synchronize Objects Labels and API Names" to set a custom API name`
: t`Input must be in camel case and cannot start with a number`;
const fillNameFromLabel = (label: string) => {
isDefined(label) &&
@ -131,7 +134,7 @@ export const SettingsDataModelFieldIconLabelForm = ({
defaultValue={fieldMetadataItem?.label}
render={({ field: { onChange, value } }) => (
<TextInput
placeholder="Employees"
placeholder={t`Employees`}
value={value}
onChange={(value) => {
onChange(value);
@ -160,8 +163,8 @@ export const SettingsDataModelFieldIconLabelForm = ({
render={({ field: { onChange, value } }) => (
<>
<TextInput
label="API Name"
placeholder="employees"
label={t`API Name`}
placeholder={t`employees`}
value={value}
onChange={onChange}
disabled={
@ -205,8 +208,8 @@ export const SettingsDataModelFieldIconLabelForm = ({
<Card rounded>
<SettingsOptionCardContentToggle
Icon={IconRefresh}
title="Synchronize Field Label and API Name"
description="Should changing a field's label also change the API name?"
title={t`Synchronize Field Label and API Name`}
description={t`Should changing a field's label also change the API name?`}
checked={value ?? true}
disabled={
isDefined(fieldMetadataItem) &&

View File

@ -132,12 +132,8 @@ export const SettingsObjectNewFieldSelector = ({
<UndecoratedLink
to={getSettingsPath(
SettingsPath.ObjectNewFieldConfigure,
{
objectNamePlural,
},
{
fieldType: key,
},
{ objectNamePlural },
{ fieldType: key },
)}
fullWidth
onClick={() => {

View File

@ -5,6 +5,7 @@ import { ComponentDecorator } from 'twenty-ui';
import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { SettingsDataModelFieldIconLabelForm } from '../SettingsDataModelFieldIconLabelForm';
@ -24,6 +25,7 @@ const meta: Meta<typeof SettingsDataModelFieldIconLabelForm> = {
FormProviderDecorator,
IconsProviderDecorator,
ComponentDecorator,
I18nFrontDecorator,
],
};

View File

@ -8,6 +8,7 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { SettingsDataModelFieldSettingsFormCard } from '../SettingsDataModelFieldSettingsFormCard';
@ -33,6 +34,7 @@ const meta: Meta<typeof SettingsDataModelFieldSettingsFormCard> = {
ObjectMetadataItemsDecorator,
SnackBarDecorator,
FormProviderDecorator,
I18nFrontDecorator,
],
args: {
fieldMetadataItem,

View File

@ -4,6 +4,7 @@ import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues';
import { useLingui } from '@lingui/react/macro';
import { IconSlash } from 'twenty-ui';
export const settingsDataModelFieldDateFormSchema = z.object({
@ -27,6 +28,8 @@ export const SettingsDataModelFieldDateForm = ({
disabled,
fieldMetadataItem,
}: SettingsDataModelFieldDateFormProps) => {
const { t } = useLingui();
const { control } = useFormContext<SettingsDataModelFieldDateFormValues>();
const { initialDisplayAsRelativeDateValue } =
@ -42,7 +45,7 @@ export const SettingsDataModelFieldDateForm = ({
render={({ field: { onChange, value } }) => (
<SettingsOptionCardContentToggle
Icon={IconSlash}
title="Display as relative date"
title={t`Display as relative date`}
checked={value ?? false}
disabled={disabled}
onChange={onChange}

View File

@ -117,9 +117,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown
dropdownId={SELECT_COLOR_DROPDOWN_ID}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{
scope: SELECT_COLOR_DROPDOWN_ID,
}}
dropdownHotkeyScope={{ scope: SELECT_COLOR_DROPDOWN_ID }}
clickableComponent={<StyledColorSample colorName={option.color} />}
dropdownComponents={
<DropdownMenuItemsContainer>
@ -160,9 +158,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown
dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
dropdownPlacement="right-start"
dropdownHotkeyScope={{
scope: SELECT_ACTIONS_DROPDOWN_ID,
}}
dropdownHotkeyScope={{ scope: SELECT_ACTIONS_DROPDOWN_ID }}
clickableComponent={
<StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} />
}

View File

@ -80,9 +80,7 @@ export const SettingsObjectFieldActiveActionDropdown = ({
)}
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{
scope: dropdownId,
}}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { Card, FloatingButton, IconEye } from 'twenty-ui';
import { SettingsPath } from '@/types/SettingsPath';
import { useLingui } from '@lingui/react/macro';
import DarkCoverImage from '../../assets/cover-dark.png';
import LightCoverImage from '../../assets/cover-light.png';
@ -25,12 +26,13 @@ const StyledButtonContainer = styled.div`
padding-top: ${({ theme }) => theme.spacing(5)};
`;
export const SettingsObjectCoverImage = () => {
const { t } = useLingui();
return (
<StyledCoverImageContainer>
<StyledButtonContainer>
<FloatingButton
Icon={IconEye}
title="Visualize"
title={t`Visualize`}
size="small"
to={'/settings/' + SettingsPath.ObjectOverview}
/>

View File

@ -65,9 +65,7 @@ export const SettingsObjectInactiveMenuDropDown = ({
)}
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{
scope: dropdownId,
}}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};

View File

@ -11,6 +11,7 @@ import {
SettingsDataModelObjectIdentifiersForm,
SettingsDataModelObjectIdentifiersFormValues,
} from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm';
import { Trans } from '@lingui/react/macro';
import { Card, CardContent } from 'twenty-ui';
type SettingsDataModelObjectSettingsFormCardProps = {
@ -57,7 +58,9 @@ export const SettingsDataModelObjectSettingsFormCard = ({
return (
<Card fullWidth>
<StyledTopCardContent divider>
<SettingsDataModelCardTitle>Preview</SettingsDataModelCardTitle>
<SettingsDataModelCardTitle>
<Trans>Preview</Trans>
</SettingsDataModelCardTitle>
{labelIdentifierFieldMetadataItem ? (
<StyledFieldPreviewCard
objectMetadataItem={objectMetadataItem}

View File

@ -1,9 +1,12 @@
import { useLingui } from '@lingui/react/macro';
import { Button, IconBook2 } from 'twenty-ui';
export const SettingsReadDocumentationButton = () => {
const { t } = useLingui();
return (
<Button
title="Read documentation"
title={t`Read documentation`}
variant="secondary"
accent="default"
size="small"

View File

@ -128,9 +128,7 @@ export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
/>
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{
scope: dropDownId,
}}
dropdownHotkeyScope={{ scope: dropDownId }}
/>
</TableCell>
</StyledTableRow>

View File

@ -5,6 +5,7 @@ import { useDebouncedCallback } from 'use-debounce';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { TextInput } from '@/ui/input/components/TextInput';
import { useLingui } from '@lingui/react/macro';
import isEmpty from 'lodash.isempty';
import { useUpdateWorkspaceMutation } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
@ -28,6 +29,7 @@ export const NameField = ({
autoSave = true,
onNameUpdate,
}: NameFieldProps) => {
const { t } = useLingui();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
@ -86,7 +88,7 @@ export const NameField = ({
return (
<StyledComboInputContainer>
<TextInput
label="Name"
label={t`Name`}
value={displayName}
onChange={setDisplayName}
placeholder="Apple"