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

Posted

技术标签:

【中文标题】在 Redux 中间件中使用 React-intl 翻译的消息【英文标题】:Use React-intl translated messages in Redux middleware 【发布时间】:2016-08-07 12:41:36 【问题描述】:

我在我的应用程序中支持多种语言并为此使用 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】:

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

【讨论】:

【参考方案2】:

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

    intl 注入到&lt;IntlProvider&gt; 下方的自定义组件中,使其通过应用程序范围的单例在componentWillReceiveProps 中可用。接下来从其他地方访问该单身人士并使用intl.formatMessage 和其他人。

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

【讨论】:

【参考方案3】:

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

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)

【讨论】:

这也是一个有用的线程; github.com/formatjs/react-intl/issues/416【参考方案4】:

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

1.在您的根 tsx 文件中,例如应用程序.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()
    在您的 saga 文件中。

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;

【讨论】:

getChildContext() 不再存在。它已被 createIntl() 取代【参考方案5】:

我认为您应该避免在中间件中这样做。您可以使用已翻译的消息发送您的操作。

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

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

【讨论】:

【参考方案6】:

现在支持和可行在 React 生命周期之外格式化字符串。可以查看createIntl官方文档here。代码可能类似于:

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

【讨论】:

【参考方案7】:

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

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,如上面Simon Somlai 答案的第1 步所述。现在,在任何帮助程序/实用程序类中使用 intl 时,您可以这样做:

import  appIntl  from 'modules/core/IntlGlobalProvider';

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

【讨论】:

以上是关于在 Redux 中间件中使用 React-intl 翻译的消息的主要内容,如果未能解决你的问题,请参考以下文章

使用 React-intl 的 Redux-form 字段级验证和错误翻译

React-Intl 如何从变量切换语言环境和消息

如何在 reducer 中处理 Redux 的 redux-promise 中间件 AJAX 错误?

在 redux 中使用 thunk 中间件与使用常规函数作为异步操作创建者相比有啥好处? [关闭]

Redux 中间件和 GraphQL

为啥 axios .catch() 在 redux 中间件中捕获 javascript 错误?