I support several languages in my application and use React-intl for this. I have the Redux middleware where I make a call to the server and in the case of error I want to show an error on the UI.
I know that I can do something like:
1) dispatch an action from middleware with a message key:
{type: SHOW_ERROR, message: 'message_error_key'}
2) in my React component use:
<FormattedMessage id={this.props.message_error_key}/>
But is there a way to dispatch an action with already translated message from middleware?
{type: SHOW_ERROR, message: [translated_message_should_be_here]}
It might not be the prettiest solution, but here's how we solved this problem;
1) First we created a 'IntlGlobalProvider' component that inherits the context and props from the IntlProvider in our component tree;
<ApolloProvider store={store} client={client}>
<IntlProvider>
<IntlGlobalProvider>
<Router history={history} children={routes} />
</IntlGlobalProvider>
</IntlProvider>
</ApolloProvider>
2) (inside IntlGlobalProvider.js) Then out of the context we get the intl functionality we want and expose this by a singleton.
// NPM Modules
import { intlShape } from 'react-intl'
// ======================================================
// React intl passes the messages and format functions down the component
// tree using the 'context' scope. the injectIntl HOC basically takes these out
// of the context and injects them into the props of the component. To be able to
// import this translation functionality as a module anywhere (and not just inside react components),
// this function inherits props & context from its parent and exports a singleton that'll
// expose all that shizzle.
// ======================================================
var INTL
const IntlGlobalProvider = (props, context) => {
INTL = context.intl
return props.children
}
IntlGlobalProvider.contextTypes = {
intl: intlShape.isRequired
}
// ======================================================
// Class that exposes translations
// ======================================================
var instance
class IntlTranslator {
// Singleton
constructor() {
if (!instance) {
instance = this;
}
return instance;
}
// ------------------------------------
// Formatting Functions
// ------------------------------------
formatMessage (message, values) {
return INTL.formatMessage(message, values)
}
}
export const intl = new IntlTranslator()
export default IntlGlobalProvider
3) Import it anywhere as a module
import { defineMessages } from 'react-intl'
import { intl } from 'modules/core/IntlGlobalProvider'
const intlStrings = defineMessages({
translation: {
id: 'myid',
defaultMessage: 'Hey there',
description: 'someStuff'
},
intl.formatMessage(intlStrings.translation)
I don’t think you can access formatMessage
directly from middleware because it seems only exposed to components via injectIntl
. You can probably file an issue to describe your use case, and maybe a plain JavaScript API to access formatMessage()
outside the components will be considered, but it doesn’t seem available now.
Inspired by Simon Somlai's answer above, here the equivalent version using react hooks:
import React from 'react';
import { useIntl } from 'react-intl';
// 'intl' service singleton reference
let intl;
export function IntlGlobalProvider({ children }) {
intl = useIntl(); // Keep the 'intl' service reference
return children;
}
// Getter function to expose the read-only 'intl' service
export function appIntl() {
return intl;
}
Then setup IntlGlobalProvider
as explained by step 1 of Simon Somlai's answer above. Now when using intl
inside any helper/utility class you can do:
import { appIntl } from 'modules/core/IntlGlobalProvider';
const translation = appIntl().formatMessage({ id: 'hello' });
console.log(translation);
I ran into a somewhat similar problem when trying to initialize a reducer's default state to localized messages. It seems that using any part of react-intl outside of components is nothing which has been considered in the API. Two ideas:
Inject intl
into a custom component below <IntlProvider>
which makes it available in componentWillReceiveProps
through an application-wide singleton. Next access that singleton from elsewhere and use intl.formatMessage
and others.
It is possible to use the Format.js components that React-intl is part of to implement the required functionality. In this case yahoo/intl-messageformat and yahoo/intl-format-cache can be considered. This would of course not integrate well with react-intl out of the box.
It is supported and feasible now to format strings outside of React lifecycles. You can check the createIntl
official documentation here. The code might looks similar to this:
intl.js
import { createIntl, createIntlCache } from 'react-intl';
let cache;
let intl;
/**
* Generate IntlShape object
* @param {Object} props
* @param {String} props.locale - User specified language
* @param {Object} props.messages - Messages
* @returns {Object}
*/
const generateIntl = props => {
if (cache) {
cache = null;
}
cache = createIntlCache();
intl = createIntl(props, cache);
return intl;
};
export { generateIntl, intl };
root-component.jsx
import React from 'react';
import { RawIntlProvider, FormattedMessage } from 'react-intl';
import { generateIntl } from './intl';
const messages = { hello: 'Hello' };
const intlValue = generateIntl({ locale: 'en', messages });
export const RootComponent = () => {
return (
<RawIntlProvider value={intlValue}>
<FormattedMessage id="hello" />
</RawIntlProvider>
);
};
intl-consumer-script.js
import { intl } from './intl';
const translation = intl.formatMessage({ id: 'hello' });
console.log(translation);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With