Petr Gazarov
Petr Gazarov

Reputation: 3811

HTML tags in i18next translation files in React

I'm using i18next in a project and can't get around including html tags in translation files and having them properly rendered.

An example of my .json translation file:

"en": {
  "product": {
    "header": "Welcome, <strong>User!</strong>"
  }
}

There is an excellent answer to this question, but relating to JQuery. I'm not using JQuery, my project is React and here is the setup that I have:

import i18next from 'i18next';
import en from 'locales/en';

i18next.
  init({
    lng: 'en',
    fallbackLng: false,
    resources: en,
    debug: false,

    interpolation: {
      escapeValue: false
    }
  });

export default i18next.t.bind(i18next);

In component I have:

import t from 'i18n';

t('product.header')

Html that I want:

Welcome, <strong>User!</strong>

Html I'm getting:

Welcome, &lt;strong&gt;User!&lt;/strong&gt

Thanks

Upvotes: 42

Views: 52347

Answers (7)

J&#246;cker
J&#246;cker

Reputation: 6738

An approach I prefer is to use markdown in your translation and then use react-markdown to convert it to HTML.

You would set your translations like this:

"en": {
  "product": {
    "header": "Welcome, **User!**"
  }
}

And in your component

const MyComponent = () => {
    const { t } = useTranslationWithMarkdown();
    return (
        <h1>
            {t("product.header")}
        </h1>
    );
};

I build this custom hook for that:

import React from "react";
import { useTranslation } from "react-i18next";
import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";

export function useTranslationWithMarkdown() {
    const { t, ...rest } = useTranslation();
    const tMarkdown = React.useCallback(
        (key: string, options?: Record<string, unknown>) => {
            return <Markdown remarkPlugins={[remarkGfm]}>{t(key, options)}</Markdown>;
        },
        [t],
    );
    return { t: tMarkdown, ...rest };
}

Upvotes: 0

Chase Choi
Chase Choi

Reputation: 1012

Now you can use Trans without converting every tag to number tag:

"en": {
  "product": {
    "header": "Welcome, <strong>User!</strong>"
  }
}

"strong" tags in translation file will be replaced with components' the value of "strong" key

<Trans i18nKey="en.product.header" components={{ strong: <strong /> }} />

The result is "Welcome, <strong>User!</strong>" as html as you expected.

Upvotes: 5

Maksim Leali
Maksim Leali

Reputation: 27

You could try and use the property dangerouslySetInnerHTML. This lets you use HTML in your JSON that will be rendered in the tag.

Note that you can't have both, this property and children

Example of usage

<p dangerouslySetInnerHTML={{__html: t(`users_desc`) }} />

in the json you can write something like this

{
...
"users_desc": "Users  are <b> Amazing </b>"
...
}

This will render a tag

with a tag as child.

Upvotes: -1

mahdieh mahmoodi
mahdieh mahmoodi

Reputation: 1

First you need to write your json file like this:

"en":{
   "Welcome":"Welcome",
   "User":"User"
}

Then write your HTML code as follows:

<header>{t('Welcome')},<strong>{t('User')}!<strong>
<header>

Upvotes: -4

Jan Turoň
Jan Turoň

Reputation: 466

Since [email protected], you can use tags inside your translation string and replace it with components prop in Trans component or t property from useTranslation hook:

https://react.i18next.com/latest/trans-component#using-with-react-components

Example of usage:

<Trans
  i18nKey="myKey" // optional -> fallbacks to defaults if not provided
  defaults="hello <italic>beautiful</italic> <bold>{{what}}</bold>" // optional defaultValue
  values={{ what: 'world'}}
  components={{ italic: <i />, bold: <strong /> }}
/>

Upvotes: 17

viktor_vangel
viktor_vangel

Reputation: 1012

Don't put the HTML tags in the translation. It's a bad idea anyway. Separation of concerns guys will be all whiny about it.

Use the <Trans> component if react-i18next https://react.i18next.com/latest/trans-component

Do like so:

// Component.js

<Trans>Welcome, <strong>User!</strong>, here's your <strong>broom</strong></Trans>

And the corresponding translation file:

// your locales/starwars_en.js file

translations: {
  "Welcome, <1>User!</1>, here's your <3>broom</3>": "Welcome, <1>young padawan!</1>, here's your <3>light saber</3>",
}

These numbers <1> and <3> will strike you as random but wait for it:

Trans.children = [
  'Welcome, ',                        // index 0
  '<strong>User!</strong>'            // index 1
  ', please don't be ',               // index 2
  '<strong>weak</strong>',            // index 3
  ' unread messages. ',               // index 4
]

SIDE NOTE (Can be considered a hack but saves tons of time): The guys at react.i18next.com, don't have this in their docs, but you can use the base language as a key (English in this case). It saves you time, not to double translate like they showed in their docs and I quote:

// Component file

import React from 'react';
import { Trans } from 'react-i18next'

function MyComponent({ person, messages }) {
  const { name } = person;
  const count = messages.length;

  return (
    <Trans i18nKey="userMessagesUnread" count={count}>
      Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.
    </Trans>
  );
}
// translation file

"userMessagesUnread": "Hello <1>{{name}}</1>, you have {{count}} unread message. <5>Go to message</5>.",
"userMessagesUnread_plural": "Hello <1>{{name}}</1>, you have {{count}} unread messages.  <5>Go to messages</5>.",

Anyway "Kudos!" to the i18next team! You are awesome, guys!

Here - go nuts!

Upvotes: 19

jamuhl
jamuhl

Reputation: 4498

not a problem of react-i18next - you just can't add html into a react element. react will safe you from xss vulnerability and escape the content:

more detail and possible solution: https://facebook.github.io/react/tips/dangerously-set-inner-html.html

but be aware if you put user content there not escaped you open your site to xss attacks.

more secure reading the react-i18next readme: https://github.com/i18next/react-i18next#interpolate-component

which makes you split content

so the "best" solution - at least what we do - is using markdown in the translations and use eg: https://github.com/acdlite/react-remarkable to convert that to formatted content.

Upvotes: 7

Related Questions