react-i18next - import i18n instance & use directly vs using useTranslation hook - reactjs

I have a project written in React & support hooks.
I'm trying to use react-i18next to support translations.
Everything works well as I've follow the documentation.
However I stumble upon some problems when I want to use the t() function on helpers / non-component .js files.
Then, I solved it by importing i18n directly from the init file ./i18n.ts that looks something like this
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
i18n
.use(initReactI18next)
.init({
resources,
ns: [
'common',
'dashboard',
'landing'
],
defaultNS: 'common',
fallbackLng: 'en',
supportedLngs: ['de', 'en'],
interpolation: {
escapeValue: false,
},
});
export default i18n;
and I realized that I don't have to use the hook at all since I can just use it like this anywehre across the code, even on my functional component file
import i18n from "#root/i18n"
...
i18n.t('namespace:path')
I would like to know why is it recommended to use the useTranslation hook / withTranslation HOC if you can just import it like this?
I read that useTranslation apply suspense but it seems like the initReactI18next also have suspense applied by default.
I'm curious on if there's any side-effect on not using the recommended hook / HOC ?

I found this answer on their github issues.
But those constants never will be updated on languageChange! So
either:
update them to new language value based on event triggered by i18next: https://www.i18next.com/overview/api#onlanguagechanged
or better have only the keys in the constants and translate as close to the rendering as possible

Related

Unable to fetch for public folder locales in tests for React + i18next

I've got a project which uses React, i18next for internationalization and jest for testing.
The issue
Whenever I try to run my tests, it seems the Backend plugin is doing some fetches to load the translations from the public/locales folder, which ends up in an ugly error in the console, which (the main part) says:
Error: Error: connect ECONNREFUSED 127.0.0.1:80 at Object.dispatchError
Furthermore, anywhere where I want to use any of the i18n functionalities tests fail. For example, i18n.exists(message) always returns false when running in tests.
My guess is that Backend is actually making a fetch call to public folder, and it fails because during tests the server is not listening and does not return anything from the public folder, so basically no translations are loaded.
The project configuration
// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: false,
defaultNS: 'common',
fallbackLng: ['es'],
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
ns: ['common', 'comments'],
react: {
useSuspense: false,
},
});
export default i18n;
// setupTests.js
import '#testing-library/jest-dom';
jest.mock('react-i18next', () => ({
...jest.requireActual('react-i18next'),
useTranslation: () => ({
...jest.requireActual('react-i18next').useTranslation(),
t: (key: string, interpolation: any) =>
`T_${key}${
interpolation
? `--${Object.keys(interpolation)
?.map((k) => `${k}:${interpolation[k]}`)
.join('--')}`
: ''
}`,
}),
}));
And the locales folder structure:
What I've tried
I've tried to remove the Backend plugin from the use clauses, and then this error disappears. However, translations do not load anymore in the live app, and the other i18n functionalities keep failing in the tests.
Also, I've tried to load manually the resources option, importing directly the json file as a resource, and then it seems to work both: errors disappear and i18n.exists works as expected. But I don't want to have to load all the json files for each namespace manually, since multiple languages are expected to be supported.
The question
How can I get Backend to be able to load the information from the public/locales folder, exactly the same as it does in the live app, so that i18n functionalities are working as expected?
Maybe should I mock the calls to public/locales folder? If so, how can I do so for all tests (I'm using fetch-mock-jest to mock fetch calls).
Thanks!

How to use react-i18next in a utility function outside the component tree?

I followed the react-i18next docs and internationalized my React app.
I use the useTranslation hook, which gives me the "t" object. All nice and smooth for now, but I have some non-Hook utility functions which are outside the component tree.
How to get access to the translation object there?
This seems to work, import i18next rather than react-i18next:
// import { useTranslation } from "react-i18next";
import i18next from "i18next";
function hello() {
return i18next.t("hello");
}
This one worked for me,
//import i18next
import i18next from 'i18next';
//Then where ever you need to try like this
i18next.t('common:messages.errorMessage')
Not sure if it will work, but can you try this:
import i18n from "react-i18next";
// Then where ever you need try to this
i18n.t("target")

How to use cookies in i18next configuration?

I want to use react-cookie in my i18n.js file to set a language.
I was trying like this, but it send this error:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
import LanguageDetector from 'i18next-browser-languagedetector';
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import * as en from './locales/en.json'
import * as pl from './locales/pl.json'
import { useCookies } from 'react-cookie'
const { cookies } = useCookies(['i18n_locale'])
const resources = {
en: {
translation: en.default
},
"pl-PL": {
translation: pl.default
}
}
i18n
.use(initReactI18next)
.use(LanguageDetector)
.init({
resources,
lng: cookies.i18n_locale ? cookies.i18n_locale : window.navigator.language,
keySeparator: false,
interpolation: {
escapeValue: false
}
})
export default useCookies(i18n)
I want it working!
React hooks have certain rules associated with their use (see this page). One of them is mentioned right there in the error message:
Hooks can only be called inside of the body of a function component.
Regarding react-cookie usage: first off, wrap your root element in the provider component. This will allow invoking useCookies down the tree in any function-based component. Check out this example from lib's docs.

How to mock i18n.addResourceBundle?

I'm having some issues with testing i18next, we are using jest + enzyme in a react project + redux.
We implemented react-i18next like so in a lib/i18n file:
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { reactI18nextModule } from 'react-i18next';
import moment from 'moment';
import 'moment/locale/nl';
i18n
.on('languageChanged', (lng) => {
moment.locale(lng);
})
.use(LanguageDetector)
.use(reactI18nextModule)
.init({
fallbackLng: 'en',
debug: true,
react: {
wait: true,
},
});
export default i18n;
we created a __mocks__ folder and used the provided example in (https://github.com/i18next/react-i18next) __mocks__/react-18next.js we are seeing that once we import i18n and try to manipulate its properties from a non-jsx file this mock is not called. Our use case is the following:
We have an action that requests all the translations and uses the i18n.addResourceBundle in the response to set all the translations.
in this action, we import the ../lib/i18n.js file created for the effect, but once we start testing our action we keep on getting the following errors
TypeError: _i18next.default.on is not a function
6 |
7 | i18n
> 8 | .on('languageChanged', (lng) => {
| ^
9 | moment.locale(lng);
10 | })
11 | .use(LanguageDetector)
at Object.on (src/lib/i18n.js:8:4)
at Object.<anonymous> (src/actions/translation.js:2:1)
at Object.<anonymous> (src/actions/translation.test.js:2:1)
we detected that the __mocks__/react-i18next.js does not get called, but if we create a __mocks__/i18next.js file it will mock the imported i18n instance what makes sense since within the configuration file lib/i18n.js we are importing it import i18n from 'i18next'; but once again the provided mock file within your project does not seem to work.
We are considering the following scenarios and would like to ask for an opinion:
- migrate our fetch action to i18next-xhr-backend and we would like to ask you if this would fix the issue? since we no longer are going to import the config file and therefore no need to mock it, but we would lose our redux action logging functionality since we are using redux store to keep track of the user actions.
- mock the file correctly in order to maintain the current functionality and for this, we would like to ask if anyone already experienced the same behavior.
Occurs in react-i18next version
"react-i18next": "^9.0.2",
I have managed to overcome that issue, I believe.
Replace line 35 in the mock with:
var selectedLanguage = "en-gb";
useMock.i18n = {
language: selectedLanguage,
changeLanguage: (lng) => selectedLanguage = lng,
};

Translations in a sub package

Im trying to translate some stuff in my React application. This works fine with i18next and react-i18next. Im using the withNamespaces HOC to render the translations and scan them with PoEdit. So far so good.
There are however two issues that i am facing. I also have a library that holds all my UI components. More like all the styling which extends from semantic-ui itself. Over there also some translations are applicable and i wanted to use the same react-i18next there as well. While testing in storybook all looks good however when i run npm link and link the package to my main application i suddenly get this error:
caught TypeError: (0 , _reactI18next.withNamespaces) is not a function
The second question i do have is that how can i extend the translations? For example i am having this library which translates field A to be "How are you doing?". However when running a project for a customer i notice that the customer wants another translation for something that is part of the lib.
Is there a way then still to overwrite it? Since the fact the translations are bundled of course and loaded internally in the component.
Below is some code how it looks:
import i18n from 'i18next';
import { reactI18nextModule } from 'react-i18next';
import XHR from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(XHR)
.use(LanguageDetector)
.use(reactI18nextModule)
.init({
load: 'languageOnly',
backend: {
loadPath: '../dist/locales/{{lng}}/{{ns}}.json'
},
fallbackLng: {
'en-US': ['en']
},
ns: ['uielements'],
defaultNS: 'uielements',
fallbackNS: 'uielements',
debug: false,
keySeparator: '##',
interpolation: {
escapeValue: false // not needed for react!!
},
react: {
wait: true,
bindI18n: 'languageChanged loaded',
bindStore: 'added removed',
nsMode: 'default'
}
});
export default i18n;
And the components itself:
export default withNamespaces()(Dialog);
I had the same issue. It turned out that withNamespaces HOC component was introduced in react-i18next version 8.0.0. Ensure that you have the latest version:
npm i i18next#latest react-i18next#latest
I just checked i18next v12.0.0 and react-i18next v8.3.8. Everything works fine.

Resources