skip to Main Content

I have an application where I want to use 2 configurations of i18next, one for the app and another for my tests, the problem is that when I run my tests, if the instance is used for testing and my tests pass but I have the warning in the terminal:

init: i18next is already initialized. You should call init just once!

this is what it looks like at the terminal

i18n for app:

// i18n.ts
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n.use(Backend)
    .use(LanguageDetector)
    .use(initReactI18next)
    .init({
        load: 'languageOnly',
        fallbackLng: localStorage.getItem('lang') ?? 'es',
        debug: import.meta.env.DEV,
    });

export default i18n;

usage in main app:

// main.tsx

...

import './i18n';

...

and the config for my test:

// i18nForTests.ts

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

i18n.use(initReactI18next).init({
    lng: 'es',
    fallbackLng: 'es',
    ns: ['translationsNS'],
    defaultNS: 'translationsNS',
    debug: false,
    interpolation: {
        escapeValue: false, // not needed for react!!
    },
    resources: { es: { translationsNS: {} } },
});

export default i18n;

usage:

// setupTests.ts

...
import './i18nForTests';
...

Is there another way to do it? I don’t want the "Backend" and "LanguageDetector" middleware to be used in my tests.

I have already tried the methods described in the documentation here

Even with passing my instance for testing, but this way for some reason, it uses the main instance instead of the test instance.

import i18nForTest from "./i18nForTests"

const WithProviders = ({ children }: { children: React.ReactNode }) => (
    <MemoryRouter>
        <I18nextProvider i18n={i18nForTest}>
            <ChakraProvider>{children}</ChakraProvider>
        </I18nextProvider>
    </MemoryRouter>
);

warning with I18nextProvider

I’m using vitest with react-testing-library, here is the config:

import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.ts',
    coverage: {
      provider: 'v8'
    },
  }
})

2

Answers


  1. Chosen as BEST ANSWER

    Solved

    The problem is that in the test environment I was taking into account both instances because both were initialized automatically in each file through the chaining of the init method:

    i18n.use(Backend)
            .use(LanguageDetector)
            .use(initReactI18next)
            .init({ // this part
                load: 'languageOnly',
                fallbackLng: localStorage.getItem('lang') ?? 'es',
                debug: import.meta.env.DEV,
            });
    

    The solution was as obvious as @Mike 'Pomax' Kamermans pointed out, control the initialization, in my case I did it by putting the code inside a function and executing it where it was needed, the same for the test configuration:

    function initI18nProd() {
        i18n.use(Backend)
            .use(LanguageDetector)
            .use(initReactI18next)
            .init({
                load: 'languageOnly',
                fallbackLng: localStorage.getItem('lang') ?? 'es',
                debug: import.meta.env.DEV,
            });
    }
    
    export default initI18nProd;
    

    and in the implementation:

    // main.ts
    ...
    import initI18nProd from './i18n';
    
    initI18nProd();
    ...
    
    // setupTests.ts
    import initI18nTests from './i18nForTests';
    
    initI18nTests()
    

  2. Seems like you need to use the "createInstance" method of i18next – here is the documentation – https://www.i18next.com/overview/api#createinstance

    // i18n.ts
    import i18n as i18next from 'i18next';
    import Backend from 'i18next-http-backend';
    import LanguageDetector from 'i18next-browser-languagedetector';
    import { initReactI18next } from 'react-i18next';
    
    const i18n = i18next.createInstance();
    i18n.use(Backend)
        .use(LanguageDetector)
        .use(initReactI18next)
        .init({
            load: 'languageOnly',
            fallbackLng: localStorage.getItem('lang') ?? 'es',
            debug: import.meta.env.DEV,
        });
    
    export default i18n;

    And the same goes for tests

    // i18nForTests.ts
    
    import i18n as i18next from 'i18next';
    import { initReactI18next } from 'react-i18next';
    
    const i18n = i18next.createInstance();
    i18n.use(initReactI18next).init({
        lng: 'es',
        fallbackLng: 'es',
        ns: ['translationsNS'],
        defaultNS: 'translationsNS',
        debug: false,
        interpolation: {
            escapeValue: false, // not needed for react!!
        },
        resources: { es: { translationsNS: {} } },
    });
    
    export default i18n;
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search