Express & Fastify
Node.js HTTP adapter for Express, Fastify, and Koa
This adapter is Node.js-specific. It bridges Node.js IncomingHttpHeaders to Web Standards Headers. If you're using Cloudflare Workers, Deno native (Deno.serve()), or Bun's HTTP server, skip this adapter — their request.headers is already a Web Standards Headers object and works directly with i18n.detectLocaleFromHeaders(request.headers).
@better-i18n/server/node provides two utilities for Node.js HTTP servers:
betterI18nMiddleware(i18n)— drop-in Express/Connect middlewarefromNodeHeaders(nodeHeaders)— convertsIncomingHttpHeadersto Web StandardsHeadersfor manual use with Fastify, Koa, or raw Node.js
Express
Create the i18n singleton
import { createServerI18n } from "@better-i18n/server";
export const i18n = createServerI18n({
project: "my-org/api",
defaultLocale: "en",
});Register the middleware
import express from "express";
import { betterI18nMiddleware } from "@better-i18n/server/node";
import { i18n } from "./i18n";
const app = express();
app.use(betterI18nMiddleware(i18n));
app.get("/users/:id", async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: req.t("errors.notFound") });
}
res.json({ user, locale: req.locale });
});
export default app;Add TypeScript types
betterI18nMiddleware injects req.locale and req.t at runtime, but TypeScript doesn't know about them. Add a declaration file to augment the Express Request type:
import type { Translator } from "@better-i18n/server";
declare global {
namespace Express {
interface Request {
locale: string;
t: Translator;
}
}
}This follows the same pattern as @types/passport and other Express middleware packages — augmenting Express.Request in a global declaration file is the idiomatic TypeScript approach.
Fastify
Fastify doesn't use Express middleware, but fromNodeHeaders converts Fastify's req.headers (IncomingHttpHeaders) to a Web Standards Headers object that detectLocaleFromHeaders accepts:
import fp from "fastify-plugin";
import { fromNodeHeaders } from "@better-i18n/server/node";
import { i18n } from "../i18n";
export default fp(async (fastify) => {
fastify.decorateRequest("locale", "");
fastify.decorateRequest("t", null);
fastify.addHook("onRequest", async (request) => {
const headers = fromNodeHeaders(request.headers);
const locale = await i18n.detectLocaleFromHeaders(headers);
const t = await i18n.getTranslator(locale);
request.locale = locale;
request.t = t;
});
});Augment Fastify's request type:
import type { Translator } from "@better-i18n/server";
declare module "fastify" {
interface FastifyRequest {
locale: string;
t: Translator;
}
}