@base44/app-plugin-commerce 0.9.4 → 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.
@@ -0,0 +1,128 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { Link } from "react-router-dom";
4
+ import { useCart, useCartUI, useFormatMoney } from "@/commerce/storefront";
5
+ import { makeT } from "./i18n/index.js";
6
+ import { CartLineRow, LoadingState, SfuiRoot } from "./internal.jsx";
7
+
8
+ const BRAND_KEYS = {
9
+ title: "minicart.title",
10
+ checkoutLabel: "minicart.checkout",
11
+ viewCartLabel: "minicart.viewCart",
12
+ emptyTitle: "minicart.empty.title",
13
+ emptyCta: "minicart.empty.cta",
14
+ };
15
+
16
+ /**
17
+ * The cart drawer — a complete overlay: portal, backdrop, dialog semantics,
18
+ * focus trap, body scroll-lock. Mount it ONCE inside the store's layout (never
19
+ * on a route); it opens and closes through <CartUIProvider>'s state, so
20
+ * `useAddToCart` flows and the store's cart button drive it with no extra
21
+ * wiring. Escape and close-on-navigate are the provider's job already.
22
+ *
23
+ * @param {object} props
24
+ * @param {Record<string,string>} [props.brand] wording overrides (title,
25
+ * checkoutLabel, viewCartLabel, emptyTitle, emptyCta).
26
+ * @param {"right"|"left"} [props.side="right"]
27
+ * @param {string} [props.checkoutHref="/checkout"]
28
+ * @param {string} [props.cartHref] renders a "view cart" link when given.
29
+ * @param {(item) => string} [props.productHref]
30
+ */
31
+ export function MiniCart({ brand, side = "right", checkoutHref = "/checkout", cartHref, productHref }) {
32
+ const tt = makeT(brand, BRAND_KEYS);
33
+ const { open, closeCart } = useCartUI();
34
+ const { status, cart, itemCount } = useCart();
35
+ const formatMoney = useFormatMoney();
36
+ const panelRef = useRef(null);
37
+ const restoreRef = useRef(null);
38
+
39
+ // Focus management + scroll lock, for exactly the open window.
40
+ useEffect(() => {
41
+ if (!open) return;
42
+ restoreRef.current = document.activeElement;
43
+ const prevOverflow = document.body.style.overflow;
44
+ document.body.style.overflow = "hidden";
45
+ panelRef.current?.focus();
46
+ const trap = (e) => {
47
+ if (e.key !== "Tab" || !panelRef.current) return;
48
+ const focusables = panelRef.current.querySelectorAll(
49
+ 'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])',
50
+ );
51
+ if (!focusables.length) return;
52
+ const first = focusables[0];
53
+ const last = focusables[focusables.length - 1];
54
+ if (e.shiftKey && document.activeElement === first) {
55
+ e.preventDefault();
56
+ last.focus();
57
+ } else if (!e.shiftKey && document.activeElement === last) {
58
+ e.preventDefault();
59
+ first.focus();
60
+ }
61
+ };
62
+ document.addEventListener("keydown", trap);
63
+ return () => {
64
+ document.removeEventListener("keydown", trap);
65
+ document.body.style.overflow = prevOverflow;
66
+ restoreRef.current?.focus?.();
67
+ };
68
+ }, [open]);
69
+
70
+ if (!open || typeof document === "undefined") return null;
71
+
72
+ return createPortal(
73
+ <SfuiRoot as="div">
74
+ <div className="sfui-drawer-backdrop" onClick={closeCart} aria-hidden="true" />
75
+ <div
76
+ className="sfui-drawer"
77
+ data-side={side}
78
+ role="dialog"
79
+ aria-modal="true"
80
+ aria-label={tt("minicart.title")}
81
+ tabIndex={-1}
82
+ ref={panelRef}
83
+ >
84
+ <div className="sfui-drawer-head">
85
+ <h2 className="sfui-heading sfui-h2">{tt("minicart.title")}</h2>
86
+ <button type="button" className="sfui-drawer-close" onClick={closeCart} aria-label={tt("minicart.close")}>
87
+ ×
88
+ </button>
89
+ </div>
90
+ <div className="sfui-drawer-body">
91
+ {status === "loading" && <LoadingState />}
92
+ {status === "empty" && (
93
+ <div className="sfui-state">
94
+ <h3 className="sfui-heading sfui-h2">{tt("minicart.empty.title")}</h3>
95
+ <button type="button" className="sfui-btn sfui-btn-ghost sfui-btn-inline" onClick={closeCart}>
96
+ {tt("minicart.empty.cta")}
97
+ </button>
98
+ </div>
99
+ )}
100
+ {status === "ready" && (
101
+ <ul className="sfui-lines">
102
+ {cart.items.map((item) => (
103
+ <CartLineRow key={item.item_key} item={item} tt={tt} productHref={productHref} compact />
104
+ ))}
105
+ </ul>
106
+ )}
107
+ </div>
108
+ {status === "ready" && (
109
+ <div className="sfui-drawer-foot">
110
+ <div className="sfui-drawer-subtotal">
111
+ <span>{tt("minicart.subtotal")}</span>
112
+ <span>{formatMoney(cart.totals?.subtotal)}</span>
113
+ </div>
114
+ <Link className="sfui-btn" to={checkoutHref} onClick={closeCart}>
115
+ {tt("minicart.checkout")}
116
+ </Link>
117
+ {cartHref && (
118
+ <Link className="sfui-btn sfui-btn-ghost" to={cartHref} onClick={closeCart}>
119
+ {tt("minicart.viewCart")} {itemCount ? `(${itemCount})` : ""}
120
+ </Link>
121
+ )}
122
+ </div>
123
+ )}
124
+ </div>
125
+ </SfuiRoot>,
126
+ document.body,
127
+ );
128
+ }
@@ -0,0 +1,159 @@
1
+ import React from "react";
2
+ import { Link } from "react-router-dom";
3
+ import { useFormatMoney, useOrderReturn } from "@/commerce/storefront";
4
+ import { orderTotalsLines } from "@/commerce/utils";
5
+ import { makeT, t } from "./i18n/index.js";
6
+ import { ErrorState, LoadingState, SfuiRoot } from "./internal.jsx";
7
+
8
+ const BRAND_KEYS = {
9
+ paidTitle: "order.paid.title",
10
+ paidBody: "order.paid.body",
11
+ unpaidTitle: "order.unpaid.title",
12
+ unpaidBody: "order.unpaid.body",
13
+ backToStore: "order.backToStore",
14
+ };
15
+
16
+ /**
17
+ * The mandatory /order-received page, complete: every payment link returns
18
+ * here, and this page is how a manual (offline) order tells the customer how
19
+ * to pay. Renders all of useOrderReturn's states — paid, unpaid (payment
20
+ * instructions + pay-now link), cancelled, error — plus the order's lines and
21
+ * totals. Mount it on the `/order-received` route.
22
+ *
23
+ * @param {object} props
24
+ * @param {Record<string,string>} [props.brand] wording overrides (paidTitle,
25
+ * paidBody, unpaidTitle, unpaidBody, backToStore).
26
+ * @param {string} [props.homeHref="/"]
27
+ */
28
+ export function OrderReceivedPage({ brand, homeHref = "/" }) {
29
+ const tt = makeT(brand, BRAND_KEYS);
30
+ const { status, order, lines, paymentLink, paymentInstructions, error, reload } = useOrderReturn();
31
+
32
+ return (
33
+ <SfuiRoot className="sfui-order">
34
+ <div className="sfui-order-inner">
35
+ {status === "loading" && <LoadingState />}
36
+
37
+ {status === "error" && (
38
+ <ErrorState title={tt("order.error.title")} body={error?.message ?? tt("order.error.body")} onRetry={reload} />
39
+ )}
40
+
41
+ {status === "cancelled" && (
42
+ <div className="sfui-order-head">
43
+ <h1 className="sfui-heading sfui-h1">{tt("order.cancelled.title")}</h1>
44
+ <p className="sfui-muted">{tt("order.cancelled.body")}</p>
45
+ {paymentLink?.url && (
46
+ <a className="sfui-btn sfui-btn-inline" href={paymentLink.url}>
47
+ {tt("order.retryPayment")}
48
+ </a>
49
+ )}
50
+ <Link className="sfui-link" to={homeHref}>
51
+ {tt("order.backToStore")}
52
+ </Link>
53
+ </div>
54
+ )}
55
+
56
+ {(status === "paid" || status === "unpaid") && (
57
+ <>
58
+ <div className="sfui-order-head">
59
+ <div className="sfui-order-badge" aria-hidden="true">
60
+ {status === "paid" ? "✓" : "…"}
61
+ </div>
62
+ <h1 className="sfui-heading sfui-h1">
63
+ {status === "paid" ? tt("order.paid.title") : tt("order.unpaid.title")}
64
+ </h1>
65
+ <p className="sfui-muted">
66
+ {status === "paid" ? tt("order.paid.body") : tt("order.unpaid.body")}
67
+ </p>
68
+ </div>
69
+
70
+ {status === "unpaid" && (paymentInstructions || paymentLink?.url) && (
71
+ <div className="sfui-instructions">
72
+ <h2 className="sfui-heading sfui-h2">{tt("order.instructionsTitle")}</h2>
73
+ {paymentInstructions?.description && <p className="sfui-muted">{paymentInstructions.description}</p>}
74
+ {paymentInstructions?.account_details && (
75
+ <dl>
76
+ {Object.entries(paymentInstructions.account_details).map(([k, v]) => (
77
+ <div key={k}>
78
+ <dt>{k}</dt>
79
+ <dd>{String(v)}</dd>
80
+ </div>
81
+ ))}
82
+ </dl>
83
+ )}
84
+ {paymentLink?.url && (
85
+ <a className="sfui-btn" href={paymentLink.url}>
86
+ {tt("order.payNow")}
87
+ </a>
88
+ )}
89
+ </div>
90
+ )}
91
+
92
+ {order && (
93
+ <div className="sfui-panel sfui-order-card">
94
+ <div className="sfui-order-meta">
95
+ <div>
96
+ <dt className="sfui-label">{tt("order.orderLabel")}</dt>
97
+ <dd>{order.number ?? order.id}</dd>
98
+ </div>
99
+ <div style={{ textAlign: "end" }}>
100
+ <dt className="sfui-label">{tt("order.dateLabel")}</dt>
101
+ <dd>{order.created_date ? new Date(order.created_date).toLocaleDateString() : ""}</dd>
102
+ </div>
103
+ </div>
104
+ <ul className="sfui-lines">
105
+ {lines.map((l, i) => (
106
+ <li className="sfui-line" key={i}>
107
+ <div className="sfui-line-media" aria-hidden="true">
108
+ {l.image?.src && <img src={l.image.src} alt="" loading="lazy" />}
109
+ </div>
110
+ <div className="sfui-line-main">
111
+ <p className="sfui-line-name">{l.name}</p>
112
+ {l.attributesLabel && <p className="sfui-line-attrs">{l.attributesLabel}</p>}
113
+ <p className="sfui-line-attrs">{tt("order.qty", { count: l.quantity })}</p>
114
+ </div>
115
+ <div className="sfui-line-end">
116
+ <span className="sfui-line-price">{l.totalLabel}</span>
117
+ </div>
118
+ </li>
119
+ ))}
120
+ </ul>
121
+ <OrderTotals order={order} />
122
+ </div>
123
+ )}
124
+
125
+ <p style={{ textAlign: "center", margin: 0 }}>
126
+ <Link className="sfui-link" to={homeHref}>
127
+ {tt("order.backToStore")}
128
+ </Link>
129
+ </p>
130
+ </>
131
+ )}
132
+ </div>
133
+ </SfuiRoot>
134
+ );
135
+ }
136
+
137
+ function OrderTotals({ order }) {
138
+ const formatMoney = useFormatMoney();
139
+ const lines = orderTotalsLines(order, {
140
+ formatMoney,
141
+ labels: {
142
+ subtotal: t("common.totals.subtotal"),
143
+ discount: t("common.totals.discount"),
144
+ shipping: t("common.totals.shipping"),
145
+ tax: t("common.totals.tax"),
146
+ total: t("common.totals.total"),
147
+ },
148
+ }).filter((l) => !l.hidden);
149
+ return (
150
+ <dl className="sfui-totals">
151
+ {lines.map((l) => (
152
+ <div key={l.key} data-emphasis={l.emphasis || undefined}>
153
+ <dt>{l.label}</dt>
154
+ <dd>{l.formatted}</dd>
155
+ </div>
156
+ ))}
157
+ </dl>
158
+ );
159
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * storefront-ui localization — the admin i18n pattern, for the shipped
3
+ * customer-facing surfaces (CartPage, MiniCart, CheckoutPage,
4
+ * OrderReceivedPage). Every functional label renders through `t()`; the
5
+ * wording lives in `./locales/<lang>.js` (en, de, es, fr, ja, pt ship with the
6
+ * plugin; en is the default and the fallback).
7
+ *
8
+ * Switching the storefront's language is a one-line change: point the `active`
9
+ * import below at another locale file. A language outside the six: copy
10
+ * `./locales/en.js` to `./locales/<code>.js`, set `$locale`, translate every
11
+ * value (keep `{placeholders}` verbatim), point the import at it. Missing keys
12
+ * fall back to English, so a partial file renders mixed rather than broken.
13
+ *
14
+ * Brand wording (titles, CTAs, trust notes — the store's voice) does NOT live
15
+ * here: it comes in per-component via the `brand` prop, which overrides the
16
+ * matching keys. Precedence: brand prop → active locale → English.
17
+ */
18
+ import en from "./locales/en.js";
19
+ import active from "./locales/en.js"; // ← the storefront's language: "./locales/de.js", "./locales/es.js", "./locales/fr.js", "./locales/ja.js", "./locales/pt.js"
20
+
21
+ /** BCP-47 tag of the active language — set it on the page's <html lang> too. */
22
+ export const locale = active.$locale || "en";
23
+
24
+ function lookup(overrides, key, vars) {
25
+ let s = overrides?.[key] ?? active[key] ?? en[key] ?? key;
26
+ if (vars) {
27
+ for (const k of Object.keys(vars)) s = s.split(`{${k}}`).join(String(vars[k] ?? ""));
28
+ }
29
+ return s;
30
+ }
31
+
32
+ /** Message lookup with `{name}` interpolation and English fallback. */
33
+ export function t(key, vars) {
34
+ return lookup(null, key, vars);
35
+ }
36
+
37
+ /**
38
+ * A `t` bound to one component's brand overrides. `brand` maps SHORT names to
39
+ * strings; `keymap` maps those short names to the full i18n keys, so
40
+ * `brand={{ title: "Your bag" }}` overrides exactly `cart.title` and nothing
41
+ * else. Unknown short names are ignored.
42
+ */
43
+ export function makeT(brand, keymap) {
44
+ if (!brand) return t;
45
+ const overrides = {};
46
+ for (const short of Object.keys(brand)) {
47
+ const full = keymap[short];
48
+ if (full && brand[short] != null) overrides[full] = brand[short];
49
+ }
50
+ return (key, vars) => lookup(overrides, key, vars);
51
+ }
@@ -0,0 +1,89 @@
1
+ /** Deutsch — formelle Anrede (Sie) durchgehend. Schlüssel wie in en.js. */
2
+ export default {
3
+ $locale: "de",
4
+
5
+ "common.loading": "Wird geladen…",
6
+ "common.error.title": "Etwas ist schiefgelaufen",
7
+ "common.error.retry": "Erneut versuchen",
8
+ "common.remove": "Entfernen",
9
+ "common.quantity": "Menge",
10
+ "common.decrease": "Menge verringern",
11
+ "common.increase": "Menge erhöhen",
12
+ "common.free": "Kostenlos",
13
+ "common.totals.subtotal": "Zwischensumme",
14
+ "common.totals.discount": "Rabatt",
15
+ "common.totals.shipping": "Versand",
16
+ "common.totals.tax": "Steuern",
17
+ "common.totals.total": "Gesamtsumme",
18
+ "common.coupon.label": "Rabattcode",
19
+ "common.coupon.placeholder": "Code eingeben",
20
+ "common.coupon.apply": "Einlösen",
21
+ "common.coupon.applied": "Code {code} eingelöst",
22
+ "common.coupon.remove": "Code entfernen",
23
+
24
+ "cart.title": "Ihr Warenkorb",
25
+ "cart.summaryTitle": "Bestellübersicht",
26
+ "cart.checkoutCta": "Zur Kasse",
27
+ "cart.continueShopping": "Weiter einkaufen",
28
+ "cart.empty.title": "Ihr Warenkorb ist leer",
29
+ "cart.empty.body": "Alles, was Sie hinzufügen, erscheint hier.",
30
+ "cart.empty.cta": "Zum Shop",
31
+ "cart.notes.label": "Anmerkungen zur Bestellung",
32
+ "cart.notes.placeholder": "Gibt es etwas, das wir wissen sollten?",
33
+ "cart.taxNote": "Versandkosten und Steuern werden an der Kasse berechnet.",
34
+
35
+ "minicart.title": "Ihr Warenkorb",
36
+ "minicart.close": "Warenkorb schließen",
37
+ "minicart.subtotal": "Zwischensumme",
38
+ "minicart.checkout": "Zur Kasse",
39
+ "minicart.viewCart": "Warenkorb ansehen",
40
+ "minicart.empty.title": "Ihr Warenkorb ist leer",
41
+ "minicart.empty.cta": "Weiter stöbern",
42
+ "minicart.button": "Warenkorb öffnen",
43
+
44
+ "checkout.title": "Kasse",
45
+ "checkout.contactTitle": "Kontakt & Rechnungsadresse",
46
+ "checkout.shipToDifferent": "An eine andere Adresse liefern",
47
+ "checkout.shippingAddressTitle": "Lieferadresse",
48
+ "checkout.shippingTitle": "Versandart",
49
+ "checkout.paymentTitle": "Zahlung",
50
+ "checkout.summaryTitle": "Bestellübersicht",
51
+ "checkout.notes.label": "Anmerkungen zur Bestellung",
52
+ "checkout.notes.placeholder": "Gibt es etwas, das wir wissen sollten?",
53
+ "checkout.terms": "Ich stimme den Verkaufsbedingungen zu",
54
+ "checkout.placeOrder": "Jetzt bestellen",
55
+ "checkout.placing": "Ihre Bestellung wird aufgegeben…",
56
+ "checkout.submitted": "Bestellung aufgegeben — Sie werden zur Bestätigung weitergeleitet…",
57
+ "checkout.empty.title": "Es gibt noch nichts zu bestellen",
58
+ "checkout.empty.cta": "Zurück zum Shop",
59
+ "checkout.hint.missing_address": "Geben Sie Ihre Adresse ein, um Lieferoptionen zu sehen.",
60
+ "checkout.hint.none_available": "An diese Adresse können wir leider noch nicht liefern.",
61
+ "checkout.hint.syncing": "Lieferoptionen werden aktualisiert…",
62
+ "checkout.hint.payment_none": "Die Kasse ist vorübergehend nicht verfügbar — keine Zahlungsart aktiviert.",
63
+ "checkout.blocker.cart_loading": "Ihr Warenkorb wird geladen…",
64
+ "checkout.blocker.empty_cart": "Ihr Warenkorb ist leer.",
65
+ "checkout.blocker.billing_incomplete": "Füllen Sie die erforderlichen Kontaktfelder aus.",
66
+ "checkout.blocker.shipping_address_incomplete": "Füllen Sie die Lieferadresse aus.",
67
+ "checkout.blocker.shipping_recalculating": "Versandkosten werden aktualisiert…",
68
+ "checkout.blocker.shipping_address_required": "Geben Sie Ihre Adresse ein, um Lieferoptionen zu sehen.",
69
+ "checkout.blocker.shipping_method_required": "Wählen Sie eine Versandart.",
70
+ "checkout.blocker.shipping_not_available": "An diese Adresse können wir leider noch nicht liefern.",
71
+ "checkout.blocker.payment_method_required": "Wählen Sie eine Zahlungsart.",
72
+ "checkout.error.card_payment_in_preview": "Kartenzahlung ist in der Vorschau nicht möglich — öffnen Sie den veröffentlichten Shop.",
73
+
74
+ "order.orderLabel": "Bestellung",
75
+ "order.dateLabel": "Datum",
76
+ "order.qty": "Menge {count}",
77
+ "order.backToStore": "Zurück zum Shop",
78
+ "order.paid.title": "Vielen Dank — Ihre Bestellung ist bestätigt",
79
+ "order.paid.body": "Eine Bestätigung ist auf dem Weg in Ihr Postfach.",
80
+ "order.unpaid.title": "Bestellung eingegangen — Zahlung ausstehend",
81
+ "order.unpaid.body": "Ihre Bestellung wird zurückgehalten, bis die Zahlung eingeht. Nutzen Sie die folgenden Angaben.",
82
+ "order.instructionsTitle": "Zahlungshinweise",
83
+ "order.payNow": "Zahlung abschließen",
84
+ "order.cancelled.title": "Diese Bestellung wurde storniert",
85
+ "order.cancelled.body": "Falls das nicht beabsichtigt war, können Sie die Zahlung erneut versuchen.",
86
+ "order.retryPayment": "Zahlung erneut versuchen",
87
+ "order.error.title": "Wir konnten diese Bestellung nicht verifizieren",
88
+ "order.error.body": "Der Link ist möglicherweise unvollständig — verwenden Sie den Link aus Ihrer Bestätigungs-E-Mail.",
89
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * English — the storefront-ui default language and the fallback for every
3
+ * other locale file. Keys are flat and dot-namespaced by surface; `{name}`
4
+ * marks an interpolated value and must survive translation verbatim.
5
+ *
6
+ * Adding a key: add it here first, then to every other file in this folder.
7
+ */
8
+ export default {
9
+ $locale: "en",
10
+
11
+ // ── shared ──────────────────────────────────────────────────────
12
+ "common.loading": "Loading…",
13
+ "common.error.title": "Something went wrong",
14
+ "common.error.retry": "Try again",
15
+ "common.remove": "Remove",
16
+ "common.quantity": "Quantity",
17
+ "common.decrease": "Decrease quantity",
18
+ "common.increase": "Increase quantity",
19
+ "common.free": "Free",
20
+ "common.totals.subtotal": "Subtotal",
21
+ "common.totals.discount": "Discount",
22
+ "common.totals.shipping": "Shipping",
23
+ "common.totals.tax": "Tax",
24
+ "common.totals.total": "Total",
25
+ "common.coupon.label": "Discount code",
26
+ "common.coupon.placeholder": "Enter code",
27
+ "common.coupon.apply": "Apply",
28
+ "common.coupon.applied": "Code {code} applied",
29
+ "common.coupon.remove": "Remove code",
30
+
31
+ // ── cart page ───────────────────────────────────────────────────
32
+ "cart.title": "Your cart",
33
+ "cart.summaryTitle": "Order summary",
34
+ "cart.checkoutCta": "Proceed to checkout",
35
+ "cart.continueShopping": "Continue shopping",
36
+ "cart.empty.title": "Your cart is empty",
37
+ "cart.empty.body": "Everything you add will show up here.",
38
+ "cart.empty.cta": "Browse the store",
39
+ "cart.notes.label": "Order notes",
40
+ "cart.notes.placeholder": "Anything we should know about your order?",
41
+ "cart.taxNote": "Shipping and taxes are calculated at checkout.",
42
+
43
+ // ── minicart ────────────────────────────────────────────────────
44
+ "minicart.title": "Your cart",
45
+ "minicart.close": "Close cart",
46
+ "minicart.subtotal": "Subtotal",
47
+ "minicart.checkout": "Checkout",
48
+ "minicart.viewCart": "View cart",
49
+ "minicart.empty.title": "Your cart is empty",
50
+ "minicart.empty.cta": "Keep browsing",
51
+ "minicart.button": "Open cart",
52
+
53
+ // ── checkout ────────────────────────────────────────────────────
54
+ "checkout.title": "Checkout",
55
+ "checkout.contactTitle": "Contact & billing address",
56
+ "checkout.shipToDifferent": "Deliver to a different address",
57
+ "checkout.shippingAddressTitle": "Delivery address",
58
+ "checkout.shippingTitle": "Delivery method",
59
+ "checkout.paymentTitle": "Payment",
60
+ "checkout.summaryTitle": "Order summary",
61
+ "checkout.notes.label": "Order notes",
62
+ "checkout.notes.placeholder": "Anything we should know about your order?",
63
+ "checkout.terms": "I agree to the terms of sale",
64
+ "checkout.placeOrder": "Place order",
65
+ "checkout.placing": "Placing your order…",
66
+ "checkout.submitted": "Order placed — taking you to your confirmation…",
67
+ "checkout.empty.title": "There's nothing to check out yet",
68
+ "checkout.empty.cta": "Back to the store",
69
+ "checkout.hint.missing_address": "Enter your address to see delivery options.",
70
+ "checkout.hint.none_available": "We can't deliver to that address yet.",
71
+ "checkout.hint.syncing": "Updating delivery options…",
72
+ "checkout.hint.payment_none": "Checkout is temporarily unavailable — no payment method is enabled.",
73
+ "checkout.blocker.cart_loading": "Loading your cart…",
74
+ "checkout.blocker.empty_cart": "Your cart is empty.",
75
+ "checkout.blocker.billing_incomplete": "Fill in the required contact fields.",
76
+ "checkout.blocker.shipping_address_incomplete": "Fill in the delivery address.",
77
+ "checkout.blocker.shipping_recalculating": "Updating delivery costs…",
78
+ "checkout.blocker.shipping_address_required": "Enter your address to see delivery options.",
79
+ "checkout.blocker.shipping_method_required": "Choose a delivery method.",
80
+ "checkout.blocker.shipping_not_available": "We can't deliver to that address yet.",
81
+ "checkout.blocker.payment_method_required": "Choose how you'd like to pay.",
82
+ "checkout.error.card_payment_in_preview": "Card payment can't be completed in preview — open the published store to pay.",
83
+
84
+ // ── order received ──────────────────────────────────────────────
85
+ "order.orderLabel": "Order",
86
+ "order.dateLabel": "Date",
87
+ "order.qty": "Qty {count}",
88
+ "order.backToStore": "Back to the store",
89
+ "order.paid.title": "Thank you — your order is confirmed",
90
+ "order.paid.body": "A confirmation is on its way to your inbox.",
91
+ "order.unpaid.title": "Order received — awaiting payment",
92
+ "order.unpaid.body": "Your order is on hold until payment is received. Use the details below to complete it.",
93
+ "order.instructionsTitle": "Payment instructions",
94
+ "order.payNow": "Complete payment",
95
+ "order.cancelled.title": "This order was cancelled",
96
+ "order.cancelled.body": "If that wasn't intended, you can retry the payment.",
97
+ "order.retryPayment": "Retry payment",
98
+ "order.error.title": "We couldn't verify this order",
99
+ "order.error.body": "The link may be incomplete — try the link from your confirmation email.",
100
+ };
@@ -0,0 +1,89 @@
1
+ /** Español — tono cercano y claro, neutro latinoamericano. Claves como en en.js. */
2
+ export default {
3
+ $locale: "es",
4
+
5
+ "common.loading": "Cargando…",
6
+ "common.error.title": "Algo salió mal",
7
+ "common.error.retry": "Intentar de nuevo",
8
+ "common.remove": "Eliminar",
9
+ "common.quantity": "Cantidad",
10
+ "common.decrease": "Disminuir cantidad",
11
+ "common.increase": "Aumentar cantidad",
12
+ "common.free": "Gratis",
13
+ "common.totals.subtotal": "Subtotal",
14
+ "common.totals.discount": "Descuento",
15
+ "common.totals.shipping": "Envío",
16
+ "common.totals.tax": "Impuestos",
17
+ "common.totals.total": "Total",
18
+ "common.coupon.label": "Código de descuento",
19
+ "common.coupon.placeholder": "Ingresa el código",
20
+ "common.coupon.apply": "Aplicar",
21
+ "common.coupon.applied": "Código {code} aplicado",
22
+ "common.coupon.remove": "Quitar código",
23
+
24
+ "cart.title": "Tu carrito",
25
+ "cart.summaryTitle": "Resumen del pedido",
26
+ "cart.checkoutCta": "Ir a pagar",
27
+ "cart.continueShopping": "Seguir comprando",
28
+ "cart.empty.title": "Tu carrito está vacío",
29
+ "cart.empty.body": "Todo lo que agregues aparecerá aquí.",
30
+ "cart.empty.cta": "Explorar la tienda",
31
+ "cart.notes.label": "Notas del pedido",
32
+ "cart.notes.placeholder": "¿Algo que debamos saber sobre tu pedido?",
33
+ "cart.taxNote": "El envío y los impuestos se calculan al pagar.",
34
+
35
+ "minicart.title": "Tu carrito",
36
+ "minicart.close": "Cerrar carrito",
37
+ "minicart.subtotal": "Subtotal",
38
+ "minicart.checkout": "Pagar",
39
+ "minicart.viewCart": "Ver carrito",
40
+ "minicart.empty.title": "Tu carrito está vacío",
41
+ "minicart.empty.cta": "Seguir explorando",
42
+ "minicart.button": "Abrir carrito",
43
+
44
+ "checkout.title": "Pago",
45
+ "checkout.contactTitle": "Contacto y dirección de facturación",
46
+ "checkout.shipToDifferent": "Enviar a una dirección diferente",
47
+ "checkout.shippingAddressTitle": "Dirección de entrega",
48
+ "checkout.shippingTitle": "Método de envío",
49
+ "checkout.paymentTitle": "Pago",
50
+ "checkout.summaryTitle": "Resumen del pedido",
51
+ "checkout.notes.label": "Notas del pedido",
52
+ "checkout.notes.placeholder": "¿Algo que debamos saber sobre tu pedido?",
53
+ "checkout.terms": "Acepto las condiciones de venta",
54
+ "checkout.placeOrder": "Realizar pedido",
55
+ "checkout.placing": "Realizando tu pedido…",
56
+ "checkout.submitted": "Pedido realizado — te llevamos a tu confirmación…",
57
+ "checkout.empty.title": "Todavía no hay nada para pagar",
58
+ "checkout.empty.cta": "Volver a la tienda",
59
+ "checkout.hint.missing_address": "Ingresa tu dirección para ver las opciones de entrega.",
60
+ "checkout.hint.none_available": "Aún no podemos hacer entregas en esa dirección.",
61
+ "checkout.hint.syncing": "Actualizando opciones de entrega…",
62
+ "checkout.hint.payment_none": "El pago no está disponible por el momento — no hay ningún método de pago habilitado.",
63
+ "checkout.blocker.cart_loading": "Cargando tu carrito…",
64
+ "checkout.blocker.empty_cart": "Tu carrito está vacío.",
65
+ "checkout.blocker.billing_incomplete": "Completa los campos de contacto obligatorios.",
66
+ "checkout.blocker.shipping_address_incomplete": "Completa la dirección de entrega.",
67
+ "checkout.blocker.shipping_recalculating": "Actualizando costos de envío…",
68
+ "checkout.blocker.shipping_address_required": "Ingresa tu dirección para ver las opciones de entrega.",
69
+ "checkout.blocker.shipping_method_required": "Elige un método de envío.",
70
+ "checkout.blocker.shipping_not_available": "Aún no podemos hacer entregas en esa dirección.",
71
+ "checkout.blocker.payment_method_required": "Elige cómo quieres pagar.",
72
+ "checkout.error.card_payment_in_preview": "El pago con tarjeta no puede completarse en la vista previa — abre la tienda publicada para pagar.",
73
+
74
+ "order.orderLabel": "Pedido",
75
+ "order.dateLabel": "Fecha",
76
+ "order.qty": "Cant. {count}",
77
+ "order.backToStore": "Volver a la tienda",
78
+ "order.paid.title": "Gracias — tu pedido está confirmado",
79
+ "order.paid.body": "Una confirmación va en camino a tu correo.",
80
+ "order.unpaid.title": "Pedido recibido — pago pendiente",
81
+ "order.unpaid.body": "Tu pedido queda en espera hasta recibir el pago. Usa los datos de abajo para completarlo.",
82
+ "order.instructionsTitle": "Instrucciones de pago",
83
+ "order.payNow": "Completar el pago",
84
+ "order.cancelled.title": "Este pedido fue cancelado",
85
+ "order.cancelled.body": "Si no fue tu intención, puedes reintentar el pago.",
86
+ "order.retryPayment": "Reintentar el pago",
87
+ "order.error.title": "No pudimos verificar este pedido",
88
+ "order.error.body": "Puede que el enlace esté incompleto — usa el enlace de tu correo de confirmación.",
89
+ };