Solution for i18next on server on vercel

Hey! I have sollution for i18next on server on vercel.

if you want more details on how to solve this problem with transactions on the server side, write, I will write a solution that does not cause an error :slight_smile:

Hey, @mazurro1! I’m sure folks will find this interesting. Feel free to share :smile:

All you need to do is fetch the transactions and everything starts working :slight_smile:
The rest of the code from the official remix site: remix-i18next | Remix Resources

entry.servet.tsx:

import { PassThrough } from "node:stream";

import {
  createReadableStreamFromReadable,
  type EntryContext,
} from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { createInstance } from "i18next";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";
import { I18nextProvider, initReactI18next } from "react-i18next";

import { cookiesKey } from "./constants/cookies";
import { E_Language } from "./constants/languages";
import { getCookieValue } from "./data/cookies.server";
import i18n from "./i18n";
import i18next from "./i18next.server";

const ABORT_DELAY = 5000;

const checkFileExists = async (url: string): Promise<boolean> => {
  try {
    const response = await fetch(url, { method: "HEAD" });
    return response.ok;
  } catch (error) {
    console.error(`Error checking file existence at ${url}:`, error);
    return false;
  }
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fetchFile = async (url: string): Promise<any> => {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`Failed to load ${url}: ${response.statusText}`);
    }
    return await response.json();
  } catch (error) {
    throw new Error(`Error fetching ${url}: ${error}`);
  }
};

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

  const instance = createInstance();
  const ns = i18next.getRouteNamespaces(remixContext);

  const CustomBackend = {
    read: (language: string, namespace: string, callback: Function) => {
      const protocol = request.headers.get("x-forwarded-proto") || "http";
      const host = request.headers.get("host");
      const baseUrl =
        process.env.NODE_ENV === "development"
          ? "https://your-url-prd.com"
          : `${protocol}://${host}`;

      const url = `${baseUrl}/locales/${language}/${namespace}.json`;

      const handleRequest = async () => {
        try {
          const exists = await checkFileExists(url);
          if (!exists) {
            callback(new Error(`File not found: ${url}`), false);
            return;
          }

          const data = await fetchFile(url);
          callback(null, data);
        } catch (error) {
          console.error(error);
          callback(error, false);
        }
      };

      handleRequest();
    },

    type: "backend",
  };

  await instance
    .use(initReactI18next)
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    .use(CustomBackend)
    .use(LanguageDetector)
    .init({
      ...i18n,
      ns,
    });

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

    const { abort, pipe } = renderToPipeableStream(
      <I18nextProvider i18n={instance}>
        <RemixServer context={remixContext} url={request.url} />
      </I18nextProvider>,
      {
        [callbackName]: () => {
          const 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);
        },
        onError(error: unknown) {
          didError = true;

          console.error(error);
        },
        onShellError(error: unknown) {
          reject(error);
        },
      },
    );

    setTimeout(abort, ABORT_DELAY);
  });
}