Comments (8)
Thanks for taking a look at this
It wouldn't have occurred to me that the initialization ended up being asynchronous, especially as its not awaitable. But I can see that opting to include an async backend plugin whose job it is to process the preload: [...]
list that it would cause this outcome when that list is not empty.
With this in mind the simplest route for me is probably to flesh out the mock to enforce an empty preload:
jest.mock('i18next-http-backend', () => ({
__esModule: true,
default: class Backend {
static type = 'backend';
init (_s: any, _o: any, a: any) { a.preload = []; }
read () {}
readMulti () {}
create () {}
}
}));
I guess, the other option would be to jump through hoops to test this global behavior from within the context of a React Component and ensure that I18next instance is guaranteed to already be initialized. This would likely involve wrapping the Provider and connecting/disconnecting the listeners from inside. I'm not sure its worth the effort for our needs (considering everything is working, apart from the tests), but if anyone else is interested, it could look something like...
...
// Initialize
instance
.use(i18nReact)
.use(i18nHttpBackend)
.init();
// Listener
const I18nListener: FunctionComponent<PropsWithChildren> = ({ children }) => {
const [ isListening, setIsListening ] = useState(false);
const { i18n } = useTranslation();
useEffect(() => {
const change = (lang: string) => {
document.documentElement?.setAttribute('lang', lang);
document.documentElement?.setAttribute('dir', i18n.dir(lang));
};
i18n.on('languageChanged', change);
setIsListening(true);
return () => i18n.off('languageChanged', change);
}, []);
return isListening ? children : null;
};
// Instance
export const I18n = instance;
// Provider
export const I18nProvider: FunctionComponent<PropsWithChildren> = ({ children }) => (
<I18nextProvider i18n={I18n}>
<I18nListener>
{children}
</I18nListener>
</I18nextProvider>
);
The test would then be:
// Mocks
let setAttributeSpy: jest.SpyInstance;
// Tests
describe('Internationalization', () => {
beforeEach(() => {
jest.resetAllMocks();
setAttributeSpy = jest.spyOn(window.document.documentElement, 'setAttribute');
setAttributeSpy.mockImplementation(() => void 0);
});
afterEach(() => {
setAttributeSpy.mockRestore();
});
it('updates the html element attributes when the language changes', async () => {
// Setup
const Component = () => {
const { i18n } = useTranslation();
useEffect(() => void i18n.changeLanguage('cimode'), []);
return <p>Rendered</p>;
};
// Test
render(
<I18nProvider>
<Component />
</I18nProvider>
);
// Check
await screen.findByText('Rendered');
expect(setAttributeSpy).toBeCalledTimes(2);
expect(setAttributeSpy.mock.calls[0][0]).toBe('lang');
expect(setAttributeSpy.mock.calls[0][1]).toBe('cimode');
expect(setAttributeSpy.mock.calls[1][0]).toBe('dir');
expect(setAttributeSpy.mock.calls[1][1]).toBe('ltr');
});
});
I should be good to move forward with the simpler mock route above. Marking as closed.
I appreciate your time and help
from i18next.
Sorry, but I'm not able to reproduce your issue.
Can you please create a minimal reproducible example?
I tried something like this:
const { createInstance } = require('i18next');
const Backend = require('i18next-http-backend');
const Languages = {
NATIVE: 'en-US',
AVAILABLE: ['en-US', 'fr-FR'],
};
const instance = createInstance({
debug: true,
fallbackLng: Languages.NATIVE,
preload: [Languages.NATIVE],
suportedLngs: Languages.AVAILABLE,
});
instance.on('initialized', () => console.log('I18n initialized'));
instance.on('languageChanged', (lng) => {
console.log('I18n Language Changed', lng);
});
instance.use(Backend).init();
setTimeout(() => {
instance.changeLanguage('cimode');
}, 2000);
from i18next.
I created a small example on stackblitz but wasn't able to reproduce it either, so went local... Interestingly using React
via Vite
+ Vitest
I wasn't able to reproduce it locally either...
...
preload: [], // β
Success in Vite/Vitest
...
// vs
...
preload: [Languages.NATIVE], // β
Success in Vite/Vitest
...
So I decided to match our project as closely as I could and used React
via CRA
... bingo!
...
preload: [], // β
Success in CRA/Jest
...
// vs
...
preload: [Languages.NATIVE], // β Failure in CRA/Jest
...
While reviewing our original project I noted that we have a mock in our setup files: jest.mock('i18next-http-backend')
. I tried adding this to both projects and got some interesting results...
With the mock enabled, BOTH projects fail for preload: [Languages.NATIVE]
and only vite
continues to succeed with preload: []
.
So to summarize...
Vite + Vitest | no Mock |
vi.mock('i18next-http-backend') |
---|---|---|
preload: [] |
β | β |
preload: [Languages.NATIVE] |
β | β |
CRA + Jest | no Mock |
jest.mock('i18next-http-backend') |
---|---|---|
preload: [] |
β | β |
preload: [Languages.NATIVE] |
β | β |
Not too sure what to make of the results to be honest. Its a little odd that vite
would be different to CRA
, but I know that vite for example uses MODE
and import.meta.env
, vs. NODE_ENV
and process.env
. Also probably how window
, this
, global
, and globalThis
are handled too. So might be related. The only other clue I can give is that removing the i18next-http-backend
plugin entirely from the mix, everything works, so it seems like the issue might actually stemming from that plugin instead.
I've attached both local projects to play with... bug-i18next-events.zip
from i18next.
For what its worth... downgrading both projects via npm i [email protected]
makes everything work (with or without mocks).
from i18next.
I suspect that's a timing issue...
your tests are also a bit strange imo... the tests do not wait for i18next to be initialized... i18next-http-backend will load the translations asynchronously... so you have to wait for it to have loaded the translations, before testing anything...
from i18next.
fyi: something like this:
import { I18n } from '.';
import { setTimeout as wait } from 'node:timers/promises'
// Mocks
// jest.mock('i18next-http-backend');
let setAttributeSpy: jest.SpyInstance;
// Tests
describe('Internationalization', () => {
beforeEach(async () => {
// wait for i18next to be initialized...
while (!I18n.isInitialized) await wait(100);
jest.resetAllMocks();
setAttributeSpy = jest.spyOn(window.document.documentElement, 'setAttribute');
});
afterEach(() => {
setAttributeSpy.mockRestore();
});
it('updates the html element attributes when the language changes', async () => {
// Setup
const lang = 'cimode';
setAttributeSpy.mockImplementation(() => void 0);
// Test
I18n.changeLanguage(lang);
// Check
expect(setAttributeSpy).toBeCalledTimes(2);
expect(setAttributeSpy.mock.calls[0][0]).toBe('lang');
expect(setAttributeSpy.mock.calls[0][1]).toBe('cimode');
expect(setAttributeSpy.mock.calls[1][0]).toBe('dir');
expect(setAttributeSpy.mock.calls[1][1]).toBe('ltr');
});
});
from i18next.
this proofs, it's timing related... depending on how fast i18next loads the translations your test (without waiting) will succeed or not...
from i18next.
if you don't like to wait for i18next to be initialized, you need to set the preload option to an empty array...
from i18next.
Related Issues (20)
- Interpolation of multiple datetimes doesn't respect the format params HOT 2
- Support pluralization rules for constructed languages (conlangs) HOT 2
- Correct usage of returnObjects with TypeScript is not documented HOT 4
- t.v4.d.ts:189:47 - error TS1005: '?' expected HOT 4
- skipOnVariable not working for non key input in `t` HOT 3
- 23.7.13 -> 23.7.14 Regression with 'Type instantiation is excessively deep and possibly infinite' HOT 3
- Translations are not working when network connection is down HOT 4
- ASCII Decoding HOT 1
- Correct way to use i18n.t() inside index.ts files in React + TypeScript application HOT 2
- RangeError: Maximum call stack size exceeded at deepExtend HOT 2
- i18next::backendConnector: TypeError: Failed to parse URL HOT 1
- Get the wrong API call HOT 3
- React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. HOT 3
- getFixedExists should support function getFixedT HOT 1
- Error: You are passing a wrong module! Please check the object you are passing to i18next.use() HOT 1
- The βconstβ keyword is extraneous in the ParseKeys type definition. HOT 2
- Not existing context not detected as type error if covered by string union HOT 8
- Allow default value in nested translation HOT 3
- Autocompletion doesn't work on large files in webstorm HOT 1
- Don't get the correct types if the key contains _ HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from i18next.