@cartbase/storefront 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartbase/storefront",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Storefront SDK + UI component library for Cartbase stores: typed API client, checkout orchestration, cart drawer, product/catalog components, tracking. Source-shipped TypeScript — add it to transpilePackages.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -103,7 +103,6 @@
103
103
  "./checkout": "./src/checkout/index.ts",
104
104
  "./checkout/context": "./src/checkout/context.tsx",
105
105
  "./checkout/labels": "./src/checkout/labels.ts",
106
- "./checkout/labels-bg": "./src/checkout/labels-bg.ts",
107
106
  "./checkout/error-message": "./src/checkout/error-message.tsx",
108
107
  "./checkout/stripe-wrapper": "./src/checkout/stripe-wrapper.tsx",
109
108
  "./checkout/payment-wrapper": "./src/checkout/payment-wrapper.tsx",
@@ -127,7 +126,6 @@
127
126
  "./cart-drawer": "./src/cart-drawer/index.ts",
128
127
  "./cart-drawer/context": "./src/cart-drawer/context.tsx",
129
128
  "./cart-drawer/labels": "./src/cart-drawer/labels.ts",
130
- "./cart-drawer/labels-bg": "./src/cart-drawer/labels-bg.ts",
131
129
  "./cart-drawer/cart-drawer": "./src/cart-drawer/cart-drawer.tsx",
132
130
  "./cart-drawer/header": "./src/cart-drawer/header.tsx",
133
131
  "./cart-drawer/promo-banner": "./src/cart-drawer/promo-banner.tsx",
@@ -151,7 +149,6 @@
151
149
  "./products": "./src/products/index.ts",
152
150
  "./products/context": "./src/products/context.tsx",
153
151
  "./products/labels": "./src/products/labels.ts",
154
- "./products/labels-bg": "./src/products/labels-bg.ts",
155
152
  "./products/thumbnail": "./src/products/thumbnail.tsx",
156
153
  "./products/preview-price": "./src/products/preview-price.tsx",
157
154
  "./products/product-price": "./src/products/product-price.tsx",
@@ -167,7 +164,6 @@
167
164
  "./products/product-template": "./src/products/product-template.tsx",
168
165
  "./store": "./src/store/index.ts",
169
166
  "./store/labels": "./src/store/labels.ts",
170
- "./store/labels-bg": "./src/store/labels-bg.ts",
171
167
  "./store/pagination": "./src/store/pagination.tsx",
172
168
  "./store/sort-select": "./src/store/sort-select.tsx",
173
169
  "./store/paginated-products": "./src/store/paginated-products.tsx",
@@ -179,7 +175,6 @@
179
175
  "./order": "./src/order/index.ts",
180
176
  "./order/context": "./src/order/context.tsx",
181
177
  "./order/labels": "./src/order/labels.ts",
182
- "./order/labels-bg": "./src/order/labels-bg.ts",
183
178
  "./order/order-confirmation-header": "./src/order/order-confirmation-header.tsx",
184
179
  "./order/order-item": "./src/order/order-item.tsx",
185
180
  "./order/order-items-list": "./src/order/order-items-list.tsx",
@@ -198,7 +193,11 @@
198
193
  "./common/country-select": "./src/common/country-select.tsx",
199
194
  "./common/language-select": "./src/common/language-select.tsx",
200
195
  "./common/skeleton": "./src/common/skeleton.tsx",
201
- "./reviews-ui": "./src/reviews-ui/index.ts"
196
+ "./reviews-ui": "./src/reviews-ui/index.ts",
197
+ "./locales": "./src/locales/index.ts",
198
+ "./locales/en": "./src/locales/en.ts",
199
+ "./locales/es": "./src/locales/es.ts",
200
+ "./locales/bg": "./src/locales/bg.ts"
202
201
  },
203
202
  "dependencies": {
204
203
  "@stripe/react-stripe-js": "^6.1.0",
@@ -28,7 +28,6 @@ export {
28
28
  defaultCartDrawerLabels,
29
29
  type CartDrawerLabels,
30
30
  } from "./labels"
31
- export { bulgarianCartDrawerLabels } from "./labels-bg"
32
31
 
33
32
  export { CartDrawer } from "./cart-drawer"
34
33
  export { CartDrawerHeader } from "./header"
@@ -1,119 +1,117 @@
1
- /**
2
- * Translates address-save errors into user-friendly Bulgarian copy for the
3
- * checkout address surface.
4
- *
5
- * Ported from `@1click/ui/src/checkout/address-error-copy.ts` (v2.3.1) and
6
- * adapted to the Cartbase error envelope: `StoreApiError` carries a structured
7
- * `code` (POST /api/store/carts/:id docs/storefront/carts.md), so
8
- * translation is code-first; the substring patterns stay as the fallback for
9
- * network failures and any legacy wire.
10
- *
11
- * The mapping is deliberately narrow: only translate errors we recognize;
12
- * everything else falls back to a clean generic message rather than
13
- * half-translated text.
14
- */
15
-
16
- const GENERIC_FALLBACK =
17
- "Възникна проблем при запазване на адреса. Моля, проверете данните и опитайте отново."
18
-
19
- const NETWORK_FALLBACK =
20
- "Няма връзка със сървъра. Моля, проверете интернет връзката и опитайте отново."
21
-
22
- /**
23
- * Cartbase cart-update error codes BG copy (POST /api/store/carts/:id,
24
- * docs/storefront/carts.md). Code-first, matched before any pattern.
25
- */
26
- export const ADDRESS_ERROR_CODE_COPY: Record<string, string> = {
27
- validation_failed:
28
- "Някое от полетата не е валидно. Моля, проверете данните и опитайте отново.",
29
- invalid_region: "Невалидна държава. Моля, презаредете страницата.",
30
- region_required: "Регионът на доставка не е валиден. Моля, презаредете страницата.",
31
- cart_completed:
32
- "Поръчката вече е финализирана. Моля, проверете имейла си за потвърждение.",
33
- cart_not_found: "Сесията на количката изтече. Моля, презаредете страницата.",
34
- }
35
-
36
- /**
37
- * Patterns matched in order — first match wins. List most specific
38
- * first, generic last. Match is case-insensitive (regexes carry /i).
39
- * Kept verbatim from the production port.
40
- */
41
- const PATTERNS: Array<{ match: RegExp; copy: string }> = [
42
- // Email validation — confirmed (in @1click production) as the most common
43
- // address-save failure shape.
44
- {
45
- match: /invalid[\s_]?(?:request:?\s*)?invalid[\s_]?email|invalid[\s_]?email/i,
46
- copy: "Невалиден имейл адрес. Моля, проверете изписването.",
47
- },
48
- // Postal / postcode validation
49
- {
50
- match: /invalid[\s_]?postal|invalid[\s_]?post(?:code|al[\s_]?code)|postal[\s_]?code[\s_]?(?:is[\s_]?)?invalid/i,
51
- copy: "Невалиден пощенски код. Моля, въведете 4-цифрен код.",
52
- },
53
- // Phone validation
54
- {
55
- match: /invalid[\s_]?phone|phone[\s_]?(?:number[\s_]?)?(?:is[\s_]?)?invalid/i,
56
- copy: "Невалиден телефонен номер. Моля, проверете цифрите.",
57
- },
58
- // Country / region mismatch
59
- {
60
- match: /invalid[\s_]?country|country[\s_]?(?:code[\s_]?)?(?:is[\s_]?)?invalid|no[\s_]?region|invalid[\s_]?region/i,
61
- copy: "Невалидна държава. Моля, презаредете страницата.",
62
- },
63
- // Cart already completed — race with order placement
64
- {
65
- match: /cart[\s_]?(?:is[\s_]?)?already[\s_]?completed|already[\s_]?an[\s_]?order|completed[\s_]?cart/i,
66
- copy: "Поръчката вече е финализирана. Моля, проверете имейла си за потвърждение.",
67
- },
68
- // Cart not found / expired
69
- {
70
- match: /cart[\s_]?(?:not[\s_]?found|does[\s_]?not[\s_]?exist|expired)/i,
71
- copy: "Сесията на количката изтече. Моля, презаредете страницата.",
72
- },
73
- // Network / fetch failure
74
- {
75
- match: /failed[\s_]?to[\s_]?fetch|network[\s_]?error|networkerror|econnreset|etimedout/i,
76
- copy: NETWORK_FALLBACK,
77
- },
78
- // The Next.js canned Server Components production message surface a
79
- // clean fallback instead of the English placeholder. (The Cartbase SDK
80
- // throws from the browser, so this only fires for stores that proxy the
81
- // save through a server action.)
82
- {
83
- match: /server[\s_]?components[\s_]?render|digest property is included/i,
84
- copy: GENERIC_FALLBACK,
85
- },
86
- ]
87
-
88
- /**
89
- * Translate any thrown / returned address-save error into
90
- * customer-facing Bulgarian copy. Returns the generic fallback when
91
- * the error doesn't match a known code or pattern.
92
- */
93
- export function translateAddressError(err: unknown): string {
94
- if (err && typeof err === "object") {
95
- const code = (err as { code?: unknown }).code
96
- if (typeof code === "string" && ADDRESS_ERROR_CODE_COPY[code]) {
97
- return ADDRESS_ERROR_CODE_COPY[code]
98
- }
99
- }
100
-
101
- const raw = extractMessage(err)
102
- if (!raw) return GENERIC_FALLBACK
103
-
104
- for (const { match, copy } of PATTERNS) {
105
- if (match.test(raw)) return copy
106
- }
107
- return GENERIC_FALLBACK
108
- }
109
-
110
- function extractMessage(err: unknown): string {
111
- if (!err) return ""
112
- if (typeof err === "string") return err
113
- if (err instanceof Error) return err.message
114
- if (typeof err === "object" && err !== null) {
115
- const e = err as { message?: string; code?: string; type?: string }
116
- return [e.message, e.code, e.type].filter(Boolean).join(" ")
117
- }
118
- return String(err)
119
- }
1
+ import type { AddressErrorCopy } from "./labels"
2
+
3
+ /**
4
+ * Recognizes an address-save failure and picks the copy for it.
5
+ *
6
+ * THE COPY IS NOT HERE. Until 2026-09-13 this file held thirteen hardcoded
7
+ * BULGARIAN sentences, so every merchant who installed the package showed
8
+ * Bulgarian on the one screen where a shopper is about to pay, with no way
9
+ * to override it. The copy now lives in the checkout label pack like all
10
+ * other copy, which means it is English by default and translated by
11
+ * whichever locale the store mounts.
12
+ *
13
+ * WHAT STAYS HERE IS THE RECOGNITION, and it stays on purpose: the codes
14
+ * and the patterns match the API's own error text, which is English wire
15
+ * format, not user-facing copy. Translating the matcher would break it.
16
+ *
17
+ * Ported from `@1click/ui/src/checkout/address-error-copy.ts` (v2.3.1) and
18
+ * adapted to the Cartbase error envelope: `StoreApiError` carries a
19
+ * structured `code` (POST /api/store/carts/:id, docs/storefront/carts.md),
20
+ * so recognition is code-first and the substring patterns are the fallback
21
+ * for network failures and any legacy wire.
22
+ *
23
+ * The mapping is deliberately narrow: only failures we recognize get their
24
+ * own sentence, and everything else gets the generic one rather than
25
+ * half-translated technical text.
26
+ */
27
+
28
+ /** Cartbase cart-update error codes, mapped to a copy key. Code-first. */
29
+ export const ADDRESS_CODE_KEYS: Record<string, keyof AddressErrorCopy> = {
30
+ validation_failed: "validationFailed",
31
+ invalid_region: "invalidRegion",
32
+ region_required: "regionRequired",
33
+ cart_completed: "cartCompleted",
34
+ cart_not_found: "cartNotFound",
35
+ }
36
+
37
+ /**
38
+ * Patterns matched in order, first match wins: most specific first,
39
+ * generic last. Case-insensitive. Kept verbatim from the production port.
40
+ */
41
+ const PATTERNS: Array<{ match: RegExp; key: keyof AddressErrorCopy }> = [
42
+ // Email validation — confirmed (in @1click production) as the most common
43
+ // address-save failure shape.
44
+ {
45
+ match: /invalid[\s_]?(?:request:?\s*)?invalid[\s_]?email|invalid[\s_]?email/i,
46
+ key: "invalidEmail",
47
+ },
48
+ {
49
+ match:
50
+ /invalid[\s_]?postal|invalid[\s_]?post(?:code|al[\s_]?code)|postal[\s_]?code[\s_]?(?:is[\s_]?)?invalid/i,
51
+ key: "invalidPostalCode",
52
+ },
53
+ {
54
+ match: /invalid[\s_]?phone|phone[\s_]?(?:number[\s_]?)?(?:is[\s_]?)?invalid/i,
55
+ key: "invalidPhone",
56
+ },
57
+ {
58
+ match:
59
+ /invalid[\s_]?country|country[\s_]?(?:code[\s_]?)?(?:is[\s_]?)?invalid|no[\s_]?region|invalid[\s_]?region/i,
60
+ key: "invalidCountry",
61
+ },
62
+ // Cart already completed — race with order placement.
63
+ {
64
+ match:
65
+ /cart[\s_]?(?:is[\s_]?)?already[\s_]?completed|already[\s_]?an[\s_]?order|completed[\s_]?cart/i,
66
+ key: "cartCompleted",
67
+ },
68
+ {
69
+ match: /cart[\s_]?(?:not[\s_]?found|does[\s_]?not[\s_]?exist|expired)/i,
70
+ key: "cartNotFound",
71
+ },
72
+ {
73
+ match: /failed[\s_]?to[\s_]?fetch|network[\s_]?error|networkerror|econnreset|etimedout/i,
74
+ key: "network",
75
+ },
76
+ // The Next.js canned Server Components production message — surface a
77
+ // clean fallback instead of the English placeholder. (The Cartbase SDK
78
+ // throws from the browser, so this only fires for stores that proxy the
79
+ // save through a server action.)
80
+ {
81
+ match: /server[\s_]?components[\s_]?render|digest property is included/i,
82
+ key: "generic",
83
+ },
84
+ ]
85
+
86
+ /** Which copy an error asks for, independent of language. */
87
+ export function addressErrorKey(err: unknown): keyof AddressErrorCopy {
88
+ if (err && typeof err === "object") {
89
+ const code = (err as { code?: unknown }).code
90
+ if (typeof code === "string" && ADDRESS_CODE_KEYS[code]) return ADDRESS_CODE_KEYS[code]
91
+ }
92
+ const raw = extractMessage(err)
93
+ if (!raw) return "generic"
94
+ for (const { match, key } of PATTERNS) {
95
+ if (match.test(raw)) return key
96
+ }
97
+ return "generic"
98
+ }
99
+
100
+ /**
101
+ * Turn any thrown or returned address-save error into the sentence a
102
+ * shopper reads, in the store's own language.
103
+ */
104
+ export function translateAddressError(err: unknown, copy: AddressErrorCopy): string {
105
+ return copy[addressErrorKey(err)]
106
+ }
107
+
108
+ function extractMessage(err: unknown): string {
109
+ if (!err) return ""
110
+ if (typeof err === "string") return err
111
+ if (err instanceof Error) return err.message
112
+ if (typeof err === "object" && err !== null) {
113
+ const e = err as { message?: string; code?: string; type?: string }
114
+ return [e.message, e.code, e.type].filter(Boolean).join(" ")
115
+ }
116
+ return String(err)
117
+ }