@rovela-ai/sdk 0.10.1 → 0.11.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.
Files changed (108) hide show
  1. package/dist/admin/api/settings.d.ts.map +1 -1
  2. package/dist/admin/api/settings.js +15 -0
  3. package/dist/admin/api/settings.js.map +1 -1
  4. package/dist/admin/components/FilesManager.js +1 -1
  5. package/dist/admin/components/FilesManager.js.map +1 -1
  6. package/dist/admin/components/MediaLibraryPicker.js +1 -1
  7. package/dist/admin/components/MediaLibraryPicker.js.map +1 -1
  8. package/dist/admin/components/StoreSettings.d.ts.map +1 -1
  9. package/dist/admin/components/StoreSettings.js +28 -1
  10. package/dist/admin/components/StoreSettings.js.map +1 -1
  11. package/dist/admin/components/VariantManager.d.ts.map +1 -1
  12. package/dist/admin/components/VariantManager.js +14 -40
  13. package/dist/admin/components/VariantManager.js.map +1 -1
  14. package/dist/admin/types.d.ts +2 -0
  15. package/dist/admin/types.d.ts.map +1 -1
  16. package/dist/cart/components/AddToCartButton.d.ts +1 -1
  17. package/dist/cart/components/AddToCartButton.d.ts.map +1 -1
  18. package/dist/cart/components/AddToCartButton.js +4 -2
  19. package/dist/cart/components/AddToCartButton.js.map +1 -1
  20. package/dist/cart/components/CartDrawer.d.ts.map +1 -1
  21. package/dist/cart/components/CartDrawer.js +4 -2
  22. package/dist/cart/components/CartDrawer.js.map +1 -1
  23. package/dist/cart/components/CartIcon.d.ts.map +1 -1
  24. package/dist/cart/components/CartIcon.js +4 -2
  25. package/dist/cart/components/CartIcon.js.map +1 -1
  26. package/dist/cart/components/CartItem.d.ts.map +1 -1
  27. package/dist/cart/components/CartItem.js +3 -1
  28. package/dist/cart/components/CartItem.js.map +1 -1
  29. package/dist/cart/components/CartSummary.d.ts.map +1 -1
  30. package/dist/cart/components/CartSummary.js +6 -4
  31. package/dist/cart/components/CartSummary.js.map +1 -1
  32. package/dist/cart/components/QuantitySelector.d.ts.map +1 -1
  33. package/dist/cart/components/QuantitySelector.js +4 -2
  34. package/dist/cart/components/QuantitySelector.js.map +1 -1
  35. package/dist/checkout/components/CheckoutButton.d.ts.map +1 -1
  36. package/dist/checkout/components/CheckoutButton.js +4 -2
  37. package/dist/checkout/components/CheckoutButton.js.map +1 -1
  38. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  39. package/dist/checkout/components/CheckoutFlow.js +42 -38
  40. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  41. package/dist/checkout/components/OrderSummary.d.ts.map +1 -1
  42. package/dist/checkout/components/OrderSummary.js +5 -2
  43. package/dist/checkout/components/OrderSummary.js.map +1 -1
  44. package/dist/checkout/components/ShippingForm.d.ts.map +1 -1
  45. package/dist/checkout/components/ShippingForm.js +28 -25
  46. package/dist/checkout/components/ShippingForm.js.map +1 -1
  47. package/dist/checkout/components/ShippingOptions.d.ts.map +1 -1
  48. package/dist/checkout/components/ShippingOptions.js +8 -5
  49. package/dist/checkout/components/ShippingOptions.js.map +1 -1
  50. package/dist/core/StoreSettingsProvider.d.ts +2 -0
  51. package/dist/core/StoreSettingsProvider.d.ts.map +1 -1
  52. package/dist/core/StoreSettingsProvider.js +4 -0
  53. package/dist/core/StoreSettingsProvider.js.map +1 -1
  54. package/dist/core/api/settings.d.ts +2 -0
  55. package/dist/core/api/settings.d.ts.map +1 -1
  56. package/dist/core/api/settings.js +4 -0
  57. package/dist/core/api/settings.js.map +1 -1
  58. package/dist/core/db/queries.d.ts +31 -27
  59. package/dist/core/db/queries.d.ts.map +1 -1
  60. package/dist/core/db/queries.js +12 -0
  61. package/dist/core/db/queries.js.map +1 -1
  62. package/dist/core/db/schema.d.ts +39 -1
  63. package/dist/core/db/schema.d.ts.map +1 -1
  64. package/dist/core/db/schema.js +8 -0
  65. package/dist/core/db/schema.js.map +1 -1
  66. package/dist/core/i18n/LanguageSwitcher.d.ts +7 -0
  67. package/dist/core/i18n/LanguageSwitcher.d.ts.map +1 -0
  68. package/dist/core/i18n/LanguageSwitcher.js +44 -0
  69. package/dist/core/i18n/LanguageSwitcher.js.map +1 -0
  70. package/dist/core/i18n/context.d.ts +27 -0
  71. package/dist/core/i18n/context.d.ts.map +1 -0
  72. package/dist/core/i18n/context.js +47 -0
  73. package/dist/core/i18n/context.js.map +1 -0
  74. package/dist/core/i18n/index.d.ts +19 -0
  75. package/dist/core/i18n/index.d.ts.map +1 -0
  76. package/dist/core/i18n/index.js +14 -0
  77. package/dist/core/i18n/index.js.map +1 -0
  78. package/dist/core/i18n/messages/en.d.ts +9 -0
  79. package/dist/core/i18n/messages/en.d.ts.map +1 -0
  80. package/dist/core/i18n/messages/en.js +135 -0
  81. package/dist/core/i18n/messages/en.js.map +1 -0
  82. package/dist/core/i18n/messages/fr.d.ts +10 -0
  83. package/dist/core/i18n/messages/fr.d.ts.map +1 -0
  84. package/dist/core/i18n/messages/fr.js +136 -0
  85. package/dist/core/i18n/messages/fr.js.map +1 -0
  86. package/dist/core/i18n/resolve.d.ts +64 -0
  87. package/dist/core/i18n/resolve.d.ts.map +1 -0
  88. package/dist/core/i18n/resolve.js +95 -0
  89. package/dist/core/i18n/resolve.js.map +1 -0
  90. package/dist/core/i18n/translate.d.ts +26 -0
  91. package/dist/core/i18n/translate.d.ts.map +1 -0
  92. package/dist/core/i18n/translate.js +46 -0
  93. package/dist/core/i18n/translate.js.map +1 -0
  94. package/dist/core/i18n/types.d.ts +46 -0
  95. package/dist/core/i18n/types.d.ts.map +1 -0
  96. package/dist/core/i18n/types.js +50 -0
  97. package/dist/core/i18n/types.js.map +1 -0
  98. package/dist/core/index.d.ts +1 -0
  99. package/dist/core/index.d.ts.map +1 -1
  100. package/dist/core/index.js +2 -0
  101. package/dist/core/index.js.map +1 -1
  102. package/dist/media/components/ImageGalleryUpload.d.ts.map +1 -1
  103. package/dist/media/components/ImageGalleryUpload.js +8 -19
  104. package/dist/media/components/ImageGalleryUpload.js.map +1 -1
  105. package/dist/media/components/ImageUpload.d.ts.map +1 -1
  106. package/dist/media/components/ImageUpload.js +11 -30
  107. package/dist/media/components/ImageUpload.js.map +1 -1
  108. package/package.json +1 -1
@@ -0,0 +1,136 @@
1
+ /**
2
+ * French catalog. Sourced from EasyElec's production French storefront
3
+ * (hand-translated by a French merchant team, live-validated) — not machine
4
+ * translation. Conventions: "vous" form, French e-commerce vocabulary
5
+ * (Panier, Livraison, TVA, Code promo), non-breaking-space-free punctuation
6
+ * kept simple for email-client + font portability.
7
+ */
8
+ export const fr = {
9
+ // ── Common ────────────────────────────────────────────────────────────────
10
+ 'common.back': 'Retour',
11
+ 'common.change': 'Modifier',
12
+ 'common.edit': 'Modifier',
13
+ 'common.remove': 'Retirer',
14
+ 'common.free': 'Gratuit',
15
+ 'common.freeUpper': 'GRATUIT',
16
+ 'common.total': 'Total',
17
+ 'common.subtotal': 'Sous-total',
18
+ 'common.shipping': 'Livraison',
19
+ 'common.tax': 'TVA',
20
+ 'common.discount': 'Remise',
21
+ 'common.contact': 'Contact',
22
+ 'common.continue': 'Continuer',
23
+ 'common.saving': 'Enregistrement…',
24
+ 'common.processing': 'Traitement…',
25
+ // ── Cart ──────────────────────────────────────────────────────────────────
26
+ 'cart.title': 'Panier',
27
+ 'cart.ariaLabel': 'Panier',
28
+ 'cart.close': 'Fermer le panier',
29
+ 'cart.empty': 'Votre panier est vide',
30
+ 'cart.emptyHint': 'Ajoutez des articles pour commencer !',
31
+ 'cart.item': 'article',
32
+ 'cart.items': 'articles',
33
+ 'cart.removeItem': 'Retirer {name} du panier',
34
+ 'cart.addToCart': 'Ajouter au panier',
35
+ 'cart.added': 'Ajouté !',
36
+ 'cart.addFailed': "Impossible d'ajouter au panier",
37
+ 'cart.quantity': 'Quantité',
38
+ 'cart.estimatedTotal': 'Total estimé',
39
+ 'cart.calculatedAtCheckout': 'Calculée au paiement',
40
+ 'cart.checkout': 'Passer la commande',
41
+ // ── Checkout flow ─────────────────────────────────────────────────────────
42
+ 'checkout.stepPickup': 'Retrait',
43
+ 'checkout.stepAddress': 'Adresse',
44
+ 'checkout.stepShipping': 'Livraison',
45
+ 'checkout.stepReview': 'Vérification',
46
+ 'checkout.closedToday': "Fermé aujourd'hui",
47
+ 'checkout.openToday': "Ouvert aujourd'hui {open}–{close}",
48
+ 'checkout.shipTo': 'Livrer à',
49
+ 'checkout.billTo': 'Facturer à',
50
+ 'checkout.sameAsShipping': "Identique à l'adresse de livraison",
51
+ 'checkout.shippingMethod': 'Mode de livraison',
52
+ 'checkout.signInToCheckOut': 'Connectez-vous pour commander',
53
+ 'checkout.accountRequired': 'Cette boutique nécessite un compte pour finaliser votre commande.',
54
+ 'checkout.signIn': 'Se connecter',
55
+ 'checkout.createAccount': 'Créer un compte',
56
+ 'checkout.codeCheckFailed': 'Impossible de vérifier le code. Veuillez réessayer.',
57
+ 'checkout.codeInvalid': "Ce code n'est pas valide.",
58
+ 'checkout.enterFirstLastName': 'Veuillez renseigner votre prénom et votre nom.',
59
+ 'checkout.enterEmail': 'Veuillez renseigner votre e-mail.',
60
+ 'checkout.usuallyReadyIn': 'Prête sous {time}',
61
+ 'checkout.shippingCalcFailed': 'Impossible de calculer la livraison',
62
+ 'checkout.shippingCalcFailedRetry': 'Impossible de calculer la livraison. Veuillez réessayer.',
63
+ 'checkout.cartEmpty': 'Votre panier est vide.',
64
+ 'checkout.continueShopping': 'Continuer mes achats',
65
+ 'checkout.discountCode': 'Code promo',
66
+ 'checkout.enterCode': 'Saisir un code',
67
+ 'checkout.apply': 'Appliquer',
68
+ 'checkout.promotionApplied': 'Promotion appliquée : {title}',
69
+ 'checkout.promotionAppliedFreeShipping': 'Promotion appliquée : {title} (livraison offerte)',
70
+ 'checkout.delivery': 'Livraison',
71
+ 'checkout.pickupInStore': 'Retrait en magasin',
72
+ 'checkout.shippingAddress': 'Adresse de livraison',
73
+ 'checkout.emailPlaceholder': 'vous@exemple.fr',
74
+ 'checkout.pickupStore': 'Magasin de retrait',
75
+ 'checkout.chooseAnotherStore': 'Choisir un autre magasin ({count} de plus)',
76
+ 'checkout.continueToReview': 'Vérifier la commande',
77
+ 'checkout.chooseDeliveryAddress': 'Choisissez une adresse de livraison',
78
+ 'checkout.deliverHere': 'Livrer ici',
79
+ 'checkout.useNewAddress': 'Utiliser une nouvelle adresse',
80
+ 'checkout.useSavedAddress': 'Utiliser une adresse enregistrée',
81
+ 'checkout.shippingMethodTitle': 'Mode de livraison',
82
+ 'checkout.reviewYourOrder': 'Vérifiez votre commande',
83
+ 'checkout.paymentMethod': 'Moyen de paiement',
84
+ 'checkout.card': 'Carte bancaire',
85
+ 'checkout.secureViaStripe': 'Paiement sécurisé via Stripe',
86
+ 'checkout.placeOrder': 'Passer la commande',
87
+ 'checkout.proceedToPayment': 'Procéder au paiement',
88
+ 'checkout.manualConfirmNote': 'Votre commande sera confirmée par e-mail avec les instructions de paiement.',
89
+ 'checkout.stripeRedirectNote': 'Vous allez être redirigé vers Stripe pour régler votre commande en toute sécurité.',
90
+ 'checkout.orderSummary': 'Récapitulatif',
91
+ 'checkout.qty': 'Qté : {count}',
92
+ 'checkout.redirecting': 'Redirection…',
93
+ 'checkout.each': "{price} l'unité",
94
+ 'checkout.businessDays': '{days} jours ouvrés',
95
+ 'checkout.calculatingShipping': 'Calcul des options de livraison…',
96
+ 'checkout.noShippingOptions': 'Aucune option de livraison disponible pour votre adresse.',
97
+ 'checkout.firstName': 'Prénom',
98
+ 'checkout.lastName': 'Nom',
99
+ 'checkout.email': 'E-mail',
100
+ // Preparation-time values as stored on locations.
101
+ 'prep.1 hour': '1 heure',
102
+ 'prep.2 hours': '2 heures',
103
+ 'prep.4 hours': '4 heures',
104
+ 'prep.24 hours': '24 heures',
105
+ 'prep.2-4 days': '2 à 4 jours',
106
+ // ── Shipping form ─────────────────────────────────────────────────────────
107
+ 'form.email': 'E-mail',
108
+ 'form.phone': 'Téléphone',
109
+ 'form.firstName': 'Prénom',
110
+ 'form.lastName': 'Nom',
111
+ 'form.country': 'Pays',
112
+ 'form.address': 'Adresse',
113
+ 'form.address2': "Complément d'adresse (facultatif)",
114
+ 'form.city': 'Ville',
115
+ 'form.state': 'État',
116
+ 'form.zipCode': 'Code postal',
117
+ 'form.postalCode': 'Code postal',
118
+ 'form.phonePlaceholder': '+33 6 12 34 56 78',
119
+ 'form.emailPlaceholder': 'vous@exemple.fr',
120
+ 'form.streetPlaceholder': 'Numéro et rue',
121
+ 'form.address2Placeholder': 'Appartement, bâtiment…',
122
+ 'form.billingAddress': 'Adresse de facturation',
123
+ 'form.billingDifferent': "L'adresse de facturation est différente de l'adresse de livraison",
124
+ 'form.emailRequired': "L'e-mail est requis",
125
+ 'form.emailInvalid': 'Veuillez saisir une adresse e-mail valide',
126
+ 'form.phoneRequiredDelivery': 'Le téléphone est requis pour le contact de livraison',
127
+ 'form.phoneRequired': 'Le téléphone est requis',
128
+ 'form.firstNameRequired': 'Le prénom est requis',
129
+ 'form.lastNameRequired': 'Le nom est requis',
130
+ 'form.addressRequired': "L'adresse est requise",
131
+ 'form.cityRequired': 'La ville est requise',
132
+ 'form.postalCodeRequired': 'Le code postal est requis',
133
+ 'form.countryRequired': 'Le pays est requis',
134
+ 'form.stateRequired': "L'État est requis",
135
+ };
136
+ //# sourceMappingURL=fr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fr.js","sourceRoot":"","sources":["../../../../src/core/i18n/messages/fr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @rovela-ai/sdk/core/i18n/resolve
3
+ *
4
+ * Pure locale-resolution functions shared by the template's root layout.
5
+ * The template proxy (edge, DB-free) only PARSES the URL and forwards
6
+ * detection hints as headers; the DECISIONS happen here, server-side, where
7
+ * store settings are available. No next/headers import — callers pass the
8
+ * values in, so these stay testable and browser-safe.
9
+ */
10
+ import { type StoreLocale } from './types';
11
+ export interface StoreLocaleConfig {
12
+ defaultLocale: StoreLocale;
13
+ /** Always non-empty and always contains the default. */
14
+ enabledLocales: StoreLocale[];
15
+ }
16
+ /**
17
+ * Normalize raw settings values (nullable, legacy rows) into a usable
18
+ * config. Pre-i18n rows (both NULL) resolve to en-only — byte-identical
19
+ * legacy behavior.
20
+ */
21
+ export declare function resolveStoreLocaleConfig(settings?: {
22
+ defaultLocale?: string | null;
23
+ enabledLocales?: string[] | null;
24
+ } | null): StoreLocaleConfig;
25
+ export interface RequestLocaleInput {
26
+ /** Locale parsed from the URL prefix by the proxy (x-rovela-locale). */
27
+ prefixLocale?: string | null;
28
+ /** Visitor preference cookie (rovela-locale). */
29
+ cookieLocale?: string | null;
30
+ /** IP-country suggestion from the proxy (x-rovela-geo-locale). */
31
+ geoLocale?: string | null;
32
+ config: StoreLocaleConfig;
33
+ }
34
+ /**
35
+ * The locale this request renders in. URL prefix wins (a shared /fr link
36
+ * must show French regardless of the visitor's cookie); otherwise the store
37
+ * default — bare URLs always serve the default language, and preference
38
+ * routing happens via redirect (below), never via content-swap on the same
39
+ * URL (that would serve two languages on one URL — the SEO failure mode the
40
+ * path-prefix architecture exists to avoid).
41
+ */
42
+ export declare function resolveRequestLocale({ prefixLocale, config }: RequestLocaleInput): StoreLocale;
43
+ export interface LocaleRedirectInput extends RequestLocaleInput {
44
+ /** ORIGINAL request pathname, prefix included (x-rovela-pathname). */
45
+ pathname?: string | null;
46
+ }
47
+ /**
48
+ * Where this request should redirect for locale correctness, or null.
49
+ *
50
+ * - Prefixed URL for a locale the store doesn't serve → strip the prefix
51
+ * (no /fr ghost pages on an en-only store).
52
+ * - Bare URL + the visitor prefers an enabled non-default locale (explicit
53
+ * cookie first, else IP-country hint) → the prefixed equivalent. This is
54
+ * the "IP detection" behavior: first visit from France lands on /fr; the
55
+ * cookie the provider then writes keeps it sticky, and switching back to
56
+ * the default writes a default-locale cookie which suppresses this.
57
+ */
58
+ export declare function resolveLocaleRedirect({ prefixLocale, cookieLocale, geoLocale, pathname, config, }: LocaleRedirectInput): string | null;
59
+ /**
60
+ * Build the URL for the CURRENT page in another locale — the language
61
+ * switcher's navigation target. `pathname` may carry a locale prefix.
62
+ */
63
+ export declare function localePath(pathname: string, target: StoreLocale, defaultLocale: StoreLocale): string;
64
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/core/i18n/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,SAAS,CAAA;AAEhB,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,WAAW,CAAA;IAC1B,wDAAwD;IACxD,cAAc,EAAE,WAAW,EAAE,CAAA;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,CAAC,EAAE;IAClD,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CACjC,GAAG,IAAI,GAAG,iBAAiB,CAU3B;AAED,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,iBAAiB,CAAA;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,kBAAkB,GAAG,WAAW,CAK9F;AAED,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,MAAM,GACP,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CA0BrC;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,GAAG,MAAM,CAYpG"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @rovela-ai/sdk/core/i18n/resolve
3
+ *
4
+ * Pure locale-resolution functions shared by the template's root layout.
5
+ * The template proxy (edge, DB-free) only PARSES the URL and forwards
6
+ * detection hints as headers; the DECISIONS happen here, server-side, where
7
+ * store settings are available. No next/headers import — callers pass the
8
+ * values in, so these stay testable and browser-safe.
9
+ */
10
+ import { DEFAULT_LOCALE, SUPPORTED_LOCALES, isSupportedLocale, } from './types';
11
+ /**
12
+ * Normalize raw settings values (nullable, legacy rows) into a usable
13
+ * config. Pre-i18n rows (both NULL) resolve to en-only — byte-identical
14
+ * legacy behavior.
15
+ */
16
+ export function resolveStoreLocaleConfig(settings) {
17
+ const def = isSupportedLocale(settings?.defaultLocale)
18
+ ? settings.defaultLocale
19
+ : DEFAULT_LOCALE;
20
+ const enabled = (settings?.enabledLocales ?? [])
21
+ .filter(isSupportedLocale)
22
+ // preserve declaration order, dedupe
23
+ .filter((l, i, arr) => arr.indexOf(l) === i);
24
+ if (!enabled.includes(def))
25
+ enabled.unshift(def);
26
+ return { defaultLocale: def, enabledLocales: enabled };
27
+ }
28
+ /**
29
+ * The locale this request renders in. URL prefix wins (a shared /fr link
30
+ * must show French regardless of the visitor's cookie); otherwise the store
31
+ * default — bare URLs always serve the default language, and preference
32
+ * routing happens via redirect (below), never via content-swap on the same
33
+ * URL (that would serve two languages on one URL — the SEO failure mode the
34
+ * path-prefix architecture exists to avoid).
35
+ */
36
+ export function resolveRequestLocale({ prefixLocale, config }) {
37
+ if (isSupportedLocale(prefixLocale) && config.enabledLocales.includes(prefixLocale)) {
38
+ return prefixLocale;
39
+ }
40
+ return config.defaultLocale;
41
+ }
42
+ /**
43
+ * Where this request should redirect for locale correctness, or null.
44
+ *
45
+ * - Prefixed URL for a locale the store doesn't serve → strip the prefix
46
+ * (no /fr ghost pages on an en-only store).
47
+ * - Bare URL + the visitor prefers an enabled non-default locale (explicit
48
+ * cookie first, else IP-country hint) → the prefixed equivalent. This is
49
+ * the "IP detection" behavior: first visit from France lands on /fr; the
50
+ * cookie the provider then writes keeps it sticky, and switching back to
51
+ * the default writes a default-locale cookie which suppresses this.
52
+ */
53
+ export function resolveLocaleRedirect({ prefixLocale, cookieLocale, geoLocale, pathname, config, }) {
54
+ if (!pathname || !pathname.startsWith('/'))
55
+ return null;
56
+ // Prefixed URL: only "not enabled" needs correction.
57
+ if (isSupportedLocale(prefixLocale)) {
58
+ if (!config.enabledLocales.includes(prefixLocale) || prefixLocale === config.defaultLocale) {
59
+ const bare = pathname.replace(new RegExp(`^/${prefixLocale}(?=/|$)`), '') || '/';
60
+ return bare;
61
+ }
62
+ return null;
63
+ }
64
+ // Bare URL: route an explicit or detected preference to its prefix.
65
+ const preferred = isSupportedLocale(cookieLocale)
66
+ ? cookieLocale
67
+ : isSupportedLocale(geoLocale)
68
+ ? geoLocale
69
+ : null;
70
+ if (preferred &&
71
+ preferred !== config.defaultLocale &&
72
+ config.enabledLocales.includes(preferred)) {
73
+ return `/${preferred}${pathname === '/' ? '' : pathname}`;
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * Build the URL for the CURRENT page in another locale — the language
79
+ * switcher's navigation target. `pathname` may carry a locale prefix.
80
+ */
81
+ export function localePath(pathname, target, defaultLocale) {
82
+ // Strip any existing supported-locale prefix.
83
+ let bare = pathname;
84
+ for (const l of SUPPORTED_LOCALES) {
85
+ const re = new RegExp(`^/${l}(?=/|$)`);
86
+ if (re.test(bare)) {
87
+ bare = bare.replace(re, '') || '/';
88
+ break;
89
+ }
90
+ }
91
+ if (target === defaultLocale)
92
+ return bare;
93
+ return `/${target}${bare === '/' ? '' : bare}`;
94
+ }
95
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/core/i18n/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @rovela-ai/sdk/core/i18n/translate
3
+ *
4
+ * The pure translation function — server-safe (no React). Client components
5
+ * reach it through `useT()` (context.tsx); server components and emails call
6
+ * `getT(locale)` directly with an explicit locale.
7
+ *
8
+ * Lookup contract (load-bearing): locale catalog → English catalog → the key
9
+ * itself. A missing key can never crash or blank a surface — worst case the
10
+ * visitor sees English, and a raw key in the UI is a loud, greppable bug.
11
+ */
12
+ import { type StoreLocale } from './types';
13
+ export type TranslateVars = Record<string, string | number>;
14
+ export type Translator = (key: string, vars?: TranslateVars) => string;
15
+ /** Build a translator bound to a locale. */
16
+ export declare function getT(locale: StoreLocale): Translator;
17
+ /** Direct one-shot translation (emails, server helpers). */
18
+ export declare function translate(locale: StoreLocale, key: string, vars?: TranslateVars): string;
19
+ /**
20
+ * Translate a DYNAMIC value through a key prefix, falling back to the raw
21
+ * value (not the key) when no catalog entry exists. For merchant-stored
22
+ * enum-ish strings like preparation times: `translateDynamic(t, 'prep', '2
23
+ * hours')` → '2 heures' in fr, '2 hours' when unmapped.
24
+ */
25
+ export declare function translateDynamic(t: Translator, prefix: string, value: string): string;
26
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../../src/core/i18n/translate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAuC,KAAK,WAAW,EAAE,MAAM,SAAS,CAAA;AAI/E,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;AAU3D,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,KAAK,MAAM,CAAA;AAEtE,4CAA4C;AAC5C,wBAAgB,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,CAOpD;AAED,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAExF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIrF"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @rovela-ai/sdk/core/i18n/translate
3
+ *
4
+ * The pure translation function — server-safe (no React). Client components
5
+ * reach it through `useT()` (context.tsx); server components and emails call
6
+ * `getT(locale)` directly with an explicit locale.
7
+ *
8
+ * Lookup contract (load-bearing): locale catalog → English catalog → the key
9
+ * itself. A missing key can never crash or blank a surface — worst case the
10
+ * visitor sees English, and a raw key in the UI is a loud, greppable bug.
11
+ */
12
+ import { en } from './messages/en';
13
+ import { fr } from './messages/fr';
14
+ import { DEFAULT_LOCALE } from './types';
15
+ const CATALOGS = { en, fr };
16
+ /** `{name}` interpolation. Unknown slots are left verbatim (greppable). */
17
+ function interpolate(template, vars) {
18
+ if (!vars)
19
+ return template;
20
+ return template.replace(/\{(\w+)\}/g, (match, name) => name in vars ? String(vars[name]) : match);
21
+ }
22
+ /** Build a translator bound to a locale. */
23
+ export function getT(locale) {
24
+ const catalog = CATALOGS[locale] ?? CATALOGS[DEFAULT_LOCALE];
25
+ const fallback = CATALOGS[DEFAULT_LOCALE];
26
+ return (key, vars) => {
27
+ const template = catalog[key] ?? fallback[key] ?? key;
28
+ return interpolate(template, vars);
29
+ };
30
+ }
31
+ /** Direct one-shot translation (emails, server helpers). */
32
+ export function translate(locale, key, vars) {
33
+ return getT(locale)(key, vars);
34
+ }
35
+ /**
36
+ * Translate a DYNAMIC value through a key prefix, falling back to the raw
37
+ * value (not the key) when no catalog entry exists. For merchant-stored
38
+ * enum-ish strings like preparation times: `translateDynamic(t, 'prep', '2
39
+ * hours')` → '2 heures' in fr, '2 hours' when unmapped.
40
+ */
41
+ export function translateDynamic(t, prefix, value) {
42
+ const key = `${prefix}.${value}`;
43
+ const out = t(key);
44
+ return out === key ? value : out;
45
+ }
46
+ //# sourceMappingURL=translate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../../src/core/i18n/translate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAClC,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAClC,OAAO,EAAE,cAAc,EAAyC,MAAM,SAAS,CAAA;AAE/E,MAAM,QAAQ,GAAwC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAA;AAIhE,2EAA2E;AAC3E,SAAS,WAAW,CAAC,QAAgB,EAAE,IAAoB;IACzD,IAAI,CAAC,IAAI;QAAE,OAAO,QAAQ,CAAA;IAC1B,OAAO,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CAC5D,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAC1C,CAAA;AACH,CAAC;AAID,4CAA4C;AAC5C,MAAM,UAAU,IAAI,CAAC,MAAmB;IACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,cAAc,CAAC,CAAA;IAC5D,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAA;IACzC,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QACnB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAA;QACrD,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IACpC,CAAC,CAAA;AACH,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,SAAS,CAAC,MAAmB,EAAE,GAAW,EAAE,IAAoB;IAC9E,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAa,EAAE,MAAc,EAAE,KAAa;IAC3E,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,KAAK,EAAE,CAAA;IAChC,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;IAClB,OAAO,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAA;AAClC,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @rovela-ai/sdk/core/i18n/types
3
+ *
4
+ * Locale types for the storefront i18n core. The SDK ships its own message
5
+ * catalogs (en + fr) so its components translate themselves — no external
6
+ * i18n library, no peer-dependency dance. Store-authored content (product
7
+ * names, custom sections) is a SEPARATE layer (catalog translations, jsonb).
8
+ */
9
+ /** Locales the SDK ships catalogs for. Extending = add a catalog + append here. */
10
+ export declare const SUPPORTED_LOCALES: readonly ["en", "fr"];
11
+ export type StoreLocale = (typeof SUPPORTED_LOCALES)[number];
12
+ /** The default when nothing is configured — matches pre-i18n behavior. */
13
+ export declare const DEFAULT_LOCALE: StoreLocale;
14
+ /** Runtime guard for values coming from cookies / headers / settings. */
15
+ export declare function isSupportedLocale(value: unknown): value is StoreLocale;
16
+ /**
17
+ * Flat message catalog: `namespace.key` → string. Interpolation slots use
18
+ * `{name}` syntax, substituted by `t()`.
19
+ */
20
+ export type MessageCatalog = Record<string, string>;
21
+ /** BCP-47 tag used for Intl formatting per locale. */
22
+ export declare const LOCALE_INTL: Record<StoreLocale, string>;
23
+ /**
24
+ * The cookie that persists an explicit visitor choice (language switcher) or
25
+ * a first-visit geo detection. Read by the template proxy for bare-path
26
+ * redirects; written client-side by the LocaleProvider / switcher.
27
+ */
28
+ export declare const LOCALE_COOKIE = "rovela-locale";
29
+ /**
30
+ * Request headers set by the template proxy (see template/proxy.ts):
31
+ * - LOCALE_HEADER: the locale parsed from a URL prefix (`/fr/...`), when present.
32
+ * - GEO_LOCALE_HEADER: the locale suggested by the visitor's IP country
33
+ * (`x-vercel-ip-country`), detection hint only.
34
+ * - PATHNAME_HEADER: the ORIGINAL request pathname (prefix included) so
35
+ * server components can build locale-switch + redirect URLs.
36
+ */
37
+ export declare const LOCALE_HEADER = "x-rovela-locale";
38
+ export declare const GEO_LOCALE_HEADER = "x-rovela-geo-locale";
39
+ export declare const PATHNAME_HEADER = "x-rovela-pathname";
40
+ /**
41
+ * IP country → locale suggestion. Deliberately minimal: only countries whose
42
+ * dominant language we ship a catalog for. Everything else falls through to
43
+ * the store default.
44
+ */
45
+ export declare const COUNTRY_TO_LOCALE: Record<string, StoreLocale>;
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/i18n/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,uBAAwB,CAAA;AAEtD,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D,0EAA0E;AAC1E,eAAO,MAAM,cAAc,EAAE,WAAkB,CAAA;AAE/C,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAEtE;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEnD,sDAAsD;AACtD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAGnD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,oBAAoB,CAAA;AAC9C,eAAO,MAAM,iBAAiB,wBAAwB,CAAA;AACtD,eAAO,MAAM,eAAe,sBAAsB,CAAA;AAElD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAKzD,CAAA"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @rovela-ai/sdk/core/i18n/types
3
+ *
4
+ * Locale types for the storefront i18n core. The SDK ships its own message
5
+ * catalogs (en + fr) so its components translate themselves — no external
6
+ * i18n library, no peer-dependency dance. Store-authored content (product
7
+ * names, custom sections) is a SEPARATE layer (catalog translations, jsonb).
8
+ */
9
+ /** Locales the SDK ships catalogs for. Extending = add a catalog + append here. */
10
+ export const SUPPORTED_LOCALES = ['en', 'fr'];
11
+ /** The default when nothing is configured — matches pre-i18n behavior. */
12
+ export const DEFAULT_LOCALE = 'en';
13
+ /** Runtime guard for values coming from cookies / headers / settings. */
14
+ export function isSupportedLocale(value) {
15
+ return typeof value === 'string' && SUPPORTED_LOCALES.includes(value);
16
+ }
17
+ /** BCP-47 tag used for Intl formatting per locale. */
18
+ export const LOCALE_INTL = {
19
+ en: 'en-US',
20
+ fr: 'fr-FR',
21
+ };
22
+ /**
23
+ * The cookie that persists an explicit visitor choice (language switcher) or
24
+ * a first-visit geo detection. Read by the template proxy for bare-path
25
+ * redirects; written client-side by the LocaleProvider / switcher.
26
+ */
27
+ export const LOCALE_COOKIE = 'rovela-locale';
28
+ /**
29
+ * Request headers set by the template proxy (see template/proxy.ts):
30
+ * - LOCALE_HEADER: the locale parsed from a URL prefix (`/fr/...`), when present.
31
+ * - GEO_LOCALE_HEADER: the locale suggested by the visitor's IP country
32
+ * (`x-vercel-ip-country`), detection hint only.
33
+ * - PATHNAME_HEADER: the ORIGINAL request pathname (prefix included) so
34
+ * server components can build locale-switch + redirect URLs.
35
+ */
36
+ export const LOCALE_HEADER = 'x-rovela-locale';
37
+ export const GEO_LOCALE_HEADER = 'x-rovela-geo-locale';
38
+ export const PATHNAME_HEADER = 'x-rovela-pathname';
39
+ /**
40
+ * IP country → locale suggestion. Deliberately minimal: only countries whose
41
+ * dominant language we ship a catalog for. Everything else falls through to
42
+ * the store default.
43
+ */
44
+ export const COUNTRY_TO_LOCALE = {
45
+ FR: 'fr',
46
+ BE: 'fr',
47
+ LU: 'fr',
48
+ MC: 'fr',
49
+ };
50
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/i18n/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,mFAAmF;AACnF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,IAAI,CAAU,CAAA;AAItD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAgB,IAAI,CAAA;AAE/C,yEAAyE;AACzE,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,iBAAuC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC9F,CAAC;AAQD,sDAAsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAAgC;IACtD,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,OAAO;CACZ,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,iBAAiB,CAAA;AAC9C,MAAM,CAAC,MAAM,iBAAiB,GAAG,qBAAqB,CAAA;AACtD,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAA;AAElD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAgC;IAC5D,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;CACT,CAAA"}
@@ -15,6 +15,7 @@
15
15
  * ```
16
16
  */
17
17
  export * from './types';
18
+ export * from './i18n';
18
19
  export { StoreSettingsProvider, useStoreSettings, useStoreSettingsOptional, } from './StoreSettingsProvider';
19
20
  export type { StoreSettings, StoreSettingsContextValue, StoreSettingsProviderProps, } from './StoreSettingsProvider';
20
21
  export { cn, formatPrice, parsePrice, getCurrencySymbol, generateSlug, generateSKU, formatDate, formatRelativeTime, truncate, capitalize, titleCase, clamp, percentage, isValidEmail, isValidPhone, compact, pick, omit, sleep, retry, } from './utils';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,cAAc,SAAS,CAAA;AAGvB,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,yBAAyB,CAAA;AAEhC,YAAY,EACV,aAAa,EACb,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAEL,EAAE,EAEF,WAAW,EACX,UAAU,EACV,iBAAiB,EAEjB,YAAY,EAEZ,WAAW,EAEX,UAAU,EACV,kBAAkB,EAElB,QAAQ,EACR,UAAU,EACV,SAAS,EAET,KAAK,EACL,UAAU,EAEV,YAAY,EACZ,YAAY,EAEZ,OAAO,EACP,IAAI,EACJ,IAAI,EAEJ,KAAK,EACL,KAAK,GACN,MAAM,SAAS,CAAA;AAEhB,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAGjD,OAAO,EACL,eAAe,EACf,SAAS,EACT,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,GACf,MAAM,OAAO,CAAA;AACd,YAAY,EACV,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,OAAO,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,cAAc,SAAS,CAAA;AAGvB,cAAc,QAAQ,CAAA;AAGtB,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,yBAAyB,CAAA;AAEhC,YAAY,EACV,aAAa,EACb,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAEL,EAAE,EAEF,WAAW,EACX,UAAU,EACV,iBAAiB,EAEjB,YAAY,EAEZ,WAAW,EAEX,UAAU,EACV,kBAAkB,EAElB,QAAQ,EACR,UAAU,EACV,SAAS,EAET,KAAK,EACL,UAAU,EAEV,YAAY,EACZ,YAAY,EAEZ,OAAO,EACP,IAAI,EACJ,IAAI,EAEJ,KAAK,EACL,KAAK,GACN,MAAM,SAAS,CAAA;AAEhB,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAGjD,OAAO,EACL,eAAe,EACf,SAAS,EACT,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,GACf,MAAM,OAAO,CAAA;AACd,YAAY,EACV,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,OAAO,CAAA"}
@@ -16,6 +16,8 @@
16
16
  */
17
17
  // Types - all shared domain types (browser-safe)
18
18
  export * from './types';
19
+ // i18n core (browser-safe: provider + hooks + server-safe translators)
20
+ export * from './i18n';
19
21
  // Store Settings Provider and Hook (browser-safe)
20
22
  export { StoreSettingsProvider, useStoreSettings, useStoreSettingsOptional, } from './StoreSettingsProvider';
21
23
  // Utilities - browser-safe functions
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iDAAiD;AACjD,cAAc,SAAS,CAAA;AAEvB,kDAAkD;AAClD,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,yBAAyB,CAAA;AAQhC,qCAAqC;AACrC,OAAO;AACL,cAAc;AACd,EAAE;AACF,mBAAmB;AACnB,WAAW,EACX,UAAU,EACV,iBAAiB;AACjB,kBAAkB;AAClB,YAAY;AACZ,iBAAiB;AACjB,WAAW;AACX,kBAAkB;AAClB,UAAU,EACV,kBAAkB;AAClB,mBAAmB;AACnB,QAAQ,EACR,UAAU,EACV,SAAS;AACT,mBAAmB;AACnB,KAAK,EACL,UAAU;AACV,aAAa;AACb,YAAY,EACZ,YAAY;AACZ,mBAAmB;AACnB,OAAO,EACP,IAAI,EACJ,IAAI;AACJ,kBAAkB;AAClB,KAAK,EACL,KAAK,GACN,MAAM,SAAS,CAAA;AAIhB,yDAAyD;AACzD,OAAO,EACL,eAAe,EACf,SAAS,EACT,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,GACf,MAAM,OAAO,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iDAAiD;AACjD,cAAc,SAAS,CAAA;AAEvB,uEAAuE;AACvE,cAAc,QAAQ,CAAA;AAEtB,kDAAkD;AAClD,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,yBAAyB,CAAA;AAQhC,qCAAqC;AACrC,OAAO;AACL,cAAc;AACd,EAAE;AACF,mBAAmB;AACnB,WAAW,EACX,UAAU,EACV,iBAAiB;AACjB,kBAAkB;AAClB,YAAY;AACZ,iBAAiB;AACjB,WAAW;AACX,kBAAkB;AAClB,UAAU,EACV,kBAAkB;AAClB,mBAAmB;AACnB,QAAQ,EACR,UAAU,EACV,SAAS;AACT,mBAAmB;AACnB,KAAK,EACL,UAAU;AACV,aAAa;AACb,YAAY,EACZ,YAAY;AACZ,mBAAmB;AACnB,OAAO,EACP,IAAI,EACJ,IAAI;AACJ,kBAAkB;AAClB,KAAK,EACL,KAAK,GACN,MAAM,SAAS,CAAA;AAIhB,yDAAyD;AACzD,OAAO,EACL,eAAe,EACf,SAAS,EACT,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,GACf,MAAM,OAAO,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ImageGalleryUpload.d.ts","sourceRoot":"","sources":["../../../src/media/components/ImageGalleryUpload.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AASvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAU,EACV,QAAQ,EACR,MAAM,EACN,cAAc,EACd,MAAM,EACN,MAAM,EACN,QAAQ,EACR,SAAc,EACd,SAAe,EACf,QAAgB,EAChB,SAAS,GACV,EAAE,uBAAuB,2CAiZzB;AAED,eAAe,kBAAkB,CAAA"}
1
+ {"version":3,"file":"ImageGalleryUpload.d.ts","sourceRoot":"","sources":["../../../src/media/components/ImageGalleryUpload.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AASvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAU,EACV,QAAQ,EACR,MAAM,EACN,cAAc,EACd,MAAM,EACN,MAAM,EACN,QAAQ,EACR,SAAc,EACd,SAAe,EACf,QAAgB,EAChB,SAAS,GACV,EAAE,uBAAuB,2CAgYzB;AAED,eAAe,kBAAkB,CAAA"}
@@ -46,7 +46,6 @@ export function ImageGalleryUpload({ value = [], onChange, videos, onVideosChang
46
46
  // Combined list, images first then videos. Drag-to-reorder operates on
47
47
  // this list; on drop we re-split by URL extension and call both setters.
48
48
  const combined = useMemo(() => [...value, ...videosArr], [value, videosArr]);
49
- const [deletingIndex, setDeletingIndex] = useState(null);
50
49
  const [pickerOpen, setPickerOpen] = useState(false);
51
50
  const [dragIndex, setDragIndex] = useState(null);
52
51
  const [dragOverIndex, setDragOverIndex] = useState(null);
@@ -101,25 +100,16 @@ export function ImageGalleryUpload({ value = [], onChange, videos, onVideosChang
101
100
  await upload(file);
102
101
  }
103
102
  }, [upload, disabled, isUploading, maxImages, combined.length]);
104
- // Handle deletefigure out which array the URL lives in, drop it
105
- const handleDelete = useCallback(async (index) => {
106
- if (disabled || deletingIndex !== null)
103
+ // Handle removeDETACH ONLY, never delete from R2 (load-bearing since
104
+ // the shared library landed: one URL can back multiple products, and every
105
+ // file stays visible + deletable on the admin Files page. Deletion is the
106
+ // Files page's job — the Shopify model.)
107
+ const handleDelete = useCallback((index) => {
108
+ if (disabled)
107
109
  return;
108
110
  const url = combined[index];
109
111
  if (!url)
110
112
  return;
111
- setDeletingIndex(index);
112
- try {
113
- await fetch('/api/media', {
114
- method: 'DELETE',
115
- headers: { 'Content-Type': 'application/json' },
116
- body: JSON.stringify({ url }),
117
- });
118
- }
119
- catch (err) {
120
- console.error('Delete error:', err);
121
- // Continue regardless — local state should reflect user intent.
122
- }
123
113
  // Remove from whichever array owns it
124
114
  const isVideo = isVideoUrl(url);
125
115
  if (isVideo) {
@@ -130,8 +120,7 @@ export function ImageGalleryUpload({ value = [], onChange, videos, onVideosChang
130
120
  const nextImages = value.filter((u) => u !== url);
131
121
  persistBoth(nextImages, videosArr);
132
122
  }
133
- setDeletingIndex(null);
134
- }, [combined, value, videosArr, persistBoth, disabled, deletingIndex]);
123
+ }, [combined, value, videosArr, persistBoth, disabled]);
135
124
  // Drag and drop reordering — operates on the combined list, re-splits by
136
125
  // extension on drop so each kind ends up in its own array.
137
126
  const handleDragStart = useCallback((e, index) => {
@@ -197,7 +186,7 @@ export function ImageGalleryUpload({ value = [], onChange, videos, onVideosChang
197
186
  // bg-muted/30 backdrop, reads as intentional. Videos kept
198
187
  // as object-cover (different aesthetic, often have baked-in
199
188
  // bars). Same policy on storefront ProductGallery + ProductCard.
200
- _jsx(Image, { src: url, alt: `Image ${index + 1}`, fill: true, className: "object-contain", sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw" })), index === 0 && !tileIsVideo && (_jsx("div", { className: "absolute top-2 left-2 px-2 py-0.5 rounded-full bg-primary text-primary-foreground text-xs font-medium", children: "Primary" })), tileIsVideo && (_jsx("div", { className: "absolute top-2 left-2 px-2 py-0.5 rounded-full bg-black/70 text-white text-xs font-medium", children: "Video" })), _jsx("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors", children: _jsxs("div", { className: "absolute inset-0 flex items-center justify-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity", children: [index !== 0 && !tileIsVideo && (_jsx("button", { type: "button", onClick: () => handleMakePrimary(index), disabled: disabled, className: cn('rounded-full bg-white/90 p-2 text-gray-700', 'hover:bg-white transition-colors', 'disabled:opacity-50 disabled:cursor-not-allowed'), title: "Make primary", children: _jsx(Star, { className: "h-4 w-4" }) })), _jsx("button", { type: "button", onClick: () => handleDelete(index), disabled: disabled || deletingIndex !== null, className: cn('rounded-full bg-destructive p-2 text-destructive-foreground', 'hover:bg-destructive/90 transition-colors', 'disabled:opacity-50 disabled:cursor-not-allowed'), title: tileIsVideo ? 'Remove video' : 'Remove image', children: deletingIndex === index ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin" })) : (_jsx(Trash2, { className: "h-4 w-4" })) })] }) }), _jsx("div", { className: "absolute top-2 right-2 p-1 rounded bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity", children: _jsx(GripVertical, { className: "h-4 w-4 text-white" }) })] }, url));
189
+ _jsx(Image, { src: url, alt: `Image ${index + 1}`, fill: true, className: "object-contain", sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw" })), index === 0 && !tileIsVideo && (_jsx("div", { className: "absolute top-2 left-2 px-2 py-0.5 rounded-full bg-primary text-primary-foreground text-xs font-medium", children: "Primary" })), tileIsVideo && (_jsx("div", { className: "absolute top-2 left-2 px-2 py-0.5 rounded-full bg-black/70 text-white text-xs font-medium", children: "Video" })), _jsx("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors", children: _jsxs("div", { className: "absolute inset-0 flex items-center justify-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity", children: [index !== 0 && !tileIsVideo && (_jsx("button", { type: "button", onClick: () => handleMakePrimary(index), disabled: disabled, className: cn('rounded-full bg-white/90 p-2 text-gray-700', 'hover:bg-white transition-colors', 'disabled:opacity-50 disabled:cursor-not-allowed'), title: "Make primary", children: _jsx(Star, { className: "h-4 w-4" }) })), _jsx("button", { type: "button", onClick: () => handleDelete(index), disabled: disabled, className: cn('rounded-full bg-destructive p-2 text-destructive-foreground', 'hover:bg-destructive/90 transition-colors', 'disabled:opacity-50 disabled:cursor-not-allowed'), title: tileIsVideo ? 'Remove video' : 'Remove image', children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) }), _jsx("div", { className: "absolute top-2 right-2 p-1 rounded bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity", children: _jsx(GripVertical, { className: "h-4 w-4 text-white" }) })] }, url));
201
190
  }) })), canAddMore && (_jsx(DropZone, { onFiles: handleFiles, multiple: maxImages - combined.length > 1, accept: dropZoneAccept, maxSize: maxSizeMB * 1024 * 1024, disabled: disabled || isUploading, className: "min-h-[120px]", children: _jsx("div", { className: "text-muted-foreground", children: isUploading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Loader2, { className: "mx-auto h-8 w-8 animate-spin" }), _jsx("p", { className: "text-sm", children: "Uploading..." }), progress && (_jsxs("div", { className: "w-full max-w-[200px] mx-auto", children: [_jsx("div", { className: "h-1.5 bg-muted rounded-full overflow-hidden", children: _jsx("div", { className: "h-full bg-primary transition-all duration-300", style: { width: `${progress.percentage}%` } }) }), _jsxs("p", { className: "text-xs mt-1", children: [progress.percentage, "%"] })] }))] })) : (_jsxs(_Fragment, { children: [_jsx(ImagePlus, { className: "mx-auto h-10 w-10 mb-2" }), _jsx("p", { className: "text-sm font-medium", children: combined.length === 0
202
191
  ? 'Add product media'
203
192
  : 'Add more media' }), _jsx("p", { className: "text-xs mt-1", children: "Drop images or videos, or click to browse" }), _jsxs("p", { className: "text-xs text-muted-foreground/70", children: [combined.length, " / ", maxImages, " items"] })] })) }) })), canAddMore && !isUploading && (_jsxs("button", { type: "button", onClick: () => setPickerOpen(true), disabled: disabled, className: "inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground disabled:opacity-50", children: [_jsx(FolderOpen, { className: "h-3.5 w-3.5" }), "Choose from library"] })), pickerOpen && (_jsx(MediaLibraryPicker, { isOpen: pickerOpen, onClose: () => setPickerOpen(false), onPick: handleLibraryPick, maxPick: Math.max(1, maxImages - combined.length), kinds: ['image', 'video'] })), !canAddMore && (_jsxs("p", { className: "text-sm text-muted-foreground text-center", children: ["Maximum ", maxImages, " items reached. Remove one to add more."] })), error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-destructive", children: [_jsx(AlertCircle, { className: "h-4 w-4 flex-shrink-0" }), _jsx("span", { children: error }), _jsx("button", { type: "button", onClick: reset, className: "text-primary hover:underline ml-auto", children: "Retry" })] })), combined.length > 1 && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Drag to reorder. The first image is the primary product image. Videos can be reordered but cannot be set as primary." }))] }));