Paweł Chorążyk
Paweł Chorążyk

Reputation: 3643

i18next react Trans component - escaping characters correctly

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?

Upvotes: 5

Views: 14907

Answers (4)

Alf Eaton
Alf Eaton

Reputation: 5463

It's possible to make this work while setting interpolation: { escapeValue: false } globally (so that t still works as expected), by setting both tOptions={{ interpolation: { escapeValue: true } }} (so that values are escaped before interpolation) and shouldUnescape (so the whole translation string is unescaped after interpolation) on every Trans component:

<Trans
 i18nKey="userCreated"
 values={{ name: 'Tom&Jerry <Tom><' }}
 tOptions={{ interpolation: { escapeValue: true } }}
 shouldUnescape
>
  User with name <strong>{{ name }}</strong> has been created.
</Trans>

enter image description here

Upvotes: 3

Sujin Lee
Sujin Lee

Reputation: 41

If you want to use t() of useTranslation() hook instead of <Trans> component,

const Component = () => {
  const { t } = useTranslation();
  const label = t('this.is.your.key',
    'You {{ampersand}} Me',
    { ampersand: '&', interpolation: { escapeValue: false } }
  );
  return (<span>{label}</span>);
};

Upvotes: 4

termit
termit

Reputation: 200

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

Upvotes: 0

teimurjan
teimurjan

Reputation: 1975

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.

Upvotes: 5

Related Questions