Getting Started
Runtime-agnostic server-side i18n for any JavaScript backend
@better-i18n/server is a standalone i18n package for pure API servers. It runs on any JavaScript runtime — Node.js, Bun, Deno, and Cloudflare Workers — with no React or SSR framework dependency.
Not the same as @better-i18n/use-intl/server.
@better-i18n/use-intl/server provides SSR helpers for React frontends (Next.js, TanStack Start). This package is for API backends that return JSON, send emails, or run background jobs — not for rendering HTML.
When to Use This Package
| Scenario | Runtime | Package |
|---|---|---|
| Next.js / TanStack Start SSR | Node.js | @better-i18n/next or @better-i18n/use-intl |
| Hono API server | Node.js, Bun, Deno, CF Workers | @better-i18n/server + ./hono adapter |
| Express / Fastify / Koa | Node.js only | @better-i18n/server + ./node adapter |
| Background worker / email sender | Any runtime | @better-i18n/server — call getTranslator() directly |
| Cloudflare Worker / Deno script | CF Workers, Deno, Bun | @better-i18n/server — no adapter needed |
| Supabase Edge Function | Deno (Supabase runtime) | @better-i18n/server — Supabase guide |
| tRPC API (edge) | Any runtime | @better-i18n/server — tRPC guide |
| tRPC API (Node.js) | Node.js | @better-i18n/server + ./node — tRPC guide |
Installation
npm install @better-i18n/serveryarn add @better-i18n/serverpnpm add @better-i18n/serverbun add @better-i18n/serverQuick Start
Create the i18n singleton
Instantiate createServerI18n once at module scope — not inside a request handler. The singleton shares a TtlCache across all requests, avoiding redundant CDN fetches.
import { createServerI18n } from "@better-i18n/server";
export const i18n = createServerI18n({
project: "my-org/api",
defaultLocale: "en",
}); Do not call createServerI18n inside a request handler. Each call creates a new TtlCache instance, which defeats CDN caching and causes a fresh fetch on every request.
Use the translator
No middleware needed — call getTranslator directly with a locale string. This works on any runtime.
import { i18n } from "./i18n";
export async function sendWelcomeEmail(userId: string, locale: string) {
const t = await i18n.getTranslator(locale);
await mailer.send({
to: userId,
subject: t("emails.welcome.subject"),
body: t("emails.welcome.body"),
});
}import { i18n } from "../i18n";
// With namespace scoping
const t = await i18n.getTranslator("tr", "errors");
t("notFound"); // → "Bulunamadı"getTranslator fetches and caches messages via TtlCache — repeated calls for the same locale within the TTL window return instantly from memory with no CDN round-trip.
Choose an adapter (optional)
If you use a web framework and want automatic Accept-Language detection injected into the request context, pick the right adapter:
| Framework | Adapter | Guide |
|---|---|---|
| Hono (any runtime) | @better-i18n/server/hono | Hono middleware |
| Express | @better-i18n/server/node | Express & Fastify |
| Fastify / Koa | @better-i18n/server/node | Express & Fastify |
No framework? Skip adapters entirely and call i18n.detectLocaleFromHeaders(request.headers) with any Web Standards Headers object.
Next Steps
Hono
Set up the Hono middleware with full TypeScript variable types.
Express & Fastify
Node.js HTTP adapter — use betterI18nMiddleware with Express or fromNodeHeaders with Fastify.
Supabase
Detect locale and translate in Supabase Edge Functions (Deno).
tRPC
Inject locale and t into tRPC context for localized error messages.
API Reference
Full reference for createServerI18n, ServerI18n, and all exports.