I’m trying to implement translation in my remix app using remix-i18next following this guideline.
So 1st I installed these following packages:
npm install remix-i18next i18next react-i18next i18next-browser-languagedetector
npm install i18next-http-backend i18next-fs-backend
created public/locales/en/common.json
and public/locales/es/common.json
file with basic contents like:
{
"greeting": "Hello"
}
Then on the Remix projects app directory created i18n
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",
};
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;
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);
}
Next on the entry.server.jsx
with:
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);
});
}
But then started getting this following error:
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)
How can I fix this? Thanks!