Hono
On this page
@better-i18n/server/hono provides a single betterI18n(i18n) middleware that detects the request locale from the Accept-Language header and injects locale and t into Hono's context variables.
Setup #
Create the i18n singleton #
src/i18n.ts
import { createServerI18n } from "@better-i18n/server";
export const i18n = createServerI18n({
projectId: "my-org/api",
defaultLocale: "en",
});Register the middleware #
src/app.ts
import { Hono } from "hono";
import { betterI18n } from "@better-i18n/server/hono"; // [!code highlight]
import { i18n } from "./i18n";
import type { Translator } from "@better-i18n/server";
const app = new Hono<{ // [!code highlight]
Variables: { // [!code highlight]
locale: string; // [!code highlight]
t: Translator; // [!code highlight]
}; // [!code highlight]
}>(); // [!code highlight]
app.use("*", betterI18n(i18n)); // [!code highlight]
export default app;The Hono<{ Variables: ... }> generic makes c.get("locale") and c.get("t") fully type-safe — no as casts needed.
Use in route handlers #
src/routes/users.ts
import app from "../app";
app.get("/users/:id", async (c) => {
const t = c.get("t"); // [!code highlight]
const locale = c.get("locale"); // [!code highlight]
const user = await db.users.findById(c.req.param("id"));
if (!user) {
return c.json({ error: t("errors.notFound") }, 404); // [!code highlight]
}
return c.json({ user, locale });
});TypeScript Augmentation #
If you use a shared app instance across multiple files, declare the variable types once in a .d.ts file to avoid repeating the generic:
src/types/hono.d.ts
import type { Translator } from "@better-i18n/server";
declare module "hono" {
interface ContextVariableMap {
locale: string;
t: Translator;
}
}With this declaration, c.get("locale") and c.get("t") are typed globally — no need to pass Hono<{ Variables: ... }> at each new Hono() call.
Locale Override Endpoint #
For REST APIs where clients send a preferred locale in the request body or query params (bypassing Accept-Language):
src/routes/translate.ts
import { i18n } from "../i18n";
// Override locale for a specific route
app.post("/send-email", async (c) => {
const { userId, locale: requestedLocale } = await c.req.json();
// Use explicitly requested locale — bypass Accept-Language detection
const t = await i18n.getTranslator(requestedLocale ?? c.get("locale")); // [!code highlight]
const subject = t("emails.welcome.subject");
const body = t("emails.welcome.body");
await sendEmail({ userId, subject, body });
return c.json({ sent: true });
});Related #
- Express & Fastify — Node.js
IncomingHttpHeadersadapter for Express, Fastify, and Koa. - API Reference — Full reference for
betterI18n,createServerI18n, andTranslator.
Better I18N