🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
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).

latest
Source
npmnpm
Version
0.3.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.3 — standalone. This release does string resolution, {{var}}/{var} interpolation, CLDR plurals (a count var selects the plural form via Intl.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.

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}} (i18next syntax, the shape the CDN emits) and the legacy {name}.
  • Plurals (CLDR): 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.
  • 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.3)

  • CLDR plurals shipped (0.3). 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.
  • 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 Jul 2026

Did you know?

Socket

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.

Install

Related posts