Skip to content

Commit 2ed0b34

Browse files
committed
improve useTranslation to fix "Maximum update depth exceeded" but still support new react-compiler #1885 #1863
1 parent 47a367c commit 2ed0b34

File tree

4 files changed

+91
-24
lines changed

4 files changed

+91
-24
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 16.3.3
2+
3+
- improve useTranslation to fix "Maximum update depth exceeded" but still support new react-compiler [1885](https://github.com/i18next/react-i18next/issues/1885) [1863](https://github.com/i18next/react-i18next/issues/1863#issuecomment-3491246391)
4+
15
### 16.3.2
26

37
- fix: avoid "Uncaught TypeError: Cannot redefine property: \_\_original"

react-i18next.js

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3498,17 +3498,42 @@
34983498
}
34993499
}, [i18n, props.lng, namespaces, ready, useSuspense, loadCount]);
35003500
const finalI18n = i18n || {};
3501-
const ret = React.useMemo(() => {
3502-
const descriptors = Object.getOwnPropertyDescriptors(finalI18n);
3501+
const wrapperRef = React.useRef(null);
3502+
const wrapperLangRef = React.useRef();
3503+
const createI18nWrapper = original => {
3504+
const descriptors = Object.getOwnPropertyDescriptors(original);
35033505
if (descriptors.__original) delete descriptors.__original;
3504-
const i18nWrapper = Object.create(Object.getPrototypeOf(finalI18n), descriptors);
3505-
if (!Object.prototype.hasOwnProperty.call(i18nWrapper, '__original')) {
3506-
Object.defineProperty(i18nWrapper, '__original', {
3507-
value: finalI18n,
3508-
writable: false,
3509-
enumerable: false,
3510-
configurable: false
3511-
});
3506+
const wrapper = Object.create(Object.getPrototypeOf(original), descriptors);
3507+
if (!Object.prototype.hasOwnProperty.call(wrapper, '__original')) {
3508+
try {
3509+
Object.defineProperty(wrapper, '__original', {
3510+
value: original,
3511+
writable: false,
3512+
enumerable: false,
3513+
configurable: false
3514+
});
3515+
} catch (_) {}
3516+
}
3517+
return wrapper;
3518+
};
3519+
const ret = React.useMemo(() => {
3520+
const original = finalI18n;
3521+
const lang = original?.language;
3522+
let i18nWrapper = original;
3523+
if (original) {
3524+
if (wrapperRef.current && wrapperRef.current.__original === original) {
3525+
if (wrapperLangRef.current !== lang) {
3526+
i18nWrapper = createI18nWrapper(original);
3527+
wrapperRef.current = i18nWrapper;
3528+
wrapperLangRef.current = lang;
3529+
} else {
3530+
i18nWrapper = wrapperRef.current;
3531+
}
3532+
} else {
3533+
i18nWrapper = createI18nWrapper(original);
3534+
wrapperRef.current = i18nWrapper;
3535+
wrapperLangRef.current = lang;
3536+
}
35123537
}
35133538
const arr = [t, i18nWrapper, ready];
35143539
arr.t = t;

react-i18next.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/useTranslation.js

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,58 @@ export const useTranslation = (ns, props = {}) => {
128128

129129
const finalI18n = i18n || {};
130130

131-
const ret = useMemo(() => {
132-
// Copy descriptors but avoid carrying over any existing "__original"
133-
const descriptors = Object.getOwnPropertyDescriptors(finalI18n);
131+
// cache one wrapper per hook caller and only recreate it when language changes
132+
const wrapperRef = useRef(null);
133+
const wrapperLangRef = useRef();
134+
135+
// helper to create a wrapper instance (avoid duplicating descriptor logic)
136+
const createI18nWrapper = (original) => {
137+
const descriptors = Object.getOwnPropertyDescriptors(original);
134138
if (descriptors.__original) delete descriptors.__original;
135-
const i18nWrapper = Object.create(Object.getPrototypeOf(finalI18n), descriptors);
136-
137-
// Store reference to the original instance for tests/debugging if absent
138-
if (!Object.prototype.hasOwnProperty.call(i18nWrapper, '__original')) {
139-
Object.defineProperty(i18nWrapper, '__original', {
140-
value: finalI18n,
141-
writable: false,
142-
enumerable: false,
143-
configurable: false,
144-
});
139+
const wrapper = Object.create(Object.getPrototypeOf(original), descriptors);
140+
141+
if (!Object.prototype.hasOwnProperty.call(wrapper, '__original')) {
142+
try {
143+
Object.defineProperty(wrapper, '__original', {
144+
value: original,
145+
writable: false,
146+
enumerable: false,
147+
configurable: false,
148+
});
149+
} catch (_) {
150+
/* ignore */
151+
}
152+
}
153+
154+
return wrapper;
155+
};
156+
157+
const ret = useMemo(() => {
158+
const original = finalI18n;
159+
const lang = original?.language;
160+
161+
let i18nWrapper = original;
162+
163+
if (original) {
164+
// if we already created a wrapper for this original instance
165+
if (wrapperRef.current && wrapperRef.current.__original === original) {
166+
// language changed -> create fresh wrapper so identity changes
167+
if (wrapperLangRef.current !== lang) {
168+
i18nWrapper = createI18nWrapper(original);
169+
170+
wrapperRef.current = i18nWrapper;
171+
wrapperLangRef.current = lang;
172+
} else {
173+
// reuse existing wrapper when language didn't change
174+
i18nWrapper = wrapperRef.current;
175+
}
176+
} else {
177+
// first time for this original instance -> create wrapper
178+
i18nWrapper = createI18nWrapper(original);
179+
180+
wrapperRef.current = i18nWrapper;
181+
wrapperLangRef.current = lang;
182+
}
145183
}
146184

147185
const arr = [t, i18nWrapper, ready];

0 commit comments

Comments
 (0)