Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[error/warning]: You will need pass in an i18next instance by using initReactI18next #876

Closed
devboell opened this issue Jun 19, 2019 · 25 comments

Comments

@devboell
Copy link

Sorry to bother, I posted this question first on SO

I am using i18next and react, and locally it works as expected, but when I deploy on Surge I always get this error:
react-i18next:: You will need pass in an i18next instance by using initReactI18next

They way I implemented it is almost identical to the react-i18next documentation.

main.jsx

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import 'tabler-react/dist/Tabler.css'
import { BrowserRouter as Router } from 'react-router-dom'
import App from './App/container'
import i18next from './services/app/i18n'
// import './services/app/i18n' /* eslint-disable-line no-unused-vars */

import initStore from './initStore'

import '../sass/style.scss'
import 'dropzone/dist/dropzone.css'
import 'c3/c3.css'

const store = initStore()

// console.log('i18next', i18next) // if I uncomment this, it works on test server

ReactDOM.render(
  <Provider store={store}>
    <Router>
      <App />
    </Router>
  </Provider>,
  document.getElementById('root'),
)

services/app/i18n.js

import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import Fetch from 'i18next-fetch-backend'

import * as apiConfig from '../../../config/api'

import { transformTranslationsData } from './translation-helpers'

const apiEnv = apiConfig[API_ENV]


i18n
  .use(Fetch)
  .use(initReactI18next)
  .init({
    // debug: true,
    lng: 'en',
    keySeparator: false,
    fallbackLng: {
      default: [ 'en' ],
    },
    interpolation: {
      escapeValue: false, // react already safes from xss
    },
    react: {
      // wait: true,
      useSuspense: false,
    },
    backend: {
      loadPath: `${apiEnv.api}/translations?limit=0&locale={{lng}}&key=cm.*`,
      allowMultiLoading: true,
      parse: transformTranslationsData,
    },
  })

export default i18n

The thing I don't understand is that when I console.log the i18next variable in main.jsx, it prints out the initialised object, and it works when deployed. If I don't log it, it just shows the error and the translations are not available in the app.

I am using these versions:

    "i18next": "^15.1.0",
    "i18next-fetch-backend": "^2.2.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-i18next": "^10.9.0",

It is going to be tricky for me to make a small repo that reproduces this error. For now I am hoping on clues and suggestions so I can investigate further.

@jamuhl
Copy link
Member

jamuhl commented Jun 19, 2019

What happens...the console.log statement does not come for free blocking execution below - which gives i18next the needed time to initialize (that's my guess).

So what basically happens as you do not use Suspense - and my guess you also do not handle ready props...your rendering happens before i18next even called init and long before translation files are loaded...

You might set initImmediate : false on i18next.init -> that at least might get that error away - but my guess will still leave you with missings during rendering as the translations were not yet loaded.

@devboell
Copy link
Author

thanks @jamuhl,

I wasn't aware of the ready prop. I will try to use it.

@devboell
Copy link
Author

devboell commented Jun 19, 2019

Unfortunately it didn't bring me much further, I am starting to wonder if webpack is just ignoring the whole services/app/i18n.js file.

I placed a console.log in the services/app/i18n.js file above the init call, and a log in the parse: transformTranslationsData, function.
When running on the server:

The error still shows, despite adding initImmediate : false to the init options
And the tReady prop is true right away,
Neither log gets called.
Checking the heroku api logs, the translations api call is never made.

If I run it locally, tReady is first undefined, then I see the parse function called, and the tReady is true. And the translation are there.

edit I don't know why I am telling you all this :-D, I'll close the issue, thanks for your time

@jamuhl
Copy link
Member

jamuhl commented Jun 19, 2019

sometimes it helps to talk about the issue - or in the worst case some distance to the problem and solutions come up

@devboell
Copy link
Author

devboell commented Jun 20, 2019

on the off chance that someone else runs into this, the error had nothing to do with i18next, and it was merely a faulty webpack setting.

In package.json we had:

"sideEffects": false

the issue got resolved by changing it into:

"sideEffects": [
    "./src/services/app/i18n.js"
  ]

@spsaucier
Copy link

spsaucier commented Jun 26, 2019

In my case, the warning appeared during jest tests. For the time being, I've gotten around it by redefining t if in a test:

const { t } = jest ? {t:s=>s} : useTranslation();

@sgwilym
Copy link

sgwilym commented Nov 4, 2019

@devboell Thanks, you've saved me what would have probably been a few hours of hair-tearing.

@chrisfinch
Copy link

For getting rid of Jest warnings easier than modifying components themselves:

jest.mock('react-i18next', () => ({
  useTranslation: () => ({t: key => key})
}));

@DevScissors
Copy link

I am still getting a warning about i18nextReactModule in my App.test.js. I had more errors but researched and found this thread and added I18nextProvider to fix a few of them. Below are the files I am working with. Any help would be appreciated.

i18n.js

import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import usLocale from '../public/locales/en/en-us';
import ukLocale from '../public/locales/uk/en-uk';

i18n
    .use(LanguageDetector)
    .use(initReactI18next)
    .init({
        // we init with resources
        resources: {
            en: usLocale,
            uk: ukLocale,
        },
        fallbackLng: 'en',
        // have a common namespace used around the full app
        ns: ['translations'],
        defaultNS: 'translations',
        interpolation: {
            escapeValue: false // not needed for react as it escapes by default
        }
    });


export default i18n;`

App.js

import { hot } from 'react-hot-loader/root';
import React from 'react';
import './style.less';
import { useTranslation, Trans, I18nextProvider } from 'react-i18next';
import ComponentUseTranslation from '../tests/UseTranslation';

const App = () => {
    const { t, i18n } = jest ? { t: (s) => s } : useTranslation();
    return (
        <I18nextProvider i18n={i18n}>
            <div className="App">
                <div className="App-header">
                    <h2>{t('welcome')}</h2>
                    <button onClick={() => i18n.changeLanguage('uk')}>uk</button>
                    <button onClick={() => i18n.changeLanguage('en')}>en</button>
                </div>
                <div className="App-intro">
                    <Trans i18nKey="get-started">trans</Trans>
                </div>
                <div style={{ marginTop: 40 }}>
                    Learn more:&nbsp;
                    <a href="https://react.i18next.com">https://react.i18next.js</a>
                </div>
                <ComponentUseTranslation />
            </div>
        </I18nextProvider>
    );
};

export default hot(App);

App.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from '../src/App';

it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<App />, div);
    ReactDOM.unmountComponentAtNode(div);
});

UseTranslation.js

import React from 'react';
import { useTranslation, I18nextProvider } from 'react-i18next';

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

const CustomComponent = () => {
    const { t, i18n } = jest ? { t: (s) => s } : useTranslation();

    return (
        <I18nextProvider i18n={i18n}>
            <div>{t('welcome')}</div>
        </I18nextProvider>
    );
};

export default CustomComponent;

UseTranslation.test.js

import React from 'react';
import App from './UseTranslation';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

it('test render', () => {
    const mounted = mount(<App />);
    expect(mounted.contains(<div>welcome</div>)).toBe(true);
});

image

@jamuhl
Copy link
Member

jamuhl commented Mar 12, 2020

@DevScissors where do you import the i18n.js?!?

The implementation is not:

Get i18n from useTranslation and pass it to the I18nextProvider. The I18nextProvider would get it from the import and would be the most outer Element (before getting t, i18n from useTranslation). The Provider is only needed if not calling .use(initReactI18next).

import { hot } from 'react-hot-loader/root';
import React from 'react';
import './style.less';
import { useTranslation, Trans } from 'react-i18next';
import ComponentUseTranslation from '../tests/UseTranslation';

// import it
import `./i18n.js`

const App = () => {
    const { t, i18n } = jest ? { t: (s) => s } : useTranslation();
    return (
        // nono: <I18nextProvider i18n={i18n}>
            <div className="App">
                <div className="App-header">
                    <h2>{t('welcome')}</h2>
                    <button onClick={() => i18n.changeLanguage('uk')}>uk</button>
                    <button onClick={() => i18n.changeLanguage('en')}>en</button>
                </div>
                <div className="App-intro">
                    <Trans i18nKey="get-started">trans</Trans>
                </div>
                <div style={{ marginTop: 40 }}>
                    Learn more:&nbsp;
                    <a href="https://react.i18next.com">https://react.i18next.js</a>
                </div>
                <ComponentUseTranslation />
            </div>
        // </I18nextProvider>
    );
};

export default hot(App);

@DevScissors
Copy link

DevScissors commented Mar 12, 2020

@jamuhl Thank you, that fixed the warnings! I am still brand new to using i18next and pretty new to jest so this has been a bit of a rough ride. Thanks for the quick response too!

@luciomartinez
Copy link

For getting rid of Jest warnings easier than modifying components themselves:

jest.mock('react-i18next', () => ({
  useTranslation: () => ({t: key => key})
}));

Extending it for the case of handling objects:

const { t } = useTranslation();
const translations = t('my.key', { returnObjects: true });
translations.message.replace('{hash}', 'value');

It'd be:

jest.mock('react-i18next', () => ({
  useTranslation: () => ({
    t: (key) => ({
      message: key
    })
  })
}));

@eugenetumachov
Copy link

For getting rid of Jest warnings easier than modifying components themselves:

jest.mock('react-i18next', () => ({
  useTranslation: () => ({t: key => key})
}));

For those who use Trans component, it makes sense to mock it as well:

jest.mock("react-i18next", () => ({
  useTranslation: () => ({ t: key => key }),
  Trans: ({ children }) => children
}));

Otherwise there will be an error of "Invariant Violation: Element type is invalid".

@VisheshSingh
Copy link

Can someone explain where (which file) we need to write 👇

jest.mock('react-i18next', () => ({
  useTranslation: () => ({t: key => key})
}));

Do we need to have it within our test.js file or component.js file?

@rogger-frogger
Copy link

Can someone explain where (which file) we need to write 👇

jest.mock('react-i18next', () => ({
  useTranslation: () => ({t: key => key})
}));

Do we need to have it within our test.js file or component.js file?

You can place it at the top of the file right after file imports and before the describe block

@IagoLast
Copy link

IagoLast commented Nov 7, 2020

@VisheshSingh @rogger-frogger

jest allows to define a set of setup files to handle this kind of stuff

In create-react-app this file is called setupTests.js

beforeEach(() => {
  jest.mock('react-i18next', () => ({
    useTranslation: () => ({ t: (key: string) => key })
  }));
});

@RSginer
Copy link

RSginer commented Jan 28, 2021

Using Jest with next and next-i18next

My solution was put this in setupTests.js if it helps:

jest.mock('./i18n', () => ({
    withTranslation: (i18nFile) => (component) => {
        const i18nJson = require(`./public/static/locales/en-US/${i18nFile}.json`)

        return (props) => component({t: key => i18nJson[key], ...props})
    },
    i18n: {
        language: "en-US"
    }
}));

jest.mock("next/config", () => {
    return {
        default: jest.fn(() => {
            return { publicRuntimeConfig: { localeSubpaths: {} } }
        })
    }
})

@FDiskas
Copy link

FDiskas commented Apr 27, 2021

can't remember where I found this but it works really nice
src/__mocks__/react-i18next.js

const React = require('react');
const reactI18next = require('react-i18next');

const hasChildren = (node) => node && (node.children || (node.props && node.props.children));

const getChildren = (node) =>
    node && node.children ? node.children : node.props && node.props.children;

const renderNodes = (reactNodes) => {
    if (typeof reactNodes === 'string') {
        return reactNodes;
    }

    return Object.keys(reactNodes).map((key, i) => {
        const child = reactNodes[key];
        const isElement = React.isValidElement(child);

        if (typeof child === 'string') {
            return child;
        }
        if (hasChildren(child)) {
            const inner = renderNodes(getChildren(child));
            return React.cloneElement(child, { ...child.props, key: i }, inner);
        }
        if (typeof child === 'object' && !isElement) {
            return Object.keys(child).reduce((str, childKey) => `${str}${child[childKey]}`, '');
        }

        return child;
    });
};

const useMock = [(k) => k, {}];
useMock.t = (k) => k;
useMock.i18n = {};

module.exports = {
    // this mock makes sure any components using the translate HoC receive the t function as a prop
    withTranslation: () => (Component) => (props) => <Component t={(k) => k} {...props} />,
    Trans: ({ children }) =>
        Array.isArray(children) ? renderNodes(children) : renderNodes([children]),
    Translation: ({ children }) => children((k) => k, { i18n: {} }),
    useTranslation: () => useMock,

    // mock if needed
    I18nextProvider: reactI18next.I18nextProvider,
    initReactI18next: reactI18next.initReactI18next,
    setDefaults: reactI18next.setDefaults,
    getDefaults: reactI18next.getDefaults,
    setI18n: reactI18next.setI18n,
    getI18n: reactI18next.getI18n,
};

@natterstefan
Copy link

Thanks for the example @FDiskas.

can't remember where I found this but it works really nice
src/mocks/react-i18next.js

It's from here https://github.com/i18next/react-i18next/blob/7cfab9746b3ccc6f833cd9c892e7c3c804944a5e/example/test-jest/src/__mocks__/react-i18next.js.

@maxizhukov
Copy link

jest.mock('react-i18next', () => ({
useTranslation: () => ({t: key => key})
}));

Is not helping

@FDiskas
Copy link

FDiskas commented Oct 25, 2021

jest.mock('react-i18next', () => ({
useTranslation: () => ({t: key => key})
}));

Is not helping

I updated my answer. Just create that file under mocks directory. Should work

@pgriscti
Copy link

pgriscti commented Jan 21, 2022

create a src/setupTests.ts with the following:

import '@testing-library/jest-dom';

jest.mock("react-i18next", () => ({
  useTranslation: () => ({ t: (key: string) => key }),
}));

@igorlino
Copy link

igorlino commented Apr 27, 2022

Maybe useful for others, there are some good infos describing how to setup the test properly at:
https://react.i18next.com/misc/testing

I personally don't want stubbing, and there is an example for that.

@Philzen
Copy link

Philzen commented Jun 21, 2022

Maybe i'm overlooking something here, but for me the fix was a mere one-liner. Simply added

  setupFiles: ['web/src/i18n'],

to the config-array into web/jest.config.js and the warning went away. (web/src/i18n.js is the init-script also used in App.tsx)

Of course, one could define a separate file initialization script specifically for the test environment and/or write a hook-mock to be executed before each test. The latter would then be configured to be made available to every test with setupFilesAfterEnv i guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests