React Admin v3: Zoom in the i18n Layer
React-admin v3 is nearing a stable state, and the 3.0 release is around the corner. Should you upgrade? If you're developing an internationalized application, the answer is: definitely! Because we've overhauled the i18n subsystem, and the new one offers extreme flexibility and an improved developer experience.
This tutorial shows how it affects common usage of the i18n layer: translating labels, showing the current locale, and offering a language switch menu.
Translating Labels
If you need to translate messages in your own components, React-admin v2 provides a translate
HOC that injects a translate
callback:
// in src/MyHelloButton.js
import React from "react";
import { translate } from "react-admin";
const MyHelloButton = ({ translate }) => (
<button>{translate("myroot.hello.world")}</button>
);
export default translate(MyHelloButton);
The fact that the HOC and the injected callback share the same name was confusing. Besides, HOCs are so 2018. In react-admin v3, prefer the useTranslate
hook, which returns the translate
function:
// in src/MyHelloButton.js
import React from "react";
import { useTranslate } from "react-admin";
const MyHelloButton = () => {
const translate = useTranslate();
return <button>{translate("myroot.hello.world")}</button>;
};
export default MyHelloButton;
Don't use useTranslate
for Field and Input labels, or for page titles, as they are already translated:
// don't do this
<TextField source="first_name" label={translate('myroot.first_name')} />
// do this instead
<TextField source="first_name" label="myroot.first_name" />
// or even better, use the default translation key
<TextField source="first_name" />
// and translate the `resources.customers.fields.first_name` key
The translate
function returned by the translate
HOC and the useTranslate
hook are the same. It relies on Polyglot.js, an i18n library open-sourced by Airbnb, which supports replacement and pluralization:
const DeleteItemsButton = ({ items, type }) => {
const translate = useTranslate();
return (
<button>{translate('myroot.action.delete_items', { smart_count: items.length, type })}</button>
);
}
// uses this translation messages
const englishMessages = {
// ...
myroot: {
action: {
delete_items: "Delete one %{type} |||| Delete %{smart_count} %{type}s",
}
}
}
// later in the code
<DeleteItemsButton type="post" items={[{}, {}]} /> // "Delete 2 posts"
Showing The Current Locale And Allowing to Change It
Let's imagine that you want to display a languages menu, showing the available languages and allowing the user to choose another one.
In react-admin v2, you had to grab the current locale from the Redux store, and dispatch a Redux action to change it:
import React from "react";
import Button from "@material-ui/core/Button";
import { connect } from "react-redux";
import compose from "recompose/compose";
import { translate, changeLocale } from "react-admin";
const LocaleSwitcher = ({ locale, changeLocale, translate }) => {
return (
<div>
<div>{translate("myroot.language.label")}</div>
<Button disabled={locale === "fr"} onClick={() => changeLocale("fr")}>
{translate("myroot.language.french")}
</Button>
<Button disabled={locale === "en"} onClick={() => changeLocale("en")}>
{translate("myroot.language.english")}
</Button>
</div>
);
};
const mapStateToProps = state => ({
locale: state.i18n.locale,
});
const enhance = compose(
connect(
mapStateToProps,
{ changeLocale }
),
translate
);
export default enhance(LocaleSwitcher);
In v3, no more Redux: use the useLocale
and useSetLocale
hooks in addition to the useTranslate
hook:
import React from "react";
import Button from "@material-ui/core/Button";
import { useTranslate, useLocale, useSetLocale } from "react-admin";
const LocaleSwitcher = () => {
const translate = useTranslate();
const locale = useLocale();
const setLocale = useSetLocale();
return (
<div>
<div>{translate("myroot.language.label")}</div>
<Button disabled={locale === "fr"} onClick={() => setLocale("fr")}>
{translate("myroot.language.french")}
</Button>
<Button disabled={locale === "en"} onClick={() => setLocale("en")}>
{translate("myroot.language.english")}
</Button>
</div>
);
};
export default LocaleSwitcher;
This component uses zero HOC, is more idiomatic to modern react, it's easier to read and test.
Injecting A Different i18nProvider
Under the hood, all the i18n tasks are handled by the i18nProvider
. React-admin grabs it from the <Admin>
component, and puts it in a React context.
import i18nProvider from './i18n/i18nProvider';
const App = () => (
<Admin
dataProvider={dataProvider}
i18nProvider={i18nProvider}
>
<Resource name="posts" list={...}>
// ...
The i18n hooks are mostly proxies for the i18nProvider
, which they get from the context. The i18nProvider
signature is:
const i18nProvider = {
translate: (key, options) => string,
changeLocale: locale => Promise,
getLocale: () => string,
};
And that's all. By default, react-admin uses an i18nProvider
based on Polyglot.js, but you can use an alternative provider, e.g. to handle translations in GETTEXT
files rather than JSON.
Upgrade Path
As the i18nProvider
signature changed, you'll need a small change in your react-admin v2 application to be able to use v3. To ease the transition, react-admin v3 contains a module called ra-i18n-polyglot
, that is a wrapper around your old i18nProvider
to make it compatible with the new provider signature:
Besides, the Admin
component does not accept a locale
prop anymore as it is the i18nProvider
provider responsibility:
import React from 'react';
import { Admin, Resource } from 'react-admin';
+import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import frenchMessages from 'ra-language-french';
const messages = {
fr: frenchMessages,
en: englishMessages,
};
-const i18nProvider = locale => messages[locale];
+const i18nProvider = polyglotI18nProvider(locale => messages[locale], 'fr');
const App = () => (
- <Admin locale="fr" i18nProvider={i18nProvider}>
+ <Admin i18nProvider={i18nProvider}>
...
</Admin>
);
export default App;
Also, if you used to dispatch the changeLocale
action, you'll have to call the useSetLocale
hook instead.
Conclusion
With react-admin v3, you shouldn't have to look for a path in the Redux store, or mess with action creators. You shouldn't be blocked by a dependency either: you can replace any provider (i18nProvider
, but also authProvider
and dataProvider
) by your own. In short, react-admin v3 is more straightforward to learn and use.
We bet you'll love it!