Skip to content

Commit

Permalink
Merge pull request #39 from open-formulieren/feature/of-2958-translat…
Browse files Browse the repository at this point in the history
…ions-on-component

Store translations on the component
  • Loading branch information
sergei-maertens authored Oct 19, 2023
2 parents 5468fb6 + e236fd8 commit 76f19b5
Show file tree
Hide file tree
Showing 16 changed files with 370 additions and 412 deletions.
196 changes: 85 additions & 111 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"@formatjs/cli": "^6.1.1",
"@formatjs/ts-transformer": "^3.12.0",
"@fortawesome/fontawesome-free": "^6.4.0",
"@open-formulieren/types": "^0.9.0",
"@open-formulieren/types": "^0.10.0",
"@storybook/addon-actions": "^7.3.2",
"@storybook/addon-essentials": "^7.3.2",
"@storybook/addon-interactions": "^7.3.2",
Expand All @@ -76,9 +76,8 @@
"@types/lodash.camelcase": "^4.3.7",
"@types/lodash.clonedeep": "^4.5.7",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.get": "^4.4.7",
"@types/lodash.isequal": "^4.5.6",
"@types/lodash.set": "^4.3.7",
"@types/lodash.merge": "^4.6.7",
"@types/lodash.uniqueid": "^4.0.7",
"@types/react": "^18.0.33",
"@types/sanitize-html": "^2.9.0",
Expand Down Expand Up @@ -106,9 +105,8 @@
"lodash.camelcase": "^4.3.0",
"lodash.clonedeep": "^4.5.0",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"lodash.set": "^4.3.2",
"lodash.merge": "^4.6.2",
"lodash.uniqueid": "^4.0.1",
"react-intl": "^6.3.2",
"react-select": "^5.7.2",
Expand Down
15 changes: 3 additions & 12 deletions src/components/ComponentEditForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Form, Formik} from 'formik';
import {ExtendedComponentSchema} from 'formiojs/types/components/schema';
import cloneDeep from 'lodash.clonedeep';
import set from 'lodash.set';
import merge from 'lodash.merge';
import {FormattedMessage, useIntl} from 'react-intl';
import {toFormikValidationSchema} from 'zod-formik-adapter';

Expand All @@ -19,8 +19,6 @@ export interface BuilderInfo {
weight: number;
}

type ObjecEntry<T, K extends keyof T = keyof T> = [K, T[K]];

export interface ComponentEditFormProps {
isNew: boolean;
// it is (currently) possible someone drags a component type into the canvas that we
Expand All @@ -45,16 +43,9 @@ const ComponentEditForm: React.FC<ComponentEditFormProps> = ({
const registryEntry = getRegistryEntry(component);
const {edit: EditForm, editSchema: zodSchema} = registryEntry;

// FIXME: recipes may have non-default values that would be overwritten here with default
// values - we need a deep merge & some logic to detect this.
const initialValues = cloneDeep(component);
let initialValues = cloneDeep(component);
if (isNew && isKnownComponentType(component)) {
Object.entries(EditForm.defaultValues).forEach(
([key, value]: ObjecEntry<AnyComponentSchema>) => {
const val = component?.[key] || value;
set(initialValues, key, val);
}
);
initialValues = merge(EditForm.defaultValues, initialValues);
}

// we infer the specific schema from the EditForm component obtained from the registry.
Expand Down
11 changes: 2 additions & 9 deletions src/components/builder/description.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import {FormattedMessage} from 'react-intl';

import {TextField} from '../formio';
import {LABELS} from './messages';

/*
* The description contains additional useful information/hints for a given field to
* clarify what's expected by the end-user filling out the form.
*/
const Description: React.FC = () => (
<TextField
name="description"
label={
<FormattedMessage
description="Component property 'Description' label"
defaultMessage="Description"
/>
}
/>
<TextField name="description" label={<FormattedMessage {...LABELS.description} />} />
);

export default Description;
92 changes: 50 additions & 42 deletions src/components/builder/i18n.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {expect} from '@storybook/jest';
import {Meta, StoryFn} from '@storybook/react';
import {Meta, StoryFn, StoryObj} from '@storybook/react';
import {userEvent, waitFor, within} from '@storybook/testing-library';
import {Formik} from 'formik';

import {TextField} from '@/components/formio';

import {ComponentTranslations, useManageTranslations} from './i18n';
import {ComponentTranslations} from './i18n';

export default {
title: 'Formio/Builder/i18n/ComponentTranslations',
Expand All @@ -21,83 +21,91 @@ export default {
modal: {noModal: true},
builder: {
enableContext: true,
// 'de' is not part of the languages type, but this is fine at runtime
supportedLanguageCodes: ['nl', 'en', 'de'],
translationsStore: {
nl: {
'Literal 1': 'Vertaling 1',
},
en: {},
de: {},
},
},
},
args: {
translatableFields: ['translatableField', 'nested.translatableField'],
fieldLabels: {
label: 'Label',
description: 'Description',
},

initialValues: {
translatableField: 'Literal 1',
label: 'Hi there {{ firstName }}',
nonTranslatableField: '',
nested: {translatableField: ''},
openForms: {translations: {}},
openForms: {
translations: {
nl: {
label: 'Hallo daar, {{ firstName }}',
},
},
},
},
},
} as Meta<typeof ComponentTranslations>;

interface BodyProps {
translatableFields: string[];
fieldLabels: {
label: string;
description: string;
};
}

interface DummyComponent {
type: 'textfield';
key: string;
id: string;
label: string;
description: string;
}

const Body: React.FC<BodyProps> = ({translatableFields}) => {
// FIXME: proper typing with nested keys
useManageTranslations(translatableFields as any);
const Body: React.FC<BodyProps> = ({fieldLabels}) => {
return (
<>
<TextField name="translatableField" label="Translatable field" />
<TextField name="label" label="Label" />
<TextField name="description" label="Description" />
<TextField name="nonTranslatableField" label="Non-translatable field" />
<TextField name="nested.translatableField" label="Nested translatable field" />
<ComponentTranslations />
<ComponentTranslations<DummyComponent> propertyLabels={fieldLabels} />
</>
);
};

interface StoryArgs {
translatableFields: string[];
interface StoryArgs extends BodyProps {
initialValues: {
[key: string]: any;
};
}

const Template: StoryFn<React.FC<StoryArgs>> = ({translatableFields, initialValues}) => (
type Story = StoryObj<StoryArgs>;

const render: StoryFn<React.FC<StoryArgs>> = ({fieldLabels, initialValues}) => (
<Formik enableReinitialize initialValues={initialValues} onSubmit={console.log}>
<Body translatableFields={translatableFields} />
<Body fieldLabels={fieldLabels} />
</Formik>
);

export const Default = {
render: Template,
export const Default: Story = {
render: render,

play: async ({canvasElement}) => {
const canvas = within(canvasElement);

// Translations component should initialize/synchronize with available literals and
// translations from the store.
// Translations component must display the registered/existing translations.
const translationField1 = canvas.getByLabelText('Translation for "label"');
expect(translationField1).toBeVisible();

const translationField2 = canvas.queryByLabelText('Translation for "nonTranslatableField"');
expect(translationField2).toBeNull();

await waitFor(async () => {
const literal1Inputs = canvas.queryAllByDisplayValue('Literal 1');
expect(literal1Inputs).toHaveLength(2);
await canvas.findByDisplayValue('Vertaling 1');
const literal1Reference = canvas.getByText('Hi there {{ firstName }}');
expect(literal1Reference).toBeVisible();
await canvas.findByDisplayValue('Hallo daar, {{ firstName }}');
});

// Enter a value in the non-translatable field
await userEvent.type(canvas.getByLabelText('Non-translatable field'), 'Literal 2');
await waitFor(async () => {
const literal2Inputs = canvas.queryAllByDisplayValue('Literal 2');
expect(literal2Inputs).toHaveLength(1);
});

// Enter a value for the nested translatable field
await userEvent.type(canvas.getByLabelText('Nested translatable field'), 'Literal 3');
await waitFor(async () => {
const literal3Inputs = canvas.queryAllByDisplayValue('Literal 3');
expect(literal3Inputs).toHaveLength(2);
});
expect(translationField2).toBeNull();
},
};
Loading

0 comments on commit 76f19b5

Please sign in to comment.