npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sonenta/astro

v0.3.0

Published

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

Readme

@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):

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

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

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

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