sergiodxa / remix-i18next

The easiest way to translate your Remix apps
https://sergiodxa.github.io/remix-i18next/
MIT License
602 stars 44 forks source link

Error configuring remix-i18next in my remix app. #193

Closed Wakil18 closed 5 months ago

Wakil18 commented 5 months ago

Describe the bug

I was trying to configure the remix-i18next package following this guideline (Just using js instead of ts) but ended up getting these following errors.

Note: I'm not using Vite.

Errors while trying to run the app:

18:39:54 │ remix      │ /home/Desktop/shopify/my-app/build/index.js:428
18:39:54 │ remix      │ var import_i18next_fs_backend = __toESM(require("i18next-fs-backend")), import_node_path = require("node:path"), import_server2 = 
require("remix-i18next/server");
18:39:54 │ remix      │                                                                                                                                   ^
18:39:54 │ remix      │ Error [ERR_REQUIRE_ESM]: require() of ES Module 
/home/Desktop/shopify/my-app/node_modules/remix-i18next/build/server.js from 
/home/Desktop/shopify/my-app/build/index.js not supported.
18:39:54 │ remix      │ Instead change the require of server.js in 
/home/Desktop/shopify/my-app/build/index.js to a dynamic import() which is available in all CommonJS 
modules.
18:39:54 │ remix      │     at Object.<anonymous> (/home/Desktop/shopify/my-app/build/index.js:428:131)
18:39:54 │ remix      │     at async run 
(/home/Desktop/shopify/my-app/node_modules/@remix-run/serve/dist/cli.js:112:15)

Here's my package.json:

{
  "name": "efoli-push-bundles",
  "private": true,
  "scripts": {
    "build": "remix build",
    "predev": "prisma generate && prisma migrate deploy",
    "dev": "shopify app dev",
    "config:link": "shopify app config link",
    "config:push": "shopify app config push",
    "generate": "shopify app generate",
    "deploy": "shopify app deploy",
    "config:use": "shopify app config use",
    "env": "shopify app env",
    "start": "PORT=7001 npx remix-serve build/index.js",
    "docker-start": "npm run setup && npm run start",
    "setup": "prisma generate && prisma migrate deploy",
    "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
    "shopify": "shopify",
    "prisma": "prisma"
  },
  "prisma": {
    "seed": "node prisma/seed.js"
  },
  "dependencies": {
    "@prisma/client": "^5.12.0",
    "@remix-run/dev": "^2.7.1",
    "@remix-run/node": "^2.7.1",
    "@remix-run/react": "^2.7.1",
    "@remix-run/serve": "^2.7.1",
    "@shopify/app": "^3.57.0",
    "@shopify/app-bridge-react": "^4.0.0",
    "@shopify/cli": "^3.57.0",
    "@shopify/polaris": "^12.0.0",
    "@shopify/shopify-api": "^10.0.0",
    "@shopify/shopify-app-remix": "^2.8.2",
    "@shopify/shopify-app-session-storage-prisma": "^4.0.5",
    "i18next": "^23.11.5",
    "i18next-browser-languagedetector": "^8.0.0",
    "i18next-fs-backend": "^2.3.1",
    "i18next-http-backend": "^2.5.2",
    "isbot": "^5.1.0",
    "prisma": "^5.12.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-i18next": "^14.1.2",
    "remix-i18next": "^6.1.0"
  },
  "devDependencies": {
    "@remix-run/eslint-config": "^2.7.1",
    "@shopify/app-bridge-types": "^0.0.11",
    "@types/eslint": "^8.40.0",
    "@types/node": "^20.6.3",
    "@types/react": "^18.2.31",
    "@types/react-dom": "^18.2.14",
    "eslint": "^8.42.0",
    "eslint-config-prettier": "^9.1.0",
    "prettier": "^3.2.4",
    "typescript": "^5.2.2"
  },
  "workspaces": [
    "extensions/*"
  ],
  "trustedDependencies": [
    "@shopify/plugin-cloudflare"
  ],
  "author": "wakil"
}

Steps to Reproduce the Bug or Issue

So I bassically followed this exact guideline but used js instead of ts.

  1. Installed these following packages:
    
    npm install remix-i18next i18next react-i18next i18next-browser-languagedetector

npm install i18next-http-backend i18next-fs-backend


2. Created `public/locales/en/common.json` and `public/locales/es/common.json` file with basic contents like:

{ "greeting": "Hello" }


3. Then on the Remix projects app directory created `i18n.js` file with following contents:

export default { // This is the list of languages your application supports supportedLngs: ["en", "es"], // This is the language you want to use in case // if the user language is not in the supportedLngs fallbackLng: "en", // The default namespace of i18next is "translation", but you can customize it here defaultNS: "common", };


4. Then also created `i18next.server.js` file in the app directory with the following content:

import Backend from "i18next-fs-backend"; import { resolve } from "node:path"; import { RemixI18Next } from "remix-i18next/server"; // import i18n from "~/i18n"; // your i18n configuration file import i18n from "./i18n"; // your i18n configuration file

let i18next = new RemixI18Next({ detection: { supportedLanguages: i18n.supportedLngs, fallbackLanguage: i18n.fallbackLng, }, // This is the configuration for i18next used // when translating messages server-side only i18next: { ...i18n, backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"), }, }, // The i18next plugins you want RemixI18next to use for i18n.getFixedT inside loaders and actions. // E.g. The Backend plugin for loading translations from the file system // Tip: You could pass resources to the i18next configuration and avoid a backend here plugins: [Backend], });

export default i18next;


5. Next created `entry.client.jsx` with these content:

import { RemixBrowser } from "@remix-run/react"; import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import i18n from "./i18n"; import i18next from "i18next"; import { I18nextProvider, initReactI18next } from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import Backend from "i18next-http-backend"; import { getInitialNamespaces } from "remix-i18next/client";

async function hydrate() { await i18next .use(initReactI18next) // Tell i18next to use the react-i18next plugin .use(LanguageDetector) // Setup a client-side language detector .use(Backend) // Setup your backend .init({ ...i18n, // spread the configuration // This function detects the namespaces your routes rendered while SSR use ns: getInitialNamespaces(), backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" }, detection: { // Here only enable htmlTag detection, we'll detect the language only // server-side with remix-i18next, by using the <html lang> attribute // we can communicate to the client the language detected server-side order: ["htmlTag"], // Because we only use htmlTag, there's no reason to cache the language // on the browser, so we disable it caches: [], }, });

startTransition(() => {
    hydrateRoot(
        document,
        <I18nextProvider i18n={i18next}>
            <StrictMode>
                <RemixBrowser />
            </StrictMode>
        </I18nextProvider>,
    );
});

}

if (window.requestIdleCallback) { window.requestIdleCallback(hydrate); } else { // Safari doesn't support requestIdleCallback // https://caniuse.com/requestidlecallback window.setTimeout(hydrate, 1); }


6. Next on the `entry.server.jsx` did the following:

import { PassThrough } from "stream"; import { renderToPipeableStream } from "react-dom/server"; import { RemixServer } from "@remix-run/react"; import { createReadableStreamFromReadable } from "@remix-run/node"; import { isbot } from "isbot"; import { addDocumentResponseHeaders } from "./shopify.server";

// i18n imports import { createInstance } from "i18next"; import i18next from "./i18next.server"; import { I18nextProvider, initReactI18next } from "react-i18next"; import Backend from "i18next-fs-backend"; import i18n from "./i18n"; // your i18n configuration file import { resolve } from "node:path";

const ABORT_DELAY = 5000;

export default async function handleRequest( request, responseStatusCode, responseHeaders, remixContext ) { addDocumentResponseHeaders(request, responseHeaders); // const callbackName = isbot(request.headers.get("user-agent")) const callbackName = isbot(request.headers.get("user-agent") || "") ? "onAllReady" : "onShellReady";

// i18n code Start let instance = createInstance(); let lng = await i18next.getLocale(request); let ns = i18next.getRouteNamespaces(remixContext);

await instance .use(initReactI18next) // Tell our instance to use react-i18next .use(Backend) // Setup our backend .init({ ...i18n, // spread the configuration lng, // The locale we detected above ns, // The namespaces the routes about to render wants to use backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json") }, }); // i18n code End

return new Promise((resolve, reject) => { let didError = false;

const { pipe, abort } = renderToPipeableStream(
  <I18nextProvider i18n={instance}>
    <RemixServer
      context={remixContext}
      url={request.url}
      abortDelay={ABORT_DELAY}
    />
  </I18nextProvider>,

  {
    [callbackName]: () => {
      const body = new PassThrough();
      const stream = createReadableStreamFromReadable(body);
      responseHeaders.set("Content-Type", "text/html");

      resolve(
        new Response(stream, {
          headers: responseHeaders,
          // status: responseStatusCode,
          status: didError ? 500 : responseStatusCode,
        })
      );

      pipe(body);
    },
    onShellError(error) {
      reject(error);
    },
    onError(error) {
      didError = true;

      responseStatusCode = 500;
      console.error(error);
    },
  }
);

setTimeout(abort, ABORT_DELAY);

}); }


This is when I started receiving the following errors: 

18:39:54 │ remix │ /home/Desktop/shopify/my-app/build/index.js:428 18:39:54 │ remix │ var import_i18next_fs_backend = __toESM(require("i18next-fs-backend")), import_node_path = require("node:path"), import_server2 = require("remix-i18next/server"); 18:39:54 │ remix │ ^ 18:39:54 │ remix │ Error [ERR_REQUIRE_ESM]: require() of ES Module /home/Desktop/shopify/my-app/node_modules/remix-i18next/build/server.js from /home/Desktop/shopify/my-app/build/index.js not supported. 18:39:54 │ remix │ Instead change the require of server.js in /home/Desktop/shopify/my-app/build/index.js to a dynamic import() which is available in all CommonJS modules. 18:39:54 │ remix │ at Object. (/home/Desktop/shopify/my-app/build/index.js:428:131) 18:39:54 │ remix │ at async run (/home/Desktop/shopify/my-app/node_modules/@remix-run/serve/dist/cli.js:112:15)



### Expected behavior

Expected behavior is the app will run smoothly after what I've done so far and I'll move to the next steps following this guideline to implement the translation. 

**Note:** In another app my teammate solved this similar problem by using vite for the build process instead of Remix. But I don't wanna do this cause then vite imports in a different way thus to implement it he had to install a fresh project using `npm init @shopify/app@latest` which uses vite. Then he had to create each file newly here and copy the contents from the old project to the new one and change the imports syntax. I don't wanna do it unless I must need to cause this just doesn't seem right. 

### Screenshots or Videos

_No response_

### Platform

- OS: [Linux]
- Browser: [Chrome, Firefox]
- Version: [e.g. 91.1]

![image](https://github.com/sergiodxa/remix-i18next/assets/78558492/2b15a897-ab6e-4d0d-9d1e-0a344dd19e99)

### Additional context

I'm using shopify remix app template and the packages I'm using you can see it in the `package.json` above. 
sergiodxa commented 5 months ago

remix-i18next is ESM only, and some other i18next related packages are CJS only, if your app uses ESM you need to add every CJS-only dependency to serverDependenciesToBundle in your remix.config file, if your app is CJS you do the same with ESM-only packages.

And I recommend you to use Vite since the classic compiler is going to be removed in the future.

Wakil18 commented 5 months ago

Well everything works after switching to Vite.