reesaspieces
reesaspieces

Reputation: 1800

React-i18n Trans Component with translations that contain HTML tags not working

I'm trying to use react-i18next with a translation json that contains some HTML tags, like this:

// en.json
{ "welcome": "Welcome to our site, <strong>{{name}}</strong>" }

In my React component, I want the string to be rendered like this.

Welcome to our site, John

Using the useTranslation function normally printed the string literally without interpreting it as HTML, like Welcome to our site, <strong>John</strong>.

import React from 'react'
import { useTranslation } from 'react-i18next'

const App = () => {
  const { t } = useTranslation()

  return(
    <div>{t('welcome', { name: 'John' })}</div>
  )
}

I replaced it with dangerouslySetInnerHTML and it was rendered correctly.

<div dangerouslySetInnerHTML={{ __html: t('welcome', { name: 'John' }) }}></div>

However, I want to avoid using dangerouslySetInnerHTML if possible. I read in the docs that you can use something called Trans components for translations with HTML tags.

Docs: Using for <br /> and other simple html elements in translations (v10.4.0)

But I'm confused about how to use them, since the examples they show seem to be for replacing placeholder tags like <1> with actual tags like <br />. Is there a way to use Trans components (or some other method that doesn't use dangerouslySetInnerHTML) to get translated strings to be interpreted as HTML?

Thanks in advance.

Upvotes: 9

Views: 30494

Answers (4)

Fappaz
Fappaz

Reputation: 3626

Posting here because this is the first search result on the interwebz, and none of the answers worked for my case.

My translation JSON looks like this:

"readOnlyField": "<0>{{- key}}:</0> <1>{{- value}}</1>"

The only way I managed to make it work was to use Trans like this:

<Trans
  i18nKey="readOnlyField" // the key in your JSON file
  values={{ // The variables in your i18n entry from the JSON file
    key: "Storage",
    value: "2TB SSD",
  }}
  components={[<strong />, <i />]} // The component at index 0 (<strong />) will be parent of <0> ({{- key}}), and so on...
/>

So it would look like this:

Storage: 2TB SSD


Edit: You can also declare the components as a map, like so:

"fullName": "<primary>{{- lastName}}:</primary>, <secondary>{{- firstName}}</secondary>"
<Trans
  i18nKey="fullName"
  values={{
    lastName: "Doe",
    firstName: "John",
  }}
  components={{
    primary: <strong />,
    secondary: <i />
  }}
/>

There are some caveats when using map instead of array though, such as self-closing HTML tag names (e.g.: <link />) won't work.

Upvotes: 22

HotFix
HotFix

Reputation: 325

You need to use the Trans component around your <div>. If you also want to pass some more complicated components you need to pass the components as a prop in the Trans component and use their index as an intermediary HTML tag. Then also pass the translations in the options object in the t function and use them both in your translation string.

// package.json
    ...
    "bootstrap": "^5.2.3",
    "i18next-browser-languagedetector": "^7.0.1",
    "i18next-http-backend": "^2.1.1",
    "react": "^18.2.0",
    "react-bootstrap": "^0.31.5",
    "react-dom": "^18.2.0",
    "react-i18next": "^12.1.5",
    "react-scripts": "5.0.1",
    ...

In your component.

// src\App.js
<Trans
  t={t}
  components={[
    <span className="badge bg-primary"></span>,
    <span className="badge bg-success"></span>,
  ]}
>
  <p>
    {t("message", {
      somethingInsideBadge: `Some ${variable} inside the badge`,
      somethingElseInsideBadge: `Some ${variable} inside the badge`,
    })}
  </p>
</Trans>

Then in your translation file:

// public\locales\en-US\translation.json
{
  ...
  "message": "<strong>This is badge number 1:</strong> <0>{{somethingInsideBadge}}</0> <i>and badge number 2:</i> <1>{{somethingElseInsideBadge}}</1>"
  ...
}

Result

enter image description here

Upvotes: 2

o0bnji0o
o0bnji0o

Reputation: 96

I just posted an answer to a similar problem i could solve but in React Native, should work in React as well: https://stackoverflow.com/a/70112582/9506908

// render
<Trans i18nKey="yourTranslationKey" components={{ link: <Text onPress={() => console.log('do something')}}} /> }} />

// translationFile
{...
 "yourTranslationKey": "Please <link>Click me</link>",
...}```

Upvotes: 4

Lekoaf
Lekoaf

Reputation: 1039

Yeah, you're doing it wrong.

return (
  <Trans i18nKey="welcome">
    Welcome to our site, <strong>{{name}}</strong>
  </Trans>
)

And your JSON file should look like:

"welcome": "Welcome to our site, <1>{{name}}</1>"

The reason you use <1> is because Trans breaks up your string in to an array, so in this case it's: ["Welcome to our site, ", "<strong>{{name}}</strong>"] https://react.i18next.com/latest/trans-component#how-to-get-the-correct-translation-string

Upvotes: 4

Related Questions