react-i18next 17.0.9 → 17.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 17.0.11
2
+
3
+ - chore: `html-parse-stringify` updated to `^4.0.1`. The parser powering `<Trans>` is now actively maintained under the i18next org ([i18next/html-parse-stringify](https://github.com/i18next/html-parse-stringify)) after years without upstream releases. 4.x brings modern dual ESM/CJS packaging with an `exports` map, zero runtime dependencies, reworked TypeScript types and a long list of parser fixes (literal `<` in text, multiline/CRLF attribute values, comments containing `>`, doctype handling, quote-aware bracket handling).
4
+ - refactor(Trans): the internal `escapeLiteralLessThan` scanner (~80 lines) is replaced by the parser's new `allowedTags` option with identical semantics: only numbered tags, kept basic HTML tags and known component names are parsed as markup, any other tag-shaped sequence in the translation stays literal text. Rendered output is unchanged (all 493 tests pass, including the [#1880](https://github.com/i18next/react-i18next/issues/1880) and [#1893](https://github.com/i18next/react-i18next/issues/1893) escaping cases).
5
+
6
+ ## 17.0.10
7
+
8
+ - fix(warnings): the `useTranslation` and `Trans` "You will need to pass in an i18next instance" warnings now match the `useSSR` wording, mentioning the props/context alternatives and the most common unexplained cause at scale: duplicate react-i18next copies in monorepo setups. The `Trans` variant also referenced the internal `i18nextReactModule` name; it now points to the public `initReactI18next` API.
9
+ - feat(warnings): development-only warning (`SUSPENDED_WHILE_LOADING`, logged once) right before `useTranslation` suspends while translations are loading. With the default `useSuspense: true` and no `<Suspense>` boundary this previously surfaced as a blank screen or a cryptic React error; the warning now names both fixes (add a `<Suspense>` boundary or set `react.useSuspense: false`). No-op in production builds; the `process.env.NODE_ENV` check is wrapped so runtimes without a `process` global (raw ESM in the browser, some edge runtimes) stay silent instead of throwing.
10
+ - ci: weekly workflow typechecking the test suite against `@types/react@next` / `@types/react-dom@next`, so the next React major's type changes (like the React 18 `TFunctionResult`/children wave) surface before user reports.
11
+
1
12
  ## 17.0.9
2
13
 
3
14
  - fix: allow TypeScript 7 in the optional `typescript` peer dependency range (`^5 || ^6 || ^7`). With `typescript@7.0.2` in a project, `npm install` failed with an `ERESOLVE` peer conflict. Fixes [#1927](https://github.com/i18next/react-i18next/issues/1927), thanks @andikapradanaarif.
@@ -128,7 +128,8 @@ export type ErrorCode =
128
128
  | 'TRANS_INVALID_OBJ'
129
129
  | 'TRANS_INVALID_VAR'
130
130
  | 'TRANS_INVALID_COMPONENTS'
131
- | 'USE_T_BEFORE_READY';
131
+ | 'USE_T_BEFORE_READY'
132
+ | 'SUSPENDED_WHILE_LOADING';
132
133
 
133
134
  export type ErrorMeta = {
134
135
  code: ErrorCode;
@@ -93,7 +93,7 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
93
93
  const deepExtend = (target, source, overwrite) => {
94
94
  for (const prop in source) {
95
95
  if (prop !== '__proto__' && prop !== 'constructor') {
96
- if (prop in target) {
96
+ if (Object.prototype.hasOwnProperty.call(target, prop)) {
97
97
  if (isString$1(target[prop]) || target[prop] instanceof String || isString$1(source[prop]) || source[prop] instanceof String) {
98
98
  if (overwrite) target[prop] = source[prop];
99
99
  } else {
@@ -457,11 +457,15 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
457
457
  } = selector(createProxy());
458
458
  const keySeparator = opts?.keySeparator ?? '.';
459
459
  const nsSeparator = opts?.nsSeparator ?? ':';
460
+ const strict = opts?.enableSelector === 'strict';
460
461
  if (path.length > 1 && nsSeparator) {
461
462
  const ns = opts?.ns;
462
- const nsArray = Array.isArray(ns) ? ns : null;
463
- if (nsArray && nsArray.length > 1 && nsArray.slice(1).includes(path[0])) {
464
- return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
463
+ const nsList = strict ? Array.isArray(ns) ? ns : ns ? [ns] : null : Array.isArray(ns) ? ns : null;
464
+ if (nsList) {
465
+ const candidates = strict ? nsList : nsList.length > 1 ? nsList.slice(1) : [];
466
+ if (candidates.includes(path[0])) {
467
+ return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
468
+ }
465
469
  }
466
470
  }
467
471
  return path.join(keySeparator);
@@ -873,7 +877,10 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
873
877
  const useOptionsReplaceForData = options.replace && !isString$1(options.replace);
874
878
  let data = useOptionsReplaceForData ? options.replace : options;
875
879
  if (useOptionsReplaceForData && typeof options.count !== 'undefined') {
876
- data.count = options.count;
880
+ data = {
881
+ ...data,
882
+ count: options.count
883
+ };
877
884
  }
878
885
  if (this.options.interpolation.defaultVariables) {
879
886
  data = {
@@ -1183,10 +1190,10 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1183
1190
  const skipOnVariables = options?.interpolation?.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
1184
1191
  const todos = [{
1185
1192
  regex: this.regexpUnescape,
1186
- safeValue: val => regexSafe(val)
1193
+ safeValue: val => val
1187
1194
  }, {
1188
1195
  regex: this.regexp,
1189
- safeValue: val => this.escapeValue ? regexSafe(this.escape(val)) : regexSafe(val)
1196
+ safeValue: val => this.escapeValue ? this.escape(val) : val
1190
1197
  }];
1191
1198
  todos.forEach(todo => {
1192
1199
  replaces = 0;
@@ -1210,9 +1217,9 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1210
1217
  value = makeString(value);
1211
1218
  }
1212
1219
  const safeValue = todo.safeValue(value);
1213
- str = str.replace(match[0], safeValue);
1220
+ str = str.replace(match[0], regexSafe(safeValue));
1214
1221
  if (skipOnVariables) {
1215
- todo.regex.lastIndex += value.length;
1222
+ todo.regex.lastIndex += safeValue.length;
1216
1223
  todo.regex.lastIndex -= match[0].length;
1217
1224
  } else {
1218
1225
  todo.regex.lastIndex = 0;
@@ -1262,7 +1269,7 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1262
1269
  clonedOptions = clonedOptions.replace && !isString$1(clonedOptions.replace) ? clonedOptions.replace : clonedOptions;
1263
1270
  clonedOptions.applyPostProcessor = false;
1264
1271
  delete clonedOptions.defaultValue;
1265
- const keyEndIndex = /{.*}/.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
1272
+ const keyEndIndex = /{.*}/s.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
1266
1273
  if (keyEndIndex !== -1) {
1267
1274
  formatters = match[1].slice(keyEndIndex).split(this.formatSeparator).map(elem => elem.trim()).filter(Boolean);
1268
1275
  match[1] = match[1].slice(0, keyEndIndex);
@@ -1391,10 +1398,14 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1391
1398
  format(value, format, lng, options = {}) {
1392
1399
  if (!format) return value;
1393
1400
  if (value == null) return value;
1394
- const formats = format.split(this.formatSeparator);
1395
- if (formats.length > 1 && formats[0].indexOf('(') > 1 && !formats[0].includes(')') && formats.find(f => f.includes(')'))) {
1396
- const lastIndex = formats.findIndex(f => f.includes(')'));
1397
- formats[0] = [formats[0], ...formats.splice(1, lastIndex)].join(this.formatSeparator);
1401
+ const rawFormats = format.split(this.formatSeparator);
1402
+ const formats = [];
1403
+ for (let i = 0; i < rawFormats.length; i++) {
1404
+ let f = rawFormats[i];
1405
+ while (f.indexOf('(') > -1 && !f.includes(')') && i + 1 < rawFormats.length) {
1406
+ f = `${f}${this.formatSeparator}${rawFormats[++i]}`;
1407
+ }
1408
+ formats.push(f);
1398
1409
  }
1399
1410
  const result = formats.reduce((mem, f) => {
1400
1411
  const {
@@ -1653,6 +1664,7 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1653
1664
  nsSeparator: ':',
1654
1665
  pluralSeparator: '_',
1655
1666
  contextSeparator: '_',
1667
+ enableSelector: false,
1656
1668
  partialBundledLanguages: false,
1657
1669
  saveMissing: false,
1658
1670
  updateMissing: false,
@@ -2229,127 +2241,318 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2229
2241
  instance.loadNamespaces;
2230
2242
  instance.loadLanguages;
2231
2243
 
2232
- function getDefaultExportFromCjs (x) {
2233
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
2244
+ const voidElements = {
2245
+ area: true,
2246
+ base: true,
2247
+ br: true,
2248
+ col: true,
2249
+ embed: true,
2250
+ hr: true,
2251
+ img: true,
2252
+ input: true,
2253
+ link: true,
2254
+ meta: true,
2255
+ param: true,
2256
+ source: true,
2257
+ track: true,
2258
+ wbr: true,
2259
+ '!doctype': true,
2260
+ '!DOCTYPE': true
2261
+ };
2262
+ const attrRE = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?("[^"]*"|'[^']*')/g;
2263
+ function parseTag(tag) {
2264
+ const res = {
2265
+ type: 'tag',
2266
+ name: '',
2267
+ voidElement: false,
2268
+ attrs: {},
2269
+ children: []
2270
+ };
2271
+ const tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/);
2272
+ if (tagMatch) {
2273
+ res.name = tagMatch[1];
2274
+ if (voidElements[tagMatch[1]] || tag.charAt(tag.length - 2) === '/') {
2275
+ res.voidElement = true;
2276
+ }
2277
+ if (res.name.startsWith('!--')) {
2278
+ const endIndex = tag.indexOf('-->');
2279
+ return {
2280
+ type: 'comment',
2281
+ comment: endIndex !== -1 ? tag.slice(4, endIndex) : ''
2282
+ };
2283
+ }
2284
+ }
2285
+ const reg = new RegExp(attrRE);
2286
+ let result = null;
2287
+ for (;;) {
2288
+ result = reg.exec(tag);
2289
+ if (result === null) {
2290
+ break;
2291
+ }
2292
+ if (!result[0].trim()) {
2293
+ continue;
2294
+ }
2295
+ if (result[1]) {
2296
+ const attr = result[1].trim();
2297
+ let arr = [attr, null];
2298
+ const eq = attr.indexOf('=');
2299
+ if (eq > -1) {
2300
+ arr = [attr.slice(0, eq), attr.slice(eq + 1)];
2301
+ }
2302
+ res.attrs[arr[0]] = arr[1];
2303
+ reg.lastIndex--;
2304
+ } else if (result[2]) {
2305
+ res.attrs[result[2]] = result[3].trim().substring(1, result[3].length - 1);
2306
+ }
2307
+ }
2308
+ return res;
2234
2309
  }
2235
-
2236
- var voidElements;
2237
- var hasRequiredVoidElements;
2238
-
2239
- function requireVoidElements () {
2240
- if (hasRequiredVoidElements) return voidElements;
2241
- hasRequiredVoidElements = 1;
2242
- voidElements = {
2243
- "area": true,
2244
- "base": true,
2245
- "br": true,
2246
- "col": true,
2247
- "embed": true,
2248
- "hr": true,
2249
- "img": true,
2250
- "input": true,
2251
- "link": true,
2252
- "meta": true,
2253
- "param": true,
2254
- "source": true,
2255
- "track": true,
2256
- "wbr": true
2257
- };
2258
- return voidElements;
2310
+ const tagRE = /<!--[\s\S]*?-->|<[a-zA-Z0-9\-!/](?:"[^"]*"|'[^']*'|[^'">])*>/g;
2311
+ const tagNameRE = /<\/?([^\s]+?)[/\s>]/;
2312
+ const whitespaceRE = /^\s*$/;
2313
+ const rawTextRE = /^(script|style)$/i;
2314
+ const sentinel = '\u0000';
2315
+ const empty = Object.create(null);
2316
+ function restoreSentinels(nodes) {
2317
+ nodes.forEach(function (node) {
2318
+ if (node.type === 'text') {
2319
+ node.content = node.content.split(sentinel).join('<');
2320
+ return;
2321
+ }
2322
+ if (node.type === 'comment') {
2323
+ node.comment = node.comment.split(sentinel).join('<');
2324
+ return;
2325
+ }
2326
+ for (const key in node.attrs) {
2327
+ const value = node.attrs[key];
2328
+ if (typeof value === 'string' && value.indexOf(sentinel) > -1) {
2329
+ node.attrs[key] = value.split(sentinel).join('<');
2330
+ }
2331
+ }
2332
+ if (node.children.length) {
2333
+ restoreSentinels(node.children);
2334
+ }
2335
+ });
2259
2336
  }
2260
-
2261
- var voidElementsExports = requireVoidElements();
2262
- var e = /*@__PURE__*/getDefaultExportFromCjs(voidElementsExports);
2263
-
2264
- var t = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;
2265
- function n(n) {
2266
- var r = {
2267
- type: "tag",
2268
- name: "",
2269
- voidElement: false,
2270
- attrs: {},
2271
- children: []
2272
- },
2273
- i = n.match(/<\/?([^\s]+?)[/\s>]/);
2274
- if (i && (r.name = i[1], (e[i[1]] || "/" === n.charAt(n.length - 2)) && (r.voidElement = true), r.name.startsWith("!--"))) {
2275
- var s = n.indexOf("--\x3e");
2276
- return {
2277
- type: "comment",
2278
- comment: -1 !== s ? n.slice(4, s) : ""
2337
+ function parse(html, options) {
2338
+ const components = options && options.components || empty;
2339
+ const allowedTags = options && options.allowedTags;
2340
+ let restoreNeeded = false;
2341
+ if (allowedTags) {
2342
+ const isAllowed = typeof allowedTags === 'function' ? allowedTags : function (name) {
2343
+ return allowedTags.indexOf(name) > -1;
2279
2344
  };
2345
+ let out = '';
2346
+ let pos = 0;
2347
+ tagRE.lastIndex = 0;
2348
+ let am;
2349
+ while (am = tagRE.exec(html)) {
2350
+ const tag = am[0];
2351
+ out += html.slice(pos, am.index);
2352
+ const nameMatch = tag.match(tagNameRE);
2353
+ if (tag.startsWith('<!--') || nameMatch && isAllowed(nameMatch[1])) {
2354
+ out += tag;
2355
+ pos = am.index + tag.length;
2356
+ } else {
2357
+ restoreNeeded = true;
2358
+ out += sentinel;
2359
+ pos = am.index + 1;
2360
+ tagRE.lastIndex = pos;
2361
+ }
2362
+ }
2363
+ html = out + html.slice(pos);
2280
2364
  }
2281
- for (var a = new RegExp(t), c = null; null !== (c = a.exec(n));) if (c[0].trim()) if (c[1]) {
2282
- var o = c[1].trim(),
2283
- l = [o, ""];
2284
- o.indexOf("=") > -1 && (l = o.split("=")), r.attrs[l[0]] = l[1], a.lastIndex--;
2285
- } else c[2] && (r.attrs[c[2]] = c[3].trim().substring(1, c[3].length - 1));
2286
- return r;
2287
- }
2288
- var r = /<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,
2289
- i = /^\s*$/,
2290
- s = Object.create(null);
2291
- function a(e, t) {
2292
- switch (t.type) {
2293
- case "text":
2294
- return e + t.content;
2295
- case "tag":
2296
- return e += "<" + t.name + (t.attrs ? function (e) {
2297
- var t = [];
2298
- for (var n in e) t.push(n + '="' + e[n] + '"');
2299
- return t.length ? " " + t.join(" ") : "";
2300
- }(t.attrs) : "") + (t.voidElement ? "/>" : ">"), t.voidElement ? e : e + t.children.reduce(a, "") + "</" + t.name + ">";
2301
- case "comment":
2302
- return e + "\x3c!--" + t.comment + "--\x3e";
2365
+ const result = [];
2366
+ const arr = [];
2367
+ let current;
2368
+ let level = -1;
2369
+ let inComponent = false;
2370
+ let rawUntil = 0;
2371
+ let htmlLower;
2372
+ if (html.indexOf('<') !== 0) {
2373
+ const end = html.indexOf('<');
2374
+ result.push({
2375
+ type: 'text',
2376
+ content: end === -1 ? html : html.substring(0, end)
2377
+ });
2303
2378
  }
2304
- }
2305
- var c = {
2306
- parse: function (e, t) {
2307
- t || (t = {}), t.components || (t.components = s);
2308
- var a,
2309
- c = [],
2310
- o = [],
2311
- l = -1,
2312
- m = false;
2313
- if (0 !== e.indexOf("<")) {
2314
- var u = e.indexOf("<");
2315
- c.push({
2316
- type: "text",
2317
- content: -1 === u ? e : e.substring(0, u)
2318
- });
2379
+ const matches = [];
2380
+ let m;
2381
+ while (m = tagRE.exec(html)) {
2382
+ matches.push(m);
2383
+ }
2384
+ matches.forEach(function (match, i) {
2385
+ const tag = match[0];
2386
+ if (!tag) return;
2387
+ if (tag.startsWith('<!--')) return;
2388
+ let lts = 0;
2389
+ let gts = 0;
2390
+ let secondLt = -1;
2391
+ let quote = null;
2392
+ for (let j = 0; j < tag.length; j++) {
2393
+ const c = tag.charAt(j);
2394
+ if (quote) {
2395
+ if (c === quote) quote = null;
2396
+ } else if (c === '"' || c === "'") {
2397
+ quote = c;
2398
+ } else if (c === '<') {
2399
+ lts++;
2400
+ if (lts === 2) secondLt = j;
2401
+ } else if (c === '>') {
2402
+ gts++;
2403
+ }
2404
+ }
2405
+ const validSplit = secondLt > -1 && /[a-zA-Z0-9\-!/]/.test(tag.charAt(secondLt + 1));
2406
+ if (lts > gts && validSplit) {
2407
+ const firstPart = tag.substring(0, secondLt);
2408
+ const secondPart = tag.substring(firstPart.length);
2409
+ matches[i][0] = secondPart;
2410
+ matches[i].index += firstPart.length;
2319
2411
  }
2320
- return e.replace(r, function (r, s) {
2321
- if (m) {
2322
- if (r !== "</" + a.name + ">") return;
2323
- m = false;
2324
- }
2325
- var u,
2326
- f = "/" !== r.charAt(1),
2327
- h = r.startsWith("\x3c!--"),
2328
- p = s + r.length,
2329
- d = e.charAt(p);
2330
- if (h) {
2331
- var v = n(r);
2332
- return l < 0 ? (c.push(v), c) : ((u = o[l]).children.push(v), c);
2333
- }
2334
- if (f && (l++, "tag" === (a = n(r)).type && t.components[a.name] && (a.type = "component", m = true), a.voidElement || m || !d || "<" === d || a.children.push({
2335
- type: "text",
2336
- content: e.slice(p, e.indexOf("<", p))
2337
- }), 0 === l && c.push(a), (u = o[l - 1]) && u.children.push(a), o[l] = a), (!f || a.voidElement) && (l > -1 && (a.voidElement || a.name === r.slice(2, -1)) && (l--, a = -1 === l ? c : o[l]), !m && "<" !== d && d)) {
2338
- u = -1 === l ? c : o[l].children;
2339
- var x = e.indexOf("<", p),
2340
- g = e.slice(p, -1 === x ? void 0 : x);
2341
- i.test(g) && (g = " "), (x > -1 && l + u.length >= 0 || " " !== g) && u.push({
2342
- type: "text",
2343
- content: g
2412
+ });
2413
+ matches.forEach(function (match, i) {
2414
+ const tag = match[0];
2415
+ if (!tag) return;
2416
+ const index = match.index;
2417
+ if (index < rawUntil) return;
2418
+ if (inComponent) {
2419
+ if (tag !== '</' + current.name + '>') {
2420
+ return;
2421
+ } else {
2422
+ inComponent = false;
2423
+ }
2424
+ }
2425
+ const isOpen = tag.charAt(1) !== '/';
2426
+ const isComment = tag.startsWith('<!--');
2427
+ const start = index + tag.length;
2428
+ const nextChar = html.charAt(start);
2429
+ const nextMatch = matches[i + 1];
2430
+ let isText;
2431
+ if (nextChar === '<' && nextMatch) {
2432
+ const nextTag = html.substring(start, nextMatch.index);
2433
+ isText = nextTag.split('<').length > nextTag.split('>').length;
2434
+ }
2435
+ let parent;
2436
+ if (isComment) {
2437
+ const comment = parseTag(tag);
2438
+ if (level < 0) {
2439
+ result.push(comment);
2440
+ return result;
2441
+ }
2442
+ parent = arr[level];
2443
+ parent.children.push(comment);
2444
+ const text = html.slice(start, nextMatch ? nextMatch.index : undefined);
2445
+ if (text.length > 0) {
2446
+ parent.children.push({
2447
+ type: 'text',
2448
+ content: text
2344
2449
  });
2345
2450
  }
2346
- }), c;
2347
- },
2348
- stringify: function (e) {
2349
- return e.reduce(function (e, t) {
2350
- return e + a("", t);
2351
- }, "");
2451
+ return result;
2452
+ }
2453
+ if (isOpen) {
2454
+ level++;
2455
+ current = parseTag(tag);
2456
+ if (current.type === 'tag' && components[current.name]) {
2457
+ current.type = 'component';
2458
+ inComponent = true;
2459
+ }
2460
+ let isRawText = false;
2461
+ if (!inComponent && !current.voidElement && rawTextRE.test(current.name)) {
2462
+ isRawText = true;
2463
+ htmlLower || (htmlLower = html.toLowerCase());
2464
+ const closeIndex = htmlLower.indexOf('</' + current.name.toLowerCase() + '>', start);
2465
+ const contentEnd = closeIndex === -1 ? html.length : closeIndex;
2466
+ const content = html.slice(start, contentEnd);
2467
+ if (content) {
2468
+ current.children.push({
2469
+ type: 'text',
2470
+ content
2471
+ });
2472
+ }
2473
+ rawUntil = contentEnd;
2474
+ }
2475
+ if (!current.voidElement && !inComponent && !isRawText && nextChar && nextChar !== '<') {
2476
+ current.children.push({
2477
+ type: 'text',
2478
+ content: html.slice(start, nextMatch ? nextMatch.index : undefined)
2479
+ });
2480
+ }
2481
+ if (level === 0) {
2482
+ result.push(current);
2483
+ }
2484
+ parent = arr[level - 1];
2485
+ if (parent) {
2486
+ parent.children.push(current);
2487
+ }
2488
+ arr[level] = current;
2489
+ }
2490
+ if (!isOpen || current.voidElement) {
2491
+ if (level > -1 && (current.voidElement || current.name === tag.slice(2, -1))) {
2492
+ level--;
2493
+ current = level === -1 ? result : arr[level];
2494
+ }
2495
+ if (!inComponent && (nextChar !== '<' || isText) && nextChar) {
2496
+ parent = level === -1 ? result : arr[level].children;
2497
+ const end = nextMatch ? nextMatch.index : -1;
2498
+ let content = html.slice(start, end === -1 ? undefined : end);
2499
+ if (whitespaceRE.test(content)) {
2500
+ content = ' ';
2501
+ }
2502
+ if (end > -1 && level + parent.length >= 0 || content !== ' ') {
2503
+ parent.push({
2504
+ type: 'text',
2505
+ content
2506
+ });
2507
+ }
2508
+ }
2509
+ }
2510
+ });
2511
+ if (restoreNeeded) {
2512
+ restoreSentinels(result);
2513
+ }
2514
+ return result;
2515
+ }
2516
+ function attrString(attrs) {
2517
+ const buff = [];
2518
+ for (const key in attrs) {
2519
+ if (attrs[key] === null) {
2520
+ buff.push(key);
2521
+ } else {
2522
+ buff.push(key + '="' + String(attrs[key]).replace(/"/g, '&quot;') + '"');
2523
+ }
2352
2524
  }
2525
+ if (!buff.length) {
2526
+ return '';
2527
+ }
2528
+ return ' ' + buff.join(' ');
2529
+ }
2530
+ function stringifyNode(buff, doc) {
2531
+ switch (doc.type) {
2532
+ case 'text':
2533
+ return buff + doc.content;
2534
+ case 'tag':
2535
+ {
2536
+ const tagEnd = doc.voidElement && doc.name.toLowerCase() !== '!doctype' ? '/>' : '>';
2537
+ buff += '<' + doc.name + (doc.attrs ? attrString(doc.attrs) : '') + tagEnd;
2538
+ if (doc.voidElement) {
2539
+ return buff;
2540
+ }
2541
+ return buff + doc.children.reduce(stringifyNode, '') + '</' + doc.name + '>';
2542
+ }
2543
+ case 'comment':
2544
+ buff += '<!--' + doc.comment + '-->';
2545
+ return buff;
2546
+ }
2547
+ }
2548
+ function stringify(doc) {
2549
+ return doc.reduce(function (token, rootEl) {
2550
+ return token + stringifyNode('', rootEl);
2551
+ }, '');
2552
+ }
2553
+ var index = {
2554
+ parse,
2555
+ stringify
2353
2556
  };
2354
2557
 
2355
2558
  const warn = (i18n, code, msg, rest) => {
@@ -2567,46 +2770,6 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2567
2770
  });
2568
2771
  return stringNode;
2569
2772
  };
2570
- const escapeLiteralLessThan = (str, keepArray = [], knownComponentsMap = {}) => {
2571
- if (!str) return str;
2572
- const knownNames = Object.keys(knownComponentsMap);
2573
- const allValidNames = [...keepArray, ...knownNames];
2574
- let result = '';
2575
- let i = 0;
2576
- while (i < str.length) {
2577
- if (str[i] === '<') {
2578
- let isValidTag = false;
2579
- const closingMatch = str.slice(i).match(/^<\/(\d+|[a-zA-Z][a-zA-Z0-9_-]*)>/);
2580
- if (closingMatch) {
2581
- const tagName = closingMatch[1];
2582
- if (/^\d+$/.test(tagName) || allValidNames.includes(tagName)) {
2583
- isValidTag = true;
2584
- result += closingMatch[0];
2585
- i += closingMatch[0].length;
2586
- }
2587
- }
2588
- if (!isValidTag) {
2589
- const openingMatch = str.slice(i).match(/^<(\d+|[a-zA-Z][a-zA-Z0-9_-]*)(\s+[\w-]+(?:=(?:"[^"]*"|'[^']*'|[^\s>]+))?)*\s*(\/)?>/);
2590
- if (openingMatch) {
2591
- const tagName = openingMatch[1];
2592
- if (/^\d+$/.test(tagName) || allValidNames.includes(tagName)) {
2593
- isValidTag = true;
2594
- result += openingMatch[0];
2595
- i += openingMatch[0].length;
2596
- }
2597
- }
2598
- }
2599
- if (!isValidTag) {
2600
- result += '&lt;';
2601
- i += 1;
2602
- }
2603
- } else {
2604
- result += str[i];
2605
- i += 1;
2606
- }
2607
- }
2608
- return result;
2609
- };
2610
2773
  const renderNodes = (children, knownComponentsMap, targetString, i18n, i18nOptions, combinedTOpts, shouldUnescape) => {
2611
2774
  if (targetString === '') return [];
2612
2775
  const keepArray = i18nOptions.transKeepBasicHtmlNodesFor || [];
@@ -2621,8 +2784,11 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2621
2784
  });
2622
2785
  };
2623
2786
  getData(children);
2624
- const escapedString = escapeLiteralLessThan(targetString, keepArray, data);
2625
- const ast = c.parse(`<0>${escapedString}</0>`);
2787
+ const knownNames = Object.keys(data);
2788
+ const allowedTags = name => /^\d+$/.test(name) || keepArray.indexOf(name) > -1 || knownNames.indexOf(name) > -1;
2789
+ const ast = index.parse(`<0>${targetString}</0>`, {
2790
+ allowedTags
2791
+ });
2626
2792
  const opts = {
2627
2793
  ...data,
2628
2794
  ...combinedTOpts
@@ -2827,7 +2993,7 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2827
2993
  }) {
2828
2994
  const i18n = i18nFromProps || getI18n();
2829
2995
  if (!i18n) {
2830
- warnOnce(i18n, 'NO_I18NEXT_INSTANCE', `Trans: You need to pass in an i18next instance using i18nextReactModule`, {
2996
+ warnOnce(i18n, 'NO_I18NEXT_INSTANCE', `Trans: You need to pass in an i18next instance using initReactI18next or by passing it via props or context. In monorepo setups, make sure there is only one instance of react-i18next.`, {
2831
2997
  i18nKey
2832
2998
  });
2833
2999
  return children;
@@ -3575,7 +3741,7 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
3575
3741
  const i18n = i18nFromProps || i18nFromContext || getI18n();
3576
3742
  if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new ReportNamespaces();
3577
3743
  if (!i18n) {
3578
- warnOnce(i18n, 'NO_I18NEXT_INSTANCE', 'useTranslation: You will need to pass in an i18next instance by using initReactI18next');
3744
+ warnOnce(i18n, 'NO_I18NEXT_INSTANCE', 'useTranslation: You will need to pass in an i18next instance by using initReactI18next or by passing it via props or context. In monorepo setups, make sure there is only one instance of react-i18next.');
3579
3745
  }
3580
3746
  const i18nOptions = React.useMemo(() => ({
3581
3747
  ...getDefaults(),
@@ -3697,6 +3863,13 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
3697
3863
  return arr;
3698
3864
  }, [t, finalI18n, ready, finalI18n.resolvedLanguage, finalI18n.language, finalI18n.languages]);
3699
3865
  if (i18n && useSuspense && !ready) {
3866
+ let inDevelopment = false;
3867
+ try {
3868
+ inDevelopment = "development" !== 'production';
3869
+ } catch (e) {}
3870
+ if (inDevelopment) {
3871
+ warnOnce(i18n, 'SUSPENDED_WHILE_LOADING', 'useTranslation: suspended while translations are loading (useSuspense is true by default). Add a <Suspense> boundary above this component, or set react.useSuspense: false in the i18next init options. https://react.i18next.com/latest/usetranslation-hook');
3872
+ }
3700
3873
  throw new Promise(resolve => {
3701
3874
  const onLoaded = () => resolve();
3702
3875
  if (props.lng) {