Skip to content

Commit

Permalink
Merge pull request #9154 from marmelab/story-and-doc-improvement-for-…
Browse files Browse the repository at this point in the history
…translatable-fields-to-next

[Doc] add stories and doc improvement for translatable fields
  • Loading branch information
fzaninotto authored Aug 3, 2023
2 parents 0f201e0 + 14ce457 commit 402dec8
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 84 deletions.
157 changes: 99 additions & 58 deletions docs/TranslatableFields.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ title: "The TranslatableFields Component"

You may have fields which are translated in multiple languages and want users to verify each translation. To display them, you can use the `<TranslatableFields>` component.

![TranslatableFields addLabel](./img/TranslatableFields.png)
<video controls autoplay playsinline muted loop>
<source src="./img/translatable-fields-basic.webm" type="video/webm" />
<source src="./img/translatable-fields-basic.webm" type="video/mp4" />
Your browser does not support the video tag.
</video>

## Usage

`<TranslatableFields>` expects the translatable values to have the following structure:
`<TranslatableFields>` expects the translatable values of a record to have the following structure:

```js
{
const record = {
id: 123,
title: {
en: 'Doctors Without Borders',
fr: 'Médecins sans frontières',
Expand All @@ -24,79 +29,56 @@ You may have fields which are translated in multiple languages and want users to
'International humanitarian medical non-governmental organisation of French origin',
fr:
"Organisation non gouvernementale (ONG) médicale humanitaire internationale d'origine française fondée en 1971 à Paris",
},
}
}
```

Then, use `<TranslatableFields>` like so:

```jsx
<TranslatableFields locales={['en', 'fr']}>
<TextField source="title" />
<TextField source="description" />
</TranslatableFields>
```

React-admin uses the user locale as the default locale in this field. You can override this setting using the `defaultLocale` prop.
To display translatable values, wrap the fields you want to render with `<TranslatableFields>`, like so:

```jsx
<TranslatableFields locales={['en', 'fr']} defaultLocale="fr">
<TextField source="title" />
<TextField source="description" />
</TranslatableFields>
import {
Show,
SimpleShowLayout,
TextField,
TranslatableFields,
} from "react-admin";

export const OrganizationShow = () => (
<Show>
<SimpleShowLayout>
<TranslatableFields locales={['en', 'fr']}>
<TextField source="title" />
<TextField source="description" />
</TranslatableFields>
</SimpleShowLayout>
</Show>
);
```

By default, `<TranslatableFields>` will allow users to select the displayed locale using Material UI tabs with the locale code as their labels.
`<TranslatableFields>` lets users select a locale using Material UI tabs with the locale code as their labels.

You may override the tabs labels using translation keys following this format: `ra.locales.[locale_code]`. For instance, `ra.locales.en` or `ra.locales.fr`.

You may override the language selector using the `selector` prop, which accepts a React element:
**Tip**: If you want to display only one translation, you don't need `<TranslatableFields>`. Just use a regular field with a path as `source`:

```jsx
import { useTranslatableContext } from 'react-admin';

const Selector = () => {
const {
locales,
selectLocale,
selectedLocale,
} = useTranslatableContext();
{/* always display the English title */}
<TextField source="title.en" />
```

const handleChange = event => {
selectLocale(event.target.value);
};
## `defaultLocale`

return (
<select
aria-label="Select the locale"
onChange={handleChange}
value={selectedLocale}
>
{locales.map(locale => (
<option
key={locale}
value={locale}
// This allows to correctly link the containers for each locale to their labels
id={`translatable-header-${locale}`}
>
{locale}
</option>
))}
</select>
);
};
React-admin uses the user locale as the default locale in this field. You can override this setting using the `defaultLocale` prop.

<TranslatableFields
record={record}
resource="products"
locales={['en', 'fr']}
selector={<Selector />}
>
<TextField source="name" />
```jsx
<TranslatableFields locales={['en', 'fr']} defaultLocale="fr">
<TextField source="title" />
<TextField source="description" />
</TranslatableFields>
```

## `groupKey`

If you have multiple `TranslatableFields` on the same page, you should specify a `groupKey` so that react-admin can create unique identifiers for accessibility.

```jsx
Expand All @@ -106,7 +88,66 @@ If you have multiple `TranslatableFields` on the same page, you should specify a
</TranslatableFields>
```

## Using Translatable Fields In List or Show views
## `selector`

<video controls autoplay playsinline muted loop>
<source src="./img/translatable-fields-with-custom-selector.webm" type="video/webm" />
<source src="./img/translatable-fields-with-custom-selector.webm" type="video/mp4" />
Your browser does not support the video tag.
</video>

You may override the language selector using the `selector` prop, which accepts a React element:

```jsx
// in src/NgoShow.tsx
import {
Show,
SimpleShowLayout,
TextField,
TranslatableFields,
useTranslatableContext,
} from "react-admin";

const Selector = () => {
const { locales, selectLocale, selectedLocale } = useTranslatableContext();

const handleChange = (event) => {
selectLocale(event.target.value);
};

return (
<select
aria-label="Select the locale"
onChange={handleChange}
value={selectedLocale}
>
{locales.map((locale) => (
<option
key={locale}
value={locale}
// This allows to correctly link the containers for each locale to their labels
id={`translatable-header-${locale}`}
>
{locale}
</option>
))}
</select>
);
};

export const NgoShow = () => (
<Show>
<SimpleShowLayout>
<TranslatableFields locales={["en", "fr"]} selector={<Selector />}>
<TextField source="title" />
<TextField source="description" />
</TranslatableFields>
</SimpleShowLayout>
</Show>
);
```

## Using Translatable Fields In List Views

The `TranslatableFields` component is not meant to be used inside a `List` as you probably don't want to have tabs inside multiple lines. The simple solution to display a translatable value would be to specify its source like this: `name.en`. However, you may want to display its translation for the current admin locale.

Expand Down
Binary file added docs/img/translatable-fields-basic.mp4
Binary file not shown.
Binary file added docs/img/translatable-fields-basic.webm
Binary file not shown.
Binary file not shown.
Binary file not shown.
135 changes: 109 additions & 26 deletions packages/ra-ui-materialui/src/field/TranslatableFields.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
import * as React from 'react';
import {
RecordContextProvider,
Resource,
useTranslatableContext,
} from 'ra-core';
import fakeRestDataProvider from 'ra-data-fakerest';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';

import * as React from 'react';
import { AdminContext } from '../AdminContext';
import { SimpleShowLayout } from '../detail';
import { TranslatableFields } from './TranslatableFields';
import { AdminUI } from '../AdminUI';
import { Show, SimpleShowLayout } from '../detail';
import { TextField } from './TextField';
import { RecordContextProvider } from 'ra-core';
import { TranslatableFields } from './TranslatableFields';
import { ListGuesser } from '../list';

export default { title: 'ra-ui-materialui/fields/TranslatableFields' };

const defaultData = [
{
id: 123,
title: {
en: 'Doctors Without Borders',
fr: 'Médecins sans frontières',
},
description: {
en:
'International humanitarian medical non-governmental organisation of French origin',
fr:
"Organisation non gouvernementale (ONG) médicale humanitaire internationale d'origine française fondée en 1971 à Paris",
},
internal_organizations: {
OCB: {
en: 'Brussels operational center',
fr: 'Centre opérationnel de Bruxelles',
},
OCP: {
en: 'Paris operational center',
fr: 'Centre opérationnel de Paris',
},
},
},
];
const i18nProvider = polyglotI18nProvider(() => englishMessages);

const Wrapper = ({ children }) => (
<AdminContext i18nProvider={i18nProvider}>
<RecordContextProvider value={defaultData[0]}>
<SimpleShowLayout>{children}</SimpleShowLayout>
</RecordContextProvider>
</AdminContext>
);

export const Basic = () => (
<Wrapper>
<TranslatableFields locales={['en', 'fr']}>
<TextField source="title" />
<TextField source="description" />
<TextField source="title" />,
<TextField source="description" />,
</TranslatableFields>
</Wrapper>
);
Expand All @@ -27,26 +68,68 @@ export const SingleField = () => (
</Wrapper>
);

const i18nProvider = polyglotI18nProvider(() => englishMessages);
const Selector = () => {
const { locales, selectLocale, selectedLocale } = useTranslatableContext();

const Wrapper = ({ children }) => (
<AdminContext i18nProvider={i18nProvider}>
<RecordContextProvider
value={{
id: 123,
title: {
en: 'Doctors Without Borders',
fr: 'Médecins sans frontières',
},
description: {
en:
'International humanitarian medical non-governmental organisation of French origin',
fr:
"Organisation non gouvernementale (ONG) médicale humanitaire internationale d'origine française fondée en 1971 à Paris",
},
}}
const handleChange = event => {
selectLocale(event.target.value);
};

return (
<select
aria-label="Select the locale"
onChange={handleChange}
value={selectedLocale}
>
<SimpleShowLayout>{children}</SimpleShowLayout>
</RecordContextProvider>
{locales.map(locale => (
<option
key={locale}
value={locale}
id={`translatable-header-${locale}`}
>
{locale}
</option>
))}
</select>
);
};

export const CustomSelector = () => (
<Wrapper>
<TranslatableFields locales={['en', 'fr']} selector={<Selector />}>
<TextField source="title" />
<TextField source="description" />
</TranslatableFields>
</Wrapper>
);

export const NestedFields = () => (
<Wrapper>
<TranslatableFields locales={['en', 'fr']}>
<TextField source="internal_organizations.OCP" />
</TranslatableFields>
</Wrapper>
);

const dataProvider = fakeRestDataProvider({
ngos: defaultData,
});

const ShowNgo = () => (
<Show>
<SimpleShowLayout>
<TranslatableFields locales={['en', 'fr']}>
<TextField source="title" />,
<TextField source="description" />,
</TranslatableFields>
</SimpleShowLayout>
</Show>
);

export const FullApp = () => (
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
<AdminUI>
<Resource name="ngos" list={ListGuesser} show={ShowNgo} />
</AdminUI>
</AdminContext>
);

0 comments on commit 402dec8

Please sign in to comment.