Closed KostyaEsmukov closed 8 years ago
right changing that line would work it out - but lead to dangerous insert of pure html - depending on interpolating of user inputs would lead to xss vulnerability.
i won't open this door - but you can create a own component doing so - but i would suggest you don't use html in your translations - better use a markdown component and use markdown...
Yeah, I share your concern about allowing use of raw HTML.
However, user input can not be in translation values. So it's only a translator who can put harmful HTML code, but we are supposed to trust our translators and verify their work, aren't we? :)
What do you think about adding a prop like escapeHTMLinValues
set to true by default? I can submit a PR if you find this appropriate.
why not just doing it like in the sample: https://github.com/i18next/react-i18next/blob/master/example/app/components/View.js#L19 interpolating a strong component?
The a interpolated component
string is not translated here actually. I need to put some bold parts to a value. Splitting it to multiple keys looks awkward, especially if it is just 3 bold words for a long text.
BTW There's no way to use markdown out of the box, is there?
<Interpolate i18nKey='common:interpolateSample' component={<strong>{t('key'}</strong>} />
no markdown built in...nope
Yeah, that's clear.
Another option as for now is
<div dangerouslySetInnerHTML={{__html: t('foo')}} />
But they both are weird :-)
yes...why not using eg. react-remarkable and pass markdown from t function to that?
Yes it's possible, but it's no better than the dangerouslySetInnerHTML
approach, as it would process markdown from user inputs as well.
I sure can write my own components which do whatever I want (in fact I already did), but I guess it's not just me having this problem #80 , so it would be great if this package had a solution for it out of the box. And I'm ready to contribute, as soon as we come up with a consensus.
Maybe this should be a new component, extended from the Interpolate?
it will process user input - but you can configure remarkable to not allow html tags or just defined tags - so no risk there.
So i think the solution with markdown or <div dangerouslySetInnerHTML={{__html: t('foo')}} />
is the way to go...might be ugly - but the uglyness remembers what is going on.
you might do a PR adding a prop dangerouslySetInnerHTML=true
- but it need to be visible that people are doing something dangerous
closing for now...if you still want to provide a PR go for it...
Yes I will, just too busy as for now.
Hi!
Probably it's not necessarily to create new issue. My question is very close to it.
How the new line tag - <br />
could be added in translation string of i18n.js
file ?
translations: {
"key":"long translation <br /> needed"
}
Thank you!
@01Kuzma it's the same question...like said...using a component setting inner html https://github.com/i18next/react-i18next/issues/189#issuecomment-235181562 or using markdown or using https://react.i18next.com/components/trans-component.html
all options to solve this...
trans component will NOT trigger render when language changed 😢
@jkiss not the concern of that Component...it's used too often in one Component -> to much bindings to much noisy updates...languageChange is a "pagelevel" concern
First of all, what HTML formatting has to be used, except fot technical structure, should be up the translator, not up to the developer, so all markup belongs to the translation files, not to the code. E.g. how many paragraphs there should be, depends on the content, e.g. the marketing wants to publish.
I don't think it is a good idea to have HTML-tags within a string, but it should be possible to add norml JSX in ttanslations.
I suggest a syntax like this:
i18n.use(LanguageDetector).init({
resources: {
en: {
translation: {
marketing: (
<>
<h1>Coolest Product in the world</h>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Dictum
non consectetur a erat nam.</p>
<p>Arcu cursus euismod quis viverra nibh cras pulvinar. Commodo nulla
facilisi nullam vehicula. Eget magna fermentum iaculis eu non diam
phasellus vestibulum lorem.<p>
<p>Est ultricies integer quis auctor. Ornare suspendisse sed nisi lacus.
Nisl vel pretium lectus quam. Nisl rhoncus mattis rhoncus urna neque
viverra. Nulla facilisi cras fermentum odio eu feugiat pretium nibh
ipsum.</p>
</>
),
about: (
<>
<h1>About Our Product</h1>
<p>Bibendum enim facilisis gravida neque convallis a cras semper
auctor. Mattis rhoncus urna neque viverra justo nec. Tellus pellentesque
eu tincidunt tortor aliquam nulla facilisi cras. Montes nascetur ridiculus
mus mauris.</p>
<ul>
<li>Volutpat consequat mauris nunc congue nisi vitae suscipit tellus.</li>
<li>Sagittis id consectetur purus ut faucibus pulvinar elementum.</li>
</ul>
<p>In hendrerit gravida rutrum quisque non tellus orci ac auctor. Morbi
non arcu risus quis varius quam quisque. Urna cursus eget nunc
scelerisque.</p>
</>
)
}
},
},
…
});
@mwaeckerlin that's completely a different usecase from what the Trans component does -> Trans component is for embedding rich react elements into a translation - not for rendering prosa
for the prosa case (long text with formatting):
Normally in your code, you have both to be translated: simple tags and texts, such as form labels etc., but often prosa, i.e. help texts and explanations. And in my experience, it is very bad, if the programmer defines the structure, e.g. how many paragraphs there should be, not the translator. Because then, sales come in, support and marketing, and ask for completely different structures. Then, if that need code change, the result is often a hack and a mess.
Previously. I've been using react-markup for specific messages. As you suggest, react-render-html is also an option, but both for me still looks like a dirty hack. The programmer, not the translator needs to decide which texts will be parsed. Also, having markup or HTML in one huge string is not readable and so really maintainable.
IMHO having the ability to add JSX in the tanslation files, as alternative to simple strings, would be best solution. Then the translator (and communications department) has full control over the content, while the programmer just cares about the logic.
There is already one option: https://react.i18next.com/latest/trans-component#using-for-less-than-br-greater-than-and-other-simple-html-elements-in-translations-v-10-4-0 but it has it's limitations...but might ok for your case
IMHO having the ability to add JSX in the tanslation files, as alternative to simple strings, would be best solution. Then the translator (and communications department) has full control over the content, while the programmer just cares about the logic.
Just not the way it works...again JSX is not a string...you do not have JSX on the client during runtime...you have functions
@jamuhl, thank you for the link. I have seen this, but it does not fit my case, because the structure is given in the code, not in the translation file.
To better understand my point, in the past, I've seen code like this (not really in JavaScript/React, but in PHP/Joomla, but basically it's the same problem):
<h2>{t('maintitle')}</h2>
<h3>{t('subtitle')}</h3>
<p>{t('p1')}</p>
<p>{t('p2')}</p>
Then the product owner asked for having only the main title and one paragraph. With this construct, that requires a code change. Changing texts was easy on our system, just edit the new text in an online form. But changing the code means full build, full test scenarios, approvement by the change board and a maintenance window. That's why I say, text and it's structure belong together and should be separated from code.
So my current solution is this:
i18n.use(initReactI18next).init({
resources: {
en: {
translations: {
mytext: `
<h2>Main Something</h2>
<h3>Some Subtitle</h3>
<p>Here some text.</p>
<p>Here some more text.</p>
`,
},
},
},
},
[…]
});
export default i18n;
And then, where I use it, I just add:
{renderHTML(this.props.t("mytext"))}
So, as a programmer, I just define the area, where some text can be inserted, but the translators are then completely free on what and how they want to place there.
But I wish, there were a better, native solution. I I would write a translation library, something like embedded JSX would be one of the key-features.
How do others solve this flexibility for the ttranslators / texters issue? Is the above workaround often used?
@mwaeckerlin fully agree...I like the idea...providing a PR I will immediately merge it and publish an update
there’s also the option to postprocess, like: https://www.npmjs.com/package/i18next-markdown-jsx-plugin
@jamuhl, than you for your motivation, @adrai, than you for your Idea. I started to prepare a patch, then analyzing the code brought me to this already existing solution:
There is a option returnObjects
that allows objects to be returned instead of strings only. Unfortunately, that object is not directly a JSX that can be rendered. But there is another option, returnedObjectHandler
that is called, when returnObjects
is false
. It takes three parameters, the middle of it is the JSX I am looking for.
So the solution is:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
i18n.use(initReactI18next).init({
resources: {
en: {
translations: {
login: {
test: (
<>
<h1>This is my Test</h1>
<p>Hello World</p>
</>
),
},
},
},
},
fallbackLng: "en",
debug: true,
returnObjects: false,
returnedObjectHandler: (key, value, options) => value,
ns: ["translations"],
defaultNS: "translations",
interpolation: {
escapeValue: false, // not needed for react!!
formatSeparator: ".",
},
react: {
wait: true,
},
});
export default i18n;
Then just add in your JSX Code:
{this.props.t("login.test")}
No more need for renderHTML
or any other dirty trick!
Please add this to the documentation, if it is not already there (and I missed it).
@mwaeckerlin this solution has one big drawback...it only works as long your translations are located in the source code and transpiled. As the content of login.test
is a function that content is nearly impossible for a translator to work with (you can't import/export it to any translation management system)...therefore I won't recommend this as a best practice.
When you want to enter JSX directly, then I see no other solution. JSX is a JavaScript object that does not exist in this format in plain JSON.
What you could do is something like:
login: {
test: {
h1: This is my Test
p: Hello World
}
),
},
Then in returnedObjectHandler
write a mapper. But this way, you have less flexibility (no attributes, so no links, unless you invent some special syntax) and you have to explicitely map all supported html tags. But if you must work with a translation management system, it's probably an option.
Also my solution above is not that bad:
Even though the file ending is *.js
, you still can put the translations into a separate file, so the differences are very low and a translator who knows html could work with the translation file manually. The translation file would then look like:
import React from "react";
export default {
login: {
test: (
<>
<h1>This is my Test</h1>
<p>Hello World</p>
</>
),
api: {
error: {
infos: "Failed to Load Info",
load: "Failed to Load API",
},
request: {
send: "sending",
retrieve: "retrieving",
},
},
},
};
Or you can even split this in two files, one *.js
for the complex texts and a normal *.json
file for all the simple texts that can be handled by a translation tool.
What do you think is the best way to go? What is your suggestion, @jamuhl? What could be a good practice?
@mwaeckerlin like said...personally I would use:
But if it works for your case and team...it's ok 👍
To give your translator the transparent choice between: simple text, JSX, html string, markup string:
file: translations/en.js
(except JSX, this could be a .json
):
import React from "react";
export default {
login: {
test0: (
<>
<h1>This is my Test0</h1>
<p>Hello World</p>
</>
),
test1: {
html: `
<h1>This is my Test1</h1>
<p>Hello World</p>
`,
},
test2: {
markdown: `# This is my Test2
Hello World
- item 1
- item 2`,
},
test: "This is a simple text",
},
};
Then in returnedObjectHandler
, if it is an object with element html
, render html. if it has element markdown
, render markdown:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LngDetector from "i18next-browser-languagedetector";
import en from "translations/en";
import de from "translations/de";
import renderHTML from "react-render-html";
import ReactMarkdown from "react-markdown";
i18n
.use(initReactI18next)
.use(LngDetector)
.init({
resources: {
en: {
translations: en,
},
de: {
translations: de,
},
},
fallbackLng: "en",
debug: true,
returnedObjectHandler: (key, value, options) => {
if ("html" in value) return renderHTML(value.html);
if ("markdown" in value) return <ReactMarkdown source={value.markdown} />;
return value;
},
// have a common namespace used around the full app
ns: ["translations"],
defaultNS: "translations",
interpolation: {
escapeValue: false, // not needed for react!!
formatSeparator: ".",
},
react: {
wait: true,
},
});
export default i18n;
What do you think about this solution, @jamuhl?
@mwaeckerlin like said...if it works for you - go for it...
@jamuhl, isn't that what you suggested, or do I misunderstand your point?
yes...I personally just don't use the handler for that but use <ReactMarkdown source={t('markdownKey')} />
inside my render...
It's what I used to use before, but less flexible for the translator. I'll now give my suggestion a try and see, how it works together with other tools. I'll keep you updated.
Ok, @jamuhl, you were completely right: Scanners just ignore (and therefore overwrite) objects instead of strings. So I have a new solution, that works together with i18next-parser
: Instead of allowing an object in the translation, I add keywords to the texts: If the string starts with html:
, it will be converted to html, if it starts with markdown
, it will be converted to markdown.
Moreover I decided to use yaml for my translation files, since yaml allows multi line strings. So my translation file looks e.g. like this:
login:
help: >-
html:
<p>Some help text</p>
<p>Another paragraph of help text.</p>
Unfortunately, after running i18next-parser
, the nice formatting is merged into one line and looks like:
help: "html: \n<p>Some help text</p>\n<p>Another paragraph of help text.</p>"
But at least entering multiline mode handy when I edit it.
Then I add a post processor before init, instead of the returnedObjectHandler
:
i18n
.use({
type: "postProcessor",
name: "formatted-text",
process: (value, key, options, translator) => {
if (value.match(/^ *html: */))
return renderHTML(value.replace(/^ *html: */, ""));
if (value.match(/^ *markdown: */))
return <ReactMarkdown source={value.replace(/^ *markdown: */, "")} />;
return value;
},
})
.init({
postProcess: "formatted-text",
…
});
Say we have the next translation resource:
Then
<div><Interpolate i18nKey='foo' /></div>
will result in the next output:<div><b>Bar!</b></div>
, while I expect to get this:<div><b>Bar!</b></div>
, so a raw HTML in translation strings should not be escaped.Is this an intended behavior, or is a bug?
I guess replacing this line with something like
child = React.createElement(parent, {dangerouslySetInnerHTML: {__html: match}});
will work this out.