### Install Dependencies Source: https://github.com/sergiodxa/remix-i18next/blob/main/CONTRIBUTING.md Run this command to install all necessary project dependencies. ```bash bun install ``` -------------------------------- ### Install remix-i18next and dependencies Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Install the core library and necessary i18next packages for language detection and fetching. ```sh npm install remix-i18next i18next react-i18next i18next-browser-languagedetector ``` ```sh npm install i18next-fetch-backend ``` -------------------------------- ### Create a 404 Not Found Route Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Implement a catch-all route to handle 404 errors and render a custom, internationalized 404 page. Ensure the loader returns a 404 status. This setup is crucial for proper i18next instance configuration in `entry.server.tsx`. ```tsx import { useTranslation } from "react-i18next"; import { data, href, Link } from "react-router"; export async function loader() { return data(null, { status: 404 }); // Set the status to 404 } export default function Component() { let { t } = useTranslation("notFound"); return (

{t("title")}

{t("description")}

{t("backToHome")}
); } ``` -------------------------------- ### Client-side i18next Configuration Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Configure i18next on the client-side using `i18next-browser-languagedetector` to detect the language from the HTML tag, ensuring consistency with server-side detection. This setup is for the `entry.client.tsx` file. ```tsx import Fetch from "i18next-fetch-backend"; import i18next from "i18next"; import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { I18nextProvider, initReactI18next } from "react-i18next"; import { HydratedRouter } from "react-router/dom"; import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector"; async function main() { await i18next .use(initReactI18next) .use(Fetch) .use(I18nextBrowserLanguageDetector) .init({ fallbackLng: "en", // Change this to your default language // Here we only want to detect the language from the html tag // since the middleware already detected the language server-side detection: { order: ["htmlTag"], caches: [] }, // Update this to the path where your locales will be served backend: { loadPath: "/api/locales/{{lng}}/{{ns}}" }, }); startTransition(() => { hydrateRoot( document, , ); }); } main().catch((error) => console.error(error)); ``` -------------------------------- ### Setup i18next Middleware in Remix Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Configure the i18next middleware for locale detection, cookie handling, and resource loading. Ensure `react-router@7.9.0` or later is used. This code defines the locale cookie and initializes the middleware with supported languages and fallback. ```typescript import { initReactI18next, } from "react-i18next"; import { createCookie } from "react-router"; import { createI18nextMiddleware } from "remix-i18next/middleware"; import resources from "~/locales"; // Import your locales import "i18next"; // This cookie will be used to store the user locale preference export const localeCookie = createCookie("lng", { path: "/", sameSite: "lax", secure: process.env.NODE_ENV === "production", httpOnly: true, }); export const [i18nextMiddleware, getLocale, getInstance] = createI18nextMiddleware({ detection: { supportedLanguages: ["es", "en"], // Your supported languages, the fallback should be last fallbackLanguage: "en", // Your fallback language cookie: localeCookie, // The cookie to store the user preference }, i18next: { resources }, // Your locales plugins: [initReactI18next], // Plugins you may need, like react-i18next }); // This adds type-safety to the `t` function declare module "i18next" { interface CustomTypeOptions { defaultNS: "translation"; resources: typeof resources.en; // Use `en` as source of truth for the types } } ``` -------------------------------- ### Get Server-Side TFunction with RemixI18Next.getFixedT Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Initializes a server-side i18next instance for a specific locale and namespace, returning a bound TFunction. Accepts a Request object for auto-detection or a locale string directly. Can also use key prefixes for relative translations. ```typescript import { i18n } from "~/i18n.server"; export async function loader({ request }: Route.LoaderArgs) { // Pass a Request — locale is auto-detected const t = await i18n.getFixedT(request, "emails"); const subject = t("welcome.subject", { name: "Alice" }); // → "Welcome, Alice!" // Pass a locale string directly const tEs = await i18n.getFixedT("es", "emails"); const subjectEs = tEs("welcome.subject", { name: "Alice" }); // → "Bienvenida, Alice!" // With a key prefix (translates keys relative to "emails.welcome.*") const tPrefixed = await i18n.getFixedT(request, "emails", { keyPrefix: "welcome" }); const body = tPrefixed("body"); // resolves "emails.welcome.body" return { subject, subjectEs, body }; } ``` -------------------------------- ### Get Locale in Remix Loaders/Actions Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Access the detected user locale within Remix loaders and actions using the `getLocale` function provided by the middleware. This locale can be used for internationalized date formatting. ```typescript import { getLocale } from "~/middleware/i18next"; export async function loader({ context }: Route.LoaderArgs) { let locale = getLocale(context); let date = new Date().toLocaleDateString(locale, { year: "numeric", month: "2-digit", day: "2-digit", }); return { date }; } ``` -------------------------------- ### Run Tests Source: https://github.com/sergiodxa/remix-i18next/blob/main/CONTRIBUTING.md Execute this command to run the project's test suite. ```bash bun test ``` -------------------------------- ### createI18nextMiddleware.Options Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Configuration interface for `createI18nextMiddleware`. The `detection` field is required, while others are optional. ```APIDOC ## `createI18nextMiddleware.Options` — configuration interface The options object passed to `createI18nextMiddleware`. The `detection` field is required; all others are optional. ```ts import { createI18nextMiddleware } from "remix-i18next/middleware"; import { createCookie, createCookieSessionStorage } from "react-router"; import FetchBackend from "i18next-fetch-backend"; const localeCookie = createCookie("lng"); const sessionStorage = createCookieSessionStorage({ cookie: { name: "session", secrets: ["s3cr3t"] }, }); const [middleware, getLocale, getInstance] = createI18nextMiddleware({ detection: { supportedLanguages: ["en", "es", "fr", "es-MX"], // validated against this list fallbackLanguage: "en", // used when no match found cookie: localeCookie, // read locale from cookie sessionStorage, // read locale from session sessionKey: "lng", // session key (default: "lng") searchParamKey: "lng", // URL param key (default: "lng") // Detection priority order (default: searchParams → cookie → session → header) order: ["cookie", "session", "header", "searchParams"], async findLocale(request) { // Custom async resolver — runs before the order list when provided const userId = await getUserIdFromRequest(request); const user = await db.users.findUnique({ where: { id: userId } }); return user?.preferredLocale ?? null; // null → fall through to next method }, }, i18next: { // Any i18next InitOptions except "detection" and "react" defaultNS: "translation", fallbackNS: "common", resources: { en: { translation: { greeting: "Hello" } }, es: { translation: { greeting: "Hola" } }, }, }, plugins: [FetchBackend], // NewableModule[] | Module[] }); ``` ``` -------------------------------- ### Initialize RemixI18Next Server Helper Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Set up the server-side helper for projects not using React Router middleware. Configure locale detection and i18next backend. ```typescript // app/i18n.server.ts import { RemixI18Next } from "remix-i18next/server"; import { createCookie } from "react-router"; import Backend from "i18next-fs-backend"; const localeCookie = createCookie("lng"); export const i18n = new RemixI18Next({ detection: { supportedLanguages: ["en", "es"], fallbackLanguage: "en", cookie: localeCookie, }, i18next: { backend: { loadPath: "./public/locales/{{lng}}/{{ns}}.json" }, }, plugins: [Backend], }); // In entry.server.tsx import { i18n } from "~/i18n.server"; export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, entryContext: EntryContext, ) { const locale = await i18n.getLocale(request); const ns = i18n.getRouteNamespaces(entryContext); // ["translation", "dashboard"] const instance = createInstance(); await instance.use(Backend).init({ lng: locale, ns, backend: { loadPath: "./public/locales/{{lng}}/{{ns}}.json" }, }); // ...render with I18nextProvider } ``` -------------------------------- ### Run Code Quality Check Source: https://github.com/sergiodxa/remix-i18next/blob/main/CONTRIBUTING.md Use this command to perform code quality checks. ```bash bun run quality ``` -------------------------------- ### Configure createI18nextMiddleware Options Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Configure locale detection, fallback languages, and i18next options. Supports custom async locale resolution. ```typescript import { createI18nextMiddleware } from "remix-i18next/middleware"; import { createCookie, createCookieSessionStorage } from "react-router"; import FetchBackend from "i18next-fetch-backend"; const localeCookie = createCookie("lng"); const sessionStorage = createCookieSessionStorage({ cookie: { name: "session", secrets: ["s3cr3t"] }, }); const [middleware, getLocale, getInstance] = createI18nextMiddleware({ detection: { supportedLanguages: ["en", "es", "fr", "es-MX"], // validated against this list fallbackLanguage: "en", // used when no match found cookie: localeCookie, // read locale from cookie sessionStorage, // read locale from session sessionKey: "lng", // session key (default: "lng") searchParamKey: "lng", // URL param key (default: "lng") // Detection priority order (default: searchParams → cookie → session → header) order: ["cookie", "session", "header", "searchParams"], async findLocale(request) { // Custom async resolver — runs before the order list when provided const userId = await getUserIdFromRequest(request); const user = await db.users.findUnique({ where: { id: userId } }); return user?.preferredLocale ?? null; // null → fall through to next method }, }, i18next: { // Any i18next InitOptions except "detection" and "react" defaultNS: "translation", fallbackNS: "common", resources: { en: { translation: { greeting: "Hello" } }, es: { translation: { greeting: "Hola" } }, }, }, plugins: [FetchBackend], // NewableModule[] | Module[] }); ``` -------------------------------- ### Run Exports Checker Source: https://github.com/sergiodxa/remix-i18next/blob/main/CONTRIBUTING.md Run this command to check the project's exports. ```bash bun run exports ``` -------------------------------- ### Run Typechecker Source: https://github.com/sergiodxa/remix-i18next/blob/main/CONTRIBUTING.md Execute this command to check for type errors in the codebase. ```bash bun run typecheck ``` -------------------------------- ### createI18nextMiddleware Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Creates a React Router middleware tuple that handles locale detection, initializes a per-request i18next instance, and stores locale information in the router context. It returns a middleware function, a `getLocale` accessor, and an `getInstance` accessor. ```APIDOC ## `createI18nextMiddleware` — `remix-i18next/middleware` ### Description Creates a React Router middleware tuple that detects the user's locale on every request, initialises a per-request `i18next` instance, and stores both values in the router context. Returns a three-element array: the middleware function, a `getLocale` accessor, and a `getInstance` accessor. ### Usage Example ```ts // app/middleware/i18next.ts import { initReactI18next } from "react-i18next"; import { createCookie } from "react-router"; import { createI18nextMiddleware } from "remix-i18next/middleware"; import resources from "~/locales"; // { en: { translation: {...} }, es: { translation: {...} } } export const localeCookie = createCookie("lng", { path: "/", sameSite: "lax", secure: process.env.NODE_ENV === "production", httpOnly: true, }); export const [i18nextMiddleware, getLocale, getInstance] = createI18nextMiddleware({ detection: { supportedLanguages: ["en", "es"], fallbackLanguage: "en", cookie: localeCookie, // Optional: custom async resolver (runs first when provided) async findLocale(request) { // e.g. extract locale from URL pathname: /es/dashboard → "es" const segment = new URL(request.url).pathname.split("/").at(1); return segment ?? null; }, }, i18next: { resources }, // any i18next InitOptions except "detection" plugins: [initReactI18next], // additional i18next plugins }); // app/root.tsx import { i18nextMiddleware, getLocale, localeCookie } from "~/middleware/i18next"; export const middleware = [i18nextMiddleware]; export async function loader({ context }: Route.LoaderArgs) { const locale = getLocale(context); // "en" | "es" return data( { locale }, { headers: { "Set-Cookie": await localeCookie.serialize(locale) } }, ); } // In any downstream loader/action: export async function loader({ context }: Route.LoaderArgs) { const locale = getLocale(context); // detected locale string const i18n = getInstance(context); // fully initialised i18n instance const title = i18n.t("home.title"); // translate server-side return { locale, title }; } ``` ``` -------------------------------- ### Initialize Client-Side i18next with getInitialNamespaces Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Browser-side function to read route modules and return declared i18n namespaces. Call this during i18next.init in entry.client.tsx to ensure the client loads the same namespaces rendered on the server. ```typescript // app/entry.client.tsx import i18next from "i18next"; import { initReactI18next } from "react-i18next"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; import { I18nextProvider } from "react-i18next"; import FetchBackend from "i18next-fetch-backend"; import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector"; import { getInitialNamespaces } from "remix-i18next/client"; await i18next .use(initReactI18next) .use(FetchBackend) .use(I18nextBrowserLanguageDetector) .init({ fallbackLng: "en", ns: getInitialNamespaces(), // ["translation", "dashboard"] on the dashboard page defaultNS: "translation", detection: { order: ["htmlTag"], caches: [] }, // rely on server-set backend: { loadPath: "/api/locales/{{lng}}/{{ns}}" }, }); hydrateRoot( document, , ); ``` -------------------------------- ### Define Spanish translation resources Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Create translation files for the Spanish locale, mirroring the structure of the English locale. Using `satisfies typeof import("~/locales/en/translation").default` ensures that the Spanish locale keys match the English ones. ```typescript // app/locales/es/translation.ts export default { title: "remix-i18next (es)", description: "Un ejemplo de React Router + remix-i18next", } satisfies typeof import("~/locales/en/translation").default; // app/locales/es/index.ts import type { ResourceLanguage } from "i18next"; import translation from "./translation"; // import your namespaced locales export default { translation } satisfies ResourceLanguage; ``` -------------------------------- ### Enable Middleware in Remix Root Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Integrate the initialized i18next middleware into your Remix application's root configuration by exporting it from the `middleware` array in `app/root.tsx`. ```typescript import { i18nextMiddleware } from "~/middleware/i18next"; export const middleware = [i18nextMiddleware]; ``` -------------------------------- ### Initialize and Use LanguageDetector Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Configure and use the LanguageDetector for advanced locale detection scenarios. Specify supported languages, fallback, cookie settings, detection order, and search parameter key. ```typescript import { LanguageDetector } from "remix-i18next/server"; import { createCookie } from "react-router"; const detector = new LanguageDetector({ supportedLanguages: ["en", "es", "fr"], fallbackLanguage: "en", cookie: createCookie("lng"), order: ["searchParams", "cookie", "header"], searchParamKey: "locale", // override default "lng" }); // Standalone detection (e.g. in a custom server handler) const request = new Request("https://example.com/?locale=fr"); const locale = await detector.detect(request); // → "fr" const requestEs = new Request("https://example.com/", { headers: { "Accept-Language": "es-MX,es;q=0.9,en;q=0.8" }, }); const localeEs = await detector.detect(requestEs); // → "es" (loose match: es-MX not in supported list, falls back to es) ``` -------------------------------- ### Configure Root Route Middleware and Loader Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Integrates the created i18next middleware into the root route and sets the detected locale in a cookie via the loader. Ensures the locale is persisted for subsequent requests. ```typescript // app/root.tsx import { i18nextMiddleware, getLocale, localeCookie } from "~/middleware/i18next"; export const middleware = [i18nextMiddleware]; export async function loader({ context }: Route.LoaderArgs) { const locale = getLocale(context); // "en" | "es" return data( { locale }, { headers: { "Set-Cookie": await localeCookie.serialize(locale) } }, ); } ``` -------------------------------- ### Create Session Storage Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Sets up session storage using React Router's `createCookieSessionStorage`. Configure the cookie properties for session management. ```typescript import { createCookieSessionStorage } from "react-router"; export const sessionStorage = createCookieSessionStorage({ cookie: { name: "session", path: "/", sameSite: "lax", secure: process.env.NODE_ENV === "production", httpOnly: true, }, }); ``` -------------------------------- ### Configure Middleware with Session Storage Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Integrates the i18next middleware using session storage for locale detection. The middleware prioritizes the session, then the Accept-Language header, and finally the fallback language. ```typescript import { createI18nextMiddleware } from "remix-i18next/middleware"; import { sessionStorage } from "~/session"; export const [i18nextMiddleware, getLocale, getInstance] = createI18nextMiddleware({ detection: { supportedLanguages: ["es", "en"], fallbackLanguage: "en", sessionStorage, }, i18next: { resources: { en: { translation: en }, es: { translation: es } }, }, }); ``` -------------------------------- ### Server-Side Rendering with i18nextProvider in Remix Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Integrates the i18next middleware instance into the server-side rendering process using I18nextProvider. This ensures the i18next instance is available during initial render. ```patch import { PassThrough } from "node:stream"; -import type { AppLoadContext, EntryContext } from "react-router"; +import type { RouterContextProvider, EntryContext } from "react-router"; import { createReadableStreamFromReadable } from "@react-router/node"; import { ServerRouter } from "react-router"; import { isbot } from "isbot"; import type { RenderToPipeableStreamOptions } from "react-dom/server"; import { renderToPipeableStream } from "react-dom/server"; +import {I18nextProvider} from "react-i18next"; +import {getInstance} from "~/middleware/i18next"; + export const streamTimeout = 5_000; @@ -14,9 +17,7 @@ export default function handleRequest( responseStatusCode: number, responseHeaders: Headers, entryContext: EntryContext, - loadContext: AppLoadContext, - // If you have middleware enabled: - // routerContext: RouterContextProvider + routerContext: RouterContextProvider ) { return new Promise((resolve, reject) => { let shellRendered = false; @@ -37,7 +38,9 @@ export default function handleRequest( ); const { pipe, abort } = renderToPipeableStream( - , + + + , { ``` -------------------------------- ### Configure Middleware with Cookie Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Integrates the i18next middleware using a previously created cookie for locale detection. The middleware will prioritize the cookie, then the Accept-Language header, and finally the fallback language. ```typescript import { createI18nextMiddleware } from "remix-i18next/middleware"; import { localeCookie } from "~/cookies"; export const [i18nextMiddleware, getLocale, getInstance] = createI18nextMiddleware({ detection: { supportedLanguages: ["es", "en"], fallbackLanguage: "en", cookie: localeCookie, }, i18next: { resources: { en: { translation: en }, es: { translation: es } }, }, }); ``` -------------------------------- ### Create i18next Middleware and Accessors Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Sets up the i18next middleware for React Router, including locale detection via cookies and a custom URL segment resolver. It returns middleware, a locale getter, and an i18n instance getter. ```typescript // app/middleware/i18next.ts import { initReactI18next } from "react-i18next"; import { createCookie } from "react-router"; import { createI18nextMiddleware } from "remix-i18next/middleware"; import resources from "~/locales"; // { en: { translation: {...} }, es: { translation: {...} } } export const localeCookie = createCookie("lng", { path: "/", sameSite: "lax", secure: process.env.NODE_ENV === "production", httpOnly: true, }); export const [i18nextMiddleware, getLocale, getInstance] = createI18nextMiddleware({ detection: { supportedLanguages: ["en", "es"], fallbackLanguage: "en", cookie: localeCookie, // Optional: custom async resolver (runs first when provided) async findLocale(request) { // e.g. extract locale from URL pathname: /es/dashboard → "es" const segment = new URL(request.url).pathname.split("/").at(1); return segment ?? null; }, }, i18next: { resources }, // any i18next InitOptions except "detection" plugins: [initReactI18next], // additional i18next plugins }); ``` -------------------------------- ### Access Locale and i18n Instance in Loaders/Actions Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Demonstrates how to retrieve the detected locale string and the initialized i18next instance within any downstream loader or action. Allows for server-side translation using `i18n.t()`. ```typescript // In any downstream loader/action: export async function loader({ context }: Route.LoaderArgs) { const locale = getLocale(context); // detected locale string const i18n = getInstance(context); // fully initialised i18n instance const title = i18n.t("home.title"); // translate server-side return { locale, title }; } ``` -------------------------------- ### RemixI18Next class Source: https://context7.com/sergiodxa/remix-i18next/llms.txt A class-based server helper for projects not using React Router middleware. It provides `getLocale`, `getRouteNamespaces`, and `getFixedT` as standalone async methods. ```APIDOC ## `RemixI18Next` class — `remix-i18next/server` A class-based server helper for projects not using React Router middleware (e.g. React Router < 7.9 or custom server setups). Provides `getLocale`, `getRouteNamespaces`, and `getFixedT` as standalone async methods. ```ts // app/i18n.server.ts import { RemixI18Next } from "remix-i18next/server"; import { createCookie } from "react-router"; import Backend from "i18next-fs-backend"; const localeCookie = createCookie("lng"); export const i18n = new RemixI18Next({ detection: { supportedLanguages: ["en", "es"], fallbackLanguage: "en", cookie: localeCookie, }, i18next: { backend: { loadPath: "./public/locales/{{lng}}/{{ns}}.json" }, }, plugins: [Backend], }); // In entry.server.tsx import { i18n } from "~/i18n.server"; export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, entryContext: EntryContext, ) { const locale = await i18n.getLocale(request); const ns = i18n.getRouteNamespaces(entryContext); // ["translation", "dashboard"] const instance = createInstance(); await instance.use(Backend).init({ lng: locale, ns, backend: { loadPath: "./public/locales/{{lng}}/{{ns}}.json" }, }); // ...render with I18nextProvider } ``` ``` -------------------------------- ### getInitialNamespaces Source: https://context7.com/sergiodxa/remix-i18next/llms.txt A browser-side function that retrieves the list of i18n namespaces from loaded route modules. This should be called during i18next.init in entry.client.tsx to ensure the client loads the same namespaces rendered on the server. ```APIDOC ## `getInitialNamespaces` — `remix-i18next/client` Browser-side function that reads `window.__reactRouterRouteModules` and returns the list of i18n namespaces declared by all currently loaded route modules. Call it during `i18next.init` in `entry.client.tsx` so the client loads the same namespaces that were rendered on the server. ### Usage ```ts // app/entry.client.tsx import i18next from "i18next"; import { initReactI18next } from "react-i18next"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; import { I18nextProvider } from "react-i18next"; import FetchBackend from "i18next-fetch-backend"; import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector"; import { getInitialNamespaces } from "remix-i18next/client"; await i18next .use(initReactI18next) .use(FetchBackend) .use(I18nextBrowserLanguageDetector) .init({ fallbackLng: "en", ns: getInitialNamespaces(), // ["translation", "dashboard"] on the dashboard page defaultNS: "translation", detection: { order: ["htmlTag"], caches: [] }, // rely on server-set backend: { loadPath: "/api/locales/{{lng}}/{{ns}}" }, }); hydrateRoot( document, , ); ``` ``` -------------------------------- ### RemixI18Next.getFixedT Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Initializes a server-side i18next instance for a specific locale and namespace, returning a bound TFunction. It can accept either a Request object for auto-detected locale or a locale string directly. ```APIDOC ## RemixI18Next.getFixedT — get a server-side `TFunction` Initialises a private i18next instance for a specific locale and namespace and returns a `TFunction` bound to that locale. Accepts either a `Request` (locale auto-detected) or a locale string directly. ### Usage ```ts import { i18n } from "~/i18n.server"; export async function loader({ request }: Route.LoaderArgs) { // Pass a Request — locale is auto-detected const t = await i18n.getFixedT(request, "emails"); const subject = t("welcome.subject", { name: "Alice" }); // → "Welcome, Alice!" // Pass a locale string directly const tEs = await i18n.getFixedT("es", "emails"); const subjectEs = tEs("welcome.subject", { name: "Alice" }); // → "Bienvenida, Alice!" // With a key prefix (translates keys relative to "emails.welcome.*") const tPrefixed = await i18n.getFixedT(request, "emails", { keyPrefix: "welcome" }); const body = tPrefixed("body"); // resolves "emails.welcome.body" return { subject, subjectEs, body }; } ``` ``` -------------------------------- ### Define English translation resources Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Create translation files for the English locale, including namespaced translations. The `satisfies` keyword helps ensure type consistency with the default locale. ```typescript // app/locales/en/translation.ts export default { title: "remix-i18next (en)", description: "A React Router + remix-i18next example", }; // app/locales/en/index.ts import type { ResourceLanguage } from "i18next"; import translation from "./translation"; // import your namespaced locales export default { translation } satisfies ResourceLanguage; ``` -------------------------------- ### Create Locale Cookie Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Helper to create a cookie for storing the user's locale preference. Ensure `process.env.NODE_ENV` is correctly set for production security. ```typescript import { createCookie } from "react-router"; export const localeCookie = createCookie("lng", { path: "/", sameSite: "lax", secure: process.env.NODE_ENV === "production", httpOnly: true, }); ``` -------------------------------- ### Serve Locales API Route in Remix Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md This route serves locale data based on language and namespace parameters. It uses Zod for validation and pretty-cache-header for cache control in production environments. ```typescript import { data } from "react-router"; import { cacheHeader } from "pretty-cache-header"; import { z } from "zod"; import resources from "~/locales"; import type { Route } from "./+types/locales"; export async function loader({ params }: Route.LoaderArgs) { const lng = z .enum(Object.keys(resources) as Array) .safeParse(params.lng); if (lng.error) return data({ error: lng.error }, { status: 400 }); const namespaces = resources[lng.data]; const ns = z .enum(Object.keys(namespaces) as Array) .safeParse(params.ns); if (ns.error) return data({ error: ns.error }, { status: 400 }); const headers = new Headers(); // On production, we want to add cache headers to the response if (process.env.NODE_ENV === "production") { headers.set( "Cache-Control", cacheHeader({ maxAge: "5m", // Cache in the browser for 5 minutes sMaxage: "1d", // Cache in the CDN for 1 day // Serve stale content while revalidating for 7 days staleWhileRevalidate: "7d", // Serve stale content if there's an error for 7 days staleIfError: "7d", }), ); } return data(namespaces[ns.data], { headers }); } ``` -------------------------------- ### Access i18next Instance in Loaders/Actions Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Retrieve the i18next instance from the Remix context using `getInstance` to access translation functions like `t`. This instance is pre-configured with the detected locale. ```typescript import { getInstance } from "~/middleware/i18next"; export async function loader({ context }: Route.LoaderArgs) { let i18next = getInstance(context); return { title: i18next.t("title"), description: i18next.t("description") }; } ``` -------------------------------- ### getClientLocales Source: https://context7.com/sergiodxa/remix-i18next/llms.txt The getClientLocales utility parses the Accept-Language header from a Request or Headers object and returns the best-matching locale string, validated against Intl.DateTimeFormat.supportedLocalesOf. This is an internal utility but re-exported for convenience. ```APIDOC ## `getClientLocales` — `remix-i18next/server` (internal utility, re-exported) ### Description Parses the `Accept-Language` header from a `Request` or `Headers` object and returns the best-matching locale string validated against `Intl.DateTimeFormat.supportedLocalesOf`. ### Usage #### From a Request ```ts import { getClientLocales } from "remix-i18next/server"; const request = new Request("https://example.com", { headers: { "Accept-Language": "es-AR,es;q=0.9,en-US;q=0.7,en;q=0.5" }, }); const locale = getClientLocales(request); // "es-AR" or "es" depending on Intl support ``` #### From a Headers object ```ts import { getClientLocales } from "remix-i18next/server"; const headers = new Headers({ "Accept-Language": "fr-CH,fr;q=0.8,de;q=0.6" }); const localeFromHeaders = getClientLocales(headers); // "fr-CH" or "fr" ``` #### Missing header ```ts import { getClientLocales } from "remix-i18next/server"; const empty = getClientLocales(new Request("https://example.com")); // → undefined ``` ``` -------------------------------- ### Collect Route Namespaces with RemixI18Next.getRouteNamespaces Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Collect all unique i18next namespaces declared in the `handle.i18n` export of active route modules. This is crucial for SSR to pre-load only necessary namespaces. ```typescript // In a route file, declare the namespaces it needs: // app/routes/dashboard.tsx export const handle = { i18n: ["dashboard", "common"] }; // app/routes/profile.tsx export const handle = { i18n: "profile" }; // In entry.server.tsx import { i18n } from "~/i18n.server"; export default async function handleRequest( request: Request, _statusCode: number, _headers: Headers, entryContext: EntryContext, ) { const locale = await i18n.getLocale(request); const ns = i18n.getRouteNamespaces(entryContext); // e.g. ["dashboard", "common", "profile"] — deduplicated const instance = createInstance(); await instance.use(Backend).init({ lng: locale, ns }); // Only loads the 3 namespaces actually used on this page } ``` -------------------------------- ### RemixI18Next.getRouteNamespaces Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Collects namespaces from active routes by reading the `handle.i18n` export from each route module. Returns a deduplicated list of namespace strings for pre-loading during SSR. ```APIDOC ## `RemixI18Next.getRouteNamespaces` — collect namespaces from active routes Reads the `handle.i18n` export from each currently active route module and returns a deduplicated list of namespace strings. Use this to pre-load only the namespaces needed for the current page during SSR. ```ts // In a route file, declare the namespaces it needs: // app/routes/dashboard.tsx export const handle = { i18n: ["dashboard", "common"] }; // app/routes/profile.tsx export const handle = { i18n: "profile" }; // In entry.server.tsx import { i18n } from "~/i18n.server"; export default async function handleRequest( request: Request, _statusCode: number, _headers: Headers, entryContext: EntryContext, ) { const locale = await i18n.getLocale(request); const ns = i18n.getRouteNamespaces(entryContext); // e.g. ["dashboard", "common", "profile"] — deduplicated const instance = createInstance(); await instance.use(Backend).init({ lng: locale, ns }); // Only loads the 3 namespaces actually used on this page } ``` ``` -------------------------------- ### Query Locale from Database Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Use `findLocale` to query the database for the user's locale. Requires a `db.getUser` function to retrieve user data. ```typescript export let i18n = new RemixI18Next({ detection: { supportedLanguages: ["es", "en"], fallbackLanguage: "en", async findLocale(request) { let user = await db.getUser(request); return user.locale; }, }, }); ``` -------------------------------- ### Use useLocale Hook (Deprecated) Source: https://context7.com/sergiodxa/remix-i18next/llms.txt React hook to read the locale from the root route loader. Deprecated in favor of useTranslation().i18n.language. Throws if the root loader does not return the specified key or if the value is not a string. ```tsx // app/root.tsx — return locale from loader export async function loader({ context }: Route.LoaderArgs) { return { locale: getLocale(context) }; // key must match the hook's argument } // app/components/DateDisplay.tsx import { useLocale } from "remix-i18next/react"; export function DateDisplay({ date }: { date: Date }) { const locale = useLocale(); // reads root loader's "locale" key (default) // For a different key: useLocale("language") return ( ); } // Throws if root loader did not return the key or if the value is not a string. ``` -------------------------------- ### Re-export all locales Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Consolidate all defined locales (e.g., English and Spanish) into a single resource object for i18next. ```typescript import type { Resource } from "i18next"; import en from "./en"; import es from "./es"; export default { en, es } satisfies Resource; ``` -------------------------------- ### Custom Locale Detection with Remix i18next Middleware Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Configures the i18next middleware in Remix to detect the user's locale from the URL pathname. This approach allows manual locale extraction before falling back to default settings. ```typescript import { createI18nextMiddleware } from "remix-i18next/middleware"; export const [i18nextMiddleware, getLocale, getInstance] = createI18nextMiddleware({ detection: { supportedLanguages: ["es", "en"], fallbackLanguage: "en", findLocale(request) { let locale = new URL(request.url).pathname.split("/").at(1); return locale; }, }, i18next: { resources: { en: { translation: en }, es: { translation: es } }, }, }); ``` -------------------------------- ### LanguageDetector Source: https://context7.com/sergiodxa/remix-i18next/llms.txt The LanguageDetector class provides a low-level mechanism for detecting the user's locale. It can be configured with supported languages, a fallback language, and the order of detection methods (searchParams, cookie, header). It's useful for advanced use cases where the full RemixI18Next class is not required. ```APIDOC ## `LanguageDetector` — `remix-i18next/server` ### Description The low-level class that powers locale detection. Re-exported from `remix-i18next/server` for advanced use cases where the full `RemixI18Next` class is not needed. ### Usage ```ts import { LanguageDetector } from "remix-i18next/server"; import { createCookie } from "react-router"; const detector = new LanguageDetector({ supportedLanguages: ["en", "es", "fr"], fallbackLanguage: "en", cookie: createCookie("lng"), order: ["searchParams", "cookie", "header"], searchParamKey: "locale", // override default "lng" }); // Standalone detection (e.g. in a custom server handler) const request = new Request("https://example.com/?locale=fr"); const locale = await detector.detect(request); // → "fr" const requestEs = new Request("https://example.com/", { headers: { "Accept-Language": "es-MX,es;q=0.9,en;q=0.8" }, }); const localeEs = await detector.detect(requestEs); // → "es" (loose match: es-MX not in supported list, falls back to es) ``` ``` -------------------------------- ### Parse Accept-Language Header with getClientLocales Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Use getClientLocales to parse the Accept-Language header from a Request or Headers object. It returns the best-matching locale validated against Intl.DateTimeFormat.supportedLocalesOf. ```typescript import { getClientLocales } from "remix-i18next/server"; // From a Request const request = new Request("https://example.com", { headers: { "Accept-Language": "es-AR,es;q=0.9,en-US;q=0.7,en;q=0.5" }, }); const locale = getClientLocales(request); // "es-AR" or "es" depending on Intl support // From a Headers object const headers = new Headers({ "Accept-Language": "fr-CH,fr;q=0.8,de;q=0.6" }); const localeFromHeaders = getClientLocales(headers); // "fr-CH" or "fr" // Missing header → undefined const empty = getClientLocales(new Request("https://example.com")); // → undefined ``` -------------------------------- ### RemixI18Next.getLocale Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Detects the locale from a Request by running the configured detection chain and returns the best-matching supported language string. ```APIDOC ## `RemixI18Next.getLocale` — detect locale from a Request Runs the configured detection chain and returns the best-matching supported language string. ```ts import { i18n } from "~/i18n.server"; export async function loader({ request }: Route.LoaderArgs) { // Detects from: ?lng=es → Cookie → Session → Accept-Language → fallback const locale = await i18n.getLocale(request); // e.g. "es", "en", "es-MX" return { locale, formattedDate: new Date().toLocaleDateString(locale, { year: "numeric", month: "long", day: "numeric", }), }; } ``` ``` -------------------------------- ### Detect Locale with RemixI18Next.getLocale Source: https://context7.com/sergiodxa/remix-i18next/llms.txt Asynchronously detect the user's locale from the request using the configured detection chain. This method is useful within route loaders. ```typescript import { i18n } from "~/i18n.server"; export async function loader({ request }: Route.LoaderArgs) { // Detects from: ?lng=es → Cookie → Session → Accept-Language → fallback const locale = await i18n.getLocale(request); // e.g. "es", "en", "es-MX" return { locale, formattedDate: new Date().toLocaleDateString(locale, { year: "numeric", month: "long", day: "numeric", }), }; } ``` -------------------------------- ### Use Fixed Locale with i18next Instance Source: https://github.com/sergiodxa/remix-i18next/blob/main/README.md Obtain a `TFunction` instance for a specific locale (e.g., 'es') using `getFixedT` from the i18next instance. This allows for translations in a locale different from the one detected by the middleware. ```typescript import { getInstance } from "~/middleware/i18next"; export async function loader({ context }: Route.LoaderArgs) { let i18next = getInstance(context); let t = i18next.getFixedT("es"); return { title: t("title"), description: t("description") }; } ```