i18next react Trans component - escaping characters correctly

Viewed 6370

I have a react based application and use i18next's Trans component for translations.

Let's say we have a user registration form and we want to confirm that a new account has been created by displaying the following text:

User with name NAME has been created.

where NAME is provided by user and is displayed in bold.

What I have now is the following code in react:

<Trans i18n={i18next} i18nKey="userCreated" values={{name: 'Tom'}}>
   User with name <strong>{{name}}</strong> has been created.
</Trans>

and the following translation string

"userCreated": "User with name <1>{{name}}</1> has been created.",

For a simple case with name: 'Tom' it works fine and displays

User with name Tom has been created.

However I would like to let users use special characters in their names. When I change name to Tom&Jerry I'm getting

User with name Tom&amp;Jerry has been created.

After doing some research I thought that the issue is that both i18next and react escape their inputs and so the name is escaped twice. I turned off the i18next escaping in i18next.init by adding the following option

interpolation: {escapeValue: false},

That fixes the Tom&Jerry case:

User with name Tom&Jerry has been created.

but there are some inputs that break it completely, for example setting name: 'Tom<' renders like this (everything after name is bold):

User with name Tom has been created.

and setting name: '<Tom>' gives me:

User with name has been created.

(no name displayed at all). I would like it to render like this:

User with name <Tom> has been created.

Is there a way to make it work?

2 Answers

There is no elegant solution to this problem. The root of the problem is in the source code of the Trans component. You either need to create a patch for this file changing this behavior or implement a cheaty trick by yourself. If you're going to use a cheaty solution, you'd create a component UnescapedTrans which will unescape HTML entities using lodash:

import React, { Suspense } from "react";
import { Trans } from "react-i18next";
import { unescape } from "lodash";


const getUnescapeChildrenRef = ref =>
  Array.from(ref.childNodes).forEach(node => {
    if (!node.innerText) {
      return node;
    }

    node.innerText = unescape(node.innerText);
  });

const UnescapedTrans = props => (
  <div ref={getUnescapeChildrenRef}>
    <Trans {...props} tOptions={{ interpolation: { escapeValue: true } }} />
  </div>
);

CodeSandbox example.

There is more elegant way to achieve this.

You can use property "shouldUnescape" of Trans component. In my example the value for "welcome" is "<b>Special characters:</b> {{ characters }}"

import React from "react";
import { Trans } from "react-i18next";
import { escape } from "lodash";

export default function App() {
  return (
    <div className="App">
      <Trans
        i18nKey="welcome"
        shouldUnescape={true}
        components={{ b: <b /> }}
        values={{ characters: escape("<, >, &") }}
      />
    </div>
  );
}

https://codesandbox.io/s/react-i18next-forked-tn9c3k?file=/src/app.js

Related