@weaverse/next 0.1.0-alpha.7 → 0.1.0-alpha.8

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/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { buildWeaverseNextRequestInfo, generateDataFromSchema } from './chunk-6PMZ77RA.mjs';
2
2
  export { buildWeaverseNextRequestInfo, formatWeaverseNextSeoMetadata, getWeaverseNextSeoMetadata, runWeaverseComponentLoaders } from './chunk-6PMZ77RA.mjs';
3
- import { WeaverseRoot, Weaverse, WeaverseItemStore } from '@weaverse/react';
3
+ import { WeaverseRoot, Weaverse, WeaverseItemStore, useSafeExternalStore } from '@weaverse/react';
4
4
  export { isBrowser, isIframe, useChildInstances, useItemInstance, useParentInstance, useWeaverse } from '@weaverse/react';
5
5
  import { createSchema } from '@weaverse/schema';
6
6
  export { createSchema } from '@weaverse/schema';
@@ -9,12 +9,47 @@ import { createContext, memo, useContext, useMemo, useRef, useEffect, useSyncExt
9
9
  import { useRouter } from 'next/navigation';
10
10
 
11
11
  var WeaverseNextItem = class extends WeaverseItemStore {
12
+ /**
13
+ * Cached merged snapshot (base `_store` + translations). Reset whenever the
14
+ * `_store` ref or the item's translation entry ref changes, so
15
+ * `useSyncExternalStore` sees a stable object between unrelated re-renders.
16
+ */
17
+ _cachedSnapshot = null;
18
+ _snapshotStoreRef = null;
19
+ _snapshotTranslationRef = null;
12
20
  constructor(initialData, weaverse) {
13
21
  super(initialData, weaverse);
14
22
  let { data, ...rest } = initialData;
15
23
  let schemaData = generateDataFromSchema(this.Element?.schema);
16
24
  Object.assign(this._store, schemaData, data, rest);
17
25
  }
26
+ /**
27
+ * Overlay item-level translations onto the base store when the runtime is in
28
+ * translation mode. Mirrors Hydrogen's `WeaverseHydrogenItem.getSnapShot()`:
29
+ * memoized by `_store` ref + translation entry ref so the returned object is
30
+ * referentially stable, and a fast path that returns the base store untouched
31
+ * when the item has no translations.
32
+ */
33
+ getSnapShot = () => {
34
+ let base = this._store;
35
+ let translations = this.weaverse.translationMap[this._id];
36
+ if (!translations) {
37
+ return base;
38
+ }
39
+ if (this._cachedSnapshot && this._snapshotStoreRef === base && this._snapshotTranslationRef === translations) {
40
+ return this._cachedSnapshot;
41
+ }
42
+ let merged = { ...base };
43
+ for (let [key, entry] of Object.entries(translations)) {
44
+ if (entry.translatedValue !== void 0) {
45
+ merged[key] = entry.translatedValue;
46
+ }
47
+ }
48
+ this._cachedSnapshot = merged;
49
+ this._snapshotStoreRef = base;
50
+ this._snapshotTranslationRef = translations;
51
+ return merged;
52
+ };
18
53
  };
19
54
  function ensureNextItemConstructor() {
20
55
  if (!Weaverse.ItemConstructor || Weaverse.ItemConstructor === WeaverseItemStore || Weaverse.ItemConstructor === WeaverseNextItem) {
@@ -139,9 +174,136 @@ function createWeaverseNextThemeSettingsStore(options = {}) {
139
174
  };
140
175
  return store;
141
176
  }
177
+ function getNestedKey(obj, path, fallback) {
178
+ let keys = path.split(".");
179
+ let current = obj;
180
+ for (let key of keys) {
181
+ if (current == null || typeof current !== "object") {
182
+ return fallback;
183
+ }
184
+ current = current[key];
185
+ }
186
+ if (typeof current === "string") {
187
+ return current;
188
+ }
189
+ return fallback;
190
+ }
191
+ function interpolate(template, variables) {
192
+ if (!variables) {
193
+ return template;
194
+ }
195
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
196
+ let value = variables[key];
197
+ return value === void 0 ? match : String(value);
198
+ });
199
+ }
200
+ var TranslationContext = createContext(null);
201
+ TranslationContext.displayName = "WeaverseNextTranslationContext";
202
+ function createTranslate(sources) {
203
+ let { staticContent, merchantOverrides, designOverrides, externalT } = sources;
204
+ return (key, variables) => {
205
+ if (externalT) {
206
+ let external = externalT(key, variables);
207
+ if (external && external !== key) {
208
+ return external;
209
+ }
210
+ }
211
+ let design = designOverrides && Object.hasOwn(designOverrides, key) ? designOverrides[key] : void 0;
212
+ let override = merchantOverrides ? getNestedKey(merchantOverrides, key) : void 0;
213
+ let staticValue = getNestedKey(staticContent, key);
214
+ let raw = design ?? override ?? staticValue ?? key;
215
+ return interpolate(raw, variables);
216
+ };
217
+ }
218
+ var EMPTY = {};
219
+ var NOOP_SUBSCRIBE = () => () => {
220
+ };
221
+ var NOOP_SNAPSHOT = () => EMPTY;
222
+ function TranslationProvider({
223
+ staticContent,
224
+ merchantOverrides,
225
+ t: externalT,
226
+ translationStore,
227
+ themeTextStore,
228
+ children
229
+ }) {
230
+ let store = translationStore ?? themeTextStore ?? null;
231
+ let designOverrides = useSafeExternalStore(
232
+ store?.subscribe ?? NOOP_SUBSCRIBE,
233
+ store?.getSnapshot ?? NOOP_SNAPSHOT,
234
+ store?.getServerSnapshot ?? NOOP_SNAPSHOT
235
+ );
236
+ let value = useMemo(
237
+ () => ({
238
+ t: createTranslate({
239
+ staticContent,
240
+ merchantOverrides,
241
+ designOverrides,
242
+ externalT
243
+ }),
244
+ translationStore: store,
245
+ // Deprecated alias — same instance, kept for backward compatibility.
246
+ themeTextStore: store,
247
+ merchantOverrides: merchantOverrides ?? null
248
+ }),
249
+ [staticContent, merchantOverrides, designOverrides, externalT, store]
250
+ );
251
+ return /* @__PURE__ */ jsx(TranslationContext.Provider, { value, children });
252
+ }
253
+ function useTranslation() {
254
+ let ctx = useContext(TranslationContext);
255
+ if (!ctx) {
256
+ throw new Error(
257
+ "useTranslation must be used within <TranslationProvider>. Wrap your app with <WeaverseNextRootProvider>."
258
+ );
259
+ }
260
+ return ctx;
261
+ }
262
+ var ThemeTextProvider = TranslationProvider;
263
+ var useThemeText = useTranslation;
264
+
265
+ // src/translation-store.ts
266
+ var TranslationStore = class {
267
+ overrides = {};
268
+ listeners = /* @__PURE__ */ new Set();
269
+ setOverrides = (overrides) => {
270
+ this.overrides = { ...overrides };
271
+ this.emit();
272
+ };
273
+ updateOverrides = (newOverrides) => {
274
+ this.overrides = { ...this.overrides, ...newOverrides };
275
+ this.emit();
276
+ };
277
+ getSnapshot = () => this.overrides;
278
+ getServerSnapshot = () => this.overrides;
279
+ subscribe = (callback) => {
280
+ this.listeners.add(callback);
281
+ return () => {
282
+ this.listeners.delete(callback);
283
+ };
284
+ };
285
+ emit = () => {
286
+ for (const listener of Array.from(this.listeners)) {
287
+ try {
288
+ listener();
289
+ } catch (error) {
290
+ console.error("TranslationStore: Listener error", error);
291
+ }
292
+ }
293
+ };
294
+ };
295
+ var ThemeTextStore = TranslationStore;
142
296
  var WeaverseNextRootContext = createContext(null);
143
297
  function WeaverseNextRootProvider(props) {
144
- let { children, initialThemeSettings, themeSchema, publicEnv } = props;
298
+ let {
299
+ children,
300
+ initialThemeSettings,
301
+ merchantOverrides,
302
+ staticContent,
303
+ t,
304
+ themeSchema,
305
+ publicEnv
306
+ } = props;
145
307
  let storeRef = useRef(null);
146
308
  if (!storeRef.current) {
147
309
  storeRef.current = createWeaverseNextThemeSettingsStore({
@@ -151,6 +313,11 @@ function WeaverseNextRootProvider(props) {
151
313
  });
152
314
  }
153
315
  let themeSettingsStore = storeRef.current;
316
+ let translationStoreRef = useRef(null);
317
+ if (!translationStoreRef.current) {
318
+ translationStoreRef.current = new TranslationStore();
319
+ }
320
+ let translationStore = translationStoreRef.current;
154
321
  let appliedThemeSettingsRef = useRef(initialThemeSettings);
155
322
  useEffect(() => {
156
323
  if (initialThemeSettings && appliedThemeSettingsRef.current !== initialThemeSettings) {
@@ -158,25 +325,59 @@ function WeaverseNextRootProvider(props) {
158
325
  appliedThemeSettingsRef.current = initialThemeSettings;
159
326
  }
160
327
  }, [initialThemeSettings, themeSettingsStore]);
328
+ let resolvedStaticContent = staticContent ?? getSchemaStaticContent(themeSchema);
161
329
  let rootContextValue = useMemo(
162
- () => ({ themeSettingsStore }),
163
- [themeSettingsStore]
330
+ () => ({
331
+ merchantOverrides,
332
+ staticContent: resolvedStaticContent,
333
+ themeSettingsStore,
334
+ translationStore
335
+ }),
336
+ [
337
+ merchantOverrides,
338
+ resolvedStaticContent,
339
+ themeSettingsStore,
340
+ translationStore
341
+ ]
164
342
  );
165
343
  let contextValue = useMemo(
166
344
  () => ({
345
+ merchantOverrides,
167
346
  themeSettings: themeSettingsStore.getSnapshot(),
168
- themeSettingsStore
347
+ themeSettingsStore,
348
+ translationStore
169
349
  }),
170
- [themeSettingsStore]
350
+ [merchantOverrides, themeSettingsStore, translationStore]
171
351
  );
172
- return /* @__PURE__ */ jsx(WeaverseNextRootContext.Provider, { value: rootContextValue, children: /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value: contextValue, children }) });
352
+ return /* @__PURE__ */ jsx(WeaverseNextRootContext.Provider, { value: rootContextValue, children: /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
353
+ TranslationProvider,
354
+ {
355
+ merchantOverrides,
356
+ staticContent: resolvedStaticContent,
357
+ t,
358
+ translationStore,
359
+ children
360
+ }
361
+ ) }) });
173
362
  }
174
363
  var EMPTY_THEME_SETTINGS = {};
364
+ var EMPTY_STATIC_CONTENT = {};
365
+ function getSchemaStaticContent(schema) {
366
+ let i18n = schema?.i18n;
367
+ return i18n?.staticContent ?? EMPTY_STATIC_CONTENT;
368
+ }
175
369
  var WeaverseNextContext = createContext(null);
176
370
  function WeaverseNextProvider(props) {
177
371
  let { children, client, rootData, pageData, commerce, themeSettings } = props;
178
372
  let rootContext = useContext(WeaverseNextRootContext);
179
373
  let themeSettingsValue = themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS;
374
+ let ownTranslationStoreRef = useRef(null);
375
+ if (!(rootContext || ownTranslationStoreRef.current)) {
376
+ ownTranslationStoreRef.current = new TranslationStore();
377
+ }
378
+ let translationStore = rootContext?.translationStore ?? ownTranslationStoreRef.current;
379
+ let staticContent = props.staticContent ?? rootContext?.staticContent ?? getSchemaStaticContent(client?.themeSchema);
380
+ let merchantOverrides = props.merchantOverrides ?? rootContext?.merchantOverrides;
180
381
  let ownThemeSettingsStore = useMemo(() => {
181
382
  if (rootContext) {
182
383
  return null;
@@ -200,12 +401,31 @@ function WeaverseNextProvider(props) {
200
401
  rootData,
201
402
  pageData,
202
403
  commerce: commerce ?? client?.commerce,
404
+ merchantOverrides,
203
405
  themeSettings: themeSettingsStore.getSnapshot(),
204
- themeSettingsStore
406
+ themeSettingsStore,
407
+ translationStore
205
408
  }),
206
- [client, rootData, pageData, commerce, themeSettingsStore]
409
+ [
410
+ client,
411
+ rootData,
412
+ pageData,
413
+ commerce,
414
+ merchantOverrides,
415
+ themeSettingsStore,
416
+ translationStore
417
+ ]
207
418
  );
208
- return /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value, children });
419
+ return /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value, children: /* @__PURE__ */ jsx(
420
+ TranslationProvider,
421
+ {
422
+ merchantOverrides,
423
+ staticContent,
424
+ t: props.t,
425
+ translationStore,
426
+ children
427
+ }
428
+ ) });
209
429
  }
210
430
 
211
431
  // src/hooks.ts
@@ -289,9 +509,15 @@ var WeaverseNextRuntime = class extends Weaverse {
289
509
  isPreviewMode;
290
510
  isRevisionPreview;
291
511
  sectionType;
512
+ // ─── Item-level translation sidecar (design mode only) ─────────────
513
+ /**
514
+ * Translation map received from Builder in design mode, keyed by item ID.
515
+ * `WeaverseNextItem.getSnapShot()` overlays these onto the data handed to
516
+ * components, so translations render without mutating the base `_store`.
517
+ */
292
518
  translationMap = {};
293
- translationLocale = "";
294
- translationLanguageId = "";
519
+ translationLocale = String();
520
+ translationLanguageId = String();
295
521
  constructor(config) {
296
522
  let { client, data } = config;
297
523
  let page = getRenderablePage(data);
@@ -313,7 +539,9 @@ var WeaverseNextRuntime = class extends Weaverse {
313
539
  this.isPreviewMode = requestContext?.isPreviewMode ?? false;
314
540
  this.isRevisionPreview = requestContext?.isRevisionPreview ?? false;
315
541
  this.sectionType = requestContext?.sectionType;
542
+ let translationStore = config.translationStore ?? new TranslationStore();
316
543
  this.internal = {
544
+ merchantOverrides: config.merchantOverrides,
317
545
  navigate: config.navigate,
318
546
  pageAssignment: data.pageAssignment,
319
547
  project: data.project,
@@ -321,17 +549,98 @@ var WeaverseNextRuntime = class extends Weaverse {
321
549
  themeSettingsStore: config.themeSettingsStore ?? createWeaverseNextThemeSettingsStore({
322
550
  schema: client?.themeSchema,
323
551
  settings: client?.themeSettings
324
- })
552
+ }),
553
+ translationStore,
554
+ themeTextStore: translationStore
325
555
  };
326
- }
327
- extractTranslationSidecar = () => void 0;
556
+ this.extractTranslationSidecar();
557
+ }
558
+ /**
559
+ * Pull `translationMap` / `translationLocale` / `translationLanguageId` off
560
+ * the current page data (Builder attaches them in design mode). Unlike
561
+ * Hydrogen, this also clears the sidecar to defaults when the new page data
562
+ * carries none, so a reused runtime / project-data swap can't leave a stale
563
+ * map overlaying the fresh tree.
564
+ */
565
+ extractTranslationSidecar = () => {
566
+ let pageData = this.data;
567
+ let translationMap = pageData?.translationMap;
568
+ if (translationMap) {
569
+ this.translationMap = translationMap;
570
+ this.translationLocale = pageData?.translationLocale || "";
571
+ this.translationLanguageId = pageData?.translationLanguageId || "";
572
+ } else {
573
+ this.translationMap = {};
574
+ this.translationLocale = "";
575
+ this.translationLanguageId = "";
576
+ }
577
+ };
578
+ /**
579
+ * Replace the translation sidecar (Builder pushes this when translation data
580
+ * arrives or the active language changes) and refresh every item so their
581
+ * merged snapshots recompute.
582
+ */
328
583
  setTranslationSidecar = (map = {}, locale = "", languageId = "") => {
329
584
  this.translationMap = map;
330
585
  this.translationLocale = locale;
331
586
  this.translationLanguageId = languageId;
587
+ this.refreshAllItems();
588
+ };
589
+ /**
590
+ * Update a single translatable field for one item. Assigns a new per-item
591
+ * entry object so `WeaverseNextItem.getSnapShot()`'s translation-ref cache
592
+ * invalidates, then triggers only that item's subscribers.
593
+ */
594
+ updateTranslation = (itemId, key, originalValue, translatedValue) => {
595
+ if (!this.translationMap[itemId]) {
596
+ this.translationMap[itemId] = {};
597
+ }
598
+ this.translationMap[itemId] = {
599
+ ...this.translationMap[itemId],
600
+ [key]: { originalValue, translatedValue }
601
+ };
602
+ let instance = this.itemInstances.get(itemId);
603
+ if (instance) {
604
+ instance.triggerUpdate();
605
+ }
606
+ };
607
+ /**
608
+ * Collect translation changes for the save flow. Returns `undefined` unless
609
+ * both the locale and language ID are set and at least one entry exists;
610
+ * each entry key is namespaced as `data.<field>` to match Builder.
611
+ */
612
+ getTranslationChanges = () => {
613
+ let languageId = this.translationLanguageId;
614
+ if (!(this.translationLocale && languageId)) {
615
+ return;
616
+ }
617
+ let entries = [];
618
+ for (let [itemId, fields] of Object.entries(this.translationMap)) {
619
+ for (let [key, entry] of Object.entries(fields)) {
620
+ entries.push({
621
+ itemId,
622
+ key: `data.${key}`,
623
+ originalValue: entry.originalValue,
624
+ translatedValue: entry.translatedValue
625
+ });
626
+ }
627
+ }
628
+ if (entries.length === 0) {
629
+ return;
630
+ }
631
+ return { languageId, entries };
632
+ };
633
+ /**
634
+ * Re-extract the translation sidecar whenever project data is replaced (route
635
+ * change / non-design reuse), before rebuilding item stores. Takes the
636
+ * renderable page shape (`rootId` resolved) that `getRenderablePage()` and
637
+ * the reuse branch pass in.
638
+ */
639
+ setProjectData = (data) => {
640
+ this.data = data;
641
+ this.extractTranslationSidecar();
642
+ this.initProject();
332
643
  };
333
- updateTranslation = () => void 0;
334
- getTranslationChanges = () => void 0;
335
644
  };
336
645
  function createWeaverseNextRuntime(config) {
337
646
  let requestInfo = buildWeaverseNextRequestInfo(config.client?.requestContext);
@@ -365,6 +674,11 @@ function createWeaverseNextRuntime(config) {
365
674
  if (config.themeSettingsStore) {
366
675
  existing.internal.themeSettingsStore = config.themeSettingsStore;
367
676
  }
677
+ if (config.translationStore) {
678
+ existing.internal.translationStore = config.translationStore;
679
+ existing.internal.themeTextStore = config.translationStore;
680
+ }
681
+ existing.internal.merchantOverrides = config.merchantOverrides;
368
682
  registerRuntime(existing);
369
683
  return existing;
370
684
  }
@@ -543,9 +857,19 @@ var WeaverseNextRenderer = memo(function WeaverseNextRendererComponent(props) {
543
857
  client,
544
858
  data: { ...data, page },
545
859
  dataContext,
546
- themeSettingsStore: context?.themeSettingsStore
860
+ merchantOverrides: context?.merchantOverrides,
861
+ themeSettingsStore: context?.themeSettingsStore,
862
+ translationStore: context?.translationStore
547
863
  });
548
- }, [client, page, data, dataContext, context?.themeSettingsStore]);
864
+ }, [
865
+ client,
866
+ page,
867
+ data,
868
+ dataContext,
869
+ context?.merchantOverrides,
870
+ context?.themeSettingsStore,
871
+ context?.translationStore
872
+ ]);
549
873
  if (!weaverse) {
550
874
  return null;
551
875
  }
@@ -668,6 +992,6 @@ function WeaverseNextStudioConnect(props) {
668
992
  return null;
669
993
  }
670
994
 
671
- export { DEFAULT_REVALIDATE_ENDPOINT, WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRootProvider, WeaverseNextRuntime, WeaverseNextStudio, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, loadWeaverseNextStudioScript, resolveWeaverseNextStudioScriptSrc, revalidateWeaverseNextItem, useThemeSettings, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
995
+ export { DEFAULT_REVALIDATE_ENDPOINT, ThemeTextProvider, ThemeTextStore, TranslationProvider, TranslationStore, WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRootProvider, WeaverseNextRuntime, WeaverseNextStudio, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createTranslate, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, getNestedKey, interpolate, loadWeaverseNextStudioScript, resolveWeaverseNextStudioScriptSrc, revalidateWeaverseNextItem, useThemeSettings, useThemeText, useTranslation, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
672
996
  //# sourceMappingURL=index.mjs.map
673
997
  //# sourceMappingURL=index.mjs.map