私はアプリケーションでいくつかの言語をサポートしており、これには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]}
formatMessage
を介してコンポーネントにのみ公開されているように見えるため、ミドルウェアから直接 injectIntl
にアクセスできるとは思いません。ユースケースを説明するために問題を提出することができます。コンポーネントの外部でformatMessage()
にアクセスするためのプレーンなJavaScriptAPIが検討されるかもしれませんが、現在は利用できないようです。
これは最も美しい解決策ではないかもしれませんが、この問題を解決する方法は次のとおりです。
1)最初に、コンポーネントツリーのIntlProviderからコンテキストと小道具を継承する 'IntlGlobalProvider'コンポーネントを作成しました;
<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
)モジュールとしてどこにでもインポートします
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)
レデューサーのデフォルト状態をローカライズされたメッセージに初期化しようとしたときに、多少似た問題が発生しました。コンポーネントの外部でreact-intlの一部を使用することは、APIで考慮されていることではないようです。 2つのアイデア:
intl
を<IntlProvider>
の下のカスタムコンポーネントに挿入します。これにより、アプリケーション全体のシングルトンを介してcomponentWillReceiveProps
で使用できるようになります。次に、他の場所からそのシングルトンにアクセスし、intl.formatMessage
などを使用します。
React-intlが含まれている Format.js コンポーネントを使用して、必要な機能を実装することができます。この場合、 yahoo/intl-messageformat および yahoo/intl-format-cache を考慮することができます。もちろん、これはそのままではreact-intlとうまく統合できません。
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);
上記の 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);
ミドルウェアでこれを行うことは避けるべきだと思います。すでに翻訳されたメッセージを使用してアクションをディスパッチできます。
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 :("
}),
}
));
}
これがすべての状況に当てはまるわけではないことは知っていますが、このアプローチでほとんどのケースをカバーできます
一番簡単な方法は
injectIntl
from'react-intl 'でラップしますintl
を取得しますintl
intl.formatMessage
を使用できます(intl
はアクションから取得されます)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()
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;
}