Using i18next to replace a variable with a ReactNode element - javascript

I have a translation json file with the following translation:
"pageNotFound": {
"description": "The page could not be found. Click {{link}} to return to the home page"
},
The link variable I am wanting to be replaced by a ReactRouter <Link>
I have the following code in my render method which outputs the below picture.
public render() {
const { t } = this.props;
const message = t('pageNotFound.description', { link: <Link to="/">here</Link> });
return (
<div className="body-content">
<div>
{message}
</div>
</div>
);
}
I have played with the <Trans> component and I think this may be a way but it seems like you have to type the full text including <> tags which for my use case is not what i'm after as I want all text to be in the translation json if possible.
Any recommendations are welcome

You should use Trans component for this.
"pageNotFound": {
"description": "The page could not be found. Click <0>here</0> to return to the home page"
},
public render() {
const { t } = this.props;
return (
<div className="body-content">
<div>
<Trans
t={t}
i18nKey="pageNotFound.description"
components={[
<Link key={0} to="/">
here
</Link>,
]}
/>
</div>
</div>
);
}

Related

Why does my TOC component add `{"-" + 1}` to its links every time I click on one of them?

I implemented a table-of-contents component to my site following this tutorial.
It works, but every time I click on one of the TOC's links, all the links (including the clicked one) in the TOC get a {"-" + 1} appended. So if I click a link, all those links go from #first-heading, #second-heading, etc. to #first-heading-1, #second-heading-1, etc. If I click one of the links again, they will all get #first-heading-2, #second-heading-2 etc., and so on. This behavior is of course problematic, as it breaks the links.
What's causing this? How do I fix it?
I noticed the tutorial uses the remark-slug plugin for the headings, while I use the gatsby-autolink-headers plugin. Can that be the source of the problem? I've not been able to test with the former, as I get an error when trying to install it.
EDIT: I've tried with both plugins. Same problem.
TableOfContents.js
import React from "react"
import Slugger from "github-slugger"
import { Link } from "gatsby"
const slugger = new Slugger()
export default ({ headings }) => (
<div className="table-of-contents">
<h3>On this page</h3>
<ol>
{headings
.filter(heading => heading.depth !== 1)
.map(heading => (
<li key={heading.value}>
<Link
to={"#" + slugger.slug(heading.value)}
>
{heading.value}
</Link>
</li>
))}
</ol>
</div>
)
post-template.js
import * as React from "react"
import { graphql } from "gatsby"
import { MDXRenderer } from "gatsby-plugin-mdx"
import Layout from "../components/layout.js"
import Seo from "../components/seo.js"
const PostTemplate = ({ data, location }) => {
let post = data.mdx
return (
<Layout location={location}>
<Seo
title={post.frontmatter.title}
description={post.frontmatter.lead}
date={post.frontmatter.computerDate}
/>
<article className="article">
<h1 itemprop="headline">{post.frontmatter.title}</h1>
<p
className="lead"
itemprop="introduction"
>
{post.frontmatter.lead}
</p>
<MDXRenderer headings={post.headings}>
{post.body}
</MDXRenderer>
</article>
</Layout>
)
}
export default PostTemplate
export const pageQuery = graphql`
query PostBySlug($id: String!) {
site {
siteMetadata {
title
}
}
mdx(id: {eq: $id}) {
id
excerpt(pruneLength: 160)
body
frontmatter {
title
computerDate: date(formatString: "YYYY-MM-DD")
humanDate: date(formatString: "DD MMMM YYYY")
lead
}
headings {
depth
value
}
}
}
`
index.mdx
---
/* frontmatter */
---
<!-- component imported as shortcode in `layout.js` -->
<TableOfContents headings={props.headings} />
layout.js (excerpt)
import TableOfContents from "./article-components/TableOfContents"
const shortcodes = {
TableOfContents
}
export default function Layout({ children }) {
return (
<div className="layout-wrapper">
<Header />
<main>
<MDXProvider components={shortcodes}>
{children}
</MDXProvider>
</main>
</div>
)
}
It's because of the slugger. In their docs:
slugger.slug('foo')
// returns 'foo'
slugger.slug('foo')
// returns 'foo-1'
slugger.slug('bar')
// returns 'bar'
slugger.slug('foo')
// returns 'foo-2'
Because it ensures that the links are unique (like GitHub does), it appends the -1, -2, etc.
As long as you use you gatsby-autolink-headers plugin can get rid of the slugger implementation. If you need, you can use the normal link value (heading.value), the slug field (if provided), or sanitize it using a custom function like:
function slugify (text) {
return text
.toString()
.toLowerCase()
.normalize(`NFD`)
.trim()
.replace(/\s+/g, `-`)
.replace(/[^\w-]+/g, ``)
.replace(/--+/g, `-`);
};
<Link to={"#" + slugify(heading.value)}>

React: implementing a router

I tried implementing browser router, but to no success. i'm having trouble with useParams hook, and just the router in general. Looked through multiple posts and i just wasn't able to get it working. I'll post the most barebones code below, hoping someone knows the solution. I removed the traces of the router, since it didn't work.
App.js is currently empty:
const App=()=> {
return (
<Main/>
);
}
Main.jsx is my main element, where components change. There isn't a page change per se, everything is in the main element. values get passed through props into main and written into state, so the useEffect can change visibility of components based on what you chose, first category, then recipe.:
const Main =()=> {
const [showElement, setShowElement] = useState("category");
const [selectedCategory, setSelectedCategory] = useState();
const [selectedRecipe, setSelectedRecipe] = useState();
useEffect(()=> {
if (selectedRecipe) {
setShowElement("recipe")
} else if (selectedCategory) {
setShowElement("recipeSelection")
}
window.scrollTo(0, 0)
}, [selectedCategory][selectedRecipe]);
return (
<>
<Header />
<main className="main">
<div>
<div>
{showElement === "category" &&
<CategoryWindow
passSelectedCategory={setSelectedCategory}
/>
}
</div>
<div>
{showElement === "recipeSelection" &&
<RecipeSelection
value={selectedCategory}
passSelectedRecipe={setSelectedRecipe}
/>
}
</div>
<div>
{showElement === "recipe" &&
<RecipeWindow
value={selectedRecipe}
/>
}
</div>
</div>
</main>
</>
)
}
This is the recipe picker component. For example when i click on curry, i'd like the url to show /food/curry. None od the names are hardcoded, everything comes from a javascript object:
const RecipeSelection =(props)=> {
const recipies = Recipies.filter(x=>x.type === props.value);
return (
<div className="selection-div">
<div className="selection-inner">
{recipies.map(selection =>
<>
<img src={require(`../images/${selection.id}.png`)}
className="selection-single"
key={selection.id}
alt={"picture of " + selection.id}
onClick={()=> props.passSelectedRecipe(selection.id)}
>
</img>
<div className="container-h3"
onClick={()=> props.passSelectedRecipe(selection.id)}
>
<h3 className="selection-h3">{selection.name}</h3>
</div>
</>
)}
</div>
</div>
)
}

Next.js markdown-blog page code not working

import { Blogs } from "../../components/Data"
import Image from "next/image";
import Link from "next/link";
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
export default function index({ posts }) {
// const Short_Blog = Blogs.map(item =>
// <div className="BLOGS_Projects" key={item}>
// <div className="BLOGS_Projects_Image">
// <Image
// className='BLOGS_Projects_image'
// src={item['img-1']}
// layout='fill'
// // objectFit='contain'
// />
// </div>
// {/* if someone clicks on this link i want them to go to [project].js and send This item to [projcet].js */}
// <Link href={'/blogs/' + Blogs.indexOf(item)}>
// <a>{item['title']}</a>
// </Link>
// <p>{item['desc']}</p>
// </div>
// );
return (
<div className="BLOGS_Container">
<div className="BLOGS_Sub_Container">
<div className="BLOGS_New">
<h1 style={{ marginLeft: 25 + 'px' }}>Cyber-Security Blogs</h1>
<div className="BLOGS_Present">
{posts.map( post =>{
<h1 style={{zIndex: '10000'}}>{post}</h1>
})}
</div>
</div>
</div>
</div>
)
}
export async function getStaticProps() {
const files = fs.readdirSync(path.join('posts'))
const posts = files.map((filename) => {
const slug = filename.replace('.md', '')
const markdownWithMeta = fs.readFileSync(path.join('posts', filename), 'utf-8')
const { data: frontmatter } = matter(markdownWithMeta)
return {
slug,
frontmatter
}
})
return {
props: {
posts,
},
}
}
The posts object does exist but when I try to use it in the HTML, it doesn't show up anyone got any idea why this is happening?
the posts.map that I used is not giving any errors but it also doesn't show any h1 html in actual page, the actual page seems blank.
You need to return the element to render inside the callback of map function.
1st way
{posts.map((post) => {
return <h1 style={{ zIndex: "10000" }}>{post}</h1>;
})}
2nd way
{posts.map((post) => (
<h1 style={{ zIndex: "10000" }}>{post}</h1>
))}
React will not render plain html content as a string. You need to use the dangerouslySetInnerHTML prop, like so:
{
posts.map((post) => {
return <div dangerouslySetInnerHTML={{__html: post}}></div>
})
}
You can read more about it here:
https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
Alternatively you can use something like next-mdx-remote, which you can learn about here:
https://github.com/hashicorp/next-mdx-remote

Change the page HTML on button click React

So I have an already written heapsort animation [here][1] with JS/Jquery + HTML+ CSS. We are currently rebuilding +expanding the project in React. I don't know it (or web technologies in general), and so far I've managed by working on the DS while my partner does the react specific things+ animations. I've been given the green light to just use the heap animation since he liked it too. I've been banging my head trying to integrate the HTML file in to react. Trying iframe, window.location, and the other SO things just haven't worked for me. This is why I'm going with the approach of trying to set the HTML code to my HTML file when I click it.
The directory structure is:
heap.js
heapsort (dir) (copied from project):
a)index.html
b)static (dir):
i)styles.css <br>
ii)style.css <br>
iii)heaper.js (does the actual animation work) <br>
I'd like to do something where a button click in heap.js will load up the index.html. If that can load up correctly, everything should work smoothly.
function create(){
//set HTML->./heapsort/index.html
window.location="./heapsort/index.html" //does not work despite going to the correct path
}
Edit: added code
Heap.js (everything except the create function works as is supposed to)
import React, { useState } from 'react';
import { CSSTransition } from 'react-transition-group';
// import { Component } from 'react';
// import Page from './heapsort/index.html';
import VisualPage, {
About,
Complexity,
Controls,
ControlGroup,
Visualization,
} from '../VisualPage.js';
import './Styles/queue.css';
export function Heap(props) {
return (
<div className="heap">
{props.children}
</div>
);
}
export function QueueNode(props) {
return (
<CSSTransition
appear
in={props.show}
onExited={props.onExited}
timeout={200}
unmountOnExit
classNames="queue-node"
>
<div className={"queue-node" + (props.highlight ? " highlight" : "")}>
{props.children}
</div>
</CSSTransition>
);
}
function Demo() {
const [list, setList] = useState([]);
const [length, setLength] = useState(0);
function onExited() {
setList(list.slice(1));
}
function create(){
//set HTML->./heapsort/index.html
// window.location="./heapsort/index.html" //does not work despite going to the correct path
window.location.replace("./heapsort/index.html")
}
return (
<>
<Controls>
<ControlGroup>
<label htmlFor="create">Len of randomized array</label>
<input name="add" type="text" onChange={e => setLength(e.target.value)}></input>
<button onClick={create}>Create Array</button>
</ControlGroup>
</Controls>
<Visualization>
<Heap>
{list.map((node, i) => {
return (
<QueueNode
key={i}
index={i}
show={node.show}
highlight={node.highlight}
onExited={onExited}
>
{node.value}
</QueueNode>
);
})}
</Heap>
</Visualization>
</>
);
}
export default function QueuePage(props) {
return (
<VisualPage title="Array">
<About>
<h4>What is a Heap?</h4>
The bane of my existance.
</About>
<Complexity complexity={[
{
"name": "Indexing",
"complexity": "Θ(1)"
},
{
"name": "Set Element at Index",
"complexity": "Θ(1)"
},
{
"name": "Average wasted space",
"complexity": "Θ(1)",
},
]} />
<Demo />
</VisualPage>
);
}
```<br>
Index.html
-->
<input type="number" id="len" placeholder="Insert length of randomized array"></input>
<button id="click">Generate Arr of Length</button>
<button id="Refresh" onclick="refresh()">Refresh</button>
var myLink = document.getElementById('click');
function refresh(){
location.reload();
}
myLink.onclick = function () {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "./static/heap.js";
document.getElementsByTagName("head")[0].appendChild(script);
return false;
}
```
[1]: https://dl1683.github.io/DataStructuresInJavaScript/ds/heapsort/index.html
window.location doesn't redirect to another page. You must use
window.location.replace()
function create(){
window.location.replace("./heapsort/index.html")
}

HTML to rendered html

Below is a simplified version of my app where there is a template rendered on the page and it is filled with the help of two text inputs.
It's a "template generator" type app and I want to be able to copy the completed template as rendered on the page so that it can be pasted elsewhere. To do this normally you would click + drag to highlight the rendered html then right click + copy or ctrl + c.
How can I convert my HTML to a rendered version so that I can copy to clipboard with the help of my react-clipboard button? Or is there another way I can achieve this?
If anything isn't clear please let me know - thanks in advance
TL;DR: Currently when you click on "copy to clipboard" the app is copying html. I want it to copy the contents as it would be rendered on a webpage. For example hello world converted and copied should copy a red h1 that says hello world.
import React, { Component } from 'react';
import { TextField } from 'material-ui';
import Clipboard from 'react-clipboard.js';
import './App.css';
const logo = 'https://www.google.co.uk/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png';
class App extends Component {
constructor() {
super();
this.state = {
name: 'Default Name',
title: 'Default Title'
};
}
componentDidMount() {
this.setState({template: this.createTemplate(this.state.name, this.state.title) });
}
createTemplate(name, title) {
const template = `<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet">
</head>
<body>
<h1>${name}</h1>
<h2>${title}</h2>
</body>
</html>`
this.setState({template});
}
updateValue(event, type) {
if(type === 'name') {
this.setState({name: event.target.value});
this.createTemplate(event.target.value, this.state.title);
} else {
this.setState({title: event.target.value});
this.createTemplate(this.state.name, event.target.value);
}
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Signature Builder</h1>
</header>
<div>
<br/>
<TextField hintText="Name" onChange={(e) => this.updateValue(e, 'name')} />
<TextField hintText="Title" onChange={(e) => this.updateValue(e, 'title')} />
<div style={{textAlign: 'left', margin: 10}} dangerouslySetInnerHTML={{ __html: this.state.template }} />
<Clipboard data-clipboard-text={this.state.template}>
copy to clipboard
</Clipboard>
</div>
</div>
);
}
}
export default App;
The problem is that react-clipboard doesn't support copying HTML text; you'll just have to do it using normal JS. Unfortunately, clipboard APIs are fairly finicky.
You can get around some of the issues by just not using a button and capturing the onCopy event to change the content copied by the clipboard.
const copyAction = e => {
e.preventDefault()
e.clipboardData.setData('text/plain', 'This is plaintext; try pasting into a program that supports rich text')
e.clipboardData.setData('text/html', '<h1>Hello</h1><h2>World</h2>')
console.log('Copy event captured!')
}
const App = () => (
<div onCopy={copyAction}>
Copy me!
</div>)
ReactDOM.render(<App />,
document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='root' />
Of course, the content of the HTML text itself can be created as you please whether it's through your template literals or through using ReactDOMServer like coreyward suggested.
More info:
How do I copy to the clipboard in JavaScript?
https://developer.mozilla.org/en-US/docs/Web/Events/copy
https://reactjs.org/docs/events.html#clipboard-events
You can use ReactDOMServer.renderToStaticMarkup to convert your React component into a string without any of the React-specific tags. If you expect the HTML to be hydrated later you can use renderToString instead.
https://reactjs.org/docs/react-dom-server.html

Categories

Resources