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

@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { useLingui } from '@lingui/react/macro';
import { MainButton, UndecoratedLink } from 'twenty-ui';
import { useAuthorizeAppMutation } from '~/generated/graphql';
import { useNavigateApp } from '~/hooks/useNavigateApp';
@ -55,6 +56,7 @@ const StyledButtonContainer = styled.div`
width: 100%;
`;
export const Authorize = () => {
const { t } = useLingui();
const navigate = useNavigateApp();
const [searchParam] = useSearchParams();
const { redirect } = useRedirect();
@ -119,9 +121,13 @@ export const Authorize = () => {
<StyledText>{app?.name} wants to access your account</StyledText>
<StyledButtonContainer>
<UndecoratedLink to={AppPath.Index}>
<MainButton title="Cancel" variant="secondary" fullWidth />
<MainButton title={t`Cancel`} variant="secondary" fullWidth />
</UndecoratedLink>
<MainButton title="Authorize" onClick={handleAuthorize} fullWidth />
<MainButton
title={t`Authorize`}
onClick={handleAuthorize}
fullWidth
/>
</StyledButtonContainer>
</StyledCardWrapper>
</StyledContainer>

View File

@ -185,9 +185,7 @@ export const PasswordReset = () => {
<Skeleton
height={SKELETON_LOADER_HEIGHT_SIZES.standard.m}
count={2}
style={{
marginBottom: theme.spacing(2),
}}
style={{ marginBottom: theme.spacing(2) }}
/>
</SkeletonTheme>
) : (

View File

@ -47,9 +47,7 @@ export const RecordShowPage = () => {
return (
<RecordFieldValueSelectorContextProvider>
<ContextStoreComponentInstanceContext.Provider
value={{
instanceId: `record-show-${objectRecordId}`,
}}
value={{ instanceId: `record-show-${objectRecordId}` }}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }}
@ -92,9 +90,7 @@ export const RecordShowPage = () => {
</RecordShowPageHeader>
<PageBody>
<TimelineActivityContext.Provider
value={{
labelIdentifierValue: pageName,
}}
value={{ labelIdentifierValue: pageName }}
>
<RecordShowContainer
objectNameSingular={objectNameSingular}

View File

@ -8,6 +8,7 @@ import { TrialCard } from '@/billing/components/TrialCard';
import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSession';
import { billingState } from '@/client-config/states/billingState';
import styled from '@emotion/styled';
import { Trans, useLingui } from '@lingui/react/macro';
import { useRecoilState, useRecoilValue } from 'recoil';
import {
ActionLink,
@ -78,16 +79,18 @@ const StyledLinkGroup = styled.div`
}
`;
const benefits = [
'Full access',
'Unlimited contacts',
'Email integration',
'Custom objects',
'API & Webhooks',
'1 000 workflow node executions',
];
export const ChooseYourPlan = () => {
const billing = useRecoilValue(billingState);
const { t } = useLingui();
const benefits = [
t`Full access`,
t`Unlimited contacts`,
t`Email integration`,
t`Custom objects`,
t`API & Webhooks`,
t`1 000 workflow node executions`,
];
const { data: prices } = useGetProductPricesQuery({
variables: { product: 'base-plan' },
@ -133,20 +136,26 @@ export const ChooseYourPlan = () => {
const { signOut } = useAuth();
const withCreditCardTrialPeriodDuration = withCreditCardTrialPeriod?.duration;
return (
isDefined(price) &&
isDefined(billing) && (
<>
<Title noMarginTop>
{hasWithoutCreditCardTrialPeriod
? 'Choose your Trial'
: 'Get your subscription'}
? t`Choose your Trial`
: t`Get your subscription`}
</Title>
{hasWithoutCreditCardTrialPeriod ? (
<SubTitle>Cancel anytime</SubTitle>
<SubTitle>
<Trans>Cancel anytime</Trans>
</SubTitle>
) : (
withCreditCardTrialPeriod && (
<SubTitle>{`Enjoy a ${withCreditCardTrialPeriod.duration}-days free trial`}</SubTitle>
<SubTitle>
{t`Enjoy a ${withCreditCardTrialPeriodDuration}-days free trial`}
</SubTitle>
)
)}
<StyledSubscriptionContainer
@ -186,17 +195,19 @@ export const ChooseYourPlan = () => {
</StyledChooseTrialContainer>
)}
<MainButton
title="Continue"
title={t`Continue`}
onClick={handleCheckoutSession}
width={200}
Icon={() => isSubmitting && <Loader />}
disabled={isSubmitting}
/>
<StyledLinkGroup>
<ActionLink onClick={signOut}>Log out</ActionLink>
<ActionLink onClick={signOut}>
<Trans>Log out</Trans>
</ActionLink>
<span />
<ActionLink href={CAL_LINK} target="_blank" rel="noreferrer">
Book a Call
<Trans>Book a Call</Trans>
</ActionLink>
</StyledLinkGroup>
</>

View File

@ -33,6 +33,7 @@ import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useLingui } from '@lingui/react/macro';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
@ -48,6 +49,7 @@ type SettingsDataModelFieldEditFormValues = z.infer<
export const SettingsObjectFieldEdit = () => {
const navigateSettings = useNavigateSettings();
const navigateApp = useNavigateApp();
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
@ -170,11 +172,11 @@ export const SettingsObjectFieldEdit = () => {
title={fieldMetadataItem?.label}
links={[
{
children: 'Workspace',
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: 'Objects',
children: t`Objects`,
href: getSettingsPath(SettingsPath.Objects),
},
{
@ -203,8 +205,8 @@ export const SettingsObjectFieldEdit = () => {
<SettingsPageContainer>
<Section>
<H2Title
title="Icon and Name"
description="The name and icon of this field"
title={t`Icon and Name`}
description={t`The name and icon of this field`}
/>
<SettingsDataModelFieldIconLabelForm
disabled={!fieldMetadataItem.isCustom}
@ -218,13 +220,13 @@ export const SettingsObjectFieldEdit = () => {
<Section>
{fieldMetadataItem.isUnique ? (
<H2Title
title="Values"
description="The values of this field must be unique"
title={t`Values`}
description={t`The values of this field must be unique`}
/>
) : (
<H2Title
title="Values"
description="The values of this field"
title={t`Values`}
description={t`The values of this field`}
/>
)}
<SettingsDataModelFieldSettingsFormCard
@ -234,8 +236,8 @@ export const SettingsObjectFieldEdit = () => {
</Section>
<Section>
<H2Title
title="Description"
description="The description of this field"
title={t`Description`}
description={t`The description of this field`}
/>
<SettingsDataModelFieldDescriptionForm
disabled={!fieldMetadataItem.isCustom}
@ -245,15 +247,17 @@ export const SettingsObjectFieldEdit = () => {
{!isLabelIdentifier && (
<Section>
<H2Title
title="Danger zone"
description="Deactivate this field"
title={t`Danger zone`}
description={t`Deactivate this field`}
/>
<Button
Icon={
fieldMetadataItem.isActive ? IconArchive : IconArchiveOff
}
variant="secondary"
title={fieldMetadataItem.isActive ? 'Deactivate' : 'Activate'}
title={
fieldMetadataItem.isActive ? t`Deactivate` : t`Activate`
}
size="small"
onClick={
fieldMetadataItem.isActive

View File

@ -22,6 +22,7 @@ import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType';
import { useApolloClient } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import pick from 'lodash.pick';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
@ -44,6 +45,8 @@ type SettingsDataModelNewFieldFormValues = z.infer<
const DEFAULT_ICON_FOR_NEW_FIELD = 'IconUsers';
export const SettingsObjectNewFieldConfigure = () => {
const { t } = useLingui();
const navigateApp = useNavigateApp();
const navigate = useNavigateSettings();
@ -192,10 +195,16 @@ export const SettingsObjectNewFieldConfigure = () => {
{...formConfig}
>
<SubMenuTopBarContainer
title="2. Configure field"
title={t`2. Configure field`}
links={[
{ children: 'Workspace', href: '/settings/workspace' },
{ children: 'Objects', href: '/settings/objects' },
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: t`Objects`,
href: getSettingsPath(SettingsPath.Objects),
},
{
children: activeObjectMetadataItem.labelPlural,
href: getSettingsPath(SettingsPath.ObjectDetail, {
@ -227,8 +236,8 @@ export const SettingsObjectNewFieldConfigure = () => {
<SettingsPageContainer>
<Section>
<H2Title
title="Icon and Name"
description="The name and icon of this field"
title={t`Icon and Name`}
description={t`The name and icon of this field`}
/>
<SettingsDataModelFieldIconLabelForm
maxLength={FIELD_NAME_MAXIMUM_LENGTH}
@ -238,7 +247,10 @@ export const SettingsObjectNewFieldConfigure = () => {
/>
</Section>
<Section>
<H2Title title="Values" description="The values of this field" />
<H2Title
title={t`Values`}
description={t`The values of this field`}
/>
<SettingsDataModelFieldSettingsFormCard
fieldMetadataItem={{
icon: formConfig.watch('icon'),
@ -251,8 +263,8 @@ export const SettingsObjectNewFieldConfigure = () => {
</Section>
<Section>
<H2Title
title="Description"
description="The description of this field"
title={t`Description`}
description={t`The description of this field`}
/>
<SettingsDataModelFieldDescriptionForm />
</Section>

View File

@ -23,6 +23,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { Trans, useLingui } from '@lingui/react/macro';
import { useGenerateApiKeyTokenMutation } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
@ -42,6 +43,7 @@ const StyledInputContainer = styled.div`
`;
export const SettingsDevelopersApiKeyDetail = () => {
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const [isRegenerateKeyModalOpen, setIsRegenerateKeyModalOpen] =
useState(false);
@ -82,7 +84,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
navigate(SettingsPath.Developers);
}
} catch (err) {
enqueueSnackBar(`Error deleting api key: ${err}`, {
enqueueSnackBar(t`Error deleting api key: ${err}`, {
variant: SnackBarVariant.Error,
});
} finally {
@ -134,7 +136,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
}
}
} catch (err) {
enqueueSnackBar(`Error regenerating api key: ${err}`, {
enqueueSnackBar(t`Error regenerating api key: ${err}`, {
variant: SnackBarVariant.Error,
});
} finally {
@ -142,6 +144,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
}
};
const confirmationValue = t`yes`;
return (
<>
{apiKeyData?.name && (
@ -149,14 +153,14 @@ export const SettingsDevelopersApiKeyDetail = () => {
title={apiKeyData?.name}
links={[
{
children: 'Workspace',
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: 'Developers',
children: t`Developers`,
href: getSettingsPath(SettingsPath.Developers),
},
{ children: `${apiKeyName} API Key` },
{ children: t`${apiKeyName} API Key` },
]}
>
<SettingsPageContainer>
@ -164,8 +168,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
{apiKeyToken ? (
<>
<H2Title
title="API Key"
description="Copy this key as it will only be visible this one time"
title={t`API Key`}
description={t`Copy this key as it will only be visible this one time`}
/>
<ApiKeyInput apiKey={apiKeyToken} />
<StyledInfo>
@ -175,12 +179,12 @@ export const SettingsDevelopersApiKeyDetail = () => {
) : (
<>
<H2Title
title="API Key"
description="Regenerate an API key"
title={t`API Key`}
description={t`Regenerate an API key`}
/>
<StyledInputContainer>
<Button
title="Regenerate Key"
title={t`Regenerate Key`}
Icon={IconRepeat}
onClick={() => setIsRegenerateKeyModalOpen(true)}
/>
@ -196,7 +200,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
)}
</Section>
<Section>
<H2Title title="Name" description="Name of your API key" />
<H2Title title={t`Name`} description={t`Name of your API key`} />
<ApiKeyNameInput
apiKeyName={apiKeyName}
apiKeyId={apiKeyData?.id}
@ -206,11 +210,11 @@ export const SettingsDevelopersApiKeyDetail = () => {
</Section>
<Section>
<H2Title
title="Expiration"
description="When the key will be disabled"
title={t`Expiration`}
description={t`When the key will be disabled`}
/>
<TextInput
placeholder="E.g. backoffice integration"
placeholder={t`E.g. backoffice integration`}
value={formatExpiration(
apiKeyData?.expiresAt || '',
true,
@ -222,13 +226,13 @@ export const SettingsDevelopersApiKeyDetail = () => {
</Section>
<Section>
<H2Title
title="Danger zone"
description="Delete this integration"
title={t`Danger zone`}
description={t`Delete this integration`}
/>
<Button
accent="danger"
variant="secondary"
title="Delete"
title={t`Delete`}
Icon={IconTrash}
onClick={() => setIsDeleteApiKeyModalOpen(true)}
/>
@ -237,36 +241,37 @@ export const SettingsDevelopersApiKeyDetail = () => {
</SubMenuTopBarContainer>
)}
<ConfirmationModal
confirmationPlaceholder="yes"
confirmationValue="yes"
confirmationPlaceholder={confirmationValue}
confirmationValue={confirmationValue}
isOpen={isDeleteApiKeyModalOpen}
setIsOpen={setIsDeleteApiKeyModalOpen}
title="Delete API key"
title={t`Delete API key`}
subtitle={
<>
Please type "yes" to confirm you want to delete this API Key. Be
aware that any script using this key will stop working.
</>
<Trans>
Please type {`"${confirmationValue}"`} to confirm you want to delete
this API Key. Be aware that any script using this key will stop
working.
</Trans>
}
onConfirmClick={deleteIntegration}
deleteButtonText="Delete"
loading={isLoading}
/>
<ConfirmationModal
confirmationPlaceholder="yes"
confirmationValue="yes"
confirmationPlaceholder={confirmationValue}
confirmationValue={confirmationValue}
isOpen={isRegenerateKeyModalOpen}
setIsOpen={setIsRegenerateKeyModalOpen}
title="Regenerate an API key"
title={t`Regenerate an API key`}
subtitle={
<>
<Trans>
If youve lost this key, you can regenerate it, but be aware that
any script using this key will need to be updated. Please type "yes"
to confirm.
</>
any script using this key will need to be updated. Please type
{`"${confirmationValue}"`} to confirm.
</Trans>
}
onConfirmClick={regenerateApiKey}
deleteButtonText="Regenerate key"
deleteButtonText={t`Regenerate key`}
loading={isLoading}
/>
</>

View File

@ -13,6 +13,7 @@ import { SettingsPath } from '@/types/SettingsPath';
import { Select } from '@/ui/input/components/Select';
import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useLingui } from '@lingui/react/macro';
import { useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { useGenerateApiKeyTokenMutation } from '~/generated/graphql';
@ -21,6 +22,7 @@ import { isDefined } from '~/utils/isDefined';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SettingsDevelopersApiKeysNew = () => {
const { t } = useLingui();
const [generateOneApiKeyToken] = useGenerateApiKeyTokenMutation();
const navigateSettings = useNavigateSettings();
const setApiKeyToken = useSetRecoilState(apiKeyTokenState);
@ -66,17 +68,17 @@ export const SettingsDevelopersApiKeysNew = () => {
const canSave = !!formValues.name && createOneApiKey;
return (
<SubMenuTopBarContainer
title="New key"
title={t`New key`}
links={[
{
children: 'Workspace',
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: 'Developers',
children: t`Developers`,
href: getSettingsPath(SettingsPath.Developers),
},
{ children: 'New Key' },
{ children: t`New Key` },
]}
actionButton={
<SaveAndCancelButtons
@ -90,9 +92,9 @@ export const SettingsDevelopersApiKeysNew = () => {
>
<SettingsPageContainer>
<Section>
<H2Title title="Name" description="Name of your API key" />
<H2Title title={t`Name`} description={t`Name of your API key`} />
<TextInput
placeholder="E.g. backoffice integration"
placeholder={t`E.g. backoffice integration`}
value={formValues.name}
onKeyDown={(e) => {
if (e.key === Key.Enter) {
@ -110,8 +112,8 @@ export const SettingsDevelopersApiKeysNew = () => {
</Section>
<Section>
<H2Title
title="Expiration Date"
description="When the API key will expire."
title={t`Expiration Date`}
description={t`When the API key will expire.`}
/>
<Select
dropdownId="object-field-type-select"

View File

@ -4,6 +4,7 @@ import { DateFormat } from '@/localization/constants/DateFormat';
import { detectDateFormat } from '@/localization/utils/detectDateFormat';
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
import { Select } from '@/ui/input/components/Select';
import { t } from '@lingui/core/macro';
type DateTimeSettingsDateFormatSelectProps = {
value: DateFormat;
@ -22,6 +23,12 @@ export const DateTimeSettingsDateFormatSelect = ({
const systemDateFormat = DateFormat[detectDateFormat()];
const systemDateFormatLabel = formatInTimeZone(
Date.now(),
usedTimeZone,
systemDateFormat,
);
return (
<Select
dropdownId="datetime-settings-date-format"
@ -32,11 +39,7 @@ export const DateTimeSettingsDateFormatSelect = ({
value={value}
options={[
{
label: `System settings - ${formatInTimeZone(
Date.now(),
usedTimeZone,
systemDateFormat,
)}`,
label: t`System settings - ${systemDateFormatLabel}`,
value: DateFormat.SYSTEM,
},
{

View File

@ -4,6 +4,7 @@ import { TimeFormat } from '@/localization/constants/TimeFormat';
import { detectTimeFormat } from '@/localization/utils/detectTimeFormat';
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
import { Select } from '@/ui/input/components/Select';
import { useLingui } from '@lingui/react/macro';
type DateTimeSettingsTimeFormatSelectProps = {
value: TimeFormat;
@ -16,43 +17,50 @@ export const DateTimeSettingsTimeFormatSelect = ({
timeZone,
value,
}: DateTimeSettingsTimeFormatSelectProps) => {
const { t } = useLingui();
const systemTimeZone = detectTimeZone();
const usedTimeZone = timeZone === 'system' ? systemTimeZone : timeZone;
const systemTimeFormat = TimeFormat[detectTimeFormat()];
const systemTimeFormatLabel = formatInTimeZone(
Date.now(),
usedTimeZone,
systemTimeFormat,
);
const hour24Label = formatInTimeZone(
Date.now(),
usedTimeZone,
TimeFormat.HOUR_24,
);
const hour12Label = formatInTimeZone(
Date.now(),
usedTimeZone,
TimeFormat.HOUR_12,
);
return (
<Select
dropdownId="datetime-settings-time-format"
dropdownWidth={218}
label="Time format"
label={t`Time format`}
dropdownWidthAuto
fullWidth
value={value}
options={[
{
label: `System Settings - ${formatInTimeZone(
Date.now(),
usedTimeZone,
systemTimeFormat,
)}`,
label: t`System Settings - ${systemTimeFormatLabel}`,
value: TimeFormat.SYSTEM,
},
{
label: `24h (${formatInTimeZone(
Date.now(),
usedTimeZone,
TimeFormat.HOUR_24,
)})`,
label: t`24h (${hour24Label})`,
value: TimeFormat.HOUR_24,
},
{
label: `12h (${formatInTimeZone(
Date.now(),
usedTimeZone,
TimeFormat.HOUR_12,
)})`,
label: t`12h (${hour12Label})`,
value: TimeFormat.HOUR_12,
},
]}