react-i18next 17.0.10 → 17.0.12

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,12 @@
1
+ ## 17.0.12
2
+
3
+ - fix(IcuTrans): key-less `icu.macro` nodes (`<Trans>Welcome, {name}!</Trans>`, `<Select>`, `<Plural>` without `i18nKey`) rendered an empty string since 17.0.0. The macro now emits `<IcuTrans defaultTranslation="…">` without a key and `IcuTrans` passed `undefined` to `t()`, which returns `''`. Like `Trans`, `IcuTrans` now uses `defaultTranslation` as the key when `i18nKey` is not provided.
4
+
5
+ ## 17.0.11
6
+
7
+ - 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).
8
+ - 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).
9
+
1
10
  ## 17.0.10
2
11
 
3
12
  - 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.
package/README.md CHANGED
@@ -137,7 +137,7 @@ Some basics of i18next and some cool possibilities on how to optimize your local
137
137
 
138
138
  #### v9
139
139
 
140
- - react >= **0.14.0** (in case of < v16 or preact you will need to define parent in [Trans component](https://react.i18next.com/legacy-v9/trans-component#trans-props) or globally in [i18next.react options](https://react.i18next.com/legacy-v9/trans-component#additional-options-on-i-18-next-init))
140
+ - react >= **0.14.0** (in case of < v16 or preact you will need to define parent in [Trans component](https://react.i18next.com/latest/trans-component#trans-props) or globally in [i18next.react options](https://react.i18next.com/latest/trans-component#i18next-options))
141
141
  - i18next >= **2.0.0**
142
142
 
143
143
  ## Core Contributors
@@ -97,8 +97,7 @@ export interface TransSelector {
97
97
  <
98
98
  Target extends ConstrainTarget<TOpt>,
99
99
  Key extends
100
- | SelectorFn<GetSource<$NoInfer<Ns>, KPrefix>, ApplyTarget<Target, TOpt>, TOpt>
101
- | SelectorKey,
100
+ SelectorFn<GetSource<$NoInfer<Ns>, KPrefix>, ApplyTarget<Target, TOpt>, TOpt> | SelectorKey,
102
101
  const Ns extends Namespace = _DefaultNamespace,
103
102
  KPrefix = undefined,
104
103
  TContext extends string | undefined = undefined,
@@ -913,6 +913,10 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
913
913
  this.options = options;
914
914
  this.supportedLngs = this.options.supportedLngs || false;
915
915
  this.logger = baseLogger.create('languageUtils');
916
+ this.resolveHierarchyCache = {};
917
+ }
918
+ clearCache() {
919
+ this.resolveHierarchyCache = {};
916
920
  }
917
921
  getScriptPartFromCode(code) {
918
922
  code = getCleanedCode(code);
@@ -993,6 +997,25 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
993
997
  return found || [];
994
998
  }
995
999
  toResolveHierarchy(code, fallbackCode) {
1000
+ const fallbackLng = this.options.fallbackLng;
1001
+ const fallbackLngKey = Array.isArray(fallbackLng) ? fallbackLng.join('|') : fallbackLng;
1002
+ if (fallbackLngKey !== this._cachedFallbackLng) {
1003
+ this.resolveHierarchyCache = {};
1004
+ this._cachedFallbackLng = fallbackLngKey;
1005
+ }
1006
+ const hasCacheableFallback = fallbackCode === undefined || fallbackCode === false || isString$1(fallbackCode);
1007
+ const usesUncacheableOptionsFallback = fallbackCode === undefined && typeof this.options.fallbackLng === 'function';
1008
+ const cacheable = isString$1(code) && hasCacheableFallback && !usesUncacheableOptionsFallback;
1009
+ let cacheKey = null;
1010
+ if (cacheable) {
1011
+ let fallbackCacheKey;
1012
+ if (fallbackCode === undefined) fallbackCacheKey = 'undefined';else if (fallbackCode === false) fallbackCacheKey = 'boolean:false';else fallbackCacheKey = `string:${fallbackCode}`;
1013
+ cacheKey = `${code.length}:${code}|${fallbackCacheKey}`;
1014
+ }
1015
+ if (cacheKey !== null) {
1016
+ const cached = this.resolveHierarchyCache[cacheKey];
1017
+ if (cached !== undefined) return cached.slice();
1018
+ }
996
1019
  const fallbackCodes = this.getFallbackCodes((fallbackCode === false ? [] : fallbackCode) || this.options.fallbackLng || [], code);
997
1020
  const codes = [];
998
1021
  const addCode = c => {
@@ -1013,6 +1036,10 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1013
1036
  fallbackCodes.forEach(fc => {
1014
1037
  if (!codes.includes(fc)) addCode(this.formatLanguageCode(fc));
1015
1038
  });
1039
+ if (cacheKey !== null) {
1040
+ this.resolveHierarchyCache[cacheKey] = codes;
1041
+ return codes.slice();
1042
+ }
1016
1043
  return codes;
1017
1044
  }
1018
1045
  }
@@ -2241,127 +2268,318 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2241
2268
  instance.loadNamespaces;
2242
2269
  instance.loadLanguages;
2243
2270
 
2244
- function getDefaultExportFromCjs (x) {
2245
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
2271
+ const voidElements = {
2272
+ area: true,
2273
+ base: true,
2274
+ br: true,
2275
+ col: true,
2276
+ embed: true,
2277
+ hr: true,
2278
+ img: true,
2279
+ input: true,
2280
+ link: true,
2281
+ meta: true,
2282
+ param: true,
2283
+ source: true,
2284
+ track: true,
2285
+ wbr: true,
2286
+ '!doctype': true,
2287
+ '!DOCTYPE': true
2288
+ };
2289
+ const attrRE = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?("[^"]*"|'[^']*')/g;
2290
+ function parseTag(tag) {
2291
+ const res = {
2292
+ type: 'tag',
2293
+ name: '',
2294
+ voidElement: false,
2295
+ attrs: {},
2296
+ children: []
2297
+ };
2298
+ const tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/);
2299
+ if (tagMatch) {
2300
+ res.name = tagMatch[1];
2301
+ if (voidElements[tagMatch[1]] || tag.charAt(tag.length - 2) === '/') {
2302
+ res.voidElement = true;
2303
+ }
2304
+ if (res.name.startsWith('!--')) {
2305
+ const endIndex = tag.indexOf('-->');
2306
+ return {
2307
+ type: 'comment',
2308
+ comment: endIndex !== -1 ? tag.slice(4, endIndex) : ''
2309
+ };
2310
+ }
2311
+ }
2312
+ const reg = new RegExp(attrRE);
2313
+ let result = null;
2314
+ for (;;) {
2315
+ result = reg.exec(tag);
2316
+ if (result === null) {
2317
+ break;
2318
+ }
2319
+ if (!result[0].trim()) {
2320
+ continue;
2321
+ }
2322
+ if (result[1]) {
2323
+ const attr = result[1].trim();
2324
+ let arr = [attr, null];
2325
+ const eq = attr.indexOf('=');
2326
+ if (eq > -1) {
2327
+ arr = [attr.slice(0, eq), attr.slice(eq + 1)];
2328
+ }
2329
+ res.attrs[arr[0]] = arr[1];
2330
+ reg.lastIndex--;
2331
+ } else if (result[2]) {
2332
+ res.attrs[result[2]] = result[3].trim().substring(1, result[3].length - 1);
2333
+ }
2334
+ }
2335
+ return res;
2246
2336
  }
2247
-
2248
- var voidElements;
2249
- var hasRequiredVoidElements;
2250
-
2251
- function requireVoidElements () {
2252
- if (hasRequiredVoidElements) return voidElements;
2253
- hasRequiredVoidElements = 1;
2254
- voidElements = {
2255
- "area": true,
2256
- "base": true,
2257
- "br": true,
2258
- "col": true,
2259
- "embed": true,
2260
- "hr": true,
2261
- "img": true,
2262
- "input": true,
2263
- "link": true,
2264
- "meta": true,
2265
- "param": true,
2266
- "source": true,
2267
- "track": true,
2268
- "wbr": true
2269
- };
2270
- return voidElements;
2337
+ const tagRE = /<!--[\s\S]*?-->|<[a-zA-Z0-9\-!/](?:"[^"]*"|'[^']*'|[^'">])*>/g;
2338
+ const tagNameRE = /<\/?([^\s]+?)[/\s>]/;
2339
+ const whitespaceRE = /^\s*$/;
2340
+ const rawTextRE = /^(script|style)$/i;
2341
+ const sentinel = '\u0000';
2342
+ const empty = Object.create(null);
2343
+ function restoreSentinels(nodes) {
2344
+ nodes.forEach(function (node) {
2345
+ if (node.type === 'text') {
2346
+ node.content = node.content.split(sentinel).join('<');
2347
+ return;
2348
+ }
2349
+ if (node.type === 'comment') {
2350
+ node.comment = node.comment.split(sentinel).join('<');
2351
+ return;
2352
+ }
2353
+ for (const key in node.attrs) {
2354
+ const value = node.attrs[key];
2355
+ if (typeof value === 'string' && value.indexOf(sentinel) > -1) {
2356
+ node.attrs[key] = value.split(sentinel).join('<');
2357
+ }
2358
+ }
2359
+ if (node.children.length) {
2360
+ restoreSentinels(node.children);
2361
+ }
2362
+ });
2271
2363
  }
2272
-
2273
- var voidElementsExports = requireVoidElements();
2274
- var e = /*@__PURE__*/getDefaultExportFromCjs(voidElementsExports);
2275
-
2276
- var t = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;
2277
- function n(n) {
2278
- var r = {
2279
- type: "tag",
2280
- name: "",
2281
- voidElement: false,
2282
- attrs: {},
2283
- children: []
2284
- },
2285
- i = n.match(/<\/?([^\s]+?)[/\s>]/);
2286
- if (i && (r.name = i[1], (e[i[1]] || "/" === n.charAt(n.length - 2)) && (r.voidElement = true), r.name.startsWith("!--"))) {
2287
- var s = n.indexOf("--\x3e");
2288
- return {
2289
- type: "comment",
2290
- comment: -1 !== s ? n.slice(4, s) : ""
2364
+ function parse(html, options) {
2365
+ const components = options && options.components || empty;
2366
+ const allowedTags = options && options.allowedTags;
2367
+ let restoreNeeded = false;
2368
+ if (allowedTags) {
2369
+ const isAllowed = typeof allowedTags === 'function' ? allowedTags : function (name) {
2370
+ return allowedTags.indexOf(name) > -1;
2291
2371
  };
2372
+ let out = '';
2373
+ let pos = 0;
2374
+ tagRE.lastIndex = 0;
2375
+ let am;
2376
+ while (am = tagRE.exec(html)) {
2377
+ const tag = am[0];
2378
+ out += html.slice(pos, am.index);
2379
+ const nameMatch = tag.match(tagNameRE);
2380
+ if (tag.startsWith('<!--') || nameMatch && isAllowed(nameMatch[1])) {
2381
+ out += tag;
2382
+ pos = am.index + tag.length;
2383
+ } else {
2384
+ restoreNeeded = true;
2385
+ out += sentinel;
2386
+ pos = am.index + 1;
2387
+ tagRE.lastIndex = pos;
2388
+ }
2389
+ }
2390
+ html = out + html.slice(pos);
2292
2391
  }
2293
- for (var a = new RegExp(t), c = null; null !== (c = a.exec(n));) if (c[0].trim()) if (c[1]) {
2294
- var o = c[1].trim(),
2295
- l = [o, ""];
2296
- o.indexOf("=") > -1 && (l = o.split("=")), r.attrs[l[0]] = l[1], a.lastIndex--;
2297
- } else c[2] && (r.attrs[c[2]] = c[3].trim().substring(1, c[3].length - 1));
2298
- return r;
2299
- }
2300
- var r = /<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,
2301
- i = /^\s*$/,
2302
- s = Object.create(null);
2303
- function a(e, t) {
2304
- switch (t.type) {
2305
- case "text":
2306
- return e + t.content;
2307
- case "tag":
2308
- return e += "<" + t.name + (t.attrs ? function (e) {
2309
- var t = [];
2310
- for (var n in e) t.push(n + '="' + e[n] + '"');
2311
- return t.length ? " " + t.join(" ") : "";
2312
- }(t.attrs) : "") + (t.voidElement ? "/>" : ">"), t.voidElement ? e : e + t.children.reduce(a, "") + "</" + t.name + ">";
2313
- case "comment":
2314
- return e + "\x3c!--" + t.comment + "--\x3e";
2392
+ const result = [];
2393
+ const arr = [];
2394
+ let current;
2395
+ let level = -1;
2396
+ let inComponent = false;
2397
+ let rawUntil = 0;
2398
+ let htmlLower;
2399
+ if (html.indexOf('<') !== 0) {
2400
+ const end = html.indexOf('<');
2401
+ result.push({
2402
+ type: 'text',
2403
+ content: end === -1 ? html : html.substring(0, end)
2404
+ });
2315
2405
  }
2316
- }
2317
- var c = {
2318
- parse: function (e, t) {
2319
- t || (t = {}), t.components || (t.components = s);
2320
- var a,
2321
- c = [],
2322
- o = [],
2323
- l = -1,
2324
- m = false;
2325
- if (0 !== e.indexOf("<")) {
2326
- var u = e.indexOf("<");
2327
- c.push({
2328
- type: "text",
2329
- content: -1 === u ? e : e.substring(0, u)
2330
- });
2406
+ const matches = [];
2407
+ let m;
2408
+ while (m = tagRE.exec(html)) {
2409
+ matches.push(m);
2410
+ }
2411
+ matches.forEach(function (match, i) {
2412
+ const tag = match[0];
2413
+ if (!tag) return;
2414
+ if (tag.startsWith('<!--')) return;
2415
+ let lts = 0;
2416
+ let gts = 0;
2417
+ let secondLt = -1;
2418
+ let quote = null;
2419
+ for (let j = 0; j < tag.length; j++) {
2420
+ const c = tag.charAt(j);
2421
+ if (quote) {
2422
+ if (c === quote) quote = null;
2423
+ } else if (c === '"' || c === "'") {
2424
+ quote = c;
2425
+ } else if (c === '<') {
2426
+ lts++;
2427
+ if (lts === 2) secondLt = j;
2428
+ } else if (c === '>') {
2429
+ gts++;
2430
+ }
2431
+ }
2432
+ const validSplit = secondLt > -1 && /[a-zA-Z0-9\-!/]/.test(tag.charAt(secondLt + 1));
2433
+ if (lts > gts && validSplit) {
2434
+ const firstPart = tag.substring(0, secondLt);
2435
+ const secondPart = tag.substring(firstPart.length);
2436
+ matches[i][0] = secondPart;
2437
+ matches[i].index += firstPart.length;
2331
2438
  }
2332
- return e.replace(r, function (r, s) {
2333
- if (m) {
2334
- if (r !== "</" + a.name + ">") return;
2335
- m = false;
2336
- }
2337
- var u,
2338
- f = "/" !== r.charAt(1),
2339
- h = r.startsWith("\x3c!--"),
2340
- p = s + r.length,
2341
- d = e.charAt(p);
2342
- if (h) {
2343
- var v = n(r);
2344
- return l < 0 ? (c.push(v), c) : ((u = o[l]).children.push(v), c);
2345
- }
2346
- 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({
2347
- type: "text",
2348
- content: e.slice(p, e.indexOf("<", p))
2349
- }), 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)) {
2350
- u = -1 === l ? c : o[l].children;
2351
- var x = e.indexOf("<", p),
2352
- g = e.slice(p, -1 === x ? void 0 : x);
2353
- i.test(g) && (g = " "), (x > -1 && l + u.length >= 0 || " " !== g) && u.push({
2354
- type: "text",
2355
- content: g
2439
+ });
2440
+ matches.forEach(function (match, i) {
2441
+ const tag = match[0];
2442
+ if (!tag) return;
2443
+ const index = match.index;
2444
+ if (index < rawUntil) return;
2445
+ if (inComponent) {
2446
+ if (tag !== '</' + current.name + '>') {
2447
+ return;
2448
+ } else {
2449
+ inComponent = false;
2450
+ }
2451
+ }
2452
+ const isOpen = tag.charAt(1) !== '/';
2453
+ const isComment = tag.startsWith('<!--');
2454
+ const start = index + tag.length;
2455
+ const nextChar = html.charAt(start);
2456
+ const nextMatch = matches[i + 1];
2457
+ let isText;
2458
+ if (nextChar === '<' && nextMatch) {
2459
+ const nextTag = html.substring(start, nextMatch.index);
2460
+ isText = nextTag.split('<').length > nextTag.split('>').length;
2461
+ }
2462
+ let parent;
2463
+ if (isComment) {
2464
+ const comment = parseTag(tag);
2465
+ if (level < 0) {
2466
+ result.push(comment);
2467
+ return result;
2468
+ }
2469
+ parent = arr[level];
2470
+ parent.children.push(comment);
2471
+ const text = html.slice(start, nextMatch ? nextMatch.index : undefined);
2472
+ if (text.length > 0) {
2473
+ parent.children.push({
2474
+ type: 'text',
2475
+ content: text
2356
2476
  });
2357
2477
  }
2358
- }), c;
2359
- },
2360
- stringify: function (e) {
2361
- return e.reduce(function (e, t) {
2362
- return e + a("", t);
2363
- }, "");
2478
+ return result;
2479
+ }
2480
+ if (isOpen) {
2481
+ level++;
2482
+ current = parseTag(tag);
2483
+ if (current.type === 'tag' && components[current.name]) {
2484
+ current.type = 'component';
2485
+ inComponent = true;
2486
+ }
2487
+ let isRawText = false;
2488
+ if (!inComponent && !current.voidElement && rawTextRE.test(current.name)) {
2489
+ isRawText = true;
2490
+ htmlLower || (htmlLower = html.toLowerCase());
2491
+ const closeIndex = htmlLower.indexOf('</' + current.name.toLowerCase() + '>', start);
2492
+ const contentEnd = closeIndex === -1 ? html.length : closeIndex;
2493
+ const content = html.slice(start, contentEnd);
2494
+ if (content) {
2495
+ current.children.push({
2496
+ type: 'text',
2497
+ content
2498
+ });
2499
+ }
2500
+ rawUntil = contentEnd;
2501
+ }
2502
+ if (!current.voidElement && !inComponent && !isRawText && nextChar && nextChar !== '<') {
2503
+ current.children.push({
2504
+ type: 'text',
2505
+ content: html.slice(start, nextMatch ? nextMatch.index : undefined)
2506
+ });
2507
+ }
2508
+ if (level === 0) {
2509
+ result.push(current);
2510
+ }
2511
+ parent = arr[level - 1];
2512
+ if (parent) {
2513
+ parent.children.push(current);
2514
+ }
2515
+ arr[level] = current;
2516
+ }
2517
+ if (!isOpen || current.voidElement) {
2518
+ if (level > -1 && (current.voidElement || current.name === tag.slice(2, -1))) {
2519
+ level--;
2520
+ current = level === -1 ? result : arr[level];
2521
+ }
2522
+ if (!inComponent && (nextChar !== '<' || isText) && nextChar) {
2523
+ parent = level === -1 ? result : arr[level].children;
2524
+ const end = nextMatch ? nextMatch.index : -1;
2525
+ let content = html.slice(start, end === -1 ? undefined : end);
2526
+ if (whitespaceRE.test(content)) {
2527
+ content = ' ';
2528
+ }
2529
+ if (end > -1 && level + parent.length >= 0 || content !== ' ') {
2530
+ parent.push({
2531
+ type: 'text',
2532
+ content
2533
+ });
2534
+ }
2535
+ }
2536
+ }
2537
+ });
2538
+ if (restoreNeeded) {
2539
+ restoreSentinels(result);
2540
+ }
2541
+ return result;
2542
+ }
2543
+ function attrString(attrs) {
2544
+ const buff = [];
2545
+ for (const key in attrs) {
2546
+ if (attrs[key] === null) {
2547
+ buff.push(key);
2548
+ } else {
2549
+ buff.push(key + '="' + String(attrs[key]).replace(/"/g, '&quot;') + '"');
2550
+ }
2364
2551
  }
2552
+ if (!buff.length) {
2553
+ return '';
2554
+ }
2555
+ return ' ' + buff.join(' ');
2556
+ }
2557
+ function stringifyNode(buff, doc) {
2558
+ switch (doc.type) {
2559
+ case 'text':
2560
+ return buff + doc.content;
2561
+ case 'tag':
2562
+ {
2563
+ const tagEnd = doc.voidElement && doc.name.toLowerCase() !== '!doctype' ? '/>' : '>';
2564
+ buff += '<' + doc.name + (doc.attrs ? attrString(doc.attrs) : '') + tagEnd;
2565
+ if (doc.voidElement) {
2566
+ return buff;
2567
+ }
2568
+ return buff + doc.children.reduce(stringifyNode, '') + '</' + doc.name + '>';
2569
+ }
2570
+ case 'comment':
2571
+ buff += '<!--' + doc.comment + '-->';
2572
+ return buff;
2573
+ }
2574
+ }
2575
+ function stringify(doc) {
2576
+ return doc.reduce(function (token, rootEl) {
2577
+ return token + stringifyNode('', rootEl);
2578
+ }, '');
2579
+ }
2580
+ var index = {
2581
+ parse,
2582
+ stringify
2365
2583
  };
2366
2584
 
2367
2585
  const warn = (i18n, code, msg, rest) => {
@@ -2579,46 +2797,6 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2579
2797
  });
2580
2798
  return stringNode;
2581
2799
  };
2582
- const escapeLiteralLessThan = (str, keepArray = [], knownComponentsMap = {}) => {
2583
- if (!str) return str;
2584
- const knownNames = Object.keys(knownComponentsMap);
2585
- const allValidNames = [...keepArray, ...knownNames];
2586
- let result = '';
2587
- let i = 0;
2588
- while (i < str.length) {
2589
- if (str[i] === '<') {
2590
- let isValidTag = false;
2591
- const closingMatch = str.slice(i).match(/^<\/(\d+|[a-zA-Z][a-zA-Z0-9_-]*)>/);
2592
- if (closingMatch) {
2593
- const tagName = closingMatch[1];
2594
- if (/^\d+$/.test(tagName) || allValidNames.includes(tagName)) {
2595
- isValidTag = true;
2596
- result += closingMatch[0];
2597
- i += closingMatch[0].length;
2598
- }
2599
- }
2600
- if (!isValidTag) {
2601
- const openingMatch = str.slice(i).match(/^<(\d+|[a-zA-Z][a-zA-Z0-9_-]*)(\s+[\w-]+(?:=(?:"[^"]*"|'[^']*'|[^\s>]+))?)*\s*(\/)?>/);
2602
- if (openingMatch) {
2603
- const tagName = openingMatch[1];
2604
- if (/^\d+$/.test(tagName) || allValidNames.includes(tagName)) {
2605
- isValidTag = true;
2606
- result += openingMatch[0];
2607
- i += openingMatch[0].length;
2608
- }
2609
- }
2610
- }
2611
- if (!isValidTag) {
2612
- result += '&lt;';
2613
- i += 1;
2614
- }
2615
- } else {
2616
- result += str[i];
2617
- i += 1;
2618
- }
2619
- }
2620
- return result;
2621
- };
2622
2800
  const renderNodes = (children, knownComponentsMap, targetString, i18n, i18nOptions, combinedTOpts, shouldUnescape) => {
2623
2801
  if (targetString === '') return [];
2624
2802
  const keepArray = i18nOptions.transKeepBasicHtmlNodesFor || [];
@@ -2633,8 +2811,11 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2633
2811
  });
2634
2812
  };
2635
2813
  getData(children);
2636
- const escapedString = escapeLiteralLessThan(targetString, keepArray, data);
2637
- const ast = c.parse(`<0>${escapedString}</0>`);
2814
+ const knownNames = Object.keys(data);
2815
+ const allowedTags = name => /^\d+$/.test(name) || keepArray.indexOf(name) > -1 || knownNames.indexOf(name) > -1;
2816
+ const ast = index.parse(`<0>${targetString}</0>`, {
2817
+ allowedTags
2818
+ });
2638
2819
  const opts = {
2639
2820
  ...data,
2640
2821
  ...combinedTOpts
@@ -3414,7 +3595,7 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
3414
3595
  ...i18n.options.interpolation.defaultVariables
3415
3596
  };
3416
3597
  }
3417
- const translation = t(i18nKey, {
3598
+ const translation = t(i18nKey || defaultTranslation, {
3418
3599
  defaultValue: defaultTranslation,
3419
3600
  ...mergedValues,
3420
3601
  ns: namespaces