
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
@sonenta/astro
Advanced tools
Official Astro integration for Sonenta i18n — build-time CDN translations, zero client JS (SSG).
Official Astro integration for Sonenta i18n.
Your translations live in Sonenta; this integration pulls the released bundles from the CDN at build time and inlines them into your static HTML. There is zero client-side JavaScript — it's pure SSG, the way Astro content sites want i18n to work.
npm install @sonenta/astro
# or: pnpm add @sonenta/astro / yarn add @sonenta/astro
v0.3 — standalone. This release does string resolution,
{{var}}/{var}interpolation, CLDR plurals (acountvar selects the plural form viaIntl.PluralRules), source-language fallback, and device-surface variants (responsive text via build-time overlays + CSS, zero JS). Accessibility surfaces arrive additively in a later release without any breaking change to the API below — see CONTRACT.md.
// astro.config.mjs
import { defineConfig } from "astro/config";
import sonenta from "@sonenta/astro";
export default defineConfig({
// Astro's native i18n routing — the integration reads Astro.currentLocale.
i18n: { defaultLocale: "fr", locales: ["fr", "en", "es"] },
integrations: [
sonenta({
project: "your-project-uuid", // from your Sonenta dashboard
locales: ["fr", "en", "es"],
defaultLocale: "fr",
namespaces: ["common"],
}),
],
});
---
// src/pages/index.astro
import { getT, defaultLocale } from "sonenta:i18n";
const t = getT(Astro.currentLocale ?? defaultLocale);
---
<html lang={Astro.currentLocale ?? defaultLocale}>
<head><title>{t("home.title")}</title></head>
<body>
<h1>{t("home.title")}</h1>
<p>{t("home.greeting", { name: "Ada" })}</p>
</body>
</html>
That's it. astro build fetches common.json for every locale and freezes the
strings into the page. Nothing ships to the browser.
sonenta:i18n virtual moduleThe integration exposes a virtual module you can import from any .astro file
(or .ts/.js run at build time):
| Export | Description |
|---|---|
getT(locale) | Returns a t(key, vars?) bound to locale. |
locales | The locales fetched into the build. |
defaultLocale | The resolved source/default locale. |
getCatalog(locale, ns?) | Raw fetched dictionary, or null if absent. |
TypeScript types for the virtual module are injected automatically (Astro's
injectTypes), so getT is fully typed with no extra config.
t("home.cta.label").ns:key syntax: t("docs:intro").
Un-prefixed keys resolve against the first namespace (default "common").t("greeting", { name: "Ada" }) replaces {{name}}
(i18next syntax, the shape the CDN emits) and the legacy {name}.t("items", { count: 3 }) selects the plural form for the
active locale via Intl.PluralRules — reading the CDN's flat suffixed
items_one/items_other/… (or a nested { one, other } dict), with an exact
items_0 winning over the category form. {{count}} interpolates the number.
Unknown placeholders are left intact.fallbackLng (default: the
source locale). A locale whose bundle is absent (e.g. plan-limit) falls back
the same way. Still missing everywhere → the raw key is returned (i18next
parity).A surface lets one key carry different values per device — e.g. a CTA that
reads "Commencer gratuitement" on desktop and "Commencer" on mobile. Values
come from a sparse CDN overlay ({ns}.{surface}.json) layered over the base
bundle; nothing about your authoring changes except adding per-surface values
in the dashboard.
Because SSG can't know the viewport at build time, the integration fetches every surface and renders them all — a CSS media query reveals the right one. No client JavaScript.
Enable surfaces in the integration, then use the <SurfaceText> component:
// astro.config.mjs
sonenta({
project: "your-project-uuid",
locales: ["fr", "en", "es"],
surfaces: ["desktop", "mobile"], // also fetch these overlays
})
---
import SurfaceText from "@sonenta/astro/SurfaceText.astro";
const locale = Astro.currentLocale ?? "fr";
---
<a href="/signup">
<SurfaceText key="cta.start" locale={locale} />
</a>
<!-- desktop → "Commencer gratuitement", mobile → "Commencer" -->
<SurfaceText> auto-collapses: when a key has no overlay (every surface
resolves equal) it renders a single element — no wrapper spans, no <style>,
no bloat. Props: key, locale, vars?, as? (wrapper tag, default
span), class?, breakpoints?.
Prefer your own markup (e.g. Tailwind hidden md:inline)? Use the primitives:
---
import { getSurfaces, getT } from "sonenta:i18n";
const { desktop, mobile } = getSurfaces(locale, "cta.start");
const oneSurface = getT(locale).surface("cta.start", "mobile");
---
Breakpoint ladder (mirrors @sonenta/react-i18next): mobile < 640px,
tablet 640–1023px, desktop ≥ 1024px; configurable via surfaceBreakpoints
or the <SurfaceText breakpoints> prop.
Surface overlays must be published on the CDN for the key (dashboard / backend side). A key with no overlay simply renders its base value on every surface — safe by default.
Prefer an explicit top-level await? Use the runtime directly — no virtual
module, no Vite plugin:
// src/i18n.ts
import { createSonentaI18n } from "@sonenta/astro/runtime";
export const i18n = await createSonentaI18n({
project: "your-project-uuid",
locales: ["fr", "en", "es"],
defaultLocale: "fr",
});
export const getT = i18n.getT;
| Option | Type | Default | Notes |
|---|---|---|---|
project | string | — | Required. Project UUID. |
locales | string[] | — | Required. Locales to fetch + freeze. |
version | string | "main" | Released version slug / pinned hash. |
defaultLocale | string | locales[0] | Source / fallback locale. |
fallbackLng | string | string[] | [defaultLocale] | Missing-key fallback chain. |
namespaces | string[] | ["common"] | Bundle files; first is the default ns. |
surfaces | Surface[] | [] (off) | Device surfaces to fetch (desktop/mobile/tablet). |
surfaceBreakpoints | {mobile,tablet} | {640,1024} | <SurfaceText> media-query ladder. |
cdnBase | string | https://cdn.sonenta.com | CDN host (no /p). Env: SONENTA_CDN_BASE. |
apiBase | string | https://api.sonenta.dev | Reserved (forward-compat). |
fetchImpl | typeof fetch | global fetch | Custom fetch (runtime API only). |
Bundles are fetched from
{cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.json — the same
CDN layout as @sonenta/react-i18next.
t(key, { count }) selects the plural form
via Intl.PluralRules. Still no a11y surfaces (t.aria/t.alt) and no
ICU {{value, format}} formatters — a formatter placeholder passes through
untouched. See CONTRACT.md.@sonenta/feedback, @sonenta/realtime, and
@sonenta/in-context are runtime/DOM SDKs — they run inside Astro client
islands (reusing the React bindings), not in static .astro output.MIT © Sonenta
FAQs
Official Astro integration for Sonenta i18n — build-time CDN translations, zero client JS (SSG).
We found that @sonenta/astro demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.