在 Redux 中间件中使用 React-intl 翻译的消息



我在我的应用程序中支持多种语言,并为此使用 React-intl。我有 Redux 中间件,我可以在其中调用服务器,如果出现错误,我想在 UI 上显示错误。

我知道我可以做这样的事情:

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

这可能不是最漂亮的解决方案,但这是我们解决这个问题的方法;

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)

我认为您不能直接从中间件访问formatMessage,因为它似乎只能通过injectIntl暴露于组件。您可能会提交一个问题来描述您的用例,并且可能会考虑使用简单的JavaScript API来访问组件之外的formatMessage(),但现在似乎不可用。

受到上面 Simon Somlai 的回答的启发,这里是使用 react 钩子的等效版本:

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

然后按照上面Simon Somlai回答的步骤1的解释进行设置IntlGlobalProvider。现在,当在任何帮助程序/实用程序类中使用intl时,您可以执行以下操作:

import { appIntl } from 'modules/core/IntlGlobalProvider';
const translation = appIntl().formatMessage({ id: 'hello' });
console.log(translation);

现在支持并且在 React 生命周期之外格式化字符串是可行的。您可以在此处查看createIntl官方文档。代码可能如下所示:

国际机场.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>
  );
};

国际消费者脚本.js

import { intl } from './intl';
const translation = intl.formatMessage({ id: 'hello' });
console.log(translation);

在尝试将化简器的默认状态初始化为本地化消息时,我遇到了类似的问题。似乎在组件之外使用 react-intl 的任何部分在 API 中都没有考虑过。两个想法:

  1. intl注入到下面的自定义组件中<IntlProvider>这样,它就可以通过应用程序范围的单一实例在componentWillReceiveProps中使用。接下来从其他地方访问该单例并使用intl.formatMessage和其他

  2. 可以使用 Format.js 组件来实现所需的功能。在这种情况下,可以考虑yahoo/intl-messageformat和yahoo/intl-format-cache。这当然不能与开箱即用的 react-intl 很好地集成。

你必须使用 getChildContext() 来获取intl,它有formatMessage()方法。

1.In 根 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;
}

我认为

你应该避免在中间件中这样做。您可以使用已翻译的消息调度您的操作。

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

然后在您的 saga(或其他中间件(中,您可以使用这个已经翻译的消息。

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 :("
     }),
   } 
 ));
}

我知道这并不适合所有情况,但是您可以使用这种方法涵盖大多数情况

最新更新