I have a simple <Trans/> React Component which allows to translate a key with some properties (I use sprintf). For example:
<Trans planet="World">Hello %(planet)s</Trans>
renders:
Hello World
The issue is when trying to render some of those properties dynamically, say instead of "World" I want to have
<Trans color="blue">%(color)s planet</Trans>
Now, what react does is first outputting the following:
Hello [object Object]
Before going down the render path and correctly render
Hello blue planet
This results in a little flickr of showing [object Object] instead of the rendered element. I have tried to use renderToString, but then it would force me to use some dangerouslySetInnerHTML which doesn't work with other translation constrains.
Any thoughts?
import React, {PropTypes, Component} from 'react'
import {sprintf} from 'sprintf-js'
export default class Trans extends Component {
translate(key, args){
if(this.props.context && this.props.context[key]) key = this.props.context[key]
else console.error('%s is not in translated keys', key, ' - context was ', this.props.context)
if(typeof key === 'object' && key.singular){
if(this.props.isPlural)
return sprintf(key.plural, args)
else
return sprintf(key.singular, args)
}
return sprintf(key, args)
}
render() {
return (
<span>
{this.translate(this.props.children, this.props)}
</span>
)
}
}
You need to ensure that the #toString() method on the color prototype returns what you want.
Related
Currently I'm using Next.js with Next-i18next for I18N, but I understand that the React/i18next implementation is basically the same.
The problem I'm having is that I need to interpolate a next Link component inside some translation text, but depending on the language (English vs German), the order of the text and the link would change.
For instance the text I'm struggling with is: 'Accept data policy' vs 'Datenschutzerklärung akzeptieren'
As of the moment I have a quick fix by creating two values in the translation JSON files for the text and the link and then swapping the position based on the current language. Obviously this is not a sustainable solution. I have tried to utilise the 'Trans' component but this is showing some unexpected behaviour where the translation only kicks in after the page is refreshed, otherwise you see the text inside the Trans component.
example:
function LinkText({ href, children}) {
return <Link to={href || ''}>{children}</Link>;
}
return (
<Trans i18nKey="sentence">
text before link
<LinkText href="/data-policy">{t("dataPolicy")}</LinkText>
text after link
</Trans>
);
and the JSON in question:
{
"sentence": "agree to our <1><0/></1>",
"dataPolicy": "data policy"
}
Here's a link to CodeSandbox I made to replicate the problem with in React: link
(P.S The implementation of i18next doesn't seem to effectively swap out the languages in Codesandbox at the moment, but I included it as the code is there for a MWE)
Thanks in advance for your help, this has been driving me insane.
You had few missing parts,
Your i18next config was lack of a way to fetch the locale files, I've added i18next-http-backend.
You should use Trans component to inject the link to the sentence.
Your locale json should look like this:
{
"sentence": "Accept <0>data policy</0>"
}
// TranslatedLink.js
import React from 'react';
import { useTranslation, Trans } from 'react-i18next';
import { Link } from 'react-router-dom';
function LinkText({ href, children }) {
return <Link to={href || ''}>{children}</Link>;
}
export default function TranslatedLink() {
const { t } = useTranslation(['common']);
return (
<div style={{ padding: 50 }}>
<Trans i18nKey="sentence" t={t} components={[<LinkText href="/data-policy" />]} />
</div>
);
}
A working example: https://codesandbox.io/s/react-i18n-interpolation-issue-forked-ck8l4
I'm in the process of adding react-intl to a payment app I'm building but hitting a snag. I apologize if this has been addressed somewhere. I scoured the issues and documentation and couldn't find a direct answer on this (probably just overlooking it).
Use Case: Once a payment is processed I'd like to give the user the option to tweet a translated message indicating they've donated.
Problem: Twitter uses an iframe to "share tweets", and requires a text field as a string variable. When I pass my translation I get [object Object] in the tweet instead of the translated text. This makes sense based on my understanding of the translation engine. But I cant seem to find a way to pass a string rather than a translation object.
what I get when I use {translate('example_tweet')}
const translationText = object
what I need
const translationText = 'this is the translated text'
Question
How do I get the translated text as a string variable rather than an object to be rendered on a page?
Code
button
import { Share } from 'react-twitter-widgets'
import translate from '../i18n/translate'
export default function TwitterButton () {
return (
<Share
url='https://www.sampleSite.org' options={{
text: {translate('example_tweet')},
size: 'large'
}}
/>
)
}
translate
import React from 'react'
import { FormattedMessage } from 'react-intl'
const translate = (id, value = {}) => <FormattedMessage id={id} values={{ ...value }} />
export default translate
I was able to solve it without messing with react-intl. I built a function that scrapes the text I need from the page itself. So it really doesnt matter what the language is. I was hoping to figure out how to snag the translations as variables, but this gets the job done.
function makeTweetableUrl (text, pageUrl) {
const tweetableText = 'https://twitter.com/intent/tweet?url=' + pageUrl + '&text=' + encodeURIComponent(text)
return tweetableText
}
function onClickToTweet (e) {
e.preventDefault()
window.open(
makeTweetableUrl(document.querySelector('#tweetText').innerText, pageUrl),
'twitterwindow',
'height=450, width=550, toolbar=0, location=0, menubar=0, directories=0, scrollbars=0'
)
}
function TwitterButton ({ text, onClick }) {
return (
<StyledButton onClick={onClick}>{text}</StyledButton>
)
}
In my React app, I have stored a text template as an HTML string on the server which is passed up to the client to be rendered. Is it possible to render this HTML string in such a way that it can access it's parent component state/props? I've tried using dangerouslySetInnerHTML and ReactDOMServer.renderToString(), both of which render the string as HTML, but render {props.text} as a literal string, rather than a reference to the component's props.
For example, on the server I have stored:
<div>Welcome to my app</div>
<div>{props.message}</div>
<div>Have fun</div>
And the component
import React from "react"
import { connect } from "react-redux"
const mapStateToProps = (state) => {
return { state }
},
WelomeBody = (props) => {
return (
<div dangerouslySetInnerHTML={{"__html": props.state.welcomeMessageBody}} />
)
}
export default connect(mapStateToProps, null)(WelomeBody)
but this results in:
Welcome to my app
{props.message}
Have fun
Is there a way that this can be rendered so as to access the value of props.message, rather than just rendering it literally?
If what you have in mind is to send down a React component (in JSX syntax) from your server to your client and have the client somehow rehydrate/compile it into an actual, working component, this is not achievable that easily.
When you build and bundle your React app, only components that are statically referenced/imported in your application at compile time can be used at runtime on the browser.
So, in order to dynamically render a component from a template and embed variables into it, your choices are:
Render it into final HTML on your server, send down that HTML and have a simple React component perform dangerouslySetInnerHTML for it. But, like you've already observed, that content has to be the full HTML code, no templates, variables, JSX, etc. Everything is string and HTML at this point, no more processing.
Alternatively, send down a Markdown document and have a Markdown component parse it into HTML and display it.
Create a sophisticated component that can receive a string, parse it, tokenize it, substitute values in it, etc. Essentially, create your own template-processing component that can read a template (the string sent down from your server) and embed the appropriate values into it.
A simple solution (to substitute values into a template) would be:
function Filler(props) {
let html = props.template;
Object.keys(props).forEach(key => {
if (key !== "template")
html = html.replace("{" + key + "}", props[key]);
});
return <div dangerouslySetInnerHTML={{__html: html}} />;
}
and use it like
<Filler template={"<h1>Hello, {firstname} {lastname}</h1>"} firstname="John" lastname="Doe" />
But again, this is far from a full-fledged actual React component.
This is the component that I'm using:
<my-component
data="vm.data"
></my-component>
I would like to pass it a translated string, but I get a syntax error:
<my-component
data="vm.data"
string="{{ 'TOP_FIVE' | translate }}" // throws error in browser console
></my-component>
How can I pass along the translated string value?
Since you are using angular-translate the best way is to inject the $translate service to your react2angular component and extend your react component with a function that return the translated string:
// INJECT SERVICE
angular
.module("components", [])
.component(
"myComponent",
react2angular(MyComponent), ["data"], ["$translate"])
)
// REACT COMPONENT
class MyComponent extends React.Component {
translate = key => {
return this.props.$translate.instant(key)
}
render() {
return (
<p>{this.translate('TOP_FIVE')}</p>
)
}
}
Let's say I have the following component instantiations:
<ParentComponentWhichIsTested>
<Expand trigger={<span>Click me to expand this element</span>}>
<ComponentReference1></ComponentReference1>
</Expand>
<Expand trigger={<span>Click me to expand this element</span>}>
<ComponentReference2></ComponentReference1>
</Expand>
</ParentComponentWhichIsTested>
And the render method of Expand looks like this:
//in expand
render () {
return (
<div>
<ExpandTrigger>{trigger}</ExpandTrigger>
<ExpandContent>{this.props.children}</ExpandContent>
</div>
);
}
I want to assert that the type of content component for the first Expand instance was ComponentReference1 and that for the second instance it was ComponentReference2.
import { mount } from 'enzyme';
const comp = mount(...ParentComponentWhichIsTested...)
console.log(comp.props().children); //will be 2, the trigger and content nodes, but I want to see that I actually *sent* a child, not what the component rendered.
How can I check the send children? Am I doing something wrong?
Thanks in advance!