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:
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user