Progress on translations (#9703)

Start adding a few translations on setting pages, introduce
pseudo-locale, switch to dynamic import, add eslint rule
This commit is contained in:
Félix Malfait
2025-01-16 23:34:54 +01:00
committed by GitHub
parent f44b31573a
commit 7acb68929f
46 changed files with 3019 additions and 299 deletions

View File

@ -1,4 +1,5 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { H2Title, Section } from 'twenty-ui';
@ -24,78 +25,78 @@ type SettingsDataModelNewObjectFormValues = z.infer<typeof newObjectFormSchema>;
export const SettingsNewObject = () => {
const navigate = useNavigate();
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const { createOneObjectMetadataItem, findManyRecordsCache } =
useCreateOneObjectMetadataItem();
const settingsObjectsPagePath = getSettingsPagePath(SettingsPath.Objects);
const formConfig = useForm<SettingsDataModelNewObjectFormValues>({
mode: 'onTouched',
const methods = useForm<SettingsDataModelNewObjectFormValues>({
defaultValues: {},
resolver: zodResolver(newObjectFormSchema),
});
const { isValid, isSubmitting } = formConfig.formState;
const canSave = isValid && !isSubmitting;
const { handleSubmit } = methods;
const handleSave = async (
formValues: SettingsDataModelNewObjectFormValues,
) => {
const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem();
const onSubmit = async (data: SettingsDataModelNewObjectFormValues) => {
try {
const { data: response } = await createOneObjectMetadataItem(
settingsCreateObjectInputSchema.parse(formValues),
);
const createObjectInput = settingsCreateObjectInputSchema.parse(data);
navigate(
response
? `${settingsObjectsPagePath}/${response.createOneObject.namePlural}`
: settingsObjectsPagePath,
);
await createOneObjectMetadataItem(createObjectInput);
await findManyRecordsCache();
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueSnackBar(t`Object created successfully`, {
variant: SnackBarVariant.Success,
});
navigate(getSettingsPagePath(SettingsPath.Objects));
} catch (error) {
if (error instanceof z.ZodError) {
enqueueSnackBar(t`Invalid object data`, {
variant: SnackBarVariant.Error,
});
} else {
enqueueSnackBar(t`Failed to create object`, {
variant: SnackBarVariant.Error,
});
}
}
};
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<FormProvider {...formConfig}>
<>
<SubMenuTopBarContainer
title="New Object"
title={t`New Object`}
links={[
{
children: 'Workspace',
children: t`Workspace`,
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Objects',
href: settingsObjectsPagePath,
children: t`Objects`,
href: getSettingsPagePath(SettingsPath.Objects),
},
{ children: 'New' },
{ children: t`New` },
]}
actionButton={
<SaveAndCancelButtons
isSaveDisabled={!canSave}
isCancelDisabled={isSubmitting}
onCancel={() => navigate(settingsObjectsPagePath)}
onSave={formConfig.handleSubmit(handleSave)}
/>
}
>
<SettingsPageContainer>
<Section>
<H2Title
title="About"
description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
/>
<SettingsDataModelObjectAboutForm />
</Section>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}>
<Section>
<H2Title
title={t`About`}
description={t`Define the name and description of your object`}
/>
<SettingsDataModelObjectAboutForm />
</Section>
<SaveAndCancelButtons
onCancel={() =>
navigate(getSettingsPagePath(SettingsPath.Objects))
}
/>
</form>
</FormProvider>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</FormProvider>
</>
);
};

View File

@ -21,6 +21,7 @@ import { TableSection } from '@/ui/layout/table/components/TableSection';
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { isNonEmptyArray } from '@sniptt/guards';
import { useMemo, useState } from 'react';
import {
@ -38,12 +39,15 @@ import { SettingsObjectTableItem } from '~/pages/settings/data-model/types/Setti
const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
`;
const StyledSearchInput = styled(TextInput)`
padding-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
export const SettingsObjects = () => {
const theme = useTheme();
const { t } = useLingui();
const [searchTerm, setSearchTerm] = useState('');
const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem();
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
@ -109,6 +113,7 @@ export const SettingsObjects = () => {
inactiveObjectSettingsArray,
SETTINGS_OBJECT_TABLE_METADATA,
);
const filteredActiveObjectSettingsItems = useMemo(
() =>
sortedActiveObjectSettingsItems.filter(
@ -128,14 +133,15 @@ export const SettingsObjects = () => {
),
[sortedInactiveObjectSettingsItems, searchTerm],
);
return (
<SubMenuTopBarContainer
title="Data model"
title={t`Data model`}
actionButton={
<UndecoratedLink to={getSettingsPagePath(SettingsPath.NewObject)}>
<Button
Icon={IconPlus}
title="Add object"
title={t`Add object`}
accent="blue"
size="small"
/>
@ -143,11 +149,11 @@ export const SettingsObjects = () => {
}
links={[
{
children: 'Workspace',
children: t`Workspace`,
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Objects',
children: t`Objects`,
},
]}
>
@ -155,11 +161,11 @@ export const SettingsObjects = () => {
<>
<SettingsObjectCoverImage />
<Section>
<H2Title title="Existing objects" />
<H2Title title={t`Existing objects`} />
<StyledSearchInput
LeftIcon={IconSearch}
placeholder="Search an object..."
placeholder={t`Search an object...`}
value={searchTerm}
onChange={setSearchTerm}
/>
@ -181,7 +187,7 @@ export const SettingsObjects = () => {
<TableHeader></TableHeader>
</StyledObjectTableRow>
{isNonEmptyArray(sortedActiveObjectSettingsItems) && (
<TableSection title="Active">
<TableSection title={t`Active`}>
{filteredActiveObjectSettingsItems.map(
(objectSettingsItem) => (
<SettingsObjectMetadataItemTableRow
@ -205,7 +211,7 @@ export const SettingsObjects = () => {
</TableSection>
)}
{isNonEmptyArray(inactiveObjectMetadataItems) && (
<TableSection title="Inactive">
<TableSection title={t`Inactive`}>
{filteredInactiveObjectSettingsItems.map(
(objectSettingsItem) => (
<SettingsObjectMetadataItemTableRow