@palamedes/runtime
v1.25.0
Published
Palamedes runtime primitives for resolving the active i18n instance
Readme
@palamedes/runtime
Small runtime primitives for Palamedes-transformed code.
When Palamedes rewrites message macros, the generated code expects a
getI18n() function. @palamedes/runtime provides that contract for browser
code, server code, framework integrations, and backend request handlers.
When To Use This Package
Use @palamedes/runtime whenever transformed Palamedes code runs in your
application. It is the small shared contract that keeps translated code from
caring which framework is hosting it.
You typically install it together with:
Installation
pnpm add @palamedes/runtimeMinimal Example
import { createI18n } from "@palamedes/core";
import { setClientI18n } from "@palamedes/runtime";
const i18n = createI18n();
setClientI18n(i18n);The public I18nInstance contract requires an initialized locale: string.
Custom adapters registered with the client or server runtime must expose that
property before registration.
Browser client registration also works in windowless Web Worker and Service Worker globals. Graph-split bootstrap modules can therefore initialize and resolve their client instance off the main thread.
For server-side rendering or server components, register a getter for the active request-local i18n instance:
import { setServerI18nGetter } from "@palamedes/runtime";
setServerI18nGetter(() => {
return getRequestScopedI18n();
});For Node server code, prefer the server-only helper subpath. It uses
AsyncLocalStorage internally and registers the runtime getter once when the
scope is created:
import { createI18n } from "@palamedes/core";
import { createServerI18nScope } from "@palamedes/runtime/server";
const serverI18n = createServerI18nScope<ReturnType<typeof createI18n>>();
serverI18n.activate(i18n);
renderTranslatedServerComponents();
await serverI18n.run(i18n, async () => {
renderTranslatedRequestHandler();
});All scopes created by this helper share the same runtime getter, so independently
created scopes do not disconnect transformed getI18n() calls from the scope
that was activated for the current async context. Framework adapters can also
provide a stable request key for hosts that resume rendering from an earlier
async context; application code should use the framework adapter rather than
constructing that provider itself.
Next.js App Router applications must use
createNextServerI18nScope() from @palamedes/next-plugin/server. A plain
AsyncLocalStorage.enterWith() lifetime does not cover Next's separate RSC and
Client Component server-render passes after suspension.
Backend Servers
The same runtime model also works in classic backend applications such as Hono, Express, or custom Node servers.
The important requirement is request-local access to the active i18n instance.
The recommended pattern is @palamedes/runtime/server:
import { createI18n } from "@palamedes/core";
import { createServerI18nScope } from "@palamedes/runtime/server";
const serverI18n = createServerI18nScope<ReturnType<typeof createI18n>>();Per request, resolve the locale from Accept-Language, cookies, session data,
or the user profile. Use serverI18n.activate(i18n) only when the host preserves
the current Node async context after the initializer returns. Use
serverI18n.run(i18n, ...) for tightly scoped request-handler callbacks. Hosts
with multi-pass rendering or suspension need a framework adapter with a stable
request key; Next applications use @palamedes/next-plugin/server.
For a fuller walkthrough, including Hono and Express examples, see:
API
getI18n()isServerEnvironment()classifies the current runtime consistently across Palamedes packages; browser workers count as client environments, while Cloudflare Workers that expose Cloudflare's documentednavigator.userAgentmarker count as a server runtime.setClientI18n(i18n)activateServerI18n(i18n)setServerI18nGetter(getter)resetI18nRuntime()createServerI18nScope()from@palamedes/runtime/serverfor Node runtimesscope.activate(i18n)binds an i18n instance to the current async contextscope.run(i18n, callback)runs a callback inside a scoped async contextscope.get()returns the current scoped i18n instance, if one is activerequestKeyProvideris an adapter-only escape hatch for a stable host render identity; its symbol ID makes repeat registration bounded during dev HMR
When a request key is available, scope.activate() updates the instance stored
for that key. Multiple activations under the same key are last-write-wins, which
matches hosts such as Next where one render has one active instance.
scope.run() remains isolated to its callback and takes precedence over that
request-key fallback.
The @palamedes/runtime/server implementation imports Node async_hooks. In
non-Node bundles, the subpath resolves to a small fallback module that throws an
actionable Node-only error when called.
Isomorphic SSR client-component bundles can use activateServerI18n(i18n) from
the main entry point to enter an existing request scope without importing the
Node-only subpath. The server entry point must first configure the shared scope
with createServerI18nScope(). The helper does not make a shared i18n singleton
request-safe, so pass a fresh request-local instance.
Related Packages
palamedes is part of the Ferramenta family — Rust-native developer tools that keep the APIs the ecosystem already knows.
Siblings: ferroni · ferriki · ferromark · ferrolex · ferrocat · ferrovia · ferralk · ferrugo.
License
MIT OR Apache-2.0 © 2026 Sebastian Software
