在 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
注入到<IntlProvider>
下方的自定义组件中,使其通过应用程序范围的单例在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 字段级验证和错误翻译
如何在 reducer 中处理 Redux 的 redux-promise 中间件 AJAX 错误?