@usequeek/theme-kit 0.1.20 → 0.1.22
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/README.md +264 -77
- package/api/collections.ts +5 -5
- package/api/shop.ts +1 -1
- package/client.ts +2 -0
- package/components/auth/auth-email-login-step.tsx +9 -7
- package/components/auth/auth-email-otp-step.tsx +9 -7
- package/components/auth/auth-email-otp-verify-step.tsx +8 -6
- package/components/auth/auth-email-register-step.tsx +10 -8
- package/components/auth/auth-signup-step.tsx +8 -6
- package/components/blog/category-filter.tsx +6 -5
- package/components/blog/pagination.tsx +8 -7
- package/components/blog/post-meta.tsx +15 -8
- package/components/blog/related-posts.tsx +5 -3
- package/components/blog/share-buttons.tsx +10 -8
- package/components/checkout/checkout-contact.tsx +9 -7
- package/components/checkout/default-checkout-shell.tsx +17 -15
- package/components/checkout/policy-links.tsx +5 -3
- package/components/image.tsx +5 -5
- package/components/language-switcher.css +131 -0
- package/components/language-switcher.tsx +144 -0
- package/components/markdown.tsx +1 -1
- package/components/powered-by-queek.tsx +4 -4
- package/components/product-media-frame.tsx +1 -1
- package/components/product-metafields.tsx +2 -3
- package/components/variant-picker.tsx +6 -9
- package/demo.ts +10 -14
- package/design-token-preview.tsx +0 -2
- package/edit-mode-active.tsx +3 -4
- package/hooks/use-announcement.ts +1 -1
- package/hooks/use-auth-flow.tsx +19 -18
- package/hooks/use-locales.ts +107 -0
- package/hooks/use-posts.ts +1 -1
- package/hooks/use-product-detail.ts +1 -2
- package/hooks/use-product-sections.ts +4 -6
- package/hooks/use-products.ts +5 -8
- package/index.ts +16 -15
- package/locales/en.default.json +49 -7
- package/next-reference.d.ts +2 -4
- package/package.json +3 -2
- package/page-renderer.tsx +5 -6
- package/provider.tsx +22 -2
- package/scripts/verify-package.ts +6 -8
- package/sdk/client.ts +13 -12
- package/sdk/error-handler.ts +1 -1
- package/sdk/platform.ts +6 -8
- package/sdk/queek-client.ts +11 -12
- package/sdk/server-store-client.ts +13 -17
- package/shared-blocks/metaobjects.tsx +1 -1
- package/stores/cart-store.ts +2 -2
- package/stores/checkout-store.ts +1 -1
- package/stores/edit-preview-store.ts +5 -6
- package/stores/user-store.ts +4 -5
- package/strings/pseudo-locale.ts +263 -0
- package/strings/theme-strings.ts +185 -20
- package/tests/auth-email-errors.test.tsx +55 -0
- package/tests/currency-contract.test.ts +2 -3
- package/tests/display-format.test.ts +356 -2
- package/tests/fixtures/external-theme.tsx +18 -2
- package/tests/language-switcher.test.tsx +350 -0
- package/tests/list-pagination.test.tsx +8 -8
- package/tests/locale-switch.test.ts +197 -0
- package/tests/locale-transport.test.tsx +3 -3
- package/tests/markdown-render.test.ts +5 -6
- package/tests/orders-walk.test.ts +5 -5
- package/tests/page-renderer-signature.test.tsx +1 -1
- package/tests/powered-by-queek.test.tsx +3 -3
- package/tests/powered-by-url.test.ts +2 -2
- package/tests/public-surface.test.ts +1 -4
- package/tests/shopper-chrome-strings.test.tsx +271 -0
- package/tests/storefront-client-scope.test.ts +5 -5
- package/tests/theme-hook-placement.test.tsx +179 -0
- package/tests/theme-strings-client.test.tsx +5 -5
- package/tests/theme-strings-pseudo.test.tsx +171 -0
- package/tests/theme-strings-typed.test.tsx +97 -0
- package/tests/theme-strings.test.tsx +10 -7
- package/tests/theme-typed-keys.fixture.ts +76 -0
- package/tests/use-shop-prefetch.test.tsx +1 -1
- package/theme-mount.tsx +6 -11
- package/types/block.ts +7 -7
- package/types/media.ts +8 -9
- package/types/product.ts +8 -12
- package/types/theme.ts +26 -25
- package/types/vendor.ts +10 -12
- package/utils/announcement.ts +3 -3
- package/utils/benefit-icon.tsx +9 -11
- package/utils/brand.ts +7 -10
- package/utils/format.ts +100 -14
- package/utils/image-srcset.ts +2 -2
- package/utils/locale-switch.ts +208 -0
- package/utils/locale.ts +56 -5
- package/utils/menu-link.ts +1 -1
- package/utils/product-media.ts +1 -1
- package/utils/product-normalizer.ts +2 -2
- package/utils/vendor-host.ts +7 -11
package/utils/locale.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Storefront locale transport
|
|
2
|
+
* Storefront locale transport.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* sets `x-queek-locale` on the incoming request for published NON-primary
|
|
4
|
+
* The API accepts `?locale=<code>` on every customer-facing read and answers
|
|
5
|
+
* translated text (unknown/unpublished locale → source text, no error). The
|
|
6
|
+
* storefront proxy sets `x-queek-locale` on the incoming request for published NON-primary
|
|
8
7
|
* locales only — the header is ABSENT on the primary locale — and suffixes
|
|
9
8
|
* revalidation tags as `<tag>:locale:<code>`.
|
|
10
9
|
*
|
|
@@ -19,6 +18,58 @@
|
|
|
19
18
|
* byte-identical to before.
|
|
20
19
|
*/
|
|
21
20
|
|
|
21
|
+
/**
|
|
22
|
+
* English display locale — the ONE Intl tag every English shopper sees dates,
|
|
23
|
+
* numbers and money in. `en-NG`, not `en-GB`: both print dates identically
|
|
24
|
+
* ("2 October 2026", "14 Feb 2026"), but only `en-NG` renders NGN with the
|
|
25
|
+
* naira sign the stores already show (`₦12,500` vs `en-GB`'s `NGN 12,500`, on
|
|
26
|
+
* Node 22 with full ICU). `DISPLAY_LOCALE` in `utils/format.ts` aliases this, so
|
|
27
|
+
* the value lives in exactly one place.
|
|
28
|
+
*/
|
|
29
|
+
export const ENGLISH_DISPLAY_LOCALE = 'en-NG';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* THE single canonical mapping from a storefront locale code to the Intl
|
|
33
|
+
* locale dates/numbers/relative time are formatted in: the shopper's locale,
|
|
34
|
+
* with English in the British/Nigerian style ("2 October 2026", never US
|
|
35
|
+
* "October 2, 2026").
|
|
36
|
+
*
|
|
37
|
+
* - Missing/invalid/unknown-structure codes and EVERY English variant (`en`,
|
|
38
|
+
* `en-GB`, `en-NG`, `en-US`, …) become `ENGLISH_DISPLAY_LOCALE`. In
|
|
39
|
+
* particular bare `en` must never reach Intl directly: `en` formats US
|
|
40
|
+
* order ("October 2, 2026").
|
|
41
|
+
* - Every other well-formed code passes through unchanged (`fr`, `ar`,
|
|
42
|
+
* `pt-BR`, `zh-CN`, `yo`, `ha`, `sw`, …).
|
|
43
|
+
*
|
|
44
|
+
* Pure (no `next/*`, no React) like the rest of this module. Never throws.
|
|
45
|
+
* Pair with `resolveSupportedLocale` when the runtime's ICU may lack the
|
|
46
|
+
* locale — this function maps, it does not probe.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveIntlLocale(locale: string | null | undefined): string {
|
|
49
|
+
const code = parseLocaleCode(locale);
|
|
50
|
+
if (!code) return ENGLISH_DISPLAY_LOCALE;
|
|
51
|
+
if ((code.split('-')[0] ?? '').toLowerCase() === 'en') return ENGLISH_DISPLAY_LOCALE;
|
|
52
|
+
return code;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Graceful ICU fallback for one formatting call: the candidate when this
|
|
57
|
+
* Node's ICU formats it natively, `ENGLISH_DISPLAY_LOCALE` otherwise (a
|
|
58
|
+
* minimal-ICU build with no French data still renders English, never an
|
|
59
|
+
* exception). Never throws — pass any Intl constructor's
|
|
60
|
+
* `supportedLocalesOf`, e.g. `(l) => Intl.DateTimeFormat.supportedLocalesOf(l)`.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveSupportedLocale(
|
|
63
|
+
candidate: string,
|
|
64
|
+
supportedLocalesOf: (locales: string[]) => readonly string[],
|
|
65
|
+
): string {
|
|
66
|
+
try {
|
|
67
|
+
return supportedLocalesOf([candidate]).length > 0 ? candidate : ENGLISH_DISPLAY_LOCALE;
|
|
68
|
+
} catch {
|
|
69
|
+
return ENGLISH_DISPLAY_LOCALE;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
22
73
|
/** Request header the proxy sets for published non-primary locales. */
|
|
23
74
|
export const LOCALE_CODE_HEADER = 'x-queek-locale';
|
|
24
75
|
|
package/utils/menu-link.ts
CHANGED
|
@@ -17,7 +17,7 @@ export function menuItemToHref(item: MenuItem, basePath: string = ''): string {
|
|
|
17
17
|
const href = `${basePath}/${item.ref}`;
|
|
18
18
|
const anchor = item.meta?.anchor;
|
|
19
19
|
// href may already end in "/" (the home page's ref is normalized to
|
|
20
|
-
// "/"
|
|
20
|
+
// "/") — never emit a doubled
|
|
21
21
|
// slash before the fragment.
|
|
22
22
|
return anchor ? `${href.replace(/\/$/, '')}#${anchor}` : href;
|
|
23
23
|
}
|
package/utils/product-media.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface MediaFrame {
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Picks the largest tier a PDP hero should ever request. `view` is capped at
|
|
17
|
-
* 1600px
|
|
17
|
+
* 1600px, which covers every theme's hero box
|
|
18
18
|
* at DPR2 — the raw `original` is uncapped and unoptimized.
|
|
19
19
|
*/
|
|
20
20
|
function heroUrl(variants: ImageVariants | null | undefined): string | null {
|
|
@@ -37,8 +37,8 @@ function toBool(value: unknown, fallback = false): boolean {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Same base/sale/compare-at shape as normalizePricing(), but a variant's own price lives on
|
|
40
|
-
* FLAT fields (price/discount_price/compare_at_price/discount_amount
|
|
41
|
-
*
|
|
40
|
+
* FLAT fields (price/discount_price/compare_at_price/discount_amount), never nested
|
|
41
|
+
* under a `pricing` key.
|
|
42
42
|
* Reading `variant.pricing?.sale_amount` (as this used to) is always undefined, so every
|
|
43
43
|
* variant silently fell back to the base product's price — picking Denim vs Taupe never
|
|
44
44
|
* changed what was shown, even though the backend had already computed the right number.
|
package/utils/vendor-host.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vendor resolution from a hostname: which store is this request for?
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* and
|
|
6
|
-
* core depended on the app. Nothing here is app-specific: it reads env and
|
|
7
|
-
* resolves a slug. (The actual Next middleware is `proxy.ts`; the old filename
|
|
8
|
-
* suggested otherwise and was never that.)
|
|
4
|
+
* Nothing here is app-specific: it reads env and resolves a slug, so the kit's
|
|
5
|
+
* server fetchers and a host app's proxy can both import it.
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
|
-
// Mirrors
|
|
8
|
+
// Mirrors the API's reserved store slugs. Keep in sync.
|
|
12
9
|
// These are subdomains the storefront proxy must NOT treat as vendor slugs.
|
|
13
10
|
export const RESERVED_VENDOR_SLUGS = new Set([
|
|
14
11
|
// Live platform subdomains
|
|
@@ -24,11 +21,10 @@ export const RESERVED_VENDOR_SLUGS = new Set([
|
|
|
24
21
|
'staff', 'team', 'queek', 'merchant', 'vendor', 'rider', 'customer',
|
|
25
22
|
]);
|
|
26
23
|
|
|
27
|
-
// Every sluggable
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// backend round-trip instead of burning calls that all 404. This is what stops
|
|
24
|
+
// Every sluggable API model (Vendor, Product, Category, Region, Service)
|
|
25
|
+
// produces lowercase alphanumeric slugs, optionally hyphen-separated, never a
|
|
26
|
+
// dot/underscore/uppercase. Anything else cannot be a real slug of any kind, so
|
|
27
|
+
// reject it before any API round-trip instead of burning calls that all 404. This is what stops
|
|
32
28
|
// scanners probing `.env`, `.ssh`, `docker-compose.yaml`, `*.php` etc —
|
|
33
29
|
// RESERVED_VENDOR_SLUGS alone only covers known words, not the infinite space
|
|
34
30
|
// of file-extension guesses.
|