i18next <Trans> avoid retranslation of nested components - javascript

I am using i18next with React for my project. For most of the translations I use the t() function with normal text. However, for some cases I would prefer to use the <Trans /> component.
I prepared some example below:
function Example() {
const { t } = useTranslation();
const reusableComponent = <p>{t('TranslateMeOnlyOnce')}</p>;
return (
<>
{reusableComponent}
<Trans i18nKey={'OnlyOnceWrapper'}>
The reusable component says {reusableComponent}
</Trans>
</>
)
}
Which expect would my ranslation file to look something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>Translate me only once</1>',
...
However, as you can see I would be required to translate the text of the nested component twice. I would really like to just translate the The reusable component says {reusableComponent} in the second translation string and not all of the first translation string for another time.
For those wondering why I would want to do this, lets say const reusableComponent is a Create Button with some function to create a new entry placed at the top of the Screen. When however, there is no entry displayed in my list of entries I want to display a message saying something like: 'Unfortunately there is no entry yet. Click on [CreateButtonIsDisplayedHere] to create a new entry'.
Let's say I experience that users find a translation 'New' instead of 'Create' more useful or the other way round, I would want to change only the translation of the button and not every other place containing this button.
I also found myself a solution to this issue already, however, in my experience this is super ugly to maintain as I need to pass the content as string and not as React child Element which pretty much take all advantages of using the <Trans /> Component and I could better use the t() function using sprintf.
function UglySolution() {
const { t } = useTranslation();
const reusableComponent = <p>{t('Translate me only Once')}</p>;
return (
<>
{reusableComponent}
<Trans
i18nKey={'OnlyOnceWrapper'}
components={[reusableComponent]}
variables={{ v: t('Translate me only Once') }}
defaults='The reusable component says <1>{{v}}</1>'
/>
</>
)
}
Which would expect my translation file to look something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>{{v}}</1>',
...
So my question is: Can I make my translation file to look something like the second example I created without using the ugly <Trans /> component from the second example and more something like the component in the first example I created?

Resusing translations
There is a way using the <Trans /> component as in the first example with reusing previous translations. Using $t(KeyToAnotherTranslation) inside the translation string.
This does not fully solve the issue as it's also not too handy to maintain but would be a lot prettier than the second example shown.
function Example() {
const { t } = useTranslation();
const reusableComponent = <p>{t('TranslateMeOnlyOnce')}</p>;
return (
<>
{reusableComponent}
<Trans i18nKey={'OnlyOnceWrapper'}>
The reusable component says {reusableComponent}
</Trans>
</>
)
}
(Taken 1:1 from the code from the question)
With a translation file looking something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>$t(TranslateMeOnlyOnce)</1>',
...
More information about variable usage in i18next.

Related

Creating a custom React Native component which automatically wraps inner text substrings with another component

I have the following component structure being rendered:
<Text>
Hello <UsernameWrapper>#gaberocksall</UsernameWrapper>! Nice to meet you.
</Text>
I would like create a custom component which automatically places #usernames into a UsernameWrapper, like this (and it should be rendered identically to the above snippet):
<AutoUsernameText>Hello #gaberocksall! Nice to meet you.</AutoUsernameText>
Or, even more generally, something along the lines of:
<MagicWrapper wrap={/#\w+/gi} wrapper={UsernameWrapper}>
Hello #gaberocksall! Nice to meet you.
</MagicWrapper>
How would I go about creating the MagicWrapper component in React Native?
We could just create a new component where we pass the username as a prop. In my opinion that is the way to go in react-native. Here is a quick implementation.
export const AutoUsernameText = (props) => {
return (
<Text>
Hello <UsernameWrapper>#{props.name}</UsernameWrapper>! Nice to meet you.
</Text>
)
}
Then, we can just use it as follows.
const SomeOtherScreen = () => {
return (
<AutoUsernameText name="gaberocksall" />
)
}
Notice that this solution depends on how UsernameWrapper is actually implemented. Since I do not know that, I assume plain strings here.
If you really want to pass it as a child, e.g.
<MagicWrapper>Hello #gaberocksal! Nice to meet you.</MagicWrapper>
then AutoUsernameText needs to parse its children and return the component you want instead. This is possible, although I do not see any good reason why one should do that. It opens many pitfalls that are not easy to avoid.
Nevertheless, we can achieve this as follows assuming that the child is a simple string using plain JS regular expressions.
export const MagicWrapper = (props) => {
// Notice that I do not guard here for potential errors ...
// props.children could be anything ...
const child = props.children
const name = child.match(/#\S+?(?=!)/g)
const content = child.split(name)
return (
<Text>
{content[0]}<UsernameWrapper>{name}</UsernameWrapper>{content[1]}
</Text>
)
}
In order to splitting specific Regex pattern like as username, url and etc, you are able to use react-native-parsed-text package. For example it's will help you changing color of the Regex pattern (also defining custom styles) or defining onPress function to doing some things. I hope it's be useful.
react-native-parsed-text github page.

React: How to use refs in a function component that I have no control over (i.e. from a library)

I can't believe I couldn't find an answer on this so please point me in the right direction if I am wrong.
I am trying to do something simple:
There's a function component from a library that I include.
I need to get a ref to it.
Crucially, React says this:
Ref forwarding lets components opt into exposing any child component’s ref as their own.
Unfortunately, this library component has not opted in using forwardRef.
What is the correct approach to get a ref to this element?
The 3rd party library component in question looks like so (simplified):
function OutboundLink(props) {
return (
<a {...props}
...
/>
)
}
My component looks like so:
function MyComp(props) {
const ref = useRef(null)
return (
<OutboundLink ref={ref} {...props} >
{children}
</OutboundLink>
)
}
but I am greeted with the errors about not being able to pass refs to a function component. Normally I would wrap in forwardRef, but can't in this case since I can't modify the library's code directly.
I did see this and this but didn't think either applied to me.
Any ideas?
You can't directly. There's no hook to let you in if the component wasn't written that way.
I think your best bet would be to wrap the component, capture a ref, and then drill into its children with DOM methods.
const wrapperRef = useRef(null)
const innerRef = useRef(null)
useEffect(() => {
if (wrapperRef.current) {
innerRef.current = wrapperRef.current.querySelector('a')
// innerRef.current is now the first <a> tag inside <OutboundLink>
}
})
return <div>
<div ref={wrapperRef}>
<OutboundLink {...props}>
{children}
</OutboundLink>
</div>
</div>
Codepen example (view the console)
But really, this is a bit of a hack. You should use a component that handles this better. Look into how hard it would be to write this component yourself from scratch. It may be trivial, and worth it.

React Js - Create component from String doesn't work

I want to create a list of component using strings.
Searching on Google/StackOverflow, this seems to be easy:
import Light from 'light.jsx';
[...]
getComponent()
{
let DynamicComponent = "Light";
return <DynamicComponent />;
}
Unfortunally, this approach doesn't work.
I get this error non console:
Warning: <Light /> is using uppercase HTML. Always use lowercase HTML
tags in React. Warning: The tag <Light> is unrecognized in this
browser. If you meant to render a React component, start its name with
an uppercase letter.
I've tried many combinations like:
return React.createElement(DynamicComponent, {}) };
return React.createElement("Light", {}) };
return <div> { React.createElement(DynamicComponent, {}) }</div>;
They all returns the same error.
Obviously if I use:
return <Light />
it works.
As you mentioned in the comment, you are putting all the import in a file like this:
import Light from './light.jsx';
import Fan from './fan.jsx';
export {
"Light": Light,
"Fan": Fan
}
And trying to use:
import * as Dynamic from 'allimport.js';
let DynamicComponent = "Dynamic.Light";
return <DynamicComponent />
Everything is proper, just remove the "" around "Dynamic.Light", it should be Dynamic.Light.
For more details check this answer: Dynamically rendering component from string: ReactJS
Sorry to bother you but I still get some errors:
(1) The import you wrote rise this error: "Unexpected token" at the first DoubleQuotes. All the export statement is red-underlined by IDE;
(2) I can't remove the doublequotes from Dynamic.Light because this assume I KNOW that a Light component exists.
But all this stuffs Im trying to make working is to prevent the developer to know them. I receive a list of what to render from DB managing this information in a loop. I need to be more flexible.
listOfComponentFromDB.map(name){
let DynamicComponent = "Dynamic."+name;
allRendered.push(<DynamicComponent />);
}
return allRendered;
With this approach to add a new component to the master render, it will be non necessary to modify the master render because you will have only to put the ne fan.jsx in the directory, add it to the import and INSERT the new row on the DB.

How to conditionally return a component in React/Native?

I've used a tool to create React-native components from .svg files. It works well and I can load them using that way.
However, how do I conditionally load them?
I've imported some company logos as such:
import Tesla from '../../static/brandlogos/svg/Tesla'
import Amazon from '../../static/brandlogos/svg/Amazon'
import Google from '../../static/brandlogos/svg/Google'
import Facebook from '../../static/brandlogos/svg/Facebook'
import Apple from '../../static/brandlogos/svg/Apple'
Indeed, if I invoke the component as such, it works:
<Amazon />
However, I wish to(of course) conditionally load a component depending on the props this component receives. So, I create a function:
renderLogo (brandName) {
const Logos = {
Amazon,
Facebook,
Tesla,
Google,
Apple
};
if (Logos[brandName]) {
return <Amazon /> // This works!
}
if (Logos[brandName]) {
return Logos[brandName] // This doesn't!
}
if (Logos[brandName]) {
return Logos.Amazon // This also doesn't!
}
}
However, I simply cannot figure out how to create either a map or array to loop through, and render the specific component. If I straight up return the component, of course it works.
But how do I save each "Logo" in an array or map, and conditionally load + return this logo only?
I could, of course, hard code everything but that would be bad.
Simply do like this
if (Logos[brandName]) {
// Keep in mind the first letter should be capital (i.e. "C" in this case)
let Comp = Logos[brandName]
return <Comp />
}
I think, this post on Dynamic Component Names with JSX answers your question nicely:
components = {
foo: FooComponent,
bar: BarComponent
};
render() {
const TagName = this.components[this.props.tag || 'foo'];
return <TagName />
}
In this example you have your tag from the prop - the most typical case for many components.

react - render dynamic component insider render function of other component

I am trying to develop a web app using react and i have a issue.
my component get a 'exists component name' and I try to render this new component inside render function of the current component.
my current component render function
render(){
let Xxx = null;
if( this.props.onHex ){
console.log( this.props.onHex );
Xxx = <this.props.onHex />
}
return(
<div className="myClass">
<div className="anotherClass">
{Xxx}
</div>
</div>
);
}
}
it not works for me, the console log returns the name of the new component "Unit". when I replace the Xxx = <this.props.onHex /> with this Xxx = <Unit /> it works and render the Unit's render function.
it looks like react not recognise <Unit/> as component.
what I am doing wrong please advise.
my Unit code:
export default class Unit extends Component{
render(){
return(
<div>test</div>
);
}
}
UPDATE:
when I use const XxxName = Unit; Xxx = <XxxName />; it works for me but I want to be able to render the component from string ( I got this string from json ).
I guess I can create all my possible components at this situation inside a file load them into array or something and get them by string, but it's not something that can live with I have a lot of components maybe if I will some how load them from separate folder ( individual file for each component ) it will be half solution. but I still looking how to load component from string.
jsFiddle with another similar issue http://jsfiddle.net/dhjxu5oL/
UPDATE 2:
I am not found elegant way to reach my goal (I don't sure if it exists) for now I am using method for each dynamic component for hope that someone will advise me with more elegant solution. check it: React / JSX Dynamic Component Name
newExampleComponent() {
return <ExampleComponent />;
}
newComponent(type) {
return this["new" + type + "Component"]();
}
let Xxx = null;
if( this.props.onHex ){
const XxxName = this.props.onHex;
Xxx = <XxxName />;
}
Check this jsfiddle for example
UPDATE:
According to React official docs
You cannot use a general expression as the React element type. If you
do want to use a general expression to indicate the type of the
element, just assign it to a capitalized variable first. This often
comes up when you want to render a different component based on a
prop:
So you need to assign this.props.onHex to a CAPITALIZED variable first then you should be able to use it.
UPDATE again
Seems you want to pass a string, not a reference to the component. There is a dirty way to do that
const xxx = this.props.onHex || "";
const XxxComp = eval(xxx);
...
return (<XxxComp />);
I created this codepen for testing

Categories