### 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") };
}
```