Palamedes In Backend Servers
Palamedes is not limited to React frameworks.
The same steady runtime model also works in regular request/response servers. That matters when translations need to appear in emails, API-rendered HTML, server actions, or small backend services that sit next to the frontend.
If transformed code can resolve the active i18n instance through
@palamedes/runtime, the same model also works in backend applications such as:
- Express
- Hono
- Fastify-style Node servers
- custom Node HTTP servers
The important requirement is not the framework name. It is that your server can expose a request-local i18n instance before translated code runs.
The Runtime Rule
Palamedes-transformed code calls getI18n() from @palamedes/runtime.
On the server, that means you must register a getter:
import { setServerI18nGetter } from "@palamedes/runtime"
setServerI18nGetter(() => getRequestScopedI18n())In backend servers, the cleanest way to do that is AsyncLocalStorage.
Canonical Node Pattern
import { AsyncLocalStorage } from "node:async_hooks"
import { createI18n } from "@palamedes/core"
import { setServerI18nGetter } from "@palamedes/runtime"
const i18nStorage = new AsyncLocalStorage<ReturnType<typeof createI18n>>()
setServerI18nGetter(() => i18nStorage.getStore())For each incoming request:
- determine the locale from
Accept-Language, cookies, session, or user profile - create or hydrate the i18n instance for that locale
- run the request inside
i18nStorage.run(i18n, ...)
That gives any translated code inside the request path access to the correct server-local instance.
Hono Example
Hono is a strong fit for this pattern because it keeps the request flow small and explicit while still running on Node.js.
import { AsyncLocalStorage } from "node:async_hooks"
import { Hono } from "hono"
import { createI18n } from "@palamedes/core"
import { defineLocaleControls } from "@palamedes/core/locale"
import { setServerI18nGetter } from "@palamedes/runtime"
import { t } from "@palamedes/core/macro"
const app = new Hono()
const i18nStorage = new AsyncLocalStorage<ReturnType<typeof createI18n>>()
const localeControls = defineLocaleControls({
locales: ["en", "de"],
defaultLocale: "en",
})
setServerI18nGetter(() => i18nStorage.getStore())
app.use(async (c, next) => {
const locale = localeControls.preferredLocale(c.req.header("accept-language"))
const i18n = createI18n()
i18n.activate(locale)
await i18nStorage.run(i18n, next)
})
app.get("/", (c) => {
return c.text(t`Welcome to Palamedes`)
})This same pattern also works when the locale comes from:
- a signed cookie
- a session record
- a database-backed user profile
- a route segment or hostname
Express Example
import { AsyncLocalStorage } from "node:async_hooks"
import express from "express"
import { createI18n } from "@palamedes/core"
import { defineLocaleControls } from "@palamedes/core/locale"
import { setServerI18nGetter } from "@palamedes/runtime"
import { t } from "@palamedes/core/macro"
const app = express()
const i18nStorage = new AsyncLocalStorage<ReturnType<typeof createI18n>>()
const localeControls = defineLocaleControls({
locales: ["en", "de"],
defaultLocale: "en",
})
setServerI18nGetter(() => i18nStorage.getStore())
app.use((req, res, next) => {
const i18n = createI18n()
i18n.activate(localeControls.preferredLocale(req.header("accept-language")))
i18nStorage.run(i18n, next)
})
app.get("/", (req, res) => {
res.send(t`Welcome to Palamedes`)
})Where Locale Can Come From
Palamedes does not force a single backend locale strategy.
Common backend sources are:
Accept-Language- request cookies
- user session data
- stored user profile locale
- route or subdomain conventions
The important rule is only this:
determine the locale before translated code runs, then expose the matching i18n instance through the runtime getter.
What This Means For The Product Story
This matters for positioning because Palamedes covers more than frontend framework tooling.
The same runtime model already covers:
- server-side code inside React frameworks
- request-local logic in backend applications
- APIs or HTML responses that need locale-aware output
That makes the cross-framework story stronger: the model is not tied only to UI render trees. It also fits classic request/response servers.