v20.0.0
20.0.0 (2022-06-17)
BREAKING CHANGES
- By adding i18n support to the Paragon design system, we are introducing a peer dependency on
[email protected]
or greater. This may be a breaking change for some consumers, if your repository:- Uses v1 of
@edx/frontend-platform
. - Uses older version of
react-intl
than v5.25.0 directly. - Does not use
react-intl
.
- Uses v1 of
For Consumers
Since Paragon now relies on react-intl
, your whole application needs to be wrapped in its context, e.g.:
import { IntlProvider } from 'react-intl';
import { messages as paragonMessages } from '@edx/paragon';
ReactDOM.render(
<IntlProvider locale={usersLocale} messages={paragonMessages[usersLocale]}>
<App />
</IntlProvider>,
document.getElementById('root')
);
Note that if you are using @edx/frontend-platform
's AppProvider
component you don't need a separate context; you would only need to add Paragon's messages during application initialization like this:
import { APP_READY, subscribe, initialize } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { messages as paragonMessages } from '@edx/paragon';
import App from './App';
// this is your app's i18n messages
import appMessages from './i18n';
subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider>
<App />
</AppProvider>,
document.getElementById('root')
);
});
initialize({
// this will add your app's messages as well as Paragon's messages to your app
messages: [
appMessages,
paragonMessages,
],
// any other configurations for you app...
});
Related, any tests that render Paragon components with translations will similarly need to be wrapped with an IntlProvider
component so the Paragon components can read from the intl
React context. For example, if you're testing MyComponent
which renders an Alert
component from Paragon, MyComponent
must be nested under an IntlProvider
with a valid locale.
const MyComponentWrapper = () => {
return (
<IntlProvider locale="en">
<MyComponent />
</IntlProvider>
);
}
For Contributors to Paragon
When developing a new component you should generally follow three rules:
-
The component should not have any hardcoded strings as it would be impossible for consumers to translate it
-
Internationalize all default values of props that expect strings, i.e.
-
For places where you need to display a string, and it's okay if it is a React element use
FormattedMessage
, e.g. (see Alert component for a full example)import { FormattedMessage } from 'react-intl'; <FormattedMessage id="pgn.Alert.closeLabel" defaultMessage="Dismiss" description="Label of a close button on Alert component" />
-
For places where the display string has to be a plain JavaScript string use
formatMessage
, this would require access tointl
object fromreact-intl
, e.g.-
For class components use
injectIntl
HOCimport { injectIntl } from 'react-intl'; class MyClassComponent extends React.Component { render() { const { altText, intl } = this.props; const intlAltText = altText || intl.formatMessage({ id: 'pgn.MyComponent.altText', defaultMessage: 'Close', description: 'Close label for Toast component', }); return ( <IconButton alt={intlCloseLabel} onClick={() => {}} variant="primary" /> ) } } export default injectIntl(MyClassComponent);
-
For functional components use
useIntl
hookimport { useIntl } from 'react-intl'; const MyFunctionComponent = ({ altText }) => { const intls = useIntl(); const intlAltText = altText || intl.formatMessage({ id: 'pgn.MyComponent.altText', defaultMessage: 'Close', description: 'Close label for Toast component', }); return ( <IconButton alt={intlCloseLabel} onClick={() => {}} variant="primary" /> ) export default MyFunctionComponent;
-
Notes on the format above:
id
is required and must be a dot-separated string of the formatpgn.<componentName>.<subcomponentName>.<propName>
- The
defaultMessage
is required, and should be the English display string. - The
description
is optional, but highly recommended, this text gives context to translators about the string.
-
-
If your component expects a string as a prop, allow the prop to also be an element since consumers may want to also pass instance of their own translated string, for example you might define a string prop like this:
MyComponent.PropTypes = { myProp: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), };