Share i18next translations between nextjs and component workspaces - reactjs

We have a yarn 2 monorepo setup with the following workspaces:
/root
/app (nextjs)
/components (individual react functional components)
/storybook
/constants
Currently, /app has the i18next translation files stored local to the workspace, but I would like to move that into the constants workspace so that all workspaces can share the same translations. I had no issue moving the translations there and loading them in both the /app and /storybook workspaces.
Also, currently, all translations happen only in /app. The /components workspace has no translations, and the translated text is passed in props to the dumb components. So, a component in /app looks something like this:
import ListBox from "#root/components/ListBox";
import {useTranslation} from "react-i18next";
export default const Page() {
const [t] = useTranslation();
const label = t("listBoxLabel"); // <-- exists in en.json as a key
return (
<div>
<ListBox label={label} />
</div>
)
}
As such, storybook also has to provide the "label" prop to render the component in stories, doing this in pretty much the same manner.
What I would like to do is instead translate the text at the /components workspace level, so that the translations can be done in one place, where it's used, to reduce prop drilling and also simplify things.
Something like this:
/components/ListBox.js:
import {useTranslation} from "react-i18next";
export default const ListBox() {
const [t] = useTranslation();
const label = t("listBoxLabel"); // <-- exists in en.json as a key
return <p>{label}</p>
}
When I run this code, it just prints out the translation key "listBoxLabel", in both storybook and the nextjs app.
I was able to fix storybook by wrapping stories with I18nextProvider and changing the dependencies in the /components workspace for i18next and react-i18next to move them from a normal dependency to a peer dependency, and storybook properly renders the translation. However, then the NextJS /app throws an error that the react-i18next Module Not Found when it imports the /components/ListBox.js code. It seems to require the /components workspace keep a normal dependency for that, but doesn't translate it in that case. If I remove the /components dependencies altogether, /storybook won't compile and start for the same reason.
Duplicating this exact code in the app or storybook, however, displays the correct translation. So, it has something to do with importing it from a sibling workspace in the monorepo, and I'm not sure exactly how to go about fixing that for the nextjs app.
Just a note: I don't use the next-i18next component. Instead, I am simply using react-i18next directly, without any providers configured. I did attempt to wrap the app with the I18nextProvider component similar to the storybook stories, but it didn't work.
Does anyone have any ideas where to look further or what to try for this? I would think loading a component from a workspace component library where the translations happen within the library would be a common scenario, but I haven't been able to find anyone else running into this issue.

I don't know exactly what's causing this, but it appears to be a nextjs, webpack, or yarn bug. I discovered the problem was related to this in my tsconfig.js file in the #root/app workspace:
{
"compilerOptions": {
"paths": {
"#root/components/*": ["../components/*"]
}
}
}
I was able to fix this by removing this setting, but it broke other code (some components were typescript), so I just explicitly declared those component modules in the custom.d.ts file as modules.
Where the next/yarn bug comes in: after I removed this line, and restarted the dev server, the i18n translations were still broken. Until I went and touched the components/ListBox.js file. This caused something to rebuild within next, and the translations showed up.
The ListBox.js file is not typescript, so I don't understand why this causes a problem.
While trying to resolve this issue, yarn kept getting into a bad state, next was telling me I didn't have typescript installed when I did, and multiple other tedious random errors...what a mess.

Related

NEXTJS error: export encountered error on following paths [duplicate]

I'm trying to build my Next.js project but it keeps giving me this error in the terminal:
Error: Build optimization failed: found page without a React Component as default export in
pages/components/context/Context
That's the React context API file, there isn't supposed to be any default export there. Is this a bug or what?
You should move your components outside the pages folder. pages/ should only be used for page components as Next.js routing is based on its structure.
Next.js has a file-system based router built on the concept of pages.
When a file is added to the pages directory it's automatically available as a route.
By default, Next.js assumes anything under the pages folder is a page component and will try to build each file as a page.
Even though the above is the default behaviour, you can configure your Next.js app to include non-page files in the pages directory.
To do so, you can modify the pageExtensions entry in the next.config.js file as shown below. Then rename your page components to have a file extension that includes .page (_document.page.js, _app.page.js, index.page.js, etc).
module.exports = {
pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js']
}
With this configuration, Next.js will ignore any file that doesn't contain .page for the purpose of building pages/API routes and routing.
In my case, I had an empty file index.js in a folder. Using Nextjs Default Router
It seems to be not declared default export keyword in context component.
Try it as follow:
const Context = ()=>{
...
}
export default Context
I had the same error.
If you comment out all other code but leave this NextJS won't get mad at you:
export default function Home1() {
return <>{/* nothing */}</>;
}
I like to keep older index files and components locally and on github so this is a nice hack. I just copy all of the existing code add it to a new file and then add 1 to it for example:
index1.js
You can also leave a comment to kind of bring you and other devs up to speed as to why you did this for example:
//good for history of index implementation and associated syntax logic

Create react app - how to copy pdf.worker.js file from pdfjs-dist/build to your project's output folder?

Since I can't use browser's pdf viewer in the network where the app is going to be used, I am testing a react-pdf package for loading PDF's with React.
I have made a component where I am sending a url of my PDF that I get from backend:
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
const PDFViewer = ({url}) => {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
function onLoadError(error) {
console.log(error);
}
function onSourceError(error) {
console.log(error);
}
return (
<div>
<Document
file={window.location.origin + url}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={onLoadError}
onSourceError={onSourceError}
>
{[...Array(numPages).keys()].map((p) => (
<Page pageNumber={p + 1} />
))}
</Document>
</div>
);
};
export default PDFViewer;
But, on opening the PDFViewer I get an error
Error: Setting up fake worker failed: "Cannot read property 'WorkerMessageHandler' of undefined"
In documentation it says that you should set up service worker and that the recommended way is to do that with CDN:
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
But, I can't use CDN links for my project, and in the documentation it also says:
Create React App uses Webpack under the hood, but instructions for Webpack will not work. Standard instructions apply.
Standard (Browserify and others)
If you use Browserify or other bundling tools, you will have to make sure on your own that pdf.worker.js file from pdfjs-dist/build is copied to your project's output folder.
There are no instructions on how to do that with create-react-app. How can I set this up locally then?
Install pdfjs-dist
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
Reference: https://github.com/mozilla/pdf.js/issues/8305
found a more efficient way of including the worker
by including the library from the dependencies of react-pdf itself, this way you will never get a version mismatch like this The API version "2.3.45" does not match the Worker version "2.1.266"
if you install pdfjs-dist manually you will have to check react pdf dependency version on every build
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "react-pdf/node_modules/pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
see similar error on pdfjs library : https://github.com/mozilla/pdf.js/issues/10997
hope it helps people
You can install worker loader module for webpack:
npm install worker-loader --save-dev
Then use it inline where you are going to work with a worker:
import SomeWorker from 'worker-loader?inline=true!../workers/some.worker'
const someWorker: Worker = new SomeWorker()
someWorker.postMessage(...)
I haven't tried this solution with react-pdf, but it might help.
You may need to add types for TypeScript if you are using it:
declare module 'worker-loader*' {
class SomeWorker extends Worker {
constructor()
}
export default SomeWorker
}
Just to add that in some .d.ts file in your project.
Install pdfjs-dist then use the webpack module:
import { pdfjs } from 'react-pdf'
import worker from 'pdfjs-dist/webpack'
pdfjs.GlobalWorkerOptions.workerSrc = worker
If your build process uses cli commands, (i.e. AWS buildspec), you can use this:
mkdir -p build && cp ./node_modules/pdfjs-dist/build/pdf.worker.js build
If you are in a corporate codebase environment and have little to no experience configuring WebPack, I wanted to share a little more info if (like me) you struggled with this for quite a long time.
My environment has several complicated WebPack config files (base, production, and development), and the resolution ended up being pretty simple but it escaped me for quite a while because I was unfamiliar with the complicated build process.
1) The Implementation
Quite simple, just as the docs recommend (I went with the minified file). Our React environment required me to use React-PDF#4.2.0, but there aren't any differences here.
import {Document, Page, pdfjs} from 'react-pdf'
pdfjs.GlobalWorkerOptions.workerSrc = 'pdf.worker.min.js'
Note: a previous solution recommended grabbing the source from the react-pdf node_modules folder, however, my codebase is setup to install dependencies separately somehow because when I npm install react-pdf, pdfjs-dist is also installed separately. Regardless, this method did not work for my codebase (importing the worker as a variable) due to the way the project is built. The import command acted like it couldn't find the proper named export inside a node_modules folder. It was top-level or nothing.
2) WebPack Config
Since I do not know WebPack at all, but found pretty easily that what I needed to do was take advantage of CopyWebpackPlugin, I searched through those existing dev and prod webpack config files, and found existing copy commands for JQuery and polyfill and added a new plugin to that array:
new CopyWebpackPlugin({from: 'node_modules/pdfjs-dist/build/pdf.worker.min.js})
I had to do this in multiples places in both config files as this large project has several entry point server files for the different services of the website.
3) Inserting Script Tag to HTML Head
This was the crucial part I was missing. There was a "ComponentFactory" file whose job it was to insert chunks of html in the <head> and tail of the html file. I wasn't used to something like this on small projects. So there, I simply copied what was already done for the jquery and polyfill, which included a string literal of the location of the assets folder the webpack was building out to. In my case, that was something like "assets/v1/". So the tag looked like this:
<script src=`${STATIC_ASSETS_URL}/pdf.worker.min.js` defer></script>
It works perfectly, however I am still getting the "Setting Up a Fake Worker" but immediately after that, it loaded it successfully in console and checking the dev tools, it was using the proper file. It's probably just a timing thing of the src set not running high enough in the code, but it was not effecting the outcome, so I let it go.
(Sidebar, if you also get the "TT unknown function" (paraphrasing) error, that can be ignored. It's just a font issue with whatever PDF you are loading and is just a warning, not an error.)
I was facing this issue once I had to use "react-pdf" from within a package.
It was solved by importing the worker conditionally into the code:
Conditional import:
export const getWorker = () => {
try {
return require('react-pdf/node_modules/pdfjs-dist/legacy/build/pdf.worker.entry.js')
} catch () {
return require('pdfjs-dist/legacy/build/pdf.worker.entry.js')
}
}
usage:
import { Document, Page, pdfjs } from 'react-pdf/dist/umd/entry.webpack'
pdfjs.GlobalWorkerOptions.workerSrc = getWorker()

Is it possible to use single-spa (import map + SystemJS) and include TypeScript declarations?

I've created a few apps that follow the same pattern as the react-microfrontend projects listed on the Single-spa examples page. However, these examples are not using TypeScript.
I am receiving a TS error when App2 tries to import a function that exists in App1, because App2 is unable to find the typings for the App1 object. And this makes sense to me, since the importmap.json that is referenced in the index.html file of the root-config project only has a URL listed for the .js file of each microfrontend. I really don't want to add #ts-ignore statements everywhere, and I also don't want to do cross-app communication without being type safe.
An example of what I'm referring to:
This file in the Planets app references the Button component from the Styleguide app. I am unable to build this unless I place a #ts-ignore on the line before the import statement, because my Planets app cannot find the type declarations for Styleguide.
I am very new to import maps and SystemJS in general, so if anyone could point me in the right direction, I'd appreciate it. I've actually gone through the trouble of building my Styleguide as a regular npm library and just including it as a dependency in the Planets package.json file... which kind of defeats the whole purpose of using Single-spa in the first place.
importmap.json
{
"imports": {
"#react-mf/planets": "//localhost:9000/react-mf-planets.js",
"#react-mf/styleguide": "//localhost:9001/react-mf-styleguide.js"
}
}
page.component.ts
// #ts-ignore
import { Button } from "#react-mf/styleguide";
...
render() {
return (
// Unable to view the available props
<Button label="Hello World" />
);
}
Unfortunately no, they cannot be shared via the import map because that operates in the browser, and browsers do not understand TypeScript syntax.
Your alternatives are:
Publish your module's types to an npm registry and npm install it into each dependent microfrontend
One caveat here: you will need to update this with every new publish of the shared module, but that can be automated with various tools such as renovate
Mock the utility module's types
More info can be found at this Github issue: https://github.com/single-spa/single-spa/issues/609#issuecomment-665132965

Embed SVGs in JSX at build time?

I'm relatively new to React, using create-react-app to build simple prototypes. I'd like to be able to export SVGs from Sketch or a similar design tool and use them in my React app, embedded directly in the JSX since that allows styling via CSS.
This is relatively straightforward with a little effort: copy the exported SVG code out of the original .svg file, tweak how the xmlns attributes are named, done.
But if I have a lot of images and am iterating on them frequently, that becomes incredibly high-effort. I'd like a way to take a directory of SVGs at build time and basically concatenate them into a single .js file, e.g.,
const icons = {
first: <svg>...contents of first svg file...</svg>,
second <svg>...contents of second file...</svg>,
...
}
This is relatively trivial to do via shell script or something, but it would be great to integrate into the build process. How would I go about doing something like this?
Thanks!
As of create-react-app version 2, you can import svg files directly as React components. As you're just starting with development and have probably installed create-react-app package recently and won't suffer any possible side-effects from updating to new version of create-react-app, just edit the react-scripts entry in package.json to say "react-scripts": "^2.0.3" and run npm install or yarn install.
Then you'll be able to do this:
import { ReactComponent as Logo } from './logo.svg';
const App = () => (
<div>
<Logo />
</div>
);
Source: create-react-app Github page readme

Webpack not including exported components

I have a React application, and I'm building the React components inside a seperate library so that it can be shared throughout other applications. As the React Components are private, they're installed in NPM via git+ssh, rahter than as an NPM package.
The basics:
My component Library has this structure:
-- components
---- component1.jsx
---- component2.jsx
---- index.js
-- package.json
The library's package.json file looks basically like this:
{
"name": "my-react-components",
"version": "1.0.0",
"description": "Collection of React components",
"main": "components/index.js",
"dependencies": {
...
}
}
The components/index.js file looks like this
export * from './component1';
export * from './component2';
Each component is written like this:
export const SomeComponent = () => { ... };
My application references these components like this
import { SomeComponent } from 'my-react-components';
My NPM dependency looks like this
"my-react-components": "git+ssh://{gitaddress}"
Here's my applications webpack config
Since I'm developing this library in unison with my application, I have been using a symlink from the library folder to the application's node_modules folder to allow the references. This has been working, and is continuuing to work fine.
The problem
When I don't use a symlink and instead use the version installed by NPM, babel or webpack or some part of the puzzle suddenly stops building my component library, and instead of referencing the components, I end up with the export * from ... tags in my compiled bundle. Cleaning the package and returning to the symlink method, and everything works fine again.
Webpack shows no errors, everything appears to build fine. I can see the difference in output however, in the symlink method, I can see each file being written into the bundle:
[1] ./node_modules/my-react-components/components/component1.jsx
[2] ./node_modules/my-react-components/components/component1.jsx
// Something like that I just made that up,
// the point is I see the paths for components that are being referenced.
When I don't use the symlink I instead just see something like
[1] ./node_modules/my-react-components/components/index.js
and as previously mentioned, that file is basically dropped straight in, no babel, or checking imports or anything.
So why does this happen with an NPM installed module, but not the exact same files in the exact same location through a symlink?
Just to note they are definitely the exact same files, I've done this immediately after pushing my lib and making no changes, deleting the node_modules folder and reinstalling, re-symlinking etc. The difference in the two methods is reproducable every time.

Resources