react-admin: translate doesn't work

I am using admin on rest 1.3.1 and I am reading at this documentation :

https://marmelab.com/admin-on-rest/Translation.html#translation-messages

It says that you can import a translate method like so:

// in src/MyHelloButton.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class MyHelloButton {
    render() {
        const { translate } = this.context;
        return <button>{translate('myroot.hello.world')}</button>;
    }
}
MyHelloButton.contextTypes = {
    translate: PropTypes.func,
};

I have a <IntlProvider /> wrapping my react app and messages are fully loaded but:

  • Using translate throw error :

Warning: Failed context type: The context translate is marked as required in getContext(MyHelloButton), but its value is undefined. in getContext(MyHelloButton) (created by HomePage)

I have read at the react-intl documentation here https://github.com/yahoo/react-intl/wiki/API#injectintl and they provide a HoC injectIntl that can be used to retrieve messages.

This is how I fixed translate.js with injectIntl:

import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';

export default (Component) => {
  function TranslateComponent(props) {
    const { intl, ...rest } = props;
    return (
      <Component {...rest} translate={(id) => intl.formatMessage({ id })} />
    );
  }

  return compose(
    connect(),
    injectIntl,
  )(TranslateComponent)
}

I am using react-intl 2.4.0.

  • Why did AOR added such method ?
  • Which one I should use ?
  • It’s a shame to only use translate if we can’t access formatDate and other helpers so I think injectIntl should be use.

Also if anybody know why I have this error, thanks:!

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 27 (27 by maintainers)

Most upvoted comments

@fzaninotto , I’ve just wrote the following adapter for v2 and that’s the proof react-admin can support both polyglot to support react-intl without any additional maintenance cost.

I use it over the original TranslationProvider, this way I don’t need to change anything else in the ra-core.

/*
 *
 * LanguageProvider
 *
 * this component connects the redux state language locale to the
 * IntlProvider component and i18n messages
 */

import React, { PureComponent, Children, cloneElement } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import { compose, withContext } from 'recompose';
import { createStructuredSelector } from 'reselect';
import { IntlProvider } from 'react-intl';
import defaultMessages from 'ra-language-english';

import { selectLocale, selectMessages } from './selectors';

/* eslint-disable no-restricted-syntax, no-continue */
export const fromPolyglot = (obj) => {
  const toReturn = {};
  for (const i in obj) {
    if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
    if (typeof obj[i] === 'object') {
      const flatObject = fromPolyglot(obj[i]);
      for (const x in flatObject) {
        if (!Object.prototype.hasOwnProperty.call(flatObject, x)) continue;
        toReturn[`${i}.${x}`] = flatObject[x];
      }
    } else {
      toReturn[i] = obj[i];
    }
  }
  return toReturn;
};

class TranslationProvider extends PureComponent {
  render() {
    return cloneElement(Children.only(this.props.children), { intl: this.props.intl });
  }
}

const withI18nContextTranslationProvider = withContext(
  {
    translate: PropTypes.func.isRequired,
  },
  (props) => {
    return {
      translate: (id, opts) => console.log(opts) || props.intl.formatMessage({ id }, opts),
    };
  }
);

const ConnectedTranslationProvider = compose(injectIntl, withI18nContextTranslationProvider)(
    TranslationProvider
);

// eslint-disable-next-line react/prefer-stateless-function
export class LanguageProvider extends PureComponent {
  render() {
    return (
      <IntlProvider locale={this.props.locale} key={this.props.locale} messages={fromPolyglot(this.props.messages) || fromPolyglot(defaultMessages)}>
        <ConnectedTranslationProvider>
          {this.props.children}
        </ConnectedTranslationProvider>
      </IntlProvider>
    );
  }
}

LanguageProvider.defaultProps = {
  locale: 'en',
};

LanguageProvider.propTypes = {
  locale: PropTypes.string,
  messages: PropTypes.object,
  children: PropTypes.element.isRequired,
};

const mapStateToProps = createStructuredSelector({
  locale: selectLocale,
  messages: selectMessages,
});

const withI18nContextLanguageProvider = withContext(
  {
    locale: PropTypes.string.isRequired,
    messages: PropTypes.object,
  },
  (props) => {
    return {
      locale: props.locale,
      messages: props.messages,
    };
  }
);

export default compose(connect(mapStateToProps), withI18nContextLanguageProvider)(
    LanguageProvider
);

That’s just a demo to be improved.

I know we have been on that topic before but we are replacing polyglot and we will proceed to i18n translation.

If you want to have both compatibility just say it and I can spend time trying to merge that for the rest of the community.

Otherwise we are happy to use this on our fork.

I guess so but a few change:

import { Children } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose, getContext, withContext } from 'recompose';

const withReactIntlContext = getContext({
    intl: PropTypes.object.isRequired,
});

const withI18nContext = withContext(
    {
        translate: PropTypes.func.isRequired,
        locale: PropTypes.string.isRequired,
    },
    ({ intl }) => {
        return {
            locale: intl.locale,
            translate: (id) => intl.formatMessage({ id }),
        };
    },
);

const TranslationProvider = ({ children }) => Children.only(children);

TranslationProvider.propTypes = {
    children: PropTypes.element,
};

const mapStateToProps = state => ({ locale: state.locale });

export default compose(
    connect(mapStateToProps),
    withReactIntlContext,
    withI18nContext,
)(TranslationProvider);

Correct me if I am wrong but this will serve the context twice (and probably duplicate) ?