New:Socket for Asana Is Now Available.Learn more
Sign In

@sonenta/astro

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sonenta/astro

Official Astro integration for Sonenta i18n — build-time CDN translations, zero client JS (SSG).

Source
npmnpm
Version
0.2.0
Version published
Maintainers
1
Created
Source

@sonenta/astro

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.2 — standalone. This release does string resolution, {var} interpolation, source-language fallback, and device-surface variants (responsive text via build-time overlays + CSS, zero JS). CLDR plurals and accessibility surfaces arrive additively in 0.3.0 (backed by @sonenta/i18n-core) without any breaking change to the API below — see CONTRACT.md.

Quick start

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

The sonenta:i18n virtual module

The integration exposes a virtual module you can import from any .astro file (or .ts/.js run at build time):

ExportDescription
getT(locale)Returns a t(key, vars?) bound to locale.
localesThe locales fetched into the build.
defaultLocaleThe 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.

Key resolution

  • Dotted keys walk nested objects: t("home.cta.label").
  • Namespaces use the i18next-style ns:key syntax: t("docs:intro"). Un-prefixed keys resolve against the first namespace (default "common").
  • Interpolation: t("greeting", { name: "Ada" }) replaces {name}. Unknown placeholders are left intact.
  • Fallback: a missing key falls back through 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).

Surface variants (responsive text, 0 JS)

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.

Without the integration (explicit build-time fetch)

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;

Configuration

OptionTypeDefaultNotes
projectstringRequired. Project UUID.
localesstring[]Required. Locales to fetch + freeze.
versionstring"main"Released version slug / pinned hash.
defaultLocalestringlocales[0]Source / fallback locale.
fallbackLngstring | string[][defaultLocale]Missing-key fallback chain.
namespacesstring[]["common"]Bundle files; first is the default ns.
surfacesSurface[][] (off)Device surfaces to fetch (desktop/mobile/tablet).
surfaceBreakpoints{mobile,tablet}{640,1024}<SurfaceText> media-query ladder.
cdnBasestringhttps://cdn.sonenta.comCDN host (no /p). Env: SONENTA_CDN_BASE.
apiBasestringhttps://api.sonenta.devReserved (forward-compat).
fetchImpltypeof fetchglobal fetchCustom fetch (runtime API only).

Bundles are fetched from {cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.json — the same CDN layout as @sonenta/react-i18next.

Notes & limits (v0.2)

  • No CLDR plurals / a11y surfaces yet. A $value that is a plural dict is treated as "no string" (resolves to fallback/raw key); t.aria/t.alt and pluralization arrive in 0.3.0 on @sonenta/i18n-core. See CONTRACT.md.
  • Add-ons are island-only. @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.
  • SSR/hybrid is not yet wired; this is build-time SSG. A request-scoped resolver (fetch-cache + revalidate) arrives with the core-backed release.

License

MIT © Sonenta

Keywords

i18n

FAQs

Package last updated on 17 Jun 2026

Related posts