@bagelink/blox 1.15.158 → 1.15.162

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.
@@ -25,6 +25,21 @@ const fs = require("node:fs/promises");
25
25
  const path = require("node:path");
26
26
  const process = require("node:process");
27
27
  const node_url = require("node:url");
28
+ function localizePath(slug, locale, defaultLocale) {
29
+ if (locale === defaultLocale) return slug === "" ? "/" : slug;
30
+ const s = slug === "" || slug === "/" ? "" : slug;
31
+ return `/${locale}${s}`;
32
+ }
33
+ function expandLocalePaths(paths, defaultLocale, supportedLocales) {
34
+ const locales = supportedLocales.length > 0 ? supportedLocales : [defaultLocale];
35
+ const out = [];
36
+ for (const slug of paths) {
37
+ for (const loc of locales) {
38
+ out.push(localizePath(slug, loc, defaultLocale));
39
+ }
40
+ }
41
+ return [...new Set(out)];
42
+ }
28
43
  async function fetchCmsSiteData(apiBase, websiteName) {
29
44
  const sitesRes = await fetch(`${apiBase}/blox/websites`);
30
45
  const sitesData = await sitesRes.json();
@@ -40,7 +55,10 @@ async function fetchCmsSiteData(apiBase, websiteName) {
40
55
  const pagesRes = await fetch(`${apiBase}/blox/websites/${site.id}/pages?locale=${website.default_locale || "en"}`);
41
56
  const pagesData = await pagesRes.json();
42
57
  const pages = Array.isArray(pagesData) ? pagesData : pagesData.data ?? [];
43
- const paths = await expandRoutes(apiBase, pages);
58
+ const basePaths = await expandRoutes(apiBase, pages);
59
+ const defaultLocale = website.default_locale || "en";
60
+ const supportedLocales = Array.isArray(website.supported_locales) && website.supported_locales.length > 0 ? website.supported_locales : [defaultLocale];
61
+ const paths = expandLocalePaths(basePaths, defaultLocale, supportedLocales);
44
62
  let redirects = [];
45
63
  try {
46
64
  const rRes = await fetch(`${apiBase}/blox/websites/${site.id}/redirects`);
@@ -156,8 +174,19 @@ async function polyfillBloxSsgGlobals(options) {
156
174
  g.window.location = _win.location;
157
175
  g.window.document = _win.document;
158
176
  }
177
+ function buildStructuredData(meta, locale) {
178
+ const perLocale = locale ? meta[`structured_data_org_${locale}`] : void 0;
179
+ const value = perLocale ?? meta.structured_data_org;
180
+ if (value == null || value === "") return "";
181
+ try {
182
+ const json = typeof value === "string" ? value : JSON.stringify(value);
183
+ return `<script type="application/ld+json">${json}${"<"}/script>`;
184
+ } catch {
185
+ return "";
186
+ }
187
+ }
159
188
  function buildPageHead(options) {
160
- const { url, resolvedData, website, stateScript } = options;
189
+ const { url, resolvedData, website, stateScript, locale } = options;
161
190
  const parts = [];
162
191
  const meta = (website == null ? void 0 : website.meta) ?? {};
163
192
  const page = resolvedData == null ? void 0 : resolvedData.page;
@@ -192,16 +221,20 @@ function buildPageHead(options) {
192
221
  if (ogImage) parts.push(`<meta name="twitter:image" content="${esc(ogImage)}">`);
193
222
  if (meta.twitter_site) parts.push(`<meta name="twitter:site" content="${esc(meta.twitter_site)}">`);
194
223
  const alternates = resolvedData == null ? void 0 : resolvedData.alternates;
224
+ const defaultLocale = (website == null ? void 0 : website.default_locale) ?? "en";
195
225
  if (alternates && canonicalBase) {
196
- for (const [locale, slug] of Object.entries(alternates)) {
197
- parts.push(`<link rel="alternate" hreflang="${esc(locale)}" href="${esc(canonicalBase + slug)}">`);
226
+ for (const [locale2, slug] of Object.entries(alternates)) {
227
+ const href = locale2 === defaultLocale ? slug : slug === "/" ? `/${locale2}` : `/${locale2}${slug}`;
228
+ parts.push(`<link rel="alternate" hreflang="${esc(locale2)}" href="${esc(canonicalBase + href)}">`);
198
229
  }
199
- const defaultSlug = alternates[(website == null ? void 0 : website.default_locale) ?? "en"] ?? url;
230
+ const defaultSlug = alternates[defaultLocale] ?? url;
200
231
  parts.push(`<link rel="alternate" hreflang="x-default" href="${esc(canonicalBase + defaultSlug)}">`);
201
232
  }
202
233
  if (meta.noindex) {
203
234
  parts.push('<meta name="robots" content="noindex, nofollow">');
204
235
  }
236
+ const structuredData = buildStructuredData(meta, locale || (website == null ? void 0 : website.default_locale));
237
+ if (structuredData) parts.push(structuredData);
205
238
  if (stateScript) {
206
239
  parts.push(stateScript);
207
240
  }
@@ -225,13 +258,6 @@ function buildSiteHead(website) {
225
258
  if (meta.verification_bing) {
226
259
  parts.push(`<meta name="msvalidate.01" content="${esc(meta.verification_bing)}">`);
227
260
  }
228
- if (meta.structured_data_org) {
229
- try {
230
- const json = typeof meta.structured_data_org === "string" ? meta.structured_data_org : JSON.stringify(meta.structured_data_org);
231
- parts.push(`<script type="application/ld+json">${json}${"<"}/script>`);
232
- } catch {
233
- }
234
- }
235
261
  const plausibleId = website.plausible_site_id;
236
262
  if (plausibleId) {
237
263
  const host = "https://analytics.bagel.to";
@@ -2,6 +2,21 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
4
  import { pathToFileURL } from "node:url";
5
+ function localizePath(slug, locale, defaultLocale) {
6
+ if (locale === defaultLocale) return slug === "" ? "/" : slug;
7
+ const s = slug === "" || slug === "/" ? "" : slug;
8
+ return `/${locale}${s}`;
9
+ }
10
+ function expandLocalePaths(paths, defaultLocale, supportedLocales) {
11
+ const locales = supportedLocales.length > 0 ? supportedLocales : [defaultLocale];
12
+ const out = [];
13
+ for (const slug of paths) {
14
+ for (const loc of locales) {
15
+ out.push(localizePath(slug, loc, defaultLocale));
16
+ }
17
+ }
18
+ return [...new Set(out)];
19
+ }
5
20
  async function fetchCmsSiteData(apiBase, websiteName) {
6
21
  const sitesRes = await fetch(`${apiBase}/blox/websites`);
7
22
  const sitesData = await sitesRes.json();
@@ -17,7 +32,10 @@ async function fetchCmsSiteData(apiBase, websiteName) {
17
32
  const pagesRes = await fetch(`${apiBase}/blox/websites/${site.id}/pages?locale=${website.default_locale || "en"}`);
18
33
  const pagesData = await pagesRes.json();
19
34
  const pages = Array.isArray(pagesData) ? pagesData : pagesData.data ?? [];
20
- const paths = await expandRoutes(apiBase, pages);
35
+ const basePaths = await expandRoutes(apiBase, pages);
36
+ const defaultLocale = website.default_locale || "en";
37
+ const supportedLocales = Array.isArray(website.supported_locales) && website.supported_locales.length > 0 ? website.supported_locales : [defaultLocale];
38
+ const paths = expandLocalePaths(basePaths, defaultLocale, supportedLocales);
21
39
  let redirects = [];
22
40
  try {
23
41
  const rRes = await fetch(`${apiBase}/blox/websites/${site.id}/redirects`);
@@ -133,8 +151,19 @@ async function polyfillBloxSsgGlobals(options) {
133
151
  g.window.location = _win.location;
134
152
  g.window.document = _win.document;
135
153
  }
154
+ function buildStructuredData(meta, locale) {
155
+ const perLocale = locale ? meta[`structured_data_org_${locale}`] : void 0;
156
+ const value = perLocale ?? meta.structured_data_org;
157
+ if (value == null || value === "") return "";
158
+ try {
159
+ const json = typeof value === "string" ? value : JSON.stringify(value);
160
+ return `<script type="application/ld+json">${json}${"<"}/script>`;
161
+ } catch {
162
+ return "";
163
+ }
164
+ }
136
165
  function buildPageHead(options) {
137
- const { url, resolvedData, website, stateScript } = options;
166
+ const { url, resolvedData, website, stateScript, locale } = options;
138
167
  const parts = [];
139
168
  const meta = (website == null ? void 0 : website.meta) ?? {};
140
169
  const page = resolvedData == null ? void 0 : resolvedData.page;
@@ -169,16 +198,20 @@ function buildPageHead(options) {
169
198
  if (ogImage) parts.push(`<meta name="twitter:image" content="${esc(ogImage)}">`);
170
199
  if (meta.twitter_site) parts.push(`<meta name="twitter:site" content="${esc(meta.twitter_site)}">`);
171
200
  const alternates = resolvedData == null ? void 0 : resolvedData.alternates;
201
+ const defaultLocale = (website == null ? void 0 : website.default_locale) ?? "en";
172
202
  if (alternates && canonicalBase) {
173
- for (const [locale, slug] of Object.entries(alternates)) {
174
- parts.push(`<link rel="alternate" hreflang="${esc(locale)}" href="${esc(canonicalBase + slug)}">`);
203
+ for (const [locale2, slug] of Object.entries(alternates)) {
204
+ const href = locale2 === defaultLocale ? slug : slug === "/" ? `/${locale2}` : `/${locale2}${slug}`;
205
+ parts.push(`<link rel="alternate" hreflang="${esc(locale2)}" href="${esc(canonicalBase + href)}">`);
175
206
  }
176
- const defaultSlug = alternates[(website == null ? void 0 : website.default_locale) ?? "en"] ?? url;
207
+ const defaultSlug = alternates[defaultLocale] ?? url;
177
208
  parts.push(`<link rel="alternate" hreflang="x-default" href="${esc(canonicalBase + defaultSlug)}">`);
178
209
  }
179
210
  if (meta.noindex) {
180
211
  parts.push('<meta name="robots" content="noindex, nofollow">');
181
212
  }
213
+ const structuredData = buildStructuredData(meta, locale || (website == null ? void 0 : website.default_locale));
214
+ if (structuredData) parts.push(structuredData);
182
215
  if (stateScript) {
183
216
  parts.push(stateScript);
184
217
  }
@@ -202,13 +235,6 @@ function buildSiteHead(website) {
202
235
  if (meta.verification_bing) {
203
236
  parts.push(`<meta name="msvalidate.01" content="${esc(meta.verification_bing)}">`);
204
237
  }
205
- if (meta.structured_data_org) {
206
- try {
207
- const json = typeof meta.structured_data_org === "string" ? meta.structured_data_org : JSON.stringify(meta.structured_data_org);
208
- parts.push(`<script type="application/ld+json">${json}${"<"}/script>`);
209
- } catch {
210
- }
211
- }
212
238
  const plausibleId = website.plausible_site_id;
213
239
  if (plausibleId) {
214
240
  const host = "https://analytics.bagel.to";
package/dist/ssg/cli.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  "use strict";
3
3
  const process = require("node:process");
4
- const prerender = require("../prerender-DOfER6E2.cjs");
4
+ const prerender = require("../prerender-BLECK5o5.cjs");
5
5
  async function resolveApiBase(mode) {
6
6
  if (process.env.BAGELINK_API_URL != null && process.env.BAGELINK_API_URL !== "") {
7
7
  return { apiBase: process.env.BAGELINK_API_URL };
package/dist/ssg/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import process from "node:process";
3
- import { p as polyfillBloxSsgGlobals, f as fetchCmsSiteData, a as prerender } from "../prerender-DidXkI2x.js";
3
+ import { p as polyfillBloxSsgGlobals, f as fetchCmsSiteData, a as prerender } from "../prerender-CzpA4sYd.js";
4
4
  async function resolveApiBase(mode) {
5
5
  if (process.env.BAGELINK_API_URL != null && process.env.BAGELINK_API_URL !== "") {
6
6
  return { apiBase: process.env.BAGELINK_API_URL };
@@ -13,6 +13,11 @@ export interface CmsSiteData {
13
13
  /** Datastore collections referenced by any page's data_bindings. */
14
14
  collections: CollectionRef[];
15
15
  }
16
+ /**
17
+ * Expand a list of default-locale paths into every supported locale.
18
+ * The default locale keeps its bare path; each other locale gets a `/xx` prefix.
19
+ */
20
+ export declare function expandLocalePaths(paths: string[], defaultLocale: string, supportedLocales: string[]): string[];
16
21
  /**
17
22
  * Fetch everything the SSG needs from the CMS in one go:
18
23
  * website settings, page routes, and redirects.
@@ -1 +1 @@
1
- {"version":3,"file":"cms-routes.d.ts","sourceRoot":"","sources":["../../src/ssg/cms-routes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAY1C,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,WAAW,CAAA;IACpB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,oEAAoE;IACpE,WAAW,EAAE,aAAa,EAAE,CAAA;CAC5B;AAMD;;;GAGG;AACH,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC,CAwCtB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC3C,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB"}
1
+ {"version":3,"file":"cms-routes.d.ts","sourceRoot":"","sources":["../../src/ssg/cms-routes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAY1C,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,WAAW,CAAA;IACpB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,oEAAoE;IACpE,WAAW,EAAE,aAAa,EAAE,CAAA;CAC5B;AAYD;;;GAGG;AACH,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,MAAM,EAAE,EACf,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,EAAE,GACxB,MAAM,EAAE,CASV;AAMD;;;GAGG;AACH,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC,CAiDtB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC3C,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"createSSREntry.d.ts","sourceRoot":"","sources":["../../src/ssg/createSSREntry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,SAAS,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAO9D,MAAM,WAAW,yBAAyB;IACzC,oCAAoC;IACpC,aAAa,EAAE,SAAS,CAAA;IACxB;;;;OAIG;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC9D;;;OAGG;IACH,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACpD,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC,OAAO,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAA;IAC5E;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CAClE;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,YAAY,CAoGnF"}
1
+ {"version":3,"file":"createSSREntry.d.ts","sourceRoot":"","sources":["../../src/ssg/createSSREntry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,SAAS,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAO9D,MAAM,WAAW,yBAAyB;IACzC,oCAAoC;IACpC,aAAa,EAAE,SAAS,CAAA;IACxB;;;;OAIG;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC9D;;;OAGG;IACH,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACpD,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC,OAAO,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAA;IAC5E;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CAClE;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,YAAY,CA6HnF"}
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
- const prerender = require("../prerender-DOfER6E2.cjs");
25
+ const prerender = require("../prerender-BLECK5o5.cjs");
26
26
  const ssg_client = require("./client.cjs");
27
27
  const pinia = require("pinia");
28
28
  const vue = require("vue");
@@ -34,6 +34,8 @@ async function renderBloxSsgPage(options) {
34
34
  renderToString,
35
35
  createAppForUrl,
36
36
  stateWindowKey = ssg_client.BLOX_STATE_WINDOW_KEY,
37
+ stateKey = url,
38
+ locale,
37
39
  website = null,
38
40
  websiteId = "",
39
41
  collections
@@ -41,23 +43,24 @@ async function renderBloxSsgPage(options) {
41
43
  const g = globalThis;
42
44
  const prevState = g[stateWindowKey];
43
45
  if (resolvedData != null) {
44
- g[stateWindowKey] = { [url]: resolvedData };
46
+ g[stateWindowKey] = { [stateKey]: resolvedData };
45
47
  }
46
48
  try {
47
49
  const { app, router } = createAppForUrl(url);
48
50
  await router.push(url);
49
51
  await router.isReady();
50
52
  const html = await renderToString(app);
51
- const stateScript = resolvedData != null ? `<script>window[${JSON.stringify(stateWindowKey)}]=${JSON.stringify({ [url]: resolvedData })};${"<"}/script>` : "";
53
+ const stateScript = resolvedData != null ? `<script>window[${JSON.stringify(stateWindowKey)}]=${JSON.stringify({ [stateKey]: resolvedData })};${"<"}/script>` : "";
52
54
  const collectionsScript = collections && Object.keys(collections).length > 0 ? `<script>window[${JSON.stringify(ssg_client.BLOX_COLLECTIONS_WINDOW_KEY)}]=${JSON.stringify(collections)};${"<"}/script>` : "";
53
55
  const websiteIdScript = websiteId ? `<script>window[${JSON.stringify(ssg_client.BLOX_WEBSITE_ID_WINDOW_KEY)}]=${JSON.stringify(websiteId)};${"<"}/script>` : "";
54
56
  const head = prerender.buildPageHead({
55
57
  url,
56
58
  resolvedData,
57
59
  website,
60
+ locale,
58
61
  stateScript: [stateScript, collectionsScript, websiteIdScript].filter(Boolean).join("\n")
59
62
  });
60
- const lang = (website == null ? void 0 : website.default_locale) || "en";
63
+ const lang = locale || (website == null ? void 0 : website.default_locale) || "en";
61
64
  const htmlAttrs = `lang="${lang}"`;
62
65
  return { html, head, htmlAttrs };
63
66
  } finally {
@@ -93,11 +96,20 @@ function createBloxSSREntry(options) {
93
96
  _websiteId = site.id;
94
97
  return _websiteId;
95
98
  }
96
- async function fetchResolvedPage(urlPath) {
99
+ function detectLocale(urlPath) {
100
+ const segments = urlPath.replace(/^\//, "").split("/");
101
+ const first = segments[0];
102
+ if (first && first !== locale && supportedLocales.includes(first)) {
103
+ const rest = `/${segments.slice(1).join("/")}`;
104
+ return { locale: first, slug: rest === "/" ? "/" : rest.replace(/\/$/, "") || "/" };
105
+ }
106
+ return { locale, slug: urlPath || "/" };
107
+ }
108
+ async function fetchResolvedPage(slug, pageLocale) {
97
109
  const websiteId = await getWebsiteId();
98
- const url = `${apiBase}/blox/websites/${websiteId}/resolve-path?path=${encodeURIComponent(urlPath)}&locale=${locale}`;
110
+ const url = `${apiBase}/blox/websites/${websiteId}/resolve-path?path=${encodeURIComponent(slug)}&locale=${pageLocale}`;
99
111
  const res = await fetch(url);
100
- if (!res.ok) throw new Error(`resolve-path ${res.status}: ${urlPath}`);
112
+ if (!res.ok) throw new Error(`resolve-path ${res.status}: ${slug}`);
101
113
  return res.json();
102
114
  }
103
115
  function buildApp(url) {
@@ -132,15 +144,20 @@ function createBloxSSREntry(options) {
132
144
  return { app, router };
133
145
  }
134
146
  async function render(url, ctx) {
147
+ const { locale: pageLocale, slug } = detectLocale(url);
135
148
  let resolvedData = null;
136
149
  try {
137
- resolvedData = await fetchResolvedPage(url);
150
+ resolvedData = await fetchResolvedPage(slug, pageLocale);
138
151
  } catch (e) {
139
152
  console.warn(`[prerender] Could not resolve: ${url} — ${e.message}`);
140
153
  }
141
154
  const { renderToString } = await import("vue/server-renderer");
142
155
  return renderBloxSsgPage({
143
156
  url,
157
+ // `CmsPageView` hydrates by the *slug* (locale prefix stripped), so the
158
+ // embedded state must be keyed by slug, not the prefixed URL.
159
+ stateKey: slug,
160
+ locale: pageLocale,
144
161
  resolvedData,
145
162
  renderToString,
146
163
  createAppForUrl: buildApp,
@@ -1,5 +1,5 @@
1
- import { b as buildPageHead } from "../prerender-DidXkI2x.js";
2
- import { c, d, f, g, e, h, i, p, a } from "../prerender-DidXkI2x.js";
1
+ import { b as buildPageHead } from "../prerender-CzpA4sYd.js";
2
+ import { c, d, f, g, e, h, i, p, a } from "../prerender-CzpA4sYd.js";
3
3
  import { BLOX_STATE_WINDOW_KEY, BLOX_COLLECTIONS_WINDOW_KEY, BLOX_WEBSITE_ID_WINDOW_KEY } from "./client.mjs";
4
4
  import { getCollectionCache, getEmbeddedWebsiteId, installBloxStateCache, installCollectionCache } from "./client.mjs";
5
5
  import { createPinia } from "pinia";
@@ -12,6 +12,8 @@ async function renderBloxSsgPage(options) {
12
12
  renderToString,
13
13
  createAppForUrl,
14
14
  stateWindowKey = BLOX_STATE_WINDOW_KEY,
15
+ stateKey = url,
16
+ locale,
15
17
  website = null,
16
18
  websiteId = "",
17
19
  collections
@@ -19,23 +21,24 @@ async function renderBloxSsgPage(options) {
19
21
  const g2 = globalThis;
20
22
  const prevState = g2[stateWindowKey];
21
23
  if (resolvedData != null) {
22
- g2[stateWindowKey] = { [url]: resolvedData };
24
+ g2[stateWindowKey] = { [stateKey]: resolvedData };
23
25
  }
24
26
  try {
25
27
  const { app, router } = createAppForUrl(url);
26
28
  await router.push(url);
27
29
  await router.isReady();
28
30
  const html = await renderToString(app);
29
- const stateScript = resolvedData != null ? `<script>window[${JSON.stringify(stateWindowKey)}]=${JSON.stringify({ [url]: resolvedData })};${"<"}/script>` : "";
31
+ const stateScript = resolvedData != null ? `<script>window[${JSON.stringify(stateWindowKey)}]=${JSON.stringify({ [stateKey]: resolvedData })};${"<"}/script>` : "";
30
32
  const collectionsScript = collections && Object.keys(collections).length > 0 ? `<script>window[${JSON.stringify(BLOX_COLLECTIONS_WINDOW_KEY)}]=${JSON.stringify(collections)};${"<"}/script>` : "";
31
33
  const websiteIdScript = websiteId ? `<script>window[${JSON.stringify(BLOX_WEBSITE_ID_WINDOW_KEY)}]=${JSON.stringify(websiteId)};${"<"}/script>` : "";
32
34
  const head = buildPageHead({
33
35
  url,
34
36
  resolvedData,
35
37
  website,
38
+ locale,
36
39
  stateScript: [stateScript, collectionsScript, websiteIdScript].filter(Boolean).join("\n")
37
40
  });
38
- const lang = (website == null ? void 0 : website.default_locale) || "en";
41
+ const lang = locale || (website == null ? void 0 : website.default_locale) || "en";
39
42
  const htmlAttrs = `lang="${lang}"`;
40
43
  return { html, head, htmlAttrs };
41
44
  } finally {
@@ -71,11 +74,20 @@ function createBloxSSREntry(options) {
71
74
  _websiteId = site.id;
72
75
  return _websiteId;
73
76
  }
74
- async function fetchResolvedPage(urlPath) {
77
+ function detectLocale(urlPath) {
78
+ const segments = urlPath.replace(/^\//, "").split("/");
79
+ const first = segments[0];
80
+ if (first && first !== locale && supportedLocales.includes(first)) {
81
+ const rest = `/${segments.slice(1).join("/")}`;
82
+ return { locale: first, slug: rest === "/" ? "/" : rest.replace(/\/$/, "") || "/" };
83
+ }
84
+ return { locale, slug: urlPath || "/" };
85
+ }
86
+ async function fetchResolvedPage(slug, pageLocale) {
75
87
  const websiteId = await getWebsiteId();
76
- const url = `${apiBase}/blox/websites/${websiteId}/resolve-path?path=${encodeURIComponent(urlPath)}&locale=${locale}`;
88
+ const url = `${apiBase}/blox/websites/${websiteId}/resolve-path?path=${encodeURIComponent(slug)}&locale=${pageLocale}`;
77
89
  const res = await fetch(url);
78
- if (!res.ok) throw new Error(`resolve-path ${res.status}: ${urlPath}`);
90
+ if (!res.ok) throw new Error(`resolve-path ${res.status}: ${slug}`);
79
91
  return res.json();
80
92
  }
81
93
  function buildApp(url) {
@@ -110,15 +122,20 @@ function createBloxSSREntry(options) {
110
122
  return { app, router };
111
123
  }
112
124
  async function render(url, ctx) {
125
+ const { locale: pageLocale, slug } = detectLocale(url);
113
126
  let resolvedData = null;
114
127
  try {
115
- resolvedData = await fetchResolvedPage(url);
128
+ resolvedData = await fetchResolvedPage(slug, pageLocale);
116
129
  } catch (e2) {
117
130
  console.warn(`[prerender] Could not resolve: ${url} — ${e2.message}`);
118
131
  }
119
132
  const { renderToString } = await import("vue/server-renderer");
120
133
  return renderBloxSsgPage({
121
134
  url,
135
+ // `CmsPageView` hydrates by the *slug* (locale prefix stripped), so the
136
+ // embedded state must be keyed by slug, not the prefixed URL.
137
+ stateKey: slug,
138
+ locale: pageLocale,
122
139
  resolvedData,
123
140
  renderToString,
124
141
  createAppForUrl: buildApp,
@@ -24,6 +24,14 @@ export declare function renderBloxSsgPage<TApp>(options: {
24
24
  router: BloxSsgRouterLike;
25
25
  };
26
26
  stateWindowKey?: string;
27
+ /**
28
+ * Key under which the resolved data is embedded in `__BLOX_STATE__`.
29
+ * Must match what the client detects (the default-locale *slug*, with any
30
+ * `/xx` locale prefix stripped). Defaults to `url`.
31
+ */
32
+ stateKey?: string;
33
+ /** Active render locale — sets `<html lang>`. Defaults to website default. */
34
+ locale?: string;
27
35
  /** Website settings for SEO tags. If omitted, only state script is emitted. */
28
36
  website?: WebsiteRead | null;
29
37
  /** Resolved website ID — embedded so client skips GET /blox/websites. */
@@ -1 +1 @@
1
- {"version":3,"file":"render-resolved-page.d.ts","sourceRoot":"","sources":["../../src/ssg/render-resolved-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAK/C,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE;IACtD,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,OAAO,GAAG,IAAI,CAAA;IAC5B,cAAc,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9C,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,GAAG,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,iBAAiB,CAAA;KAAE,CAAA;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;CACvC,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA2D/D"}
1
+ {"version":3,"file":"render-resolved-page.d.ts","sourceRoot":"","sources":["../../src/ssg/render-resolved-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAK/C,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE;IACtD,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,OAAO,GAAG,IAAI,CAAA;IAC5B,cAAc,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9C,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,GAAG,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,iBAAiB,CAAA;KAAE,CAAA;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+EAA+E;IAC/E,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;CACvC,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8D/D"}
package/dist/ssg/seo.d.ts CHANGED
@@ -20,9 +20,20 @@ export interface WebsiteMeta {
20
20
  noindex?: boolean;
21
21
  verification_google?: string;
22
22
  verification_bing?: string;
23
+ /**
24
+ * JSON-LD Organization/structured data. Site-wide default.
25
+ * Per-locale overrides can be provided as `structured_data_org_<locale>`
26
+ * (e.g. `structured_data_org_es`) and take precedence for that locale.
27
+ */
23
28
  structured_data_org?: unknown;
24
29
  [key: string]: unknown;
25
30
  }
31
+ /**
32
+ * Resolve the JSON-LD structured data for a given locale.
33
+ * Prefers `structured_data_org_<locale>` then falls back to the site-wide
34
+ * `structured_data_org`. Returns a serialized `<script>` tag or ''.
35
+ */
36
+ export declare function buildStructuredData(meta: WebsiteMeta, locale?: string): string;
26
37
  export interface RedirectEntry {
27
38
  from_path: string;
28
39
  to_path: string;
@@ -39,6 +50,8 @@ export declare function buildPageHead(options: {
39
50
  resolvedData: SeoPageData | null;
40
51
  website: WebsiteRead | null;
41
52
  stateScript?: string;
53
+ /** Active render locale — selects per-locale structured data. */
54
+ locale?: string;
42
55
  }): string;
43
56
  /**
44
57
  * Build site-wide `<head>` tags: verification, structured data, analytics, favicon.
@@ -1 +1 @@
1
- {"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/ssg/seo.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAMzD,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,QAAQ,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA;CACzD;AAED,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;CACnB;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,WAAW,GAAG,IAAI,CAAA;IAChC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG,MAAM,CAiFT;AAMD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,MAAM,CAiDjE;AAMD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC3C,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,MAAM,CAmBT;AAMD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB,GAAG,MAAM,CAmBT;AAMD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,CAK3E;AAMD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1D,MAAM,CAwBR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG;IAC9D,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,OAAO,CAAA;CACnB,CAMA"}
1
+ {"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/ssg/seo.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/blox",
3
3
  "type": "module",
4
- "version": "1.15.158",
4
+ "version": "1.15.162",
5
5
  "description": "Blox page builder library for drag-and-drop page building and static data management",
6
6
  "author": {
7
7
  "name": "Bagel Studio",