i18n
The i18n middleware adds multi-language support to TezX applications. It detects the user's preferred language, loads translation files, optionally caches them, and provides a translation function (ctx.t) in every request.
Basic Usage
import { i18n } from "tezx/middleware";
app.use(
i18n({
loadTranslations: async (lang) => import(`./locales/${lang}.json`),
detectLanguage: (ctx) => ctx.req.query.lang || "en",
defaultLanguage: "en",
})
);Usage in routes:
ctx.t("greeting.hello"); // "Hello"
ctx.t("user.welcome", { name: "Rakibul" }); // interpolates variablesHow It Works
- Detect preferred language using
detectLanguage(ctx) - Load translations dynamically with
loadTranslations(language) - Cache translations in memory or via a custom adapter
- Attach a translation helper
ctx.tto the context - Interpolate variables in messages like
{{name}} - Fallback to
defaultLanguageif translation is missing
Example — Directory & Translation
Directory structure:
/locales
├── en.json
└── bn.jsonen.json:
{
"greeting": {
"hello": "Hello",
"welcome": "Welcome, {{name}}!"
}
}Route usage:
router.get("/hi", (ctx) => ctx.t("greeting.welcome", { name: "Rakibul" }));Output:
?lang=en → "Welcome, Rakibul!"
?lang=bn → "স্বাগতম, Rakibul!"
Caching
In-memory cache (default)
app.use(
i18n({
loadTranslations,
detectLanguage,
cacheTranslations: true,
})
);External cache (Redis, file system, etc.)
const redisCache = {
async get(lang) { return JSON.parse(await redis.get(`i18n:${lang}`) || "null"); },
async set(lang, data) { await redis.set(`i18n:${lang}`, JSON.stringify(data)); },
async delete(lang) { await redis.del(`i18n:${lang}`); },
};
app.use(
i18n({
loadTranslations,
detectLanguage,
cacheTranslations: true,
cacheStorage: redisCache,
})
);Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
loadTranslations | (lang: string) => Promise<TranslationMap> | — | Load translations dynamically (JSON, DB, etc.) |
detectLanguage | (ctx: Context) => string | — | Determine the language for the request |
defaultLanguage | string | "en" | Fallback language |
defaultCacheDuration | number | 3600000 | Cache validity duration (ms) |
translationFunctionKey | string | "t" | Property name for translation function on ctx |
formatMessage | (msg, vars) => string | Interpolates {{var}} | Custom interpolation logic |
isCacheValid | (cached, lang) => boolean | Expiry check | Determines if a cache entry is valid |
cacheTranslations | boolean | false | Enable/disable caching |
cacheStorage | I18nCacheAdapter | null | Custom cache adapter |
Customization
Custom format function
i18n({ formatMessage: (msg, vars) => msg.replace(/\{(\w+)\}/g, (_, k) => vars?.[k] ?? "") });Custom language detection
detectLanguage: (ctx) =>
ctx.req.query.lang || ctx.cookies.get("lang") || ctx.req.headers["accept-language"]?.split(",")[0] || "en"File-based cache adapter
import fs from "fs/promises";
import path from "path";
const fileCache = {
async get(lang) {
try {
return JSON.parse(await fs.readFile(path.join("./cache", `${lang}.json`), "utf-8"));
} catch { return null; }
},
async set(lang, data) {
await fs.mkdir("./cache", { recursive: true });
await fs.writeFile(path.join("./cache", `${lang}.json`), JSON.stringify(data), "utf-8");
},
async delete(lang) {
await fs.unlink(path.join("./cache", `${lang}.json`)).catch(() => {});
},
};Cache Validation Example
isCacheValid: (cached, lang) => {
const file = `./locales/${lang}.json`;
const lastModified = fs.statSync(file).mtimeMs;
return cached.expiresAt > Date.now() && cached.expiresAt > lastModified;
}Error Handling
Any error during translation loading, caching, or formatting throws a structured Error. Example:
try { await next(); }
catch (err) {
if (err instanceof Error) console.error("i18n failed:", err.message);
}Using Translations in Templates or API
router.get("/about", (ctx) => ({
title: ctx.t("page.about.title"),
description: ctx.t("page.about.description"),
}));Etag
Automatically generates ETag headers for HTTP responses in Bun. Provides strong/weak ETag support and returns 304 Not Modified if the client already has the same ETag.
logger
Middleware for logging incoming HTTP requests and responses. Logs the method, pathname, status code, and execution time for each request. Useful for debugging and monitoring your TezX server.