Skip to content

Commit 9bc7ba1

Browse files
authored
Merge pull request #5810 from marmelab/translatable
Introduce Translatable inputs and fields
2 parents 1e29aed + 74b79d4 commit 9bc7ba1

28 files changed

+1424
-30
lines changed

docs/Fields.md

+133
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,139 @@ export const PostShow = (props) => (
11061106
);
11071107
```
11081108

1109+
## Translatable Fields
1110+
1111+
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:
1112+
1113+
```js
1114+
{
1115+
name: {
1116+
en: 'The english value',
1117+
fr: 'The french value',
1118+
tlh: 'The klingon value',
1119+
},
1120+
description: {
1121+
en: 'The english value',
1122+
fr: 'The french value',
1123+
tlh: 'The klingon value',
1124+
}
1125+
}
1126+
```
1127+
1128+
This is how to use it:
1129+
1130+
```jsx
1131+
<TranslatableFields locales={['en', 'fr']}>
1132+
<TextField source="name">
1133+
<TextField source="description">
1134+
</TranslatableFields>
1135+
```
1136+
1137+
React-admin uses the user locale as the default locale in this field. You can override this setting using the `defaultLocale` prop.
1138+
1139+
```jsx
1140+
<TranslatableFields locales={['en', 'fr']} defaultLocale="fr">
1141+
<TextField source="name">
1142+
<TextField source="description">
1143+
</TranslatableFields>
1144+
```
1145+
1146+
By default, `<TranslatableFields>` will allow users to select the displayed locale using Material-ui tabs with the locale code as their labels.
1147+
1148+
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`.
1149+
1150+
You may override the language selector using the `selector` prop, which accepts a React element:
1151+
1152+
```jsx
1153+
const Selector = () => {
1154+
const {
1155+
locales,
1156+
selectLocale,
1157+
selectedLocale,
1158+
} = useTranslatableContext();
1159+
1160+
const handleChange = (event): void => {
1161+
selectLocale(event.target.value);
1162+
};
1163+
1164+
return (
1165+
<select
1166+
aria-label="Select the locale"
1167+
onChange={handleChange}
1168+
value={selectedLocale}
1169+
>
1170+
{locales.map(locale => (
1171+
<option
1172+
key={locale}
1173+
value={locale}
1174+
// This allows to correctly link the containers for each locale to their labels
1175+
id={`translatable-header-${locale}`}
1176+
>
1177+
{locale}
1178+
</option>
1179+
))}
1180+
</select>
1181+
);
1182+
};
1183+
1184+
<TranslatableFields
1185+
record={record}
1186+
resource="products"
1187+
basePath="/products"
1188+
locales={['en', 'fr']}
1189+
selector={<Selector />}
1190+
>
1191+
<TextField source="name" />
1192+
<TextField source="description" />
1193+
</TranslatableFields>
1194+
```
1195+
1196+
If you have multiple `TranslatableFields` on the same page, you should specify a `groupKey` so that react-admin can create unique identifiers for accessibility.
1197+
1198+
```jsx
1199+
<TranslatableFields locales={['en', 'fr']} groupKey="essential-fields">
1200+
<TextField source="name">
1201+
<TextField source="description">
1202+
</TranslatableFields>
1203+
```
1204+
1205+
### Using Translatable Fields In List or Show views
1206+
1207+
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.
1208+
1209+
In this case, you'll have to get the current locale through the `useLocale` hook and set the translatable field `source` dynamically.
1210+
1211+
{% raw %}
1212+
```jsx
1213+
const PostList = (props) => {
1214+
const locale = useLocale();
1215+
1216+
return (
1217+
<List {...props}>
1218+
<Datagrid>
1219+
<TextField source={`name.${locale}`}>
1220+
<ReferenceArrayField
1221+
label="Tags"
1222+
reference="tags"
1223+
source="tags"
1224+
sortBy="tags.name"
1225+
sort={{ field: `name.${locale}`, order: 'ASC' }}
1226+
>
1227+
<SingleFieldList>
1228+
<ChipField source={`name.${locale}`} size="small" />
1229+
</SingleFieldList>
1230+
</ReferenceArrayField>
1231+
</Datagrid>
1232+
</List>
1233+
)
1234+
}
1235+
```
1236+
{% endraw %}
1237+
1238+
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.
1239+
1240+
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.
1241+
11091242
## Recipes
11101243

11111244
### Styling Fields

docs/Inputs.md

+105
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,111 @@ export default ArtistEdit;
16731673

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

1676+
## Translatable Inputs
1677+
1678+
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:
1679+
1680+
```js
1681+
{
1682+
name: {
1683+
en: 'The english value',
1684+
fr: 'The french value',
1685+
tlh: 'The klingon value',
1686+
},
1687+
description: {
1688+
en: 'The english value',
1689+
fr: 'The french value',
1690+
tlh: 'The klingon value',
1691+
}
1692+
}
1693+
```
1694+
1695+
This is how to use it:
1696+
1697+
```jsx
1698+
<TranslatableInputs locales={['en', 'fr']}>
1699+
<TextInput source="name">
1700+
<RichTextInput source="description">
1701+
</TranslatableInputs>
1702+
```
1703+
1704+
React-admin uses the user locale as the default locale in this field. You can override this setting using the `defaultLocale` prop.
1705+
1706+
```jsx
1707+
<TranslatableInputs locales={['en', 'fr']} defaultLocale="fr">
1708+
<TextInput source="name">
1709+
<RichTextInput source="description">
1710+
</TranslatableInputs>
1711+
```
1712+
1713+
By default, `<TranslatableInputs>` will allow users to select the displayed locale using Material-ui tabs with the locale code as their labels.
1714+
1715+
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`.
1716+
1717+
You may override the language selector using the `selector` prop, which accepts a React element:
1718+
1719+
```jsx
1720+
const Selector = () => {
1721+
const {
1722+
locales,
1723+
selectLocale,
1724+
selectedLocale,
1725+
} = useTranslatableContext();
1726+
1727+
const handleChange = (event): void => {
1728+
selectLocale(event.target.value);
1729+
};
1730+
1731+
return (
1732+
<select
1733+
aria-label="Select the locale"
1734+
onChange={handleChange}
1735+
value={selectedLocale}
1736+
>
1737+
{locales.map(locale => (
1738+
<option
1739+
key={locale}
1740+
value={locale}
1741+
// This allows to correctly link the containers for each locale to their labels
1742+
id={`translatable-header-${locale}`}
1743+
>
1744+
{locale}
1745+
</option>
1746+
))}
1747+
</select>
1748+
);
1749+
};
1750+
1751+
<TranslatableInputs
1752+
record={record}
1753+
resource="products"
1754+
basePath="/products"
1755+
locales={['en', 'fr']}
1756+
selector={<Selector />}
1757+
>
1758+
<TextInput source="name" />
1759+
<RichTextInput source="description" />
1760+
</TranslatableInputs>
1761+
```
1762+
1763+
If you have multiple `TranslatableInputs` on the same page, you should specify a `groupKey` so that react-admin can create unique identifiers for accessibility.
1764+
1765+
```jsx
1766+
<TranslatableInputs locales={['en', 'fr']} groupKey="essential-fields">
1767+
<TextInput source="name">
1768+
<RichTextInput source="description">
1769+
</TranslatableInputs>
1770+
```
1771+
1772+
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:
1773+
1774+
```jsx
1775+
<TranslatableInputs locales={['en', 'fr']}>
1776+
<TextInput source="name" validate={[required()]}>
1777+
<RichTextInput source="description" validate={[maxLength(100)]}>
1778+
</TranslatableInputs>
1779+
```
1780+
16761781
## Recipes
16771782

16781783
### Transforming Input Value to/from Record

docs/Translation.md

+24
Original file line numberDiff line numberDiff line change
@@ -697,3 +697,27 @@ const i18nProvider = polyglotI18nProvider(locale =>
697697
**Tip**: Check [the Polyglot documentation](https://airbnb.io/polyglot.js/#options-overview) for a list of options you can pass to Polyglot at startup.
698698
699699
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.
700+
701+
## Translating Record Fields
702+
703+
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:
704+
705+
- To display translatable fields, use the [`<TranslatableFields>`](/Fields.html#translatable-fields) component
706+
- To edit translatable fields, use the [`<TranslatableInputs>`](/Inputs.html#translatable-inputs) component
707+
708+
They both expect the translatable values to have the following structure:
709+
710+
```js
711+
{
712+
name: {
713+
en: 'The english value',
714+
fr: 'The french value',
715+
tlh: 'The klingon value',
716+
},
717+
description: {
718+
en: 'The english value',
719+
fr: 'The french value',
720+
tlh: 'The klingon value',
721+
}
722+
}
723+
```

0 commit comments

Comments
 (0)