Skip to content
better-i18n.com
本页内容

@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 });
});
  • Express & Fastify — Node.js IncomingHttpHeaders adapter for Express, Fastify, and Koa.
  • API Reference — Full reference for betterI18n, createServerI18n, and Translator.