diff --git a/package-lock.json b/package-lock.json index 2b1752af..c692b862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "@babel/runtime": "^7.14.5", - "html-escaper": "^2.0.2", "html-parse-stringify": "^3.0.1" }, "devDependencies": { @@ -7888,7 +7887,8 @@ "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true }, "node_modules/html-parse-stringify": { "version": "3.0.1", @@ -17684,8 +17684,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "6.2.0", @@ -18171,8 +18170,7 @@ "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true, - "requires": {} + "dev": true }, "babel-eslint": { "version": "10.1.0", @@ -21345,7 +21343,8 @@ "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true }, "html-parse-stringify": { "version": "3.0.1", @@ -22956,8 +22955,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "24.9.0", diff --git a/package.json b/package.json index ac522142..54bb989a 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ }, "dependencies": { "@babel/runtime": "^7.14.5", - "html-escaper": "^2.0.2", "html-parse-stringify": "^3.0.1" }, "devDependencies": { diff --git a/src/Trans.js b/src/Trans.js index 155804ac..86fc443b 100644 --- a/src/Trans.js +++ b/src/Trans.js @@ -1,6 +1,5 @@ import { useContext, isValidElement, cloneElement, createElement } from 'react'; import HTML from 'html-parse-stringify'; -import { unescape } from 'html-escaper'; import { getI18n, I18nContext, getDefaults } from './context'; import { warn, warnOnce } from './utils'; @@ -124,8 +123,7 @@ function renderNodes(children, targetString, i18n, i18nOptions, combinedTOpts, s childrenArray.forEach((child) => { if (typeof child === 'string') return; if (hasChildren(child)) getData(getChildren(child)); - else if (typeof child === 'object' && !isValidElement(child)) - Object.assign(data, child); + else if (typeof child === 'object' && !isValidElement(child)) Object.assign(data, child); }); } @@ -249,7 +247,9 @@ function renderNodes(children, targetString, i18n, i18nOptions, combinedTOpts, s } else if (node.type === 'text') { const wrapTextNodes = i18nOptions.transWrapTextNodes; const content = shouldUnescape - ? unescape(i18n.services.interpolator.interpolate(node.content, opts, i18n.language)) + ? i18nOptions.unescape( + i18n.services.interpolator.interpolate(node.content, opts, i18n.language), + ) : i18n.services.interpolator.interpolate(node.content, opts, i18n.language); if (wrapTextNodes) { mem.push(createElement(wrapTextNodes, { key: `${node.name}-${i}` }, content)); diff --git a/src/context.js b/src/context.js index 5efd1457..d33664ad 100644 --- a/src/context.js +++ b/src/context.js @@ -1,4 +1,5 @@ import { createContext } from 'react'; +import { unescape } from './unescape'; let defaultOptions = { bindI18n: 'languageChanged', @@ -10,6 +11,7 @@ let defaultOptions = { transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], // hashTransKey: key => key // calculate a key for Trans component based on defaultValue useSuspense: true, + unescape, }; let i18nInstance; diff --git a/src/unescape.js b/src/unescape.js new file mode 100644 index 00000000..1b01a2b1 --- /dev/null +++ b/src/unescape.js @@ -0,0 +1,18 @@ +const matchHtmlEntity = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g; + +const htmlEntities = { + '&': '&', + '&': '&', + '<': '<', + '<': '<', + '>': '>', + '>': '>', + ''': "'", + ''': "'", + '"': '"', + '"': '"', +}; + +const unescapeHtmlEntity = (m) => htmlEntities[m]; + +export const unescape = (text) => text.replace(matchHtmlEntity, unescapeHtmlEntity); diff --git a/test/i18n.js b/test/i18n.js index 5ce3f156..d1237627 100644 --- a/test/i18n.js +++ b/test/i18n.js @@ -43,6 +43,7 @@ i18n.init({ transTest3_overwrite: 'Result should be a clickable link <0 href="https://www.google.com">Google', transTestEscapedHtml: 'Escaped html should unescape correctly <0>< &>.', + transTestCustomUnescape: 'Text should be passed through custom unescape <0>­', testTransWithCtx: 'Go <1>there.', testTransWithCtx_home: 'Go <1>home.', deepPath: { diff --git a/test/trans.render.spec.js b/test/trans.render.spec.js index 3ff288fa..ec9cb915 100644 --- a/test/trans.render.spec.js +++ b/test/trans.render.spec.js @@ -644,6 +644,35 @@ describe('trans should allow escaped html', () => { }); }); +describe('trans with custom unescape', () => { + let orgValue; + beforeAll(() => { + orgValue = i18n.options.react.unescape; + i18n.options.react.unescape = (text) => text.replace('­', '\u00AD'); + }); + + afterAll(() => { + i18n.options.react.unescape = orgValue; + }); + + it('should allow unescape override', () => { + const TestComponent = () => ( + ]} shouldUnescape /> + ); + const { container } = render(); + expect(container.firstChild).toMatchInlineSnapshot(` +
+ Text should be passed through custom unescape + + \u00AD + +
+ `); + }); +}); + it('transSupportBasicHtmlNodes: false should not keep the name of simple nodes', () => { const cloneInst = i18n.cloneInstance({ react: { transSupportBasicHtmlNodes: false, defaultTransParent: 'div' },