Используйте React-intl переведенные сообщения в промежуточном программном обеспечении Redux

Я поддерживаю несколько языков в своем приложении и использую для этого React-intl. У меня есть промежуточное ПО Redux, где я вызываю сервер и в случае ошибки хочу показать ошибку в пользовательском интерфейсе.

Я знаю, что могу сделать что-то вроде:

1) отправить действие из промежуточного программного обеспечения с помощью ключа сообщения:

{type: SHOW_ERROR, message: 'message_error_key'}

2) в моем использовании компонента React:

<FormattedMessage id={this.props.message_error_key}/>

Но есть ли способ отправить действие с уже переведенным сообщением из промежуточного программного обеспечения?

{type: SHOW_ERROR, message: [translated_message_should_be_here]}

person Anton    schedule 15.04.2016    source источник


Ответы (7)


Возможно, это не самое красивое решение, но вот как мы решили эту проблему;

1) Сначала мы создали компонент IntlGlobalProvider, который наследует контекст и реквизиты от IntlProvider в нашем дереве компонентов;

<ApolloProvider store={store} client={client}>
  <IntlProvider>
      <IntlGlobalProvider>
          <Router history={history} children={routes} />
      </IntlGlobalProvider>
  </IntlProvider>
</ApolloProvider>

2) (внутри IntlGlobalProvider.js) Затем из контекста мы получаем желаемую функциональность intl и предоставляем ее с помощью синглтона.

// 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 { 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)
person Simon Somlai    schedule 07.05.2018
comment
Это тоже полезная ветка; github.com/formatjs/react-intl/issues/416 - person Simon Somlai; 24.03.2020

Я не думаю, что вы можете получить доступ к formatMessage напрямую из промежуточного программного обеспечения, потому что кажется доступен только для компонентов через injectIntl. Вероятно, вы можете сообщить о проблеме, чтобы описать свой вариант использования, и, возможно, будет рассмотрен простой JavaScript API для доступа formatMessage() вне компонентов, но в настоящее время он недоступен.

person Dan Abramov    schedule 16.04.2016
comment
Есть новости по этому поводу? - person lakmal_sathyajith; 14.05.2021

Я столкнулся с похожей проблемой при попытке инициализировать состояние редуктора по умолчанию для локализованных сообщений. Кажется, что использование любой части react-intl вне компонентов - это не то, что было рассмотрено в API. Две идеи:

  1. Внедрить intl в пользовательский компонент ниже <IntlProvider>, что сделает его доступным в componentWillReceiveProps через синглтон для всего приложения. Затем откройте этот синглтон из другого места и используйте intl.formatMessage и другие.

  2. Для реализации необходимых функций можно использовать компоненты Format.js, частью которых является React-intl. В этом случае yahoo / intl-messageformat и yahoo / intl-format-cache. Это, конечно, не будет хорошо интегрироваться с react-intl из коробки.

person Thomas Luzat    schedule 02.05.2016

Теперь поддерживается и возможно форматирование строк вне жизненных циклов React. Вы можете проверить createIntl официальную документацию здесь . Код может выглядеть примерно так:

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);
person Luca Anceschi    schedule 13.04.2020

Вдохновленный ответом Саймона Сомлая выше, здесь эквивалентная версия с использованием перехватчиков реакции:

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

Затем настройте IntlGlobalProvider, как описано в шаге 1 ответа Саймона Сомлая выше. Теперь при использовании intl внутри любого вспомогательного / служебного класса вы можете:

import { appIntl } from 'modules/core/IntlGlobalProvider';

const translation = appIntl().formatMessage({ id: 'hello' });
console.log(translation);

person A. Masson    schedule 19.04.2020

Вы должны использовать getChildContext(), чтобы получить intl, который имеет метод formatMessage().

1.В вашем корневом tsx файле, например App.tsx.

import { IntlProvider, addLocaleData} from 'react-intl'
import * as locale_en from 'react-intl/locale-data/en'
import * as locale_zh from 'react-intl/locale-data/zh'

import message_en from '@/locales/en'
import message_zh from '@/locales/zh-CN'

const messages = {
  'en': flattenMessages(message_en),
  'zh': flattenMessages(message_zh)
}

addLocaleData([...locale_en, ...locale_zh])

const intlProvider = new IntlProvider({ locale: 'zh', messages: messages['zh']})

// export intl
export const { intl } = intlProvider.getChildContext()

  1. В вашем файле саги.

import { intl } from '@/App';

function* handleSubmit() {
  try {
    yield someApi()
  } catch(error) {
    console.log(intl.formatMessage(error.message))
  }
}

Под капотом IntlProvider получает эти реквизиты и имеет метод класса getChildContext.

namespace IntlProvider {
      interface Props {
          locale?: string;
          timeZone?: string;
          formats?: any;
          messages?: any;
          defaultLocale?: string;
          defaultFormats?: any;
          textComponent?: any;
          initialNow?: any;
          onError?: (error: string) => void;
      }
  }
  
class IntlProvider extends React.Component<IntlProvider.Props> {
      getChildContext(): {
          intl: InjectedIntl;
      };
  }

Погрузитесь глубже в интерфейс InjectedIntl. Вы можете понять, почему экземпляр intl имеет метод formatMessage.

interface InjectedIntl {
    formatDate(value: DateSource, options?: FormattedDate.PropsBase): string;
    formatTime(value: DateSource, options?: FormattedTime.PropsBase): string;
    formatRelative(value: DateSource, options?: FormattedRelative.PropsBase & { now?: any }): string;
    formatNumber(value: number, options?: FormattedNumber.PropsBase): string;
    formatPlural(value: number, options?: FormattedPlural.Base): keyof FormattedPlural.PropsBase;
    formatMessage(messageDescriptor: FormattedMessage.MessageDescriptor, values?: {[key: string]: MessageValue}): string;
    formatHTMLMessage(messageDescriptor: FormattedMessage.MessageDescriptor, values?: {[key: string]: MessageValue}): string;
    locale: string;
    formats: any;
    messages: { [id: string]: string };
    defaultLocale: string;
    defaultFormats: any;
    now(): number;
    onError(error: string): void;
}

person qinmu2127    schedule 08.01.2019
comment
getChildContext () больше не существует. Он был заменен на createIntl () - person joe cool; 12.09.2019

Я считаю, что вам следует избегать этого в промежуточном программном обеспечении. Вы можете отправить свое действие с уже переведенными сообщениями.

const deleteUser = (id, messages) => {
   type: DELETE_USER,
   payload: {id, messages}
}

Затем в своей саге (или другом промежуточном программном обеспечении) вы можете использовать это уже переведенное сообщение.

function* deleteUserWatcher({
  payload: { id, messages }
}) {
  try {
    yield request.delete(`/user/${id}`);
    yield put(deleteUserSuccess(id));
    yield put(pushNotificationToStack(message.success));

  } catch (error) {
     yield put(pushNotificationToStack(message.error));
  }
}

Затем в своем компоненте вы можете отправить действие

const dispatch = useDispatch();
const { formatMessage } = useIntl();

const handleDeleteUser = id => {
  dispatch(deleteUser(id, {
     success: formatMessage({
      id: "User.delete.success",
      defaultMessage: "User has been deleted"
     }),
     error: formatMessage({
      id: "User.delete.error",
      defaultMessage: "Ups. Something went wrong. Sorry :("
     }),
   } 
 ));
}

Я знаю, что это подходит не для всех ситуаций, но вы можете охватить большинство случаев с помощью этого подхода.

person Martin Zahradnik    schedule 24.11.2019