Как использовать response-i18next с подключенным компонентом

Я хотел бы использовать реактив-i18next с моим компонентом, подключенным к реактору-редуксу, и не уверен, как это сделать.

Я упростил свой код, чтобы показать пример подключенного компонента:

import React from 'react';
import {connect} from 'react-redux';
import {userSelectors} from "./userSelectors";

interface IConnectedProps {
    activeUserName: string | undefined;
}
export class LandingPageComponent extends React.Component<IConnectedProps> {
    public render(): JSX.Element {
        return (
            <React.Suspense fallback={<Spinner/>}>
                <React.Fragment>
                    <div>
                    ... a bunch of controls using translated text
                    </div>
                    <div>{activeUserName}</div>
                </React.Fragment>
            </React.Suspense>
        );
    }
}

const mapStateToProps = (state: ICoreRootState) : IConnectedProps => ({
    activeUserName: userSelectors.getDisplayName(state),
});

export const LandingPage = connect(mapStateToProps)(LandingPageComponent);

Установленные версии пакета:

react version: 16.8.4 
react-redux version: 5.1.1 
react-i18next version: 10.6.0

Что я пробовал:

1) я получаю ошибку ниже, когда я использую withTranslation, WithTranslation следующим образом:

export class LandingPageComponent extends React.Component<IConnectedProps & WithTranslation> {...}
export const LandingPage = connect(mapStateToProps)(withTranslation()(LandingPageComponent));

Ошибка:

The above error occurred in the <withI18nextTranslation(LandingPageComponent)> component:
    in withI18nextTranslation(LandingPageComponent) (created by Connect(withI18nextTranslation(LandingPageComponent)))
    in Connect(withI18nextTranslation(LandingPageComponent))
    in Route
    in t
    in Connect(t) (at App.tsx:49)
    in Switch (at App.tsx:45)
    in App (at src/index.tsx:14)
    in Router (created by ConnectedRouter)
    in ConnectedRouter (created by Connect(ConnectedRouter))
    in Connect(ConnectedRouter) (at src/index.tsx:13)
    in Provider (at src/index.tsx:12)

2) я получаю ошибку ниже, когда я использую withTranslation, WithTranslation следующим образом:

export class LandingPageComponent extends React.Component<IConnectedProps & WithTranslation> {...}
export const LandingPage = withTranslation()(connect(mapStateToProps)(LandingPageComponent));

Ошибка:

index.js:1446 The above error occurred in the <withI18nextTranslation(Connect(LandingPageComponent))> component:
    in withI18nextTranslation(Connect(LandingPageComponent))
    in Route
    in t
    in Connect(t) (at App.tsx:49)
    in Switch (at App.tsx:45)
    in App (at src/index.tsx:14)
    in Router (created by ConnectedRouter)
    in ConnectedRouter (created by Connect(ConnectedRouter))
    in Connect(ConnectedRouter) (at src/index.tsx:13)
    in Provider (at src/index.tsx:12)

3) Я не могу использовать useTranslation, поскольку в классе не разрешено использовать хуки.

Я также попробовал следующее:

... a bunch of imports
interface ILogoutButtonProps {
    userName?: string;
}
interface IConnectedHandlers {
    readonly logout: any;
    readonly push: any;
}
class InnerLogoutComponent extends React.Component<IButtonProps & IConnectedHandlers & ILogoutButtonProps & WithTranslation, {}> {

    public render() {
        const {userName, onClick, logout: Logout, push: Push, ...buttonProps} = this.props;
        const logoutText = this.props.i18n.t(StringNames.logout);
        const buttonText = userName ? logoutText + " " + userName : logoutText;
        return (
            <Button {...buttonProps} text={buttonText} onClick={this.handleClick}/>
        );
    }

    private handleClick = (event: React.MouseEvent<HTMLElement>) : void => {
        this.props.logout()
            .then(() => this.props.push(LoginPaths.verifyUser));
    }
}
const InnerLogoutTranslatedComponent = withTranslation()(InnerLogoutComponent);

class LogoutComponentInternal extends React.Component<IButtonProps & IConnectedHandlers & ILogoutButtonProps, {}> {
    public render () {
        return (
            <InnerLogoutTranslatedComponent {...this.props}/>
        );
    }
}
export const LogoutComponent = connect(null,{logout, push})(LogoutComponentInternal);

но я получаю следующую ошибку:

Hooks can only be called inside the body of a function component. 

Заранее спасибо...

Ответ 1

На самом деле у меня возникают проблемы с определением порядка упаковки компонентов в HOC. В проекте, над которым я сейчас работаю, мы обертываем как withNamespaces(connect(withStyles(component))), что работает очень хорошо (withNamespaces по сути то же самое, что и с withTranslations). У нас возникли проблемы при подключении переведенного компонента, возможно, у вас сейчас такие же проблемы. Итак, вот наш способ сделать это:

У вас есть "нормальный" компонент, как

type InjectedProps = StateProps & ExternalProps & MyComponentsTranslations
export class MyComponent extends React.Component<InjectedProps> {
    ...
} 

(Примечание: процедура работает точно так же с функциональными компонентами)

Вы можете const MyConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponent)

и, наконец, вы делаете

import {WithNamespaces, withNamespaces} from "react-i18next"
export const LocalizedMyComponent = withNamespaces()(
  ({t,...rest}): WithNamepsaces) => (
    <MyConnectedComponent translations={{ put translations here }} {...rest} />
  )
)

Теперь хитрость в том, что мы определяем interface MyComponentsTranslations {} куда мы помещаем все необходимые переводы или функции перевода (в случае множественного числа). MyComponentsTranslations добавляется в InjectedProps чтобы сделать их доступными в исходном компоненте.

Вы всегда можете просто добавить t -function i18n в ваши компоненты, но в моем текущем проекте мы решили, что это намного чище

  • назовите явно переводы, необходимые для компонента
  • Поскольку ни исходный компонент, ни подключенный компонент не зависят от t -function, их легко проверить.

Дайте мне знать, если это работает для вас.

Кроме того, чтобы сделать все это немного более элегантным, вы можете использовать эти помощники:

export interface Translations<T> {
  translations: T
}

export const createTranslations = <T>(translations: T): Translations<T> => ({
  translations,
})

Это позволяет вам установить

type InjectedProps = StateProps & Translations<MyComponentTranslations>

и в withNamespace hoc:

<MyConnectedComponent {...createTranslations<MyComponentTranslations>({ put translations here })} {...rest} />

Ответ 3

В нашем проекте мы успешно используем это:

import { compose } from 'redux';
import { withNamespaces } from 'react-i18next';
import { connect } from 'react-redux';
...
export default compose(withNamespaces('translation'), connect(mapStateToProps))(ComponentName);

Благодаря этому мы подключаемся к Redux с помощью mapStateToProps, и у нас есть переводы.