Better I18NBetter I18N
Server SDK

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

ScenarioRuntimePackage
Next.js / TanStack Start SSRNode.js@better-i18n/next or @better-i18n/use-intl
Hono API serverNode.js, Bun, Deno, CF Workers@better-i18n/server + ./hono adapter
Express / Fastify / KoaNode.js only@better-i18n/server + ./node adapter
Background worker / email senderAny runtime@better-i18n/server — call getTranslator() directly
Cloudflare Worker / Deno scriptCF Workers, Deno, Bun@better-i18n/server — no adapter needed
Supabase Edge FunctionDeno (Supabase runtime)@better-i18n/serverSupabase guide
tRPC API (edge)Any runtime@better-i18n/servertRPC guide
tRPC API (Node.js)Node.js@better-i18n/server + ./nodetRPC guide

Installation

npm install @better-i18n/server
yarn add @better-i18n/server
pnpm add @better-i18n/server
bun add @better-i18n/server

Quick 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.

src/i18n.ts
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.

src/workers/email.ts
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"), 
  });
}
src/routes/users.ts
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:

FrameworkAdapterGuide
Hono (any runtime)@better-i18n/server/honoHono middleware
Express@better-i18n/server/nodeExpress & Fastify
Fastify / Koa@better-i18n/server/nodeExpress & Fastify

No framework? Skip adapters entirely and call i18n.detectLocaleFromHeaders(request.headers) with any Web Standards Headers object.

Next Steps

On this page