I am working on my remix vite project and currently set up i18n for localization. Initially I have found out that no button component in my project works (I have even tried console.log statements but alas). I have read somewhere that it may be due to an error on somewhere else. I have tried resolving this problem for hours but nothing worked so I started to search for other errors. While I get no error on my IDE terminal, the site inspector throws this error:
entry.client.jsx:31 Error initializing i18next: TypeError: Cannot convert undefined or null to object at Function.values (<anonymous>) at getInitialNamespaces (remix-i18next_client.js?v=582c9657:5:27) at hydrate (entry.client.jsx:14:16)
Obviously this happens because getInitialNamespaces
return a null or an undefined value. The question is how to prevent this. Nothing I did seems to work and I am on my wits end.
Here is the specific code block:
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() {
try {
const ns = getInitialNamespaces() || ['translation'];
await i18next
.use(initReactI18next)
.use(LanguageDetector)
.use(Backend)
.init({
...i18n,
ns,
defaultNs: 'translation',
backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" },
detection: {
order: ["htmlTag"],
caches: [],
},
});
} catch (error) {
console.error("Error initializing i18next:", error);
}
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);
}
I have tried just writing ns: "translation"
but this doesn’t seem to work either. I also checked remix i18n docs but couldn’t find anything either they say to write ns: getInitialNamespaces()
. The code initially wasn’t inside a try block but after throwing the error I found out that it exists the hydrate function that it is currently in thus, the try block is required.
The i18next.server.js:
import { resolve } from "node:path";
import { RemixI18Next } from "remix-i18next/server";
import i18n from "./i18n";
let i18next = new RemixI18Next({
detection: {
supportedLanguages: i18n.supportedLngs,
fallbackLanguage: i18n.fallbackLng,
order: ["path"],
},
i18next: {
...i18n,
backend: {
loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"),
},
},
});
export default i18next;
entry.server.jsx:
import { PassThrough } from "stream";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";
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";
import { resolve } from "node:path";
const ABORT_DELAY = 5000;
export default async function handleRequest(
request,
responseStatusCode,
responseHeaders,
remixContext,
) {
let callbackName = isbot(request.headers.get("user-agent"))
? "onAllReady"
: "onShellReady";
let instance = createInstance();
let ns = i18next.getRouteNamespaces(remixContext);
await instance
.use(initReactI18next)
.use(Backend)
.init({
...i18n,
lng: (() => {
const pathSegments = new URL(request.url).pathname
.split("/")
.filter(Boolean);
return pathSegments[0];
})(),
ns,
backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json") },
});
return new Promise((resolve, reject) => {
let didError = false;
let { pipe, abort } = renderToPipeableStream(
<I18nextProvider i18n={instance}>
<RemixServer context={remixContext} url={request.url} />
</I18nextProvider>,
{
[callbackName]: () => {
let body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(stream, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
}),
);
pipe(body);
},
onShellError(error) {
reject(error);
},
onError(error) {
didError = true;
console.error(error);
},
},
);
setTimeout(abort, ABORT_DELAY);
});
}
Also please note that I don’t even know if this is the reason that the buttons don’t work but this is my only lead so far.
Thank you kindy.