@sonenta/astro 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRACT.md +42 -16
- package/README.md +69 -5
- package/SurfaceText.astro +93 -0
- package/dist/{chunk-CK2DAB6G.js → chunk-RG6KTECT.js} +90 -7
- package/dist/chunk-RG6KTECT.js.map +1 -0
- package/dist/index.cjs +113 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +30 -5
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +96 -8
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +104 -16
- package/dist/runtime.d.ts +104 -16
- package/dist/runtime.js +13 -3
- package/package.json +4 -2
- package/dist/chunk-CK2DAB6G.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -20,15 +20,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
DEFAULT_SURFACE_BREAKPOINTS: () => DEFAULT_SURFACE_BREAKPOINTS,
|
|
23
24
|
buildBundleUrl: () => buildBundleUrl,
|
|
25
|
+
buildOverlayUrl: () => buildOverlayUrl,
|
|
24
26
|
createSonentaI18n: () => createSonentaI18n,
|
|
25
27
|
createT: () => createT,
|
|
26
28
|
default: () => index_default,
|
|
27
29
|
fetchAll: () => fetchAll,
|
|
30
|
+
fetchAllOverlays: () => fetchAllOverlays,
|
|
28
31
|
fetchBundle: () => fetchBundle,
|
|
29
32
|
fetchNamespace: () => fetchNamespace,
|
|
33
|
+
fetchOverlay: () => fetchOverlay,
|
|
30
34
|
resolveCdnBase: () => resolveCdnBase,
|
|
31
|
-
sonenta: () => sonenta
|
|
35
|
+
sonenta: () => sonenta,
|
|
36
|
+
surfaceForWidth: () => surfaceForWidth
|
|
32
37
|
});
|
|
33
38
|
module.exports = __toCommonJS(index_exports);
|
|
34
39
|
|
|
@@ -47,6 +52,9 @@ function moduleSource(options) {
|
|
|
47
52
|
`export const getT = (locale) => __i18n.getT(locale);`,
|
|
48
53
|
`export const locales = __i18n.locales;`,
|
|
49
54
|
`export const defaultLocale = __i18n.defaultLocale;`,
|
|
55
|
+
`export const surfaces = __i18n.surfaces;`,
|
|
56
|
+
`export const surfaceBreakpoints = __i18n.surfaceBreakpoints;`,
|
|
57
|
+
`export const getSurfaces = (locale, key, vars) => __i18n.getSurfaces(locale, key, vars);`,
|
|
50
58
|
`export const getCatalog = (locale, ns) => __i18n.getCatalog(locale, ns);`,
|
|
51
59
|
`export default __i18n;`,
|
|
52
60
|
``
|
|
@@ -69,13 +77,25 @@ function sonentaI18nVitePlugin(options) {
|
|
|
69
77
|
|
|
70
78
|
// src/integration.ts
|
|
71
79
|
var VIRTUAL_DTS = `declare module "sonenta:i18n" {
|
|
72
|
-
import type {
|
|
73
|
-
|
|
80
|
+
import type {
|
|
81
|
+
SonentaI18n, TFn, Locale, Namespace, Vars, Surface, SurfaceBreakpoints,
|
|
82
|
+
} from "@sonenta/astro/runtime";
|
|
83
|
+
/** Translation function bound to \`locale\` (\`t(key)\` + \`t.surface(key, surface)\`). */
|
|
74
84
|
export const getT: (locale: Locale) => TFn;
|
|
75
85
|
/** Locales fetched and frozen into the build. */
|
|
76
86
|
export const locales: Locale[];
|
|
77
87
|
/** Resolved source/default locale. */
|
|
78
88
|
export const defaultLocale: Locale;
|
|
89
|
+
/** Configured device surfaces (empty when surfaces are disabled). */
|
|
90
|
+
export const surfaces: Surface[];
|
|
91
|
+
/** Resolved surface breakpoint ladder. */
|
|
92
|
+
export const surfaceBreakpoints: SurfaceBreakpoints;
|
|
93
|
+
/** Resolve \`key\` for every configured surface at \`locale\`. */
|
|
94
|
+
export const getSurfaces: (
|
|
95
|
+
locale: Locale,
|
|
96
|
+
key: string,
|
|
97
|
+
vars?: Vars,
|
|
98
|
+
) => Record<Surface, string>;
|
|
79
99
|
/** Raw fetched dictionary for a locale/namespace, or null when absent. */
|
|
80
100
|
export const getCatalog: (
|
|
81
101
|
locale: Locale,
|
|
@@ -119,6 +139,15 @@ function sonenta(options) {
|
|
|
119
139
|
var DEFAULT_CDN_BASE = "https://cdn.sonenta.com";
|
|
120
140
|
var DEFAULT_VERSION = "main";
|
|
121
141
|
var DEFAULT_NAMESPACE = "common";
|
|
142
|
+
var DEFAULT_SURFACE_BREAKPOINTS = {
|
|
143
|
+
mobile: 640,
|
|
144
|
+
tablet: 1024
|
|
145
|
+
};
|
|
146
|
+
function surfaceForWidth(width, breakpoints = DEFAULT_SURFACE_BREAKPOINTS) {
|
|
147
|
+
if (width < breakpoints.mobile) return "mobile";
|
|
148
|
+
if (width < breakpoints.tablet) return "tablet";
|
|
149
|
+
return "desktop";
|
|
150
|
+
}
|
|
122
151
|
|
|
123
152
|
// src/cdn.ts
|
|
124
153
|
function trimSlashes(s) {
|
|
@@ -133,6 +162,11 @@ function buildBundleUrl(opts, locale, namespace) {
|
|
|
133
162
|
const version = opts.version ?? DEFAULT_VERSION;
|
|
134
163
|
return `${base}/p/${opts.project}/${version}/latest/${locale}/${namespace}.json`;
|
|
135
164
|
}
|
|
165
|
+
function buildOverlayUrl(opts, locale, namespace, surface) {
|
|
166
|
+
const base = resolveCdnBase(opts);
|
|
167
|
+
const version = opts.version ?? DEFAULT_VERSION;
|
|
168
|
+
return `${base}/p/${opts.project}/${version}/latest/${locale}/${namespace}.${surface}.json`;
|
|
169
|
+
}
|
|
136
170
|
async function fetchBundle(opts, locale, namespace) {
|
|
137
171
|
const url = buildBundleUrl(opts, locale, namespace);
|
|
138
172
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
@@ -170,6 +204,35 @@ async function fetchAll(opts, locales, namespaces) {
|
|
|
170
204
|
);
|
|
171
205
|
return out;
|
|
172
206
|
}
|
|
207
|
+
async function fetchOverlay(opts, locale, namespace, surface) {
|
|
208
|
+
const url = buildOverlayUrl(opts, locale, namespace, surface);
|
|
209
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
210
|
+
try {
|
|
211
|
+
const res = await doFetch(url, { method: "GET" });
|
|
212
|
+
if (!res.ok) return null;
|
|
213
|
+
const data = await res.json();
|
|
214
|
+
return data && typeof data === "object" ? data : null;
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function fetchAllOverlays(opts, locales, namespaces, surfaces) {
|
|
220
|
+
const out = {};
|
|
221
|
+
for (const l of locales) {
|
|
222
|
+
out[l] = {};
|
|
223
|
+
for (const ns of namespaces) out[l][ns] = {};
|
|
224
|
+
}
|
|
225
|
+
await Promise.all(
|
|
226
|
+
locales.flatMap(
|
|
227
|
+
(l) => namespaces.flatMap(
|
|
228
|
+
(ns) => surfaces.map(async (s) => {
|
|
229
|
+
out[l][ns][s] = await fetchOverlay(opts, l, ns, s);
|
|
230
|
+
})
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
173
236
|
|
|
174
237
|
// src/resolver.ts
|
|
175
238
|
function normalizeFallback(fallbackLng, defaultLocale) {
|
|
@@ -193,6 +256,16 @@ function walk(bundle, path) {
|
|
|
193
256
|
}
|
|
194
257
|
return cursor;
|
|
195
258
|
}
|
|
259
|
+
function unwrapValue(node) {
|
|
260
|
+
if (node && typeof node === "object" && Object.prototype.hasOwnProperty.call(node, "$value")) {
|
|
261
|
+
return node.$value;
|
|
262
|
+
}
|
|
263
|
+
return node;
|
|
264
|
+
}
|
|
265
|
+
function resolveLeaf(bundle, path) {
|
|
266
|
+
const v = unwrapValue(walk(bundle, path));
|
|
267
|
+
return typeof v === "string" ? v : void 0;
|
|
268
|
+
}
|
|
196
269
|
function interpolate(template, vars) {
|
|
197
270
|
if (!vars) return template;
|
|
198
271
|
return template.replace(
|
|
@@ -200,16 +273,30 @@ function interpolate(template, vars) {
|
|
|
200
273
|
(_, name) => name in vars ? String(vars[name]) : `{${name}}`
|
|
201
274
|
);
|
|
202
275
|
}
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
276
|
+
function resolutionOrder(locale, fallbackChain) {
|
|
277
|
+
return [locale, ...fallbackChain.filter((l) => l !== locale)];
|
|
278
|
+
}
|
|
279
|
+
function createT(data, overlays, locale, fallbackChain, defaultNamespace) {
|
|
280
|
+
const order = resolutionOrder(locale, fallbackChain);
|
|
281
|
+
const t = function t2(key, vars) {
|
|
206
282
|
const [ns, rest] = splitKey(key, defaultNamespace);
|
|
207
283
|
for (const l of order) {
|
|
208
|
-
const hit =
|
|
209
|
-
if (
|
|
284
|
+
const hit = resolveLeaf(data[l]?.[ns], rest);
|
|
285
|
+
if (hit !== void 0) return interpolate(hit, vars);
|
|
210
286
|
}
|
|
211
287
|
return key;
|
|
212
288
|
};
|
|
289
|
+
t.surface = function surface(key, surface, vars) {
|
|
290
|
+
const [ns, rest] = splitKey(key, defaultNamespace);
|
|
291
|
+
for (const l of order) {
|
|
292
|
+
const overlayHit = resolveLeaf(overlays[l]?.[ns]?.[surface], rest);
|
|
293
|
+
if (overlayHit !== void 0) return interpolate(overlayHit, vars);
|
|
294
|
+
const baseHit = resolveLeaf(data[l]?.[ns], rest);
|
|
295
|
+
if (baseHit !== void 0) return interpolate(baseHit, vars);
|
|
296
|
+
}
|
|
297
|
+
return key;
|
|
298
|
+
};
|
|
299
|
+
return t;
|
|
213
300
|
}
|
|
214
301
|
async function createSonentaI18n(options) {
|
|
215
302
|
if (!options.project) {
|
|
@@ -223,12 +310,23 @@ async function createSonentaI18n(options) {
|
|
|
223
310
|
const namespaces = options.namespaces?.length ? options.namespaces : [DEFAULT_NAMESPACE];
|
|
224
311
|
const defaultNamespace = namespaces[0];
|
|
225
312
|
const fallbackChain = normalizeFallback(options.fallbackLng, defaultLocale);
|
|
313
|
+
const surfaces = options.surfaces ?? [];
|
|
314
|
+
const surfaceBreakpoints = options.surfaceBreakpoints ?? DEFAULT_SURFACE_BREAKPOINTS;
|
|
226
315
|
const data = await fetchAll(options, locales, namespaces);
|
|
316
|
+
const overlays = surfaces.length ? await fetchAllOverlays(options, locales, namespaces, surfaces) : {};
|
|
227
317
|
return {
|
|
228
318
|
locales,
|
|
229
319
|
defaultLocale,
|
|
320
|
+
surfaces,
|
|
321
|
+
surfaceBreakpoints,
|
|
230
322
|
getT(locale) {
|
|
231
|
-
return createT(data, locale, fallbackChain, defaultNamespace);
|
|
323
|
+
return createT(data, overlays, locale, fallbackChain, defaultNamespace);
|
|
324
|
+
},
|
|
325
|
+
getSurfaces(locale, key, vars) {
|
|
326
|
+
const t = createT(data, overlays, locale, fallbackChain, defaultNamespace);
|
|
327
|
+
const out = {};
|
|
328
|
+
for (const s of surfaces) out[s] = t.surface(key, s, vars);
|
|
329
|
+
return out;
|
|
232
330
|
},
|
|
233
331
|
getCatalog(locale, namespace) {
|
|
234
332
|
return data[locale]?.[namespace ?? defaultNamespace] ?? null;
|
|
@@ -240,13 +338,18 @@ async function createSonentaI18n(options) {
|
|
|
240
338
|
var index_default = sonenta;
|
|
241
339
|
// Annotate the CommonJS export names for ESM import in node:
|
|
242
340
|
0 && (module.exports = {
|
|
341
|
+
DEFAULT_SURFACE_BREAKPOINTS,
|
|
243
342
|
buildBundleUrl,
|
|
343
|
+
buildOverlayUrl,
|
|
244
344
|
createSonentaI18n,
|
|
245
345
|
createT,
|
|
246
346
|
fetchAll,
|
|
347
|
+
fetchAllOverlays,
|
|
247
348
|
fetchBundle,
|
|
248
349
|
fetchNamespace,
|
|
350
|
+
fetchOverlay,
|
|
249
351
|
resolveCdnBase,
|
|
250
|
-
sonenta
|
|
352
|
+
sonenta,
|
|
353
|
+
surfaceForWidth
|
|
251
354
|
});
|
|
252
355
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/virtual.ts","../src/integration.ts","../src/types.ts","../src/cdn.ts","../src/resolver.ts"],"sourcesContent":["/**\n * `@sonenta/astro` — official Astro integration for Sonenta i18n.\n *\n * v0.1 (standalone, pre-`@sonenta/i18n-core`): build-time CDN fetch + string\n * resolution + `{var}` interpolation + source-language fallback, with ZERO\n * client-side JS (pure SSG). The public API here is frozen forward-compatible:\n * the 0.2.0 core-backed release swaps the internals and adds surfaces / a11y /\n * CLDR plurals / variants ADDITIVELY, without changing anything below.\n *\n * Default export = the integration; named exports = the build-time runtime\n * helpers (also available standalone at `@sonenta/astro/runtime`).\n */\n\nimport { sonenta } from \"./integration\";\n\nexport { sonenta } from \"./integration\";\nexport default sonenta;\n\nexport {\n createSonentaI18n,\n createT,\n} from \"./resolver\";\n\nexport {\n buildBundleUrl,\n fetchBundle,\n fetchNamespace,\n fetchAll,\n resolveCdnBase,\n type Bundle,\n} from \"./cdn\";\n\nexport type {\n Locale,\n Namespace,\n Vars,\n TFn,\n SonentaI18n,\n SonentaI18nOptions,\n} from \"./types\";\n","/**\n * Vite plugin that serves the `sonenta:i18n` virtual module. The integration\n * wires this into the consumer's Astro/Vite build; the generated module pulls\n * the configured bundles at build time (via `@sonenta/astro/runtime`) and\n * re-exports a ready `getT`.\n */\n\nimport type { Plugin } from \"vite\";\nimport type { SonentaI18nOptions } from \"./types\";\n\n/** Public virtual id and its Vite-internal resolved form. */\nconst VIRTUAL_ID = \"sonenta:i18n\";\nconst RESOLVED_ID = \"\\0sonenta:i18n\";\n\n/**\n * Options that can cross the build/runtime boundary: everything except the\n * non-serializable `fetchImpl` (build always uses the global `fetch`).\n */\nfunction serializableOptions(\n options: SonentaI18nOptions,\n): Omit<SonentaI18nOptions, \"fetchImpl\"> {\n const { fetchImpl: _omit, ...rest } = options;\n return rest;\n}\n\n/** The generated source of the `sonenta:i18n` module. */\nfunction moduleSource(options: SonentaI18nOptions): string {\n const opts = JSON.stringify(serializableOptions(options));\n return [\n `import { createSonentaI18n } from \"@sonenta/astro/runtime\";`,\n `const __i18n = await createSonentaI18n(${opts});`,\n `export const getT = (locale) => __i18n.getT(locale);`,\n `export const locales = __i18n.locales;`,\n `export const defaultLocale = __i18n.defaultLocale;`,\n `export const getCatalog = (locale, ns) => __i18n.getCatalog(locale, ns);`,\n `export default __i18n;`,\n ``,\n ].join(\"\\n\");\n}\n\n/** Build the Vite plugin that resolves and loads `sonenta:i18n`. */\nexport function sonentaI18nVitePlugin(options: SonentaI18nOptions): Plugin {\n return {\n name: \"sonenta:astro-i18n\",\n enforce: \"pre\",\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_ID;\n return null;\n },\n load(id) {\n if (id === RESOLVED_ID) return moduleSource(options);\n return null;\n },\n };\n}\n\nexport { VIRTUAL_ID, RESOLVED_ID };\n","/**\n * The Sonenta Astro integration — default export of `@sonenta/astro`.\n *\n * Usage (astro.config.mjs):\n *\n * import { defineConfig } from \"astro/config\";\n * import sonenta from \"@sonenta/astro\";\n *\n * export default defineConfig({\n * integrations: [\n * sonenta({\n * project: \"your-project-uuid\",\n * locales: [\"fr\", \"en\", \"es\"],\n * defaultLocale: \"fr\",\n * namespaces: [\"common\"],\n * }),\n * ],\n * });\n *\n * Then in any `.astro` page:\n *\n * ---\n * import { getT } from \"sonenta:i18n\";\n * const t = getT(Astro.currentLocale ?? \"fr\");\n * ---\n * <h1>{t(\"home.title\")}</h1>\n * <p>{t(\"home.greeting\", { name: \"Ada\" })}</p>\n */\n\nimport type { AstroIntegration } from \"astro\";\nimport { sonentaI18nVitePlugin } from \"./virtual\";\nimport type { SonentaI18nOptions } from \"./types\";\n\n/** Ambient types injected for the `sonenta:i18n` virtual module. */\nconst VIRTUAL_DTS = `declare module \"sonenta:i18n\" {\n import type { SonentaI18n, TFn, Locale, Namespace } from \"@sonenta/astro/runtime\";\n /** Translation function bound to \\`locale\\`. */\n export const getT: (locale: Locale) => TFn;\n /** Locales fetched and frozen into the build. */\n export const locales: Locale[];\n /** Resolved source/default locale. */\n export const defaultLocale: Locale;\n /** Raw fetched dictionary for a locale/namespace, or null when absent. */\n export const getCatalog: (\n locale: Locale,\n namespace?: Namespace,\n ) => Record<string, unknown> | null;\n const i18n: SonentaI18n;\n export default i18n;\n}\n`;\n\n/**\n * Create the Sonenta i18n integration. Bundles are fetched at build time and\n * exposed through the `sonenta:i18n` virtual module (zero client-side JS).\n */\nexport function sonenta(options: SonentaI18nOptions): AstroIntegration {\n if (!options?.project) {\n throw new Error(\"[@sonenta/astro] `project` (UUID) is required.\");\n }\n if (!options.locales?.length) {\n throw new Error(\"[@sonenta/astro] `locales` must be a non-empty array.\");\n }\n return {\n name: \"@sonenta/astro\",\n hooks: {\n \"astro:config:setup\": ({ updateConfig, logger }) => {\n if (options.fetchImpl) {\n logger.warn(\n \"`fetchImpl` is ignored by the integration; the build uses the global fetch. Use `@sonenta/astro/runtime` directly if you need a custom fetch.\",\n );\n }\n updateConfig({\n vite: { plugins: [sonentaI18nVitePlugin(options)] },\n });\n logger.info(\n `i18n wired for project ${options.project} (${options.locales.length} locales, build-time CDN fetch).`,\n );\n },\n \"astro:config:done\": ({ injectTypes }) => {\n injectTypes({ filename: \"sonenta-i18n.d.ts\", content: VIRTUAL_DTS });\n },\n },\n };\n}\n\nexport default sonenta;\n","/**\n * Public types for `@sonenta/astro` v0.1 (standalone, pre-core).\n *\n * FORWARD-COMPATIBILITY CONTRACT (frozen day-one — see CONTRACT.md): every\n * option key here is the SAME key the future `@sonenta/i18n-core`-backed\n * release will consume, so the later refactor (0.2.0) is a non-breaking,\n * additive internal swap. New capabilities (surfaces, a11y accessors, CLDR\n * plurals, variants) arrive ADDITIVELY; nothing here changes meaning.\n */\n\n/** A BCP-47 locale code, e.g. `\"fr\"`, `\"en\"`, `\"fr-CA\"`. */\nexport type Locale = string;\n\n/** A bundle namespace (the `{ns}.json` file on the CDN), e.g. `\"common\"`. */\nexport type Namespace = string;\n\n/** Interpolation variables for a `t()` call: `t(\"greeting\", { name: \"Ada\" })`. */\nexport type Vars = Record<string, string | number>;\n\n/** A resolved, per-locale translation function. */\nexport type TFn = (key: string, vars?: Vars) => string;\n\n/**\n * Configuration for the Sonenta Astro integration / runtime.\n *\n * Bundles are fetched at BUILD time from\n * `{cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.json`\n * (the canonical Sonenta CDN layout, identical to `@sonenta/react-i18next`).\n */\nexport interface SonentaI18nOptions {\n /** Project UUID from your Sonenta dashboard. Required. */\n project: string;\n\n /**\n * Released version slug or pinned content hash. Default `\"main\"`.\n * The CDN serves the latest release of this version under `/latest/`.\n */\n version?: string;\n\n /**\n * Locales to fetch and freeze into the static build. Required, non-empty.\n * A locale whose bundle 404s (e.g. a plan-limit-blocked language) is kept\n * as `null` and transparently falls back to the source language.\n */\n locales: Locale[];\n\n /**\n * Source / default locale. Default = `locales[0]`. Used as the implicit\n * final fallback target and as Astro's source language.\n */\n defaultLocale?: Locale;\n\n /**\n * Ordered fallback chain applied when a key is missing in the active\n * locale. Default = `[defaultLocale]`. v0.1 applies these locales in\n * order; BCP-47 variant→base inheritance (`fr-CA → fr`) is deliberately\n * left to the core-backed release.\n */\n fallbackLng?: Locale | Locale[];\n\n /**\n * Namespaces (bundle files) to fetch. Default `[\"common\"]`. The first\n * entry is the default namespace for un-prefixed keys; address others with\n * the i18next-style `\"ns:key\"` syntax.\n */\n namespaces?: Namespace[];\n\n /**\n * CDN host root for translation bundles, WITHOUT the `/p` segment.\n * Default `\"https://cdn.sonenta.com\"`. Overridable via the\n * `SONENTA_CDN_BASE` env var (also a bare host; for local dev point it at\n * your translation CDN). The `/p/{project}/{version}/latest/...` path is\n * appended by the loader.\n */\n cdnBase?: string;\n\n /**\n * API host root. Reserved for forward-compatibility (the core-backed\n * release uses it for the public language manifest and dev-mode runtime\n * fetch). Default `\"https://api.sonenta.dev\"`. Unused by v0.1's prod\n * build-time path.\n */\n apiBase?: string;\n\n /**\n * Injectable `fetch` implementation (testing / proxies / custom agents).\n * Defaults to the global `fetch`.\n */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * The resolved Sonenta i18n handle returned by `createSonentaI18n` and\n * exposed through the `sonenta:i18n` virtual module.\n */\nexport interface SonentaI18n {\n /** Build a translation function bound to `locale`. */\n getT(locale: Locale): TFn;\n /** The locales that were fetched (the configured `locales`). */\n readonly locales: Locale[];\n /** The resolved source/default locale. */\n readonly defaultLocale: Locale;\n /**\n * Raw fetched dictionary for `locale` / `namespace` (default namespace when\n * omitted), or `null` when that bundle was absent (404 / plan-limit).\n */\n getCatalog(locale: Locale, namespace?: Namespace): Record<string, unknown> | null;\n}\n\n/** Default CDN host (no `/p`). */\nexport const DEFAULT_CDN_BASE = \"https://cdn.sonenta.com\";\n/** Default API host. */\nexport const DEFAULT_API_BASE = \"https://api.sonenta.dev\";\n/** Default version slug. */\nexport const DEFAULT_VERSION = \"main\";\n/** Default namespace. */\nexport const DEFAULT_NAMESPACE = \"common\";\n","/**\n * Build-time CDN loader for Sonenta translation bundles.\n *\n * Mirrors the canonical Sonenta CDN layout used by `@sonenta/react-i18next`:\n * `{cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.json`\n *\n * Pure data fetch — no DOM, no React, no runtime. Called during `astro build`\n * so the strings inline into the static HTML (zero client JS).\n */\n\nimport {\n DEFAULT_CDN_BASE,\n DEFAULT_VERSION,\n type Locale,\n type Namespace,\n type SonentaI18nOptions,\n} from \"./types\";\n\n/** A fetched bundle (a flat or nested dictionary of message strings). */\nexport type Bundle = Record<string, unknown>;\n\n/** Strip trailing slashes so URL joins never double up. */\nfunction trimSlashes(s: string): string {\n return s.replace(/\\/+$/, \"\");\n}\n\n/**\n * Resolve the effective CDN host (no `/p`): explicit option wins, then the\n * `SONENTA_CDN_BASE` env var, then the production default.\n */\nexport function resolveCdnBase(opts: Pick<SonentaI18nOptions, \"cdnBase\">): string {\n const env =\n typeof process !== \"undefined\" ? process.env?.SONENTA_CDN_BASE : undefined;\n return trimSlashes(opts.cdnBase ?? env ?? DEFAULT_CDN_BASE);\n}\n\n/**\n * Build the bundle URL for one `(locale, namespace)` pair.\n * `{cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.json`\n */\nexport function buildBundleUrl(\n opts: Pick<SonentaI18nOptions, \"project\" | \"version\" | \"cdnBase\">,\n locale: Locale,\n namespace: Namespace,\n): string {\n const base = resolveCdnBase(opts);\n const version = opts.version ?? DEFAULT_VERSION;\n return `${base}/p/${opts.project}/${version}/latest/${locale}/${namespace}.json`;\n}\n\n/**\n * Fetch a single bundle. Resolves to `null` on 404 (locale absent from the\n * project — e.g. a plan-limit-blocked language) so callers fall back to the\n * source language. Any other non-OK status or transport error throws so a\n * misconfigured build fails loudly rather than silently shipping empty pages.\n */\nexport async function fetchBundle(\n opts: Pick<SonentaI18nOptions, \"project\" | \"version\" | \"cdnBase\" | \"fetchImpl\">,\n locale: Locale,\n namespace: Namespace,\n): Promise<Bundle | null> {\n const url = buildBundleUrl(opts, locale, namespace);\n const doFetch = opts.fetchImpl ?? fetch;\n let res: Response;\n try {\n res = await doFetch(url, { method: \"GET\" });\n } catch (e) {\n throw new Error(\n `[@sonenta/astro] CDN fetch failed for ${locale}/${namespace}: ${\n (e as Error).message\n }`,\n );\n }\n if (!res.ok) {\n if (res.status === 404) return null;\n throw new Error(`[@sonenta/astro] CDN ${res.status} on ${url}`);\n }\n return (await res.json()) as Bundle;\n}\n\n/**\n * Fetch one namespace across every locale, in parallel. Absent locales map to\n * `null`. Returns `Record<Locale, Bundle | null>`.\n */\nexport async function fetchNamespace(\n opts: Pick<\n SonentaI18nOptions,\n \"project\" | \"version\" | \"cdnBase\" | \"fetchImpl\"\n >,\n locales: Locale[],\n namespace: Namespace,\n): Promise<Record<Locale, Bundle | null>> {\n const entries = await Promise.all(\n locales.map(\n async (l) => [l, await fetchBundle(opts, l, namespace)] as const,\n ),\n );\n return Object.fromEntries(entries) as Record<Locale, Bundle | null>;\n}\n\n/**\n * Fetch every `(locale, namespace)` pair. Returns a nested map keyed by\n * locale then namespace: `data[locale][namespace] = Bundle | null`.\n */\nexport async function fetchAll(\n opts: Pick<\n SonentaI18nOptions,\n \"project\" | \"version\" | \"cdnBase\" | \"fetchImpl\"\n >,\n locales: Locale[],\n namespaces: Namespace[],\n): Promise<Record<Locale, Record<Namespace, Bundle | null>>> {\n const out: Record<Locale, Record<Namespace, Bundle | null>> = {};\n for (const l of locales) out[l] = {};\n await Promise.all(\n locales.flatMap((l) =>\n namespaces.map(async (ns) => {\n out[l]![ns] = await fetchBundle(opts, l, ns);\n }),\n ),\n );\n return out;\n}\n","/**\n * Build-time translation resolver.\n *\n * v0.1 SCOPE (frozen — see CONTRACT.md): string resolution + `{var}`\n * interpolation + source-language fallback ONLY. Deliberately NO CLDR\n * plurals, NO surfaces, NO a11y accessors, NO BCP-47 variant→base\n * inheritance — those carry real i18n SEMANTICS owned by `@sonenta/i18n-core`\n * and arrive additively in 0.2.0. Keeping them out of v0.1 means there is no\n * naive semantics to break when the core swaps in underneath this same API.\n */\n\nimport { fetchAll, type Bundle } from \"./cdn\";\nimport {\n DEFAULT_NAMESPACE,\n type Locale,\n type Namespace,\n type SonentaI18n,\n type SonentaI18nOptions,\n type TFn,\n type Vars,\n} from \"./types\";\n\n/** Normalize `fallbackLng` (scalar | array | undefined) into a locale list. */\nfunction normalizeFallback(\n fallbackLng: SonentaI18nOptions[\"fallbackLng\"],\n defaultLocale: Locale,\n): Locale[] {\n if (fallbackLng == null) return [defaultLocale];\n return Array.isArray(fallbackLng) ? fallbackLng : [fallbackLng];\n}\n\n/** Split an i18next-style `\"ns:key\"` into `[namespace, key]`. */\nfunction splitKey(key: string, defaultNamespace: Namespace): [Namespace, string] {\n const i = key.indexOf(\":\");\n if (i === -1) return [defaultNamespace, key];\n return [key.slice(0, i), key.slice(i + 1)];\n}\n\n/** Walk a dotted path (`\"home.title\"`) through a bundle; `undefined` on miss. */\nfunction walk(bundle: Bundle | null | undefined, path: string): unknown {\n if (!bundle) return undefined;\n let cursor: unknown = bundle;\n for (const part of path.split(\".\")) {\n if (cursor && typeof cursor === \"object\" && part in cursor) {\n cursor = (cursor as Record<string, unknown>)[part];\n } else {\n return undefined;\n }\n }\n return cursor;\n}\n\n/** Substitute `{var}` placeholders; unknown placeholders are left intact. */\nfunction interpolate(template: string, vars?: Vars): string {\n if (!vars) return template;\n return template.replace(/\\{(\\w+)\\}/g, (_, name: string) =>\n name in vars ? String(vars[name]) : `{${name}}`,\n );\n}\n\n/**\n * Build a `t()` bound to `locale`, given the fetched data and resolution\n * order. Lookup: active locale's namespace dict → each fallback locale's same\n * namespace dict → the raw key (i18next-parity missing-key behaviour).\n */\nexport function createT(\n data: Record<Locale, Record<Namespace, Bundle | null>>,\n locale: Locale,\n fallbackChain: Locale[],\n defaultNamespace: Namespace,\n): TFn {\n // De-duped resolution order: active locale first, then configured\n // fallbacks (skipping the active one if it reappears).\n const order = [locale, ...fallbackChain.filter((l) => l !== locale)];\n return function t(key: string, vars?: Vars): string {\n const [ns, rest] = splitKey(key, defaultNamespace);\n for (const l of order) {\n const hit = walk(data[l]?.[ns], rest);\n if (typeof hit === \"string\") return interpolate(hit, vars);\n }\n // Missing everywhere → return the raw key (unchanged), i18next-style.\n return key;\n };\n}\n\n/**\n * Fetch every configured bundle at build time and return a resolved\n * {@link SonentaI18n} handle. Call once (top-level `await`) and reuse its\n * `getT(locale)` across pages.\n */\nexport async function createSonentaI18n(\n options: SonentaI18nOptions,\n): Promise<SonentaI18n> {\n if (!options.project) {\n throw new Error(\"[@sonenta/astro] `project` (UUID) is required.\");\n }\n if (!options.locales?.length) {\n throw new Error(\"[@sonenta/astro] `locales` must be a non-empty array.\");\n }\n const locales = options.locales;\n const defaultLocale = options.defaultLocale ?? locales[0]!;\n const namespaces =\n options.namespaces?.length ? options.namespaces : [DEFAULT_NAMESPACE];\n const defaultNamespace = namespaces[0]!;\n const fallbackChain = normalizeFallback(options.fallbackLng, defaultLocale);\n\n const data = await fetchAll(options, locales, namespaces);\n\n return {\n locales,\n defaultLocale,\n getT(locale: Locale): TFn {\n return createT(data, locale, fallbackChain, defaultNamespace);\n },\n getCatalog(locale: Locale, namespace?: Namespace): Bundle | null {\n return data[locale]?.[namespace ?? defaultNamespace] ?? null;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/virtual.ts","../src/integration.ts","../src/types.ts","../src/cdn.ts","../src/resolver.ts"],"sourcesContent":["/**\n * `@sonenta/astro` — official Astro integration for Sonenta i18n.\n *\n * v0.1 (standalone, pre-`@sonenta/i18n-core`): build-time CDN fetch + string\n * resolution + `{var}` interpolation + source-language fallback, with ZERO\n * client-side JS (pure SSG). The public API here is frozen forward-compatible:\n * the 0.2.0 core-backed release swaps the internals and adds surfaces / a11y /\n * CLDR plurals / variants ADDITIVELY, without changing anything below.\n *\n * Default export = the integration; named exports = the build-time runtime\n * helpers (also available standalone at `@sonenta/astro/runtime`).\n */\n\nimport { sonenta } from \"./integration\";\n\nexport { sonenta } from \"./integration\";\nexport default sonenta;\n\nexport {\n createSonentaI18n,\n createT,\n} from \"./resolver\";\n\nexport {\n buildBundleUrl,\n buildOverlayUrl,\n fetchBundle,\n fetchNamespace,\n fetchAll,\n fetchOverlay,\n fetchAllOverlays,\n resolveCdnBase,\n type Bundle,\n} from \"./cdn\";\n\nexport {\n DEFAULT_SURFACE_BREAKPOINTS,\n surfaceForWidth,\n} from \"./types\";\n\nexport type {\n Locale,\n Namespace,\n Vars,\n TFn,\n Surface,\n SurfaceBreakpoints,\n SonentaI18n,\n SonentaI18nOptions,\n} from \"./types\";\n","/**\n * Vite plugin that serves the `sonenta:i18n` virtual module. The integration\n * wires this into the consumer's Astro/Vite build; the generated module pulls\n * the configured bundles at build time (via `@sonenta/astro/runtime`) and\n * re-exports a ready `getT`.\n */\n\nimport type { Plugin } from \"vite\";\nimport type { SonentaI18nOptions } from \"./types\";\n\n/** Public virtual id and its Vite-internal resolved form. */\nconst VIRTUAL_ID = \"sonenta:i18n\";\nconst RESOLVED_ID = \"\\0sonenta:i18n\";\n\n/**\n * Options that can cross the build/runtime boundary: everything except the\n * non-serializable `fetchImpl` (build always uses the global `fetch`).\n */\nfunction serializableOptions(\n options: SonentaI18nOptions,\n): Omit<SonentaI18nOptions, \"fetchImpl\"> {\n const { fetchImpl: _omit, ...rest } = options;\n return rest;\n}\n\n/** The generated source of the `sonenta:i18n` module. */\nfunction moduleSource(options: SonentaI18nOptions): string {\n const opts = JSON.stringify(serializableOptions(options));\n return [\n `import { createSonentaI18n } from \"@sonenta/astro/runtime\";`,\n `const __i18n = await createSonentaI18n(${opts});`,\n `export const getT = (locale) => __i18n.getT(locale);`,\n `export const locales = __i18n.locales;`,\n `export const defaultLocale = __i18n.defaultLocale;`,\n `export const surfaces = __i18n.surfaces;`,\n `export const surfaceBreakpoints = __i18n.surfaceBreakpoints;`,\n `export const getSurfaces = (locale, key, vars) => __i18n.getSurfaces(locale, key, vars);`,\n `export const getCatalog = (locale, ns) => __i18n.getCatalog(locale, ns);`,\n `export default __i18n;`,\n ``,\n ].join(\"\\n\");\n}\n\n/** Build the Vite plugin that resolves and loads `sonenta:i18n`. */\nexport function sonentaI18nVitePlugin(options: SonentaI18nOptions): Plugin {\n return {\n name: \"sonenta:astro-i18n\",\n enforce: \"pre\",\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_ID;\n return null;\n },\n load(id) {\n if (id === RESOLVED_ID) return moduleSource(options);\n return null;\n },\n };\n}\n\nexport { VIRTUAL_ID, RESOLVED_ID };\n","/**\n * The Sonenta Astro integration — default export of `@sonenta/astro`.\n *\n * Usage (astro.config.mjs):\n *\n * import { defineConfig } from \"astro/config\";\n * import sonenta from \"@sonenta/astro\";\n *\n * export default defineConfig({\n * integrations: [\n * sonenta({\n * project: \"your-project-uuid\",\n * locales: [\"fr\", \"en\", \"es\"],\n * defaultLocale: \"fr\",\n * namespaces: [\"common\"],\n * }),\n * ],\n * });\n *\n * Then in any `.astro` page:\n *\n * ---\n * import { getT } from \"sonenta:i18n\";\n * const t = getT(Astro.currentLocale ?? \"fr\");\n * ---\n * <h1>{t(\"home.title\")}</h1>\n * <p>{t(\"home.greeting\", { name: \"Ada\" })}</p>\n */\n\nimport type { AstroIntegration } from \"astro\";\nimport { sonentaI18nVitePlugin } from \"./virtual\";\nimport type { SonentaI18nOptions } from \"./types\";\n\n/** Ambient types injected for the `sonenta:i18n` virtual module. */\nconst VIRTUAL_DTS = `declare module \"sonenta:i18n\" {\n import type {\n SonentaI18n, TFn, Locale, Namespace, Vars, Surface, SurfaceBreakpoints,\n } from \"@sonenta/astro/runtime\";\n /** Translation function bound to \\`locale\\` (\\`t(key)\\` + \\`t.surface(key, surface)\\`). */\n export const getT: (locale: Locale) => TFn;\n /** Locales fetched and frozen into the build. */\n export const locales: Locale[];\n /** Resolved source/default locale. */\n export const defaultLocale: Locale;\n /** Configured device surfaces (empty when surfaces are disabled). */\n export const surfaces: Surface[];\n /** Resolved surface breakpoint ladder. */\n export const surfaceBreakpoints: SurfaceBreakpoints;\n /** Resolve \\`key\\` for every configured surface at \\`locale\\`. */\n export const getSurfaces: (\n locale: Locale,\n key: string,\n vars?: Vars,\n ) => Record<Surface, string>;\n /** Raw fetched dictionary for a locale/namespace, or null when absent. */\n export const getCatalog: (\n locale: Locale,\n namespace?: Namespace,\n ) => Record<string, unknown> | null;\n const i18n: SonentaI18n;\n export default i18n;\n}\n`;\n\n/**\n * Create the Sonenta i18n integration. Bundles are fetched at build time and\n * exposed through the `sonenta:i18n` virtual module (zero client-side JS).\n */\nexport function sonenta(options: SonentaI18nOptions): AstroIntegration {\n if (!options?.project) {\n throw new Error(\"[@sonenta/astro] `project` (UUID) is required.\");\n }\n if (!options.locales?.length) {\n throw new Error(\"[@sonenta/astro] `locales` must be a non-empty array.\");\n }\n return {\n name: \"@sonenta/astro\",\n hooks: {\n \"astro:config:setup\": ({ updateConfig, logger }) => {\n if (options.fetchImpl) {\n logger.warn(\n \"`fetchImpl` is ignored by the integration; the build uses the global fetch. Use `@sonenta/astro/runtime` directly if you need a custom fetch.\",\n );\n }\n updateConfig({\n vite: { plugins: [sonentaI18nVitePlugin(options)] },\n });\n logger.info(\n `i18n wired for project ${options.project} (${options.locales.length} locales, build-time CDN fetch).`,\n );\n },\n \"astro:config:done\": ({ injectTypes }) => {\n injectTypes({ filename: \"sonenta-i18n.d.ts\", content: VIRTUAL_DTS });\n },\n },\n };\n}\n\nexport default sonenta;\n","/**\n * Public types for `@sonenta/astro` v0.1 (standalone, pre-core).\n *\n * FORWARD-COMPATIBILITY CONTRACT (frozen day-one — see CONTRACT.md): every\n * option key here is the SAME key the future `@sonenta/i18n-core`-backed\n * release will consume, so the later refactor (0.2.0) is a non-breaking,\n * additive internal swap. New capabilities (surfaces, a11y accessors, CLDR\n * plurals, variants) arrive ADDITIVELY; nothing here changes meaning.\n */\n\n/** A BCP-47 locale code, e.g. `\"fr\"`, `\"en\"`, `\"fr-CA\"`. */\nexport type Locale = string;\n\n/** A bundle namespace (the `{ns}.json` file on the CDN), e.g. `\"common\"`. */\nexport type Namespace = string;\n\n/** Interpolation variables for a `t()` call: `t(\"greeting\", { name: \"Ada\" })`. */\nexport type Vars = Record<string, string | number>;\n\n/**\n * A device surface — the second resolution dimension layered on top of the\n * locale chain (#911). The base bundle applies to every surface; a sparse\n * `{ns}.{surface}.json` overlay overrides individual keys for that surface.\n */\nexport type Surface = \"desktop\" | \"mobile\" | \"tablet\";\n\n/**\n * Min-width (px) thresholds mapping a viewport to a surface (mobile-first\n * ladder, mirrors `@sonenta/react-i18next` #913): width `< mobile` → mobile;\n * `< tablet` → tablet; otherwise desktop. Drives the `<SurfaceText>` media\n * queries (SSG renders every surface; CSS reveals the right one).\n */\nexport interface SurfaceBreakpoints {\n /** Upper bound (exclusive) of the `mobile` surface, px. Default 640. */\n mobile: number;\n /** Upper bound (exclusive) of the `tablet` surface, px. Default 1024. */\n tablet: number;\n}\n\n/**\n * A resolved, per-locale translation function. Calling `t(key, vars?)` returns\n * the surface-agnostic base value. `t.surface(key, surface, vars?)` returns the\n * value for a specific device surface (overlay ?? base).\n */\nexport interface TFn {\n (key: string, vars?: Vars): string;\n /** Resolve `key` for a specific device `surface` (overlay wins over base). */\n surface(key: string, surface: Surface, vars?: Vars): string;\n}\n\n/**\n * Configuration for the Sonenta Astro integration / runtime.\n *\n * Bundles are fetched at BUILD time from\n * `{cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.json`\n * (the canonical Sonenta CDN layout, identical to `@sonenta/react-i18next`).\n */\nexport interface SonentaI18nOptions {\n /** Project UUID from your Sonenta dashboard. Required. */\n project: string;\n\n /**\n * Released version slug or pinned content hash. Default `\"main\"`.\n * The CDN serves the latest release of this version under `/latest/`.\n */\n version?: string;\n\n /**\n * Locales to fetch and freeze into the static build. Required, non-empty.\n * A locale whose bundle 404s (e.g. a plan-limit-blocked language) is kept\n * as `null` and transparently falls back to the source language.\n */\n locales: Locale[];\n\n /**\n * Source / default locale. Default = `locales[0]`. Used as the implicit\n * final fallback target and as Astro's source language.\n */\n defaultLocale?: Locale;\n\n /**\n * Ordered fallback chain applied when a key is missing in the active\n * locale. Default = `[defaultLocale]`. v0.1 applies these locales in\n * order; BCP-47 variant→base inheritance (`fr-CA → fr`) is deliberately\n * left to the core-backed release.\n */\n fallbackLng?: Locale | Locale[];\n\n /**\n * Namespaces (bundle files) to fetch. Default `[\"common\"]`. The first\n * entry is the default namespace for un-prefixed keys; address others with\n * the i18next-style `\"ns:key\"` syntax.\n */\n namespaces?: Namespace[];\n\n /**\n * CDN host root for translation bundles, WITHOUT the `/p` segment.\n * Default `\"https://cdn.sonenta.com\"`. Overridable via the\n * `SONENTA_CDN_BASE` env var (also a bare host; for local dev point it at\n * your translation CDN). The `/p/{project}/{version}/latest/...` path is\n * appended by the loader.\n */\n cdnBase?: string;\n\n /**\n * API host root. Reserved for forward-compatibility (the core-backed\n * release uses it for the public language manifest and dev-mode runtime\n * fetch). Default `\"https://api.sonenta.dev\"`. Unused by v0.1's prod\n * build-time path.\n */\n apiBase?: string;\n\n /**\n * Device surfaces to fetch + expose (#911). When set (e.g.\n * `[\"desktop\", \"mobile\"]`), the build also pulls each sparse overlay\n * `{ns}.{surface}.json` and `getSurfaces` / `t.surface` / `<SurfaceText>`\n * resolve per-surface values. Omit to disable surfaces entirely (no overlay\n * fetch, identical to v0.1 behaviour).\n */\n surfaces?: Surface[];\n\n /**\n * Breakpoint ladder for `<SurfaceText>`'s responsive CSS. Default\n * `{ mobile: 640, tablet: 1024 }`. Resolution itself renders every surface;\n * these only decide which one CSS reveals at each viewport width.\n */\n surfaceBreakpoints?: SurfaceBreakpoints;\n\n /**\n * Injectable `fetch` implementation (testing / proxies / custom agents).\n * Defaults to the global `fetch`.\n */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * The resolved Sonenta i18n handle returned by `createSonentaI18n` and\n * exposed through the `sonenta:i18n` virtual module.\n */\nexport interface SonentaI18n {\n /** Build a translation function bound to `locale`. */\n getT(locale: Locale): TFn;\n /** The locales that were fetched (the configured `locales`). */\n readonly locales: Locale[];\n /** The resolved source/default locale. */\n readonly defaultLocale: Locale;\n /** The configured device surfaces (empty when surfaces are disabled). */\n readonly surfaces: Surface[];\n /** The resolved surface breakpoint ladder. */\n readonly surfaceBreakpoints: SurfaceBreakpoints;\n /**\n * Resolve `key` for every configured surface at `locale`, e.g.\n * `{ desktop: \"Commencer gratuitement\", mobile: \"Commencer\" }`. A surface\n * without an overlay for the key resolves to the base value. Empty when\n * surfaces are disabled.\n */\n getSurfaces(locale: Locale, key: string, vars?: Vars): Record<Surface, string>;\n /**\n * Raw fetched dictionary for `locale` / `namespace` (default namespace when\n * omitted), or `null` when that bundle was absent (404 / plan-limit).\n */\n getCatalog(locale: Locale, namespace?: Namespace): Record<string, unknown> | null;\n}\n\n/** Default CDN host (no `/p`). */\nexport const DEFAULT_CDN_BASE = \"https://cdn.sonenta.com\";\n/** Default API host. */\nexport const DEFAULT_API_BASE = \"https://api.sonenta.dev\";\n/** Default version slug. */\nexport const DEFAULT_VERSION = \"main\";\n/** Default namespace. */\nexport const DEFAULT_NAMESPACE = \"common\";\n/** Default surface breakpoint ladder (mirrors `@sonenta/react-i18next` #913). */\nexport const DEFAULT_SURFACE_BREAKPOINTS: SurfaceBreakpoints = {\n mobile: 640,\n tablet: 1024,\n};\n\n/**\n * Map a viewport width (px) to a {@link Surface} using `breakpoints`. Pure —\n * shared with the `<SurfaceText>` CSS generation so the runtime mapping and\n * the media queries agree. `< mobile` → mobile, `< tablet` → tablet, else\n * desktop.\n */\nexport function surfaceForWidth(\n width: number,\n breakpoints: SurfaceBreakpoints = DEFAULT_SURFACE_BREAKPOINTS,\n): Surface {\n if (width < breakpoints.mobile) return \"mobile\";\n if (width < breakpoints.tablet) return \"tablet\";\n return \"desktop\";\n}\n","/**\n * Build-time CDN loader for Sonenta translation bundles.\n *\n * Mirrors the canonical Sonenta CDN layout used by `@sonenta/react-i18next`:\n * `{cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.json`\n *\n * Pure data fetch — no DOM, no React, no runtime. Called during `astro build`\n * so the strings inline into the static HTML (zero client JS).\n */\n\nimport {\n DEFAULT_CDN_BASE,\n DEFAULT_VERSION,\n type Locale,\n type Namespace,\n type SonentaI18nOptions,\n type Surface,\n} from \"./types\";\n\n/** A fetched bundle (a flat or nested dictionary of message strings). */\nexport type Bundle = Record<string, unknown>;\n\n/** Strip trailing slashes so URL joins never double up. */\nfunction trimSlashes(s: string): string {\n return s.replace(/\\/+$/, \"\");\n}\n\n/**\n * Resolve the effective CDN host (no `/p`): explicit option wins, then the\n * `SONENTA_CDN_BASE` env var, then the production default.\n */\nexport function resolveCdnBase(opts: Pick<SonentaI18nOptions, \"cdnBase\">): string {\n const env =\n typeof process !== \"undefined\" ? process.env?.SONENTA_CDN_BASE : undefined;\n return trimSlashes(opts.cdnBase ?? env ?? DEFAULT_CDN_BASE);\n}\n\n/**\n * Build the bundle URL for one `(locale, namespace)` pair.\n * `{cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.json`\n */\nexport function buildBundleUrl(\n opts: Pick<SonentaI18nOptions, \"project\" | \"version\" | \"cdnBase\">,\n locale: Locale,\n namespace: Namespace,\n): string {\n const base = resolveCdnBase(opts);\n const version = opts.version ?? DEFAULT_VERSION;\n return `${base}/p/${opts.project}/${version}/latest/${locale}/${namespace}.json`;\n}\n\n/**\n * Build the sparse surface-overlay URL for one `(locale, namespace, surface)`,\n * mirroring `@sonenta/react-i18next` #913:\n * `{cdnBase}/p/{project}/{version}/latest/{locale}/{namespace}.{surface}.json`\n */\nexport function buildOverlayUrl(\n opts: Pick<SonentaI18nOptions, \"project\" | \"version\" | \"cdnBase\">,\n locale: Locale,\n namespace: Namespace,\n surface: Surface,\n): string {\n const base = resolveCdnBase(opts);\n const version = opts.version ?? DEFAULT_VERSION;\n return `${base}/p/${opts.project}/${version}/latest/${locale}/${namespace}.${surface}.json`;\n}\n\n/**\n * Fetch a single bundle. Resolves to `null` on 404 (locale absent from the\n * project — e.g. a plan-limit-blocked language) so callers fall back to the\n * source language. Any other non-OK status or transport error throws so a\n * misconfigured build fails loudly rather than silently shipping empty pages.\n */\nexport async function fetchBundle(\n opts: Pick<SonentaI18nOptions, \"project\" | \"version\" | \"cdnBase\" | \"fetchImpl\">,\n locale: Locale,\n namespace: Namespace,\n): Promise<Bundle | null> {\n const url = buildBundleUrl(opts, locale, namespace);\n const doFetch = opts.fetchImpl ?? fetch;\n let res: Response;\n try {\n res = await doFetch(url, { method: \"GET\" });\n } catch (e) {\n throw new Error(\n `[@sonenta/astro] CDN fetch failed for ${locale}/${namespace}: ${\n (e as Error).message\n }`,\n );\n }\n if (!res.ok) {\n if (res.status === 404) return null;\n throw new Error(`[@sonenta/astro] CDN ${res.status} on ${url}`);\n }\n return (await res.json()) as Bundle;\n}\n\n/**\n * Fetch one namespace across every locale, in parallel. Absent locales map to\n * `null`. Returns `Record<Locale, Bundle | null>`.\n */\nexport async function fetchNamespace(\n opts: Pick<\n SonentaI18nOptions,\n \"project\" | \"version\" | \"cdnBase\" | \"fetchImpl\"\n >,\n locales: Locale[],\n namespace: Namespace,\n): Promise<Record<Locale, Bundle | null>> {\n const entries = await Promise.all(\n locales.map(\n async (l) => [l, await fetchBundle(opts, l, namespace)] as const,\n ),\n );\n return Object.fromEntries(entries) as Record<Locale, Bundle | null>;\n}\n\n/**\n * Fetch every `(locale, namespace)` pair. Returns a nested map keyed by\n * locale then namespace: `data[locale][namespace] = Bundle | null`.\n */\nexport async function fetchAll(\n opts: Pick<\n SonentaI18nOptions,\n \"project\" | \"version\" | \"cdnBase\" | \"fetchImpl\"\n >,\n locales: Locale[],\n namespaces: Namespace[],\n): Promise<Record<Locale, Record<Namespace, Bundle | null>>> {\n const out: Record<Locale, Record<Namespace, Bundle | null>> = {};\n for (const l of locales) out[l] = {};\n await Promise.all(\n locales.flatMap((l) =>\n namespaces.map(async (ns) => {\n out[l]![ns] = await fetchBundle(opts, l, ns);\n }),\n ),\n );\n return out;\n}\n\n/**\n * Fetch a single sparse surface overlay. Unlike {@link fetchBundle}, an\n * overlay is OPTIONAL: any miss (404), non-OK status, or transport error\n * resolves to `null` (base-only) and NEVER throws — a missing/erroring overlay\n * must never break the build or downgrade the base render. Mirrors\n * `@sonenta/react-i18next` #913's `{}`-on-miss behaviour.\n */\nexport async function fetchOverlay(\n opts: Pick<SonentaI18nOptions, \"project\" | \"version\" | \"cdnBase\" | \"fetchImpl\">,\n locale: Locale,\n namespace: Namespace,\n surface: Surface,\n): Promise<Bundle | null> {\n const url = buildOverlayUrl(opts, locale, namespace, surface);\n const doFetch = opts.fetchImpl ?? fetch;\n try {\n const res = await doFetch(url, { method: \"GET\" });\n if (!res.ok) return null;\n const data = (await res.json()) as Bundle;\n return data && typeof data === \"object\" ? data : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch every `(locale, namespace, surface)` overlay. Returns a nested map:\n * `overlays[locale][namespace][surface] = Bundle | null`. Empty when\n * `surfaces` is empty.\n */\nexport async function fetchAllOverlays(\n opts: Pick<\n SonentaI18nOptions,\n \"project\" | \"version\" | \"cdnBase\" | \"fetchImpl\"\n >,\n locales: Locale[],\n namespaces: Namespace[],\n surfaces: Surface[],\n): Promise<Record<Locale, Record<Namespace, Record<Surface, Bundle | null>>>> {\n const out: Record<Locale, Record<Namespace, Record<Surface, Bundle | null>>> =\n {};\n for (const l of locales) {\n out[l] = {};\n for (const ns of namespaces) out[l]![ns] = {} as Record<Surface, Bundle | null>;\n }\n await Promise.all(\n locales.flatMap((l) =>\n namespaces.flatMap((ns) =>\n surfaces.map(async (s) => {\n out[l]![ns]![s] = await fetchOverlay(opts, l, ns, s);\n }),\n ),\n ),\n );\n return out;\n}\n","/**\n * Build-time translation resolver.\n *\n * v0.2 SCOPE: string resolution + `{var}` interpolation + source-language\n * fallback (v0.1) PLUS device-surface variants (#911) — a sparse\n * `{ns}.{surface}.json` overlay over the base bundle, overlay-wins-per-key,\n * resolved per locale. Still NO CLDR plurals and NO a11y surfaces: a `$value`\n * that is a plural dict (object) is treated as \"no string here\" (deferred to\n * the `@sonenta/i18n-core`-backed 0.3.0). The `$asset` half of an envelope is\n * ignored for text resolution. This keeps the surface semantics a faithful,\n * regression-locked subset of react-i18next #913 so the later core swap is\n * behaviour-preserving.\n */\n\nimport { fetchAll, fetchAllOverlays, type Bundle } from \"./cdn\";\nimport {\n DEFAULT_NAMESPACE,\n DEFAULT_SURFACE_BREAKPOINTS,\n type Locale,\n type Namespace,\n type SonentaI18n,\n type SonentaI18nOptions,\n type Surface,\n type SurfaceBreakpoints,\n type TFn,\n type Vars,\n} from \"./types\";\n\n/** Nested base data: `data[locale][namespace] = Bundle | null`. */\ntype BaseData = Record<Locale, Record<Namespace, Bundle | null>>;\n/** Nested overlays: `overlays[locale][namespace][surface] = Bundle | null`. */\ntype OverlayData = Record<\n Locale,\n Record<Namespace, Record<Surface, Bundle | null>>\n>;\n\n/** Normalize `fallbackLng` (scalar | array | undefined) into a locale list. */\nfunction normalizeFallback(\n fallbackLng: SonentaI18nOptions[\"fallbackLng\"],\n defaultLocale: Locale,\n): Locale[] {\n if (fallbackLng == null) return [defaultLocale];\n return Array.isArray(fallbackLng) ? fallbackLng : [fallbackLng];\n}\n\n/** Split an i18next-style `\"ns:key\"` into `[namespace, key]`. */\nfunction splitKey(key: string, defaultNamespace: Namespace): [Namespace, string] {\n const i = key.indexOf(\":\");\n if (i === -1) return [defaultNamespace, key];\n return [key.slice(0, i), key.slice(i + 1)];\n}\n\n/** Walk a dotted path (`\"home.title\"`) through a bundle; `undefined` on miss. */\nfunction walk(bundle: Bundle | null | undefined, path: string): unknown {\n if (!bundle) return undefined;\n let cursor: unknown = bundle;\n for (const part of path.split(\".\")) {\n if (cursor && typeof cursor === \"object\" && part in cursor) {\n cursor = (cursor as Record<string, unknown>)[part];\n } else {\n return undefined;\n }\n }\n return cursor;\n}\n\n/**\n * Strip the `{ \"$value\", \"$asset\" }` envelope (#911): a node owning `$value`\n * resolves to that `$value` (the `$asset` half is ignored for text). Plain\n * nodes pass through unchanged.\n */\nfunction unwrapValue(node: unknown): unknown {\n if (\n node &&\n typeof node === \"object\" &&\n Object.prototype.hasOwnProperty.call(node, \"$value\")\n ) {\n return (node as Record<string, unknown>).$value;\n }\n return node;\n}\n\n/** Walk + unwrap to a leaf string, or `undefined` if absent / not a string. */\nfunction resolveLeaf(bundle: Bundle | null | undefined, path: string): string | undefined {\n const v = unwrapValue(walk(bundle, path));\n return typeof v === \"string\" ? v : undefined;\n}\n\n/** Substitute `{var}` placeholders; unknown placeholders are left intact. */\nfunction interpolate(template: string, vars?: Vars): string {\n if (!vars) return template;\n return template.replace(/\\{(\\w+)\\}/g, (_, name: string) =>\n name in vars ? String(vars[name]) : `{${name}}`,\n );\n}\n\n/** De-duped resolution order: active locale first, then configured fallbacks. */\nfunction resolutionOrder(locale: Locale, fallbackChain: Locale[]): Locale[] {\n return [locale, ...fallbackChain.filter((l) => l !== locale)];\n}\n\n/**\n * Build a `t()` (with `.surface`) bound to `locale`. Base lookup: active\n * locale's namespace dict → each fallback locale's same dict → raw key.\n * Surface lookup: per locale, the overlay wins over base, then the same\n * locale-fallback walk (mirrors react-i18next #913 compose-then-fallback).\n */\nexport function createT(\n data: BaseData,\n overlays: OverlayData,\n locale: Locale,\n fallbackChain: Locale[],\n defaultNamespace: Namespace,\n): TFn {\n const order = resolutionOrder(locale, fallbackChain);\n\n const t = function t(key: string, vars?: Vars): string {\n const [ns, rest] = splitKey(key, defaultNamespace);\n for (const l of order) {\n const hit = resolveLeaf(data[l]?.[ns], rest);\n if (hit !== undefined) return interpolate(hit, vars);\n }\n return key;\n } as TFn;\n\n t.surface = function surface(key: string, surface: Surface, vars?: Vars): string {\n const [ns, rest] = splitKey(key, defaultNamespace);\n for (const l of order) {\n // Overlay wins over base WITHIN a locale; then fall to the next locale.\n const overlayHit = resolveLeaf(overlays[l]?.[ns]?.[surface], rest);\n if (overlayHit !== undefined) return interpolate(overlayHit, vars);\n const baseHit = resolveLeaf(data[l]?.[ns], rest);\n if (baseHit !== undefined) return interpolate(baseHit, vars);\n }\n return key;\n };\n\n return t;\n}\n\n/**\n * Fetch every configured bundle (and surface overlay) at build time and return\n * a resolved {@link SonentaI18n} handle. Call once (top-level `await`) and\n * reuse its `getT(locale)` / `getSurfaces(locale, key)` across pages.\n */\nexport async function createSonentaI18n(\n options: SonentaI18nOptions,\n): Promise<SonentaI18n> {\n if (!options.project) {\n throw new Error(\"[@sonenta/astro] `project` (UUID) is required.\");\n }\n if (!options.locales?.length) {\n throw new Error(\"[@sonenta/astro] `locales` must be a non-empty array.\");\n }\n const locales = options.locales;\n const defaultLocale = options.defaultLocale ?? locales[0]!;\n const namespaces =\n options.namespaces?.length ? options.namespaces : [DEFAULT_NAMESPACE];\n const defaultNamespace = namespaces[0]!;\n const fallbackChain = normalizeFallback(options.fallbackLng, defaultLocale);\n const surfaces = options.surfaces ?? [];\n const surfaceBreakpoints: SurfaceBreakpoints =\n options.surfaceBreakpoints ?? DEFAULT_SURFACE_BREAKPOINTS;\n\n const data = await fetchAll(options, locales, namespaces);\n const overlays: OverlayData = surfaces.length\n ? await fetchAllOverlays(options, locales, namespaces, surfaces)\n : ({} as OverlayData);\n\n return {\n locales,\n defaultLocale,\n surfaces,\n surfaceBreakpoints,\n getT(locale: Locale): TFn {\n return createT(data, overlays, locale, fallbackChain, defaultNamespace);\n },\n getSurfaces(locale: Locale, key: string, vars?: Vars): Record<Surface, string> {\n const t = createT(data, overlays, locale, fallbackChain, defaultNamespace);\n const out = {} as Record<Surface, string>;\n for (const s of surfaces) out[s] = t.surface(key, s, vars);\n return out;\n },\n getCatalog(locale: Locale, namespace?: Namespace): Bundle | null {\n return data[locale]?.[namespace ?? defaultNamespace] ?? null;\n },\n };\n}\n"],"mappings":"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","names":["t"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstroIntegration } from 'astro';
|
|
2
2
|
import { SonentaI18nOptions } from './runtime.cjs';
|
|
3
|
-
export { Bundle, Locale, Namespace, SonentaI18n, TFn, Vars, buildBundleUrl, createSonentaI18n, createT, fetchAll, fetchBundle, fetchNamespace, resolveCdnBase } from './runtime.cjs';
|
|
3
|
+
export { Bundle, DEFAULT_SURFACE_BREAKPOINTS, Locale, Namespace, SonentaI18n, Surface, SurfaceBreakpoints, TFn, Vars, buildBundleUrl, buildOverlayUrl, createSonentaI18n, createT, fetchAll, fetchAllOverlays, fetchBundle, fetchNamespace, fetchOverlay, resolveCdnBase, surfaceForWidth } from './runtime.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The Sonenta Astro integration — default export of `@sonenta/astro`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstroIntegration } from 'astro';
|
|
2
2
|
import { SonentaI18nOptions } from './runtime.js';
|
|
3
|
-
export { Bundle, Locale, Namespace, SonentaI18n, TFn, Vars, buildBundleUrl, createSonentaI18n, createT, fetchAll, fetchBundle, fetchNamespace, resolveCdnBase } from './runtime.js';
|
|
3
|
+
export { Bundle, DEFAULT_SURFACE_BREAKPOINTS, Locale, Namespace, SonentaI18n, Surface, SurfaceBreakpoints, TFn, Vars, buildBundleUrl, buildOverlayUrl, createSonentaI18n, createT, fetchAll, fetchAllOverlays, fetchBundle, fetchNamespace, fetchOverlay, resolveCdnBase, surfaceForWidth } from './runtime.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The Sonenta Astro integration — default export of `@sonenta/astro`.
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_SURFACE_BREAKPOINTS,
|
|
2
3
|
buildBundleUrl,
|
|
4
|
+
buildOverlayUrl,
|
|
3
5
|
createSonentaI18n,
|
|
4
6
|
createT,
|
|
5
7
|
fetchAll,
|
|
8
|
+
fetchAllOverlays,
|
|
6
9
|
fetchBundle,
|
|
7
10
|
fetchNamespace,
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
fetchOverlay,
|
|
12
|
+
resolveCdnBase,
|
|
13
|
+
surfaceForWidth
|
|
14
|
+
} from "./chunk-RG6KTECT.js";
|
|
10
15
|
|
|
11
16
|
// src/virtual.ts
|
|
12
17
|
var VIRTUAL_ID = "sonenta:i18n";
|
|
@@ -23,6 +28,9 @@ function moduleSource(options) {
|
|
|
23
28
|
`export const getT = (locale) => __i18n.getT(locale);`,
|
|
24
29
|
`export const locales = __i18n.locales;`,
|
|
25
30
|
`export const defaultLocale = __i18n.defaultLocale;`,
|
|
31
|
+
`export const surfaces = __i18n.surfaces;`,
|
|
32
|
+
`export const surfaceBreakpoints = __i18n.surfaceBreakpoints;`,
|
|
33
|
+
`export const getSurfaces = (locale, key, vars) => __i18n.getSurfaces(locale, key, vars);`,
|
|
26
34
|
`export const getCatalog = (locale, ns) => __i18n.getCatalog(locale, ns);`,
|
|
27
35
|
`export default __i18n;`,
|
|
28
36
|
``
|
|
@@ -45,13 +53,25 @@ function sonentaI18nVitePlugin(options) {
|
|
|
45
53
|
|
|
46
54
|
// src/integration.ts
|
|
47
55
|
var VIRTUAL_DTS = `declare module "sonenta:i18n" {
|
|
48
|
-
import type {
|
|
49
|
-
|
|
56
|
+
import type {
|
|
57
|
+
SonentaI18n, TFn, Locale, Namespace, Vars, Surface, SurfaceBreakpoints,
|
|
58
|
+
} from "@sonenta/astro/runtime";
|
|
59
|
+
/** Translation function bound to \`locale\` (\`t(key)\` + \`t.surface(key, surface)\`). */
|
|
50
60
|
export const getT: (locale: Locale) => TFn;
|
|
51
61
|
/** Locales fetched and frozen into the build. */
|
|
52
62
|
export const locales: Locale[];
|
|
53
63
|
/** Resolved source/default locale. */
|
|
54
64
|
export const defaultLocale: Locale;
|
|
65
|
+
/** Configured device surfaces (empty when surfaces are disabled). */
|
|
66
|
+
export const surfaces: Surface[];
|
|
67
|
+
/** Resolved surface breakpoint ladder. */
|
|
68
|
+
export const surfaceBreakpoints: SurfaceBreakpoints;
|
|
69
|
+
/** Resolve \`key\` for every configured surface at \`locale\`. */
|
|
70
|
+
export const getSurfaces: (
|
|
71
|
+
locale: Locale,
|
|
72
|
+
key: string,
|
|
73
|
+
vars?: Vars,
|
|
74
|
+
) => Record<Surface, string>;
|
|
55
75
|
/** Raw fetched dictionary for a locale/namespace, or null when absent. */
|
|
56
76
|
export const getCatalog: (
|
|
57
77
|
locale: Locale,
|
|
@@ -94,14 +114,19 @@ function sonenta(options) {
|
|
|
94
114
|
// src/index.ts
|
|
95
115
|
var index_default = sonenta;
|
|
96
116
|
export {
|
|
117
|
+
DEFAULT_SURFACE_BREAKPOINTS,
|
|
97
118
|
buildBundleUrl,
|
|
119
|
+
buildOverlayUrl,
|
|
98
120
|
createSonentaI18n,
|
|
99
121
|
createT,
|
|
100
122
|
index_default as default,
|
|
101
123
|
fetchAll,
|
|
124
|
+
fetchAllOverlays,
|
|
102
125
|
fetchBundle,
|
|
103
126
|
fetchNamespace,
|
|
127
|
+
fetchOverlay,
|
|
104
128
|
resolveCdnBase,
|
|
105
|
-
sonenta
|
|
129
|
+
sonenta,
|
|
130
|
+
surfaceForWidth
|
|
106
131
|
};
|
|
107
132
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/virtual.ts","../src/integration.ts","../src/index.ts"],"sourcesContent":["/**\n * Vite plugin that serves the `sonenta:i18n` virtual module. The integration\n * wires this into the consumer's Astro/Vite build; the generated module pulls\n * the configured bundles at build time (via `@sonenta/astro/runtime`) and\n * re-exports a ready `getT`.\n */\n\nimport type { Plugin } from \"vite\";\nimport type { SonentaI18nOptions } from \"./types\";\n\n/** Public virtual id and its Vite-internal resolved form. */\nconst VIRTUAL_ID = \"sonenta:i18n\";\nconst RESOLVED_ID = \"\\0sonenta:i18n\";\n\n/**\n * Options that can cross the build/runtime boundary: everything except the\n * non-serializable `fetchImpl` (build always uses the global `fetch`).\n */\nfunction serializableOptions(\n options: SonentaI18nOptions,\n): Omit<SonentaI18nOptions, \"fetchImpl\"> {\n const { fetchImpl: _omit, ...rest } = options;\n return rest;\n}\n\n/** The generated source of the `sonenta:i18n` module. */\nfunction moduleSource(options: SonentaI18nOptions): string {\n const opts = JSON.stringify(serializableOptions(options));\n return [\n `import { createSonentaI18n } from \"@sonenta/astro/runtime\";`,\n `const __i18n = await createSonentaI18n(${opts});`,\n `export const getT = (locale) => __i18n.getT(locale);`,\n `export const locales = __i18n.locales;`,\n `export const defaultLocale = __i18n.defaultLocale;`,\n `export const getCatalog = (locale, ns) => __i18n.getCatalog(locale, ns);`,\n `export default __i18n;`,\n ``,\n ].join(\"\\n\");\n}\n\n/** Build the Vite plugin that resolves and loads `sonenta:i18n`. */\nexport function sonentaI18nVitePlugin(options: SonentaI18nOptions): Plugin {\n return {\n name: \"sonenta:astro-i18n\",\n enforce: \"pre\",\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_ID;\n return null;\n },\n load(id) {\n if (id === RESOLVED_ID) return moduleSource(options);\n return null;\n },\n };\n}\n\nexport { VIRTUAL_ID, RESOLVED_ID };\n","/**\n * The Sonenta Astro integration — default export of `@sonenta/astro`.\n *\n * Usage (astro.config.mjs):\n *\n * import { defineConfig } from \"astro/config\";\n * import sonenta from \"@sonenta/astro\";\n *\n * export default defineConfig({\n * integrations: [\n * sonenta({\n * project: \"your-project-uuid\",\n * locales: [\"fr\", \"en\", \"es\"],\n * defaultLocale: \"fr\",\n * namespaces: [\"common\"],\n * }),\n * ],\n * });\n *\n * Then in any `.astro` page:\n *\n * ---\n * import { getT } from \"sonenta:i18n\";\n * const t = getT(Astro.currentLocale ?? \"fr\");\n * ---\n * <h1>{t(\"home.title\")}</h1>\n * <p>{t(\"home.greeting\", { name: \"Ada\" })}</p>\n */\n\nimport type { AstroIntegration } from \"astro\";\nimport { sonentaI18nVitePlugin } from \"./virtual\";\nimport type { SonentaI18nOptions } from \"./types\";\n\n/** Ambient types injected for the `sonenta:i18n` virtual module. */\nconst VIRTUAL_DTS = `declare module \"sonenta:i18n\" {\n import type {
|
|
1
|
+
{"version":3,"sources":["../src/virtual.ts","../src/integration.ts","../src/index.ts"],"sourcesContent":["/**\n * Vite plugin that serves the `sonenta:i18n` virtual module. The integration\n * wires this into the consumer's Astro/Vite build; the generated module pulls\n * the configured bundles at build time (via `@sonenta/astro/runtime`) and\n * re-exports a ready `getT`.\n */\n\nimport type { Plugin } from \"vite\";\nimport type { SonentaI18nOptions } from \"./types\";\n\n/** Public virtual id and its Vite-internal resolved form. */\nconst VIRTUAL_ID = \"sonenta:i18n\";\nconst RESOLVED_ID = \"\\0sonenta:i18n\";\n\n/**\n * Options that can cross the build/runtime boundary: everything except the\n * non-serializable `fetchImpl` (build always uses the global `fetch`).\n */\nfunction serializableOptions(\n options: SonentaI18nOptions,\n): Omit<SonentaI18nOptions, \"fetchImpl\"> {\n const { fetchImpl: _omit, ...rest } = options;\n return rest;\n}\n\n/** The generated source of the `sonenta:i18n` module. */\nfunction moduleSource(options: SonentaI18nOptions): string {\n const opts = JSON.stringify(serializableOptions(options));\n return [\n `import { createSonentaI18n } from \"@sonenta/astro/runtime\";`,\n `const __i18n = await createSonentaI18n(${opts});`,\n `export const getT = (locale) => __i18n.getT(locale);`,\n `export const locales = __i18n.locales;`,\n `export const defaultLocale = __i18n.defaultLocale;`,\n `export const surfaces = __i18n.surfaces;`,\n `export const surfaceBreakpoints = __i18n.surfaceBreakpoints;`,\n `export const getSurfaces = (locale, key, vars) => __i18n.getSurfaces(locale, key, vars);`,\n `export const getCatalog = (locale, ns) => __i18n.getCatalog(locale, ns);`,\n `export default __i18n;`,\n ``,\n ].join(\"\\n\");\n}\n\n/** Build the Vite plugin that resolves and loads `sonenta:i18n`. */\nexport function sonentaI18nVitePlugin(options: SonentaI18nOptions): Plugin {\n return {\n name: \"sonenta:astro-i18n\",\n enforce: \"pre\",\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_ID;\n return null;\n },\n load(id) {\n if (id === RESOLVED_ID) return moduleSource(options);\n return null;\n },\n };\n}\n\nexport { VIRTUAL_ID, RESOLVED_ID };\n","/**\n * The Sonenta Astro integration — default export of `@sonenta/astro`.\n *\n * Usage (astro.config.mjs):\n *\n * import { defineConfig } from \"astro/config\";\n * import sonenta from \"@sonenta/astro\";\n *\n * export default defineConfig({\n * integrations: [\n * sonenta({\n * project: \"your-project-uuid\",\n * locales: [\"fr\", \"en\", \"es\"],\n * defaultLocale: \"fr\",\n * namespaces: [\"common\"],\n * }),\n * ],\n * });\n *\n * Then in any `.astro` page:\n *\n * ---\n * import { getT } from \"sonenta:i18n\";\n * const t = getT(Astro.currentLocale ?? \"fr\");\n * ---\n * <h1>{t(\"home.title\")}</h1>\n * <p>{t(\"home.greeting\", { name: \"Ada\" })}</p>\n */\n\nimport type { AstroIntegration } from \"astro\";\nimport { sonentaI18nVitePlugin } from \"./virtual\";\nimport type { SonentaI18nOptions } from \"./types\";\n\n/** Ambient types injected for the `sonenta:i18n` virtual module. */\nconst VIRTUAL_DTS = `declare module \"sonenta:i18n\" {\n import type {\n SonentaI18n, TFn, Locale, Namespace, Vars, Surface, SurfaceBreakpoints,\n } from \"@sonenta/astro/runtime\";\n /** Translation function bound to \\`locale\\` (\\`t(key)\\` + \\`t.surface(key, surface)\\`). */\n export const getT: (locale: Locale) => TFn;\n /** Locales fetched and frozen into the build. */\n export const locales: Locale[];\n /** Resolved source/default locale. */\n export const defaultLocale: Locale;\n /** Configured device surfaces (empty when surfaces are disabled). */\n export const surfaces: Surface[];\n /** Resolved surface breakpoint ladder. */\n export const surfaceBreakpoints: SurfaceBreakpoints;\n /** Resolve \\`key\\` for every configured surface at \\`locale\\`. */\n export const getSurfaces: (\n locale: Locale,\n key: string,\n vars?: Vars,\n ) => Record<Surface, string>;\n /** Raw fetched dictionary for a locale/namespace, or null when absent. */\n export const getCatalog: (\n locale: Locale,\n namespace?: Namespace,\n ) => Record<string, unknown> | null;\n const i18n: SonentaI18n;\n export default i18n;\n}\n`;\n\n/**\n * Create the Sonenta i18n integration. Bundles are fetched at build time and\n * exposed through the `sonenta:i18n` virtual module (zero client-side JS).\n */\nexport function sonenta(options: SonentaI18nOptions): AstroIntegration {\n if (!options?.project) {\n throw new Error(\"[@sonenta/astro] `project` (UUID) is required.\");\n }\n if (!options.locales?.length) {\n throw new Error(\"[@sonenta/astro] `locales` must be a non-empty array.\");\n }\n return {\n name: \"@sonenta/astro\",\n hooks: {\n \"astro:config:setup\": ({ updateConfig, logger }) => {\n if (options.fetchImpl) {\n logger.warn(\n \"`fetchImpl` is ignored by the integration; the build uses the global fetch. Use `@sonenta/astro/runtime` directly if you need a custom fetch.\",\n );\n }\n updateConfig({\n vite: { plugins: [sonentaI18nVitePlugin(options)] },\n });\n logger.info(\n `i18n wired for project ${options.project} (${options.locales.length} locales, build-time CDN fetch).`,\n );\n },\n \"astro:config:done\": ({ injectTypes }) => {\n injectTypes({ filename: \"sonenta-i18n.d.ts\", content: VIRTUAL_DTS });\n },\n },\n };\n}\n\nexport default sonenta;\n","/**\n * `@sonenta/astro` — official Astro integration for Sonenta i18n.\n *\n * v0.1 (standalone, pre-`@sonenta/i18n-core`): build-time CDN fetch + string\n * resolution + `{var}` interpolation + source-language fallback, with ZERO\n * client-side JS (pure SSG). The public API here is frozen forward-compatible:\n * the 0.2.0 core-backed release swaps the internals and adds surfaces / a11y /\n * CLDR plurals / variants ADDITIVELY, without changing anything below.\n *\n * Default export = the integration; named exports = the build-time runtime\n * helpers (also available standalone at `@sonenta/astro/runtime`).\n */\n\nimport { sonenta } from \"./integration\";\n\nexport { sonenta } from \"./integration\";\nexport default sonenta;\n\nexport {\n createSonentaI18n,\n createT,\n} from \"./resolver\";\n\nexport {\n buildBundleUrl,\n buildOverlayUrl,\n fetchBundle,\n fetchNamespace,\n fetchAll,\n fetchOverlay,\n fetchAllOverlays,\n resolveCdnBase,\n type Bundle,\n} from \"./cdn\";\n\nexport {\n DEFAULT_SURFACE_BREAKPOINTS,\n surfaceForWidth,\n} from \"./types\";\n\nexport type {\n Locale,\n Namespace,\n Vars,\n TFn,\n Surface,\n SurfaceBreakpoints,\n SonentaI18n,\n SonentaI18nOptions,\n} from \"./types\";\n"],"mappings":";;;;;;;;;;;;;;;;AAWA,IAAM,aAAa;AACnB,IAAM,cAAc;AAMpB,SAAS,oBACP,SACuC;AACvC,QAAM,EAAE,WAAW,OAAO,GAAG,KAAK,IAAI;AACtC,SAAO;AACT;AAGA,SAAS,aAAa,SAAqC;AACzD,QAAM,OAAO,KAAK,UAAU,oBAAoB,OAAO,CAAC;AACxD,SAAO;AAAA,IACL;AAAA,IACA,0CAA0C,IAAI;AAAA,IAC9C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAGO,SAAS,sBAAsB,SAAqC;AACzE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU,IAAI;AACZ,UAAI,OAAO,WAAY,QAAO;AAC9B,aAAO;AAAA,IACT;AAAA,IACA,KAAK,IAAI;AACP,UAAI,OAAO,YAAa,QAAO,aAAa,OAAO;AACnD,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACvBA,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkCb,SAAS,QAAQ,SAA+C;AACrE,MAAI,CAAC,SAAS,SAAS;AACrB,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,MAAI,CAAC,QAAQ,SAAS,QAAQ;AAC5B,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACL,sBAAsB,CAAC,EAAE,cAAc,OAAO,MAAM;AAClD,YAAI,QAAQ,WAAW;AACrB,iBAAO;AAAA,YACL;AAAA,UACF;AAAA,QACF;AACA,qBAAa;AAAA,UACX,MAAM,EAAE,SAAS,CAAC,sBAAsB,OAAO,CAAC,EAAE;AAAA,QACpD,CAAC;AACD,eAAO;AAAA,UACL,0BAA0B,QAAQ,OAAO,KAAK,QAAQ,QAAQ,MAAM;AAAA,QACtE;AAAA,MACF;AAAA,MACA,qBAAqB,CAAC,EAAE,YAAY,MAAM;AACxC,oBAAY,EAAE,UAAU,qBAAqB,SAAS,YAAY,CAAC;AAAA,MACrE;AAAA,IACF;AAAA,EACF;AACF;;;AChFA,IAAO,gBAAQ;","names":[]}
|