Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use React-intl translated messages in Redux middleware

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]}
like image 849
Anton Avatar asked Apr 15 '16 13:04

Anton


5 Answers

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)
like image 161
Simon Somlai Avatar answered Oct 25 '22 10:10

Simon Somlai


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.

like image 30
Dan Abramov Avatar answered Oct 25 '22 10:10

Dan Abramov


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);

like image 4
A. Masson Avatar answered Oct 25 '22 09:10

A. Masson


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:

  1. 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.

  2. 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.

like image 3
Thomas Luzat Avatar answered Oct 25 '22 09:10

Thomas Luzat


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);
like image 3
Luca Anceschi Avatar answered Oct 25 '22 10:10

Luca Anceschi