Skip to content

Commit

Permalink
Merge pull request #5810 from marmelab/translatable
Browse files Browse the repository at this point in the history
Introduce Translatable inputs and fields
  • Loading branch information
fzaninotto authored Jan 27, 2021
2 parents 1e29aed + 74b79d4 commit 9bc7ba1
Show file tree
Hide file tree
Showing 28 changed files with 1,424 additions and 30 deletions.
133 changes: 133 additions & 0 deletions docs/Fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,139 @@ export const PostShow = (props) => (
);
```

## Translatable Fields

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, which expects the translatable values to have the following structure:

```js
{
name: {
en: 'The english value',
fr: 'The french value',
tlh: 'The klingon value',
},
description: {
en: 'The english value',
fr: 'The french value',
tlh: 'The klingon value',
}
}
```

This is how to use it:

```jsx
<TranslatableFields locales={['en', 'fr']}>
<TextField source="name">
<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.

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

By default, `<TranslatableFields>` will allow users to select the displayed 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:

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

const handleChange = (event): void => {
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>
);
};

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

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
<TranslatableFields locales={['en', 'fr']} groupKey="essential-fields">
<TextField source="name">
<TextField source="description">
</TranslatableFields>
```

### Using Translatable Fields In List or Show 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.

In this case, you'll have to get the current locale through the `useLocale` hook and set the translatable field `source` dynamically.

{% raw %}
```jsx
const PostList = (props) => {
const locale = useLocale();

return (
<List {...props}>
<Datagrid>
<TextField source={`name.${locale}`}>
<ReferenceArrayField
label="Tags"
reference="tags"
source="tags"
sortBy="tags.name"
sort={{ field: `name.${locale}`, order: 'ASC' }}
>
<SingleFieldList>
<ChipField source={`name.${locale}`} size="small" />
</SingleFieldList>
</ReferenceArrayField>
</Datagrid>
</List>
)
}
```
{% endraw %}

Note that you can't have an [optimized](https://marmelab.com/react-admin/List.html#performance) Datagrid when doing so, as changing the locale wouldn't trigger a render of its children.

The same pattern applies to show views when you don't want to display all translations: get the locale from the `useLocale` hook and dynamically set the `source` prop of the translatable fields.

## Recipes

### Styling Fields
Expand Down
105 changes: 105 additions & 0 deletions docs/Inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,111 @@ export default ArtistEdit;

Check [the `ra-relationships` documentation](https://marmelab.com/ra-enterprise/modules/ra-relationships#referencemanytomanyinput) for more details.

## Translatable Inputs

You may have inputs which are translated in multiple languages and want users to edit translations for each language separately. To display them, you can use the `<TranslatableInputs>` component, which expects the translatable values to have the following structure:

```js
{
name: {
en: 'The english value',
fr: 'The french value',
tlh: 'The klingon value',
},
description: {
en: 'The english value',
fr: 'The french value',
tlh: 'The klingon value',
}
}
```

This is how to use it:

```jsx
<TranslatableInputs locales={['en', 'fr']}>
<TextInput source="name">
<RichTextInput source="description">
</TranslatableInputs>
```

React-admin uses the user locale as the default locale in this field. You can override this setting using the `defaultLocale` prop.

```jsx
<TranslatableInputs locales={['en', 'fr']} defaultLocale="fr">
<TextInput source="name">
<RichTextInput source="description">
</TranslatableInputs>
```

By default, `<TranslatableInputs>` will allow users to select the displayed 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:

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

const handleChange = (event): void => {
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>
);
};

<TranslatableInputs
record={record}
resource="products"
basePath="/products"
locales={['en', 'fr']}
selector={<Selector />}
>
<TextInput source="name" />
<RichTextInput source="description" />
</TranslatableInputs>
```

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

```jsx
<TranslatableInputs locales={['en', 'fr']} groupKey="essential-fields">
<TextInput source="name">
<RichTextInput source="description">
</TranslatableInputs>
```

You can add validators to any of the inputs inside a `TranslatableInputs`. If an input has some validation error, the label of its parent tab will be highlighted as invalid:

```jsx
<TranslatableInputs locales={['en', 'fr']}>
<TextInput source="name" validate={[required()]}>
<RichTextInput source="description" validate={[maxLength(100)]}>
</TranslatableInputs>
```

## Recipes

### Transforming Input Value to/from Record
Expand Down
24 changes: 24 additions & 0 deletions docs/Translation.md
Original file line number Diff line number Diff line change
Expand Up @@ -697,3 +697,27 @@ const i18nProvider = polyglotI18nProvider(locale =>
**Tip**: Check [the Polyglot documentation](https://airbnb.io/polyglot.js/#options-overview) for a list of options you can pass to Polyglot at startup.
This solution is all-or-nothing: you can't silence only *some* missing translation warnings. An alternative solution consists of passing a default translation using the `_` translation option, as explained in the [Using Specific Polyglot Features section](#using-specific-polyglot-features) above.
## Translating Record Fields
Some of your records may contain fields that are translated in multiple languages. It's common, in such cases, to offer an interface allowing admin users to see and edit each translation. React-admin provides 2 components for that:
- To display translatable fields, use the [`<TranslatableFields>`](/Fields.html#translatable-fields) component
- To edit translatable fields, use the [`<TranslatableInputs>`](/Inputs.html#translatable-inputs) component
They both expect the translatable values to have the following structure:
```js
{
name: {
en: 'The english value',
fr: 'The french value',
tlh: 'The klingon value',
},
description: {
en: 'The english value',
fr: 'The french value',
tlh: 'The klingon value',
}
}
```
Loading

0 comments on commit 9bc7ba1

Please sign in to comment.