@doswiftly/cli 0.1.19 → 0.1.21

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 (147) hide show
  1. package/dist/commands/deploy.d.ts +20 -0
  2. package/dist/commands/deploy.d.ts.map +1 -1
  3. package/dist/commands/deploy.js +225 -6
  4. package/dist/commands/deploy.js.map +1 -1
  5. package/package.json +4 -4
  6. package/templates/storefront-minimal/.github/workflows/build-template.yml +10 -0
  7. package/templates/storefront-minimal/wrangler.toml +11 -0
  8. package/templates/storefront-nextjs/.github/workflows/build-template.yml +10 -0
  9. package/templates/storefront-nextjs/wrangler.toml +11 -0
  10. package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +10 -0
  11. package/templates/storefront-nextjs-shadcn/CLAUDE.md +29 -5
  12. package/templates/storefront-nextjs-shadcn/app/{about → [locale]/about}/page.tsx +17 -14
  13. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/addresses/page.tsx +19 -15
  14. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/error.tsx +8 -5
  15. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loyalty/page.tsx +39 -34
  16. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/page.tsx +9 -7
  17. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/tracking/page.tsx +27 -25
  18. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/page.tsx +13 -9
  19. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/page.tsx +1 -2
  20. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/settings/page.tsx +1 -1
  21. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/forgot-password/page.tsx +14 -12
  22. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/login/page.tsx +5 -2
  23. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/register/page.tsx +5 -2
  24. package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/page.tsx +1 -1
  25. package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/page.tsx +14 -10
  26. package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/category-products-client.tsx +4 -2
  27. package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/page.tsx +13 -8
  28. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/error.tsx +1 -1
  29. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/page.tsx +228 -184
  30. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/success/[orderId]/page.tsx +36 -34
  31. package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/page.tsx +5 -3
  32. package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/page.tsx +13 -8
  33. package/templates/storefront-nextjs-shadcn/app/{contact → [locale]/contact}/page.tsx +24 -21
  34. package/templates/storefront-nextjs-shadcn/app/{error.tsx → [locale]/error.tsx} +13 -8
  35. package/templates/storefront-nextjs-shadcn/app/[locale]/layout.tsx +92 -0
  36. package/templates/storefront-nextjs-shadcn/app/{not-found.tsx → [locale]/not-found.tsx} +13 -18
  37. package/templates/storefront-nextjs-shadcn/app/{page.tsx → [locale]/page.tsx} +8 -4
  38. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/error.tsx +1 -1
  39. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/page.tsx +11 -8
  40. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/product-client.tsx +3 -1
  41. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/page.tsx +6 -3
  42. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/products-client.tsx +14 -10
  43. package/templates/storefront-nextjs-shadcn/app/{wishlist → [locale]/wishlist}/page.tsx +21 -25
  44. package/templates/storefront-nextjs-shadcn/app/layout.tsx +6 -68
  45. package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +25 -20
  46. package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +11 -10
  47. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +14 -12
  48. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +28 -18
  49. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +10 -8
  50. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +27 -22
  51. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +48 -43
  52. package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +1 -1
  53. package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +1 -1
  54. package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +1 -1
  55. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +7 -4
  56. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +1 -1
  57. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +7 -5
  58. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +9 -7
  59. package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +8 -5
  60. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +18 -15
  61. package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +15 -25
  62. package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +10 -8
  63. package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +9 -6
  64. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +5 -3
  65. package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +8 -5
  66. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +5 -3
  67. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +8 -7
  68. package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +1 -1
  69. package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +1 -1
  70. package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +9 -6
  71. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +21 -11
  72. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +16 -13
  73. package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +53 -28
  74. package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +7 -5
  75. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +19 -15
  76. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +12 -9
  77. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +8 -5
  78. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +1 -1
  79. package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +12 -8
  80. package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +13 -8
  81. package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +10 -8
  82. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +37 -12
  83. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +5 -2
  84. package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +24 -23
  85. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +20 -12
  86. package/templates/storefront-nextjs-shadcn/components/layout/language-switcher.tsx +54 -0
  87. package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +33 -30
  88. package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +27 -24
  89. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +23 -24
  90. package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +6 -14
  91. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +1 -1
  92. package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +4 -1
  93. package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +7 -4
  94. package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +5 -3
  95. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +8 -6
  96. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +3 -1
  97. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +3 -7
  98. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +26 -13
  99. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +25 -27
  100. package/templates/storefront-nextjs-shadcn/components/providers/language-sync-provider.tsx +27 -0
  101. package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +40 -7
  102. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +56 -70
  103. package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +7 -4
  104. package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +12 -9
  105. package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +23 -12
  106. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +7 -4
  107. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +1 -1
  108. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +2 -10
  109. package/templates/storefront-nextjs-shadcn/generated/graphql.ts +1159 -551
  110. package/templates/storefront-nextjs-shadcn/hooks/index.ts +1 -0
  111. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +22 -249
  112. package/templates/storefront-nextjs-shadcn/hooks/use-cart-di.ts +67 -0
  113. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +3 -3
  114. package/templates/storefront-nextjs-shadcn/i18n/navigation.ts +12 -0
  115. package/templates/storefront-nextjs-shadcn/i18n/request.ts +17 -0
  116. package/templates/storefront-nextjs-shadcn/i18n/routing.ts +17 -0
  117. package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +1 -0
  118. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +41 -8
  119. package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +20 -18
  120. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +2 -1
  121. package/templates/storefront-nextjs-shadcn/messages/en.json +869 -0
  122. package/templates/storefront-nextjs-shadcn/messages/pl.json +869 -0
  123. package/templates/storefront-nextjs-shadcn/next.config.ts +6 -5
  124. package/templates/storefront-nextjs-shadcn/package.json +3 -2
  125. package/templates/storefront-nextjs-shadcn/proxy.ts +115 -46
  126. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +24 -58
  127. package/templates/storefront-nextjs-shadcn/wrangler.toml +11 -0
  128. /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loading.tsx +0 -0
  129. /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/loading.tsx +0 -0
  130. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/loading.tsx +0 -0
  131. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/loading.tsx +0 -0
  132. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/page.tsx +0 -0
  133. /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/[slug]/page.tsx +0 -0
  134. /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/page.tsx +0 -0
  135. /package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/loading.tsx +0 -0
  136. /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/loading.tsx +0 -0
  137. /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/page.tsx +0 -0
  138. /package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/loading.tsx +0 -0
  139. /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/loading.tsx +0 -0
  140. /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/loading.tsx +0 -0
  141. /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/loading.tsx +0 -0
  142. /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/loading.tsx +0 -0
  143. /package/templates/storefront-nextjs-shadcn/app/{returns → [locale]/returns}/page.tsx +0 -0
  144. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/loading.tsx +0 -0
  145. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/page.tsx +0 -0
  146. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/search-client.tsx +0 -0
  147. /package/templates/storefront-nextjs-shadcn/app/{shipping → [locale]/shipping}/page.tsx +0 -0
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
- import Link from "next/link";
4
- import { usePathname } from "next/navigation";
3
+ import { useTranslations } from "next-intl";
4
+ import { Link, usePathname } from "@/i18n/navigation";
5
5
  import { ChevronRight, Home } from "lucide-react";
6
6
  import { cn } from "@/lib/utils";
7
7
 
@@ -31,15 +31,33 @@ export interface BreadcrumbsProps {
31
31
  * { label: "Laptops", href: "/products/electronics/laptops" }
32
32
  * ]} />
33
33
  */
34
+ // Known segment keys that exist in nav translations
35
+ const SEGMENT_TRANSLATION_KEYS: Record<string, string> = {
36
+ products: "products",
37
+ collections: "collections",
38
+ categories: "categories",
39
+ about: "about",
40
+ brands: "brands",
41
+ blog: "blog",
42
+ cart: "cart",
43
+ account: "account",
44
+ contact: "contact",
45
+ shipping: "shipping",
46
+ returns: "returns",
47
+ search: "searchBreadcrumb",
48
+ wishlist: "wishlist",
49
+ };
50
+
34
51
  export function Breadcrumbs({
35
52
  items,
36
53
  className,
37
54
  showHome = true,
38
55
  }: BreadcrumbsProps) {
56
+ const t = useTranslations("nav");
39
57
  const pathname = usePathname();
40
58
 
41
59
  // Auto-generate breadcrumbs from pathname if items not provided
42
- const breadcrumbItems = items || generateBreadcrumbs(pathname);
60
+ const breadcrumbItems = items || generateBreadcrumbs(pathname, t);
43
61
 
44
62
  // Don't show breadcrumbs on homepage
45
63
  if (pathname === "/" || breadcrumbItems.length === 0) {
@@ -48,7 +66,7 @@ export function Breadcrumbs({
48
66
 
49
67
  return (
50
68
  <nav
51
- aria-label="Breadcrumb"
69
+ aria-label={t("breadcrumb")}
52
70
  className={cn("flex items-center gap-2 text-sm", className)}
53
71
  >
54
72
  <ol className="flex items-center gap-2">
@@ -57,7 +75,7 @@ export function Breadcrumbs({
57
75
  <Link
58
76
  href="/"
59
77
  className="flex items-center text-muted-foreground hover:text-foreground transition-colors"
60
- aria-label="Home"
78
+ aria-label={t("home")}
61
79
  >
62
80
  <Home className="h-4 w-4" />
63
81
  </Link>
@@ -101,20 +119,27 @@ export function Breadcrumbs({
101
119
 
102
120
  /**
103
121
  * Generate breadcrumb items from pathname
122
+ * Uses translations for known segments, falls back to capitalized segment name
104
123
  */
105
- function generateBreadcrumbs(pathname: string): BreadcrumbItem[] {
124
+ function generateBreadcrumbs(
125
+ pathname: string,
126
+ t: (key: string) => string,
127
+ ): BreadcrumbItem[] {
106
128
  const segments = pathname.split("/").filter(Boolean);
107
129
  const breadcrumbs: BreadcrumbItem[] = [];
108
130
 
109
131
  let currentPath = "";
110
132
  for (const segment of segments) {
111
133
  currentPath += `/${segment}`;
112
-
113
- // Format label: remove hyphens, capitalize words
114
- const label = segment
115
- .split("-")
116
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
117
- .join(" ");
134
+
135
+ // Use translation for known segments, fallback to capitalized segment
136
+ const translationKey = SEGMENT_TRANSLATION_KEYS[segment];
137
+ const label = translationKey
138
+ ? t(translationKey)
139
+ : segment
140
+ .split("-")
141
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
142
+ .join(" ");
118
143
 
119
144
  breadcrumbs.push({
120
145
  label,
@@ -21,6 +21,7 @@ import { useState, useRef, useEffect } from 'react';
21
21
  import { useCurrencyStore } from '@doswiftly/storefront-sdk/react';
22
22
  import { useQueryClient } from '@tanstack/react-query';
23
23
  import { ChevronDown, Check, Globe } from 'lucide-react';
24
+ import { useTranslations } from 'next-intl';
24
25
 
25
26
  // ============================================================================
26
27
  // CURRENCY DATA
@@ -149,6 +150,8 @@ export function CurrencySelector({
149
150
  showIcon = true,
150
151
  variant = 'default',
151
152
  }: CurrencySelectorProps) {
153
+ const t = useTranslations("currency");
154
+
152
155
  // Currency store state
153
156
  const currency = useCurrencyStore((state) => state.currency);
154
157
  const supportedCurrencies = useCurrencyStore((state) => state.supportedCurrencies);
@@ -261,7 +264,7 @@ export function CurrencySelector({
261
264
  `}
262
265
  aria-expanded={isOpen}
263
266
  aria-haspopup="listbox"
264
- aria-label="Select currency"
267
+ aria-label={t("selectCurrency")}
265
268
  >
266
269
  {showIcon && variant !== 'minimal' && (
267
270
  <Globe className="w-4 h-4 text-gray-400" aria-hidden="true" />
@@ -287,7 +290,7 @@ export function CurrencySelector({
287
290
  ${dropdownStyles[variant]}
288
291
  `}
289
292
  role="listbox"
290
- aria-label="Currency options"
293
+ aria-label={t("currencyOptions")}
291
294
  >
292
295
  <div className="py-1 max-h-64 overflow-y-auto">
293
296
  {supportedCurrencies.map((code) => {
@@ -1,8 +1,11 @@
1
- import Link from "next/link";
1
+ import { Link } from "@/i18n/navigation";
2
+ import { getTranslations } from "next-intl/server";
2
3
 
3
- export function Footer() {
4
+ export async function Footer() {
4
5
  const currentYear = new Date().getFullYear();
5
6
  const siteName = process.env.NEXT_PUBLIC_SITE_NAME || "My Store";
7
+ const t = await getTranslations("footer");
8
+ const tc = await getTranslations("common");
6
9
 
7
10
  return (
8
11
  <footer className="border-t border-border bg-muted/50">
@@ -14,33 +17,32 @@ export function Footer() {
14
17
  {siteName}
15
18
  </Link>
16
19
  <p className="mt-4 text-sm text-muted-foreground">
17
- Quality products at affordable prices. Powered by DoSwiftly
18
- Commerce.
20
+ {t("description")}
19
21
  </p>
20
22
  </div>
21
23
 
22
24
  {/* Shop */}
23
25
  <div>
24
- <h3 className="mb-4 font-semibold text-foreground">Shop</h3>
26
+ <h3 className="mb-4 font-semibold text-foreground">{t("shop")}</h3>
25
27
  <ul className="space-y-2 text-sm text-muted-foreground">
26
28
  <li>
27
29
  <Link href="/products" className="hover:text-primary transition-colors">
28
- All Products
30
+ {t("allProducts")}
29
31
  </Link>
30
32
  </li>
31
33
  <li>
32
34
  <Link href="/collections" className="hover:text-primary transition-colors">
33
- Collections
35
+ {t("collections")}
34
36
  </Link>
35
37
  </li>
36
38
  <li>
37
39
  <Link href="/categories" className="hover:text-primary transition-colors">
38
- Categories
40
+ {t("categories")}
39
41
  </Link>
40
42
  </li>
41
43
  <li>
42
44
  <Link href="/search" className="hover:text-primary transition-colors">
43
- Search
45
+ {tc("search")}
44
46
  </Link>
45
47
  </li>
46
48
  </ul>
@@ -48,31 +50,31 @@ export function Footer() {
48
50
 
49
51
  {/* Account */}
50
52
  <div>
51
- <h3 className="mb-4 font-semibold text-foreground">Account</h3>
53
+ <h3 className="mb-4 font-semibold text-foreground">{t("myAccount")}</h3>
52
54
  <ul className="space-y-2 text-sm text-muted-foreground">
53
55
  <li>
54
56
  <Link href="/auth/login" className="hover:text-primary transition-colors">
55
- Sign In
57
+ {t("signIn")}
56
58
  </Link>
57
59
  </li>
58
60
  <li>
59
61
  <Link href="/auth/register" className="hover:text-primary transition-colors">
60
- Create Account
62
+ {t("createAccount")}
61
63
  </Link>
62
64
  </li>
63
65
  <li>
64
66
  <Link href="/account" className="hover:text-primary transition-colors">
65
- My Account
67
+ {t("myAccount")}
66
68
  </Link>
67
69
  </li>
68
70
  <li>
69
71
  <Link href="/account/orders" className="hover:text-primary transition-colors">
70
- Order History
72
+ {t("orderHistory")}
71
73
  </Link>
72
74
  </li>
73
75
  <li>
74
76
  <Link href="/cart" className="hover:text-primary transition-colors">
75
- Shopping Cart
77
+ {t("shoppingCart")}
76
78
  </Link>
77
79
  </li>
78
80
  </ul>
@@ -80,26 +82,26 @@ export function Footer() {
80
82
 
81
83
  {/* Support */}
82
84
  <div>
83
- <h3 className="mb-4 font-semibold text-foreground">Support</h3>
85
+ <h3 className="mb-4 font-semibold text-foreground">{t("support")}</h3>
84
86
  <ul className="space-y-2 text-sm text-muted-foreground">
85
87
  <li>
86
88
  <Link href="/contact" className="hover:text-primary transition-colors">
87
- Contact Us
89
+ {t("contactUs")}
88
90
  </Link>
89
91
  </li>
90
92
  <li>
91
93
  <Link href="/about" className="hover:text-primary transition-colors">
92
- About Us
94
+ {t("aboutUs")}
93
95
  </Link>
94
96
  </li>
95
97
  <li>
96
98
  <Link href="/shipping" className="hover:text-primary transition-colors">
97
- Shipping Info
99
+ {t("shippingInfo")}
98
100
  </Link>
99
101
  </li>
100
102
  <li>
101
103
  <Link href="/returns" className="hover:text-primary transition-colors">
102
- Returns & Refunds
104
+ {t("returnsRefunds")}
103
105
  </Link>
104
106
  </li>
105
107
  </ul>
@@ -108,17 +110,16 @@ export function Footer() {
108
110
 
109
111
  <div className="mt-12 border-t border-border pt-8 text-center text-sm text-muted-foreground">
110
112
  <p>
111
- © {currentYear} {siteName}. All rights reserved.
113
+ © {currentYear} {siteName}. {t("allRightsReserved")}.
112
114
  </p>
113
115
  <p className="mt-2">
114
- Powered by{" "}
115
116
  <a
116
117
  href="https://doswiftly.pl"
117
118
  target="_blank"
118
119
  rel="noopener noreferrer"
119
120
  className="text-primary hover:underline transition-colors"
120
121
  >
121
- DoSwiftly Commerce
122
+ {t("poweredBy")}
122
123
  </a>
123
124
  </p>
124
125
  </div>
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
 
3
- import Link from "next/link";
4
3
  import { useState } from "react";
4
+ import { useTranslations } from "next-intl";
5
+ import { Link } from "@/i18n/navigation";
5
6
  import { Menu, X } from "lucide-react";
6
7
  import { CurrencySelector } from "@/components/commerce/currency-selector";
7
8
  import { CartIcon } from "@/components/cart/cart-icon";
@@ -9,11 +10,13 @@ import { SearchInput } from "@/components/commerce/search-input";
9
10
  import { Navigation } from "@/components/layout/navigation";
10
11
  import { AccountMenu } from "@/components/auth/account-menu";
11
12
  import { ThemeSwitcher } from "@/components/layout/theme-switcher";
13
+ import { LanguageSwitcher } from "@/components/layout/language-switcher";
12
14
  import { useAuthStore, useAuthHydrated } from "@doswiftly/storefront-sdk/react";
13
15
  import { useHydrated } from "@doswiftly/storefront-sdk/react";
14
16
  import { useAuthSync } from "@/hooks/use-auth-sync";
15
17
 
16
18
  export function Header() {
19
+ const t = useTranslations("nav");
17
20
  const [isMenuOpen, setIsMenuOpen] = useState(false);
18
21
 
19
22
  // Auto-fix cookie/store desync (e.g., localStorage cleared but cookie persists)
@@ -30,19 +33,19 @@ export function Header() {
30
33
 
31
34
  const navigationItems = [
32
35
  {
33
- label: "Products",
36
+ label: t("products"),
34
37
  href: "/products",
35
38
  },
36
39
  {
37
- label: "Collections",
40
+ label: t("collections"),
38
41
  href: "/collections",
39
42
  },
40
43
  {
41
- label: "Categories",
44
+ label: t("categories"),
42
45
  href: "/categories",
43
46
  },
44
47
  {
45
- label: "About",
48
+ label: t("about"),
46
49
  href: "/about",
47
50
  },
48
51
  ];
@@ -65,7 +68,12 @@ export function Header() {
65
68
  <div className="flex items-center gap-3">
66
69
  {/* Search */}
67
70
  <div className="hidden w-64 lg:block">
68
- <SearchInput placeholder="Search products..." />
71
+ <SearchInput placeholder={t("search")} />
72
+ </div>
73
+
74
+ {/* Language Switcher */}
75
+ <div className="hidden md:block">
76
+ <LanguageSwitcher />
69
77
  </div>
70
78
 
71
79
  {/* Currency Selector */}
@@ -87,7 +95,7 @@ export function Header() {
87
95
  href="/auth/login"
88
96
  className="hidden md:inline-flex items-center rounded-md px-3 py-2 text-sm font-medium text-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
89
97
  >
90
- Sign In
98
+ {t("signIn")}
91
99
  </Link>
92
100
  )
93
101
  )}
@@ -99,7 +107,7 @@ export function Header() {
99
107
  <button
100
108
  className="p-2 text-foreground hover:text-primary md:hidden"
101
109
  onClick={() => setIsMenuOpen(!isMenuOpen)}
102
- aria-label="Toggle menu"
110
+ aria-label={t("menu")}
103
111
  aria-expanded={isMenuOpen}
104
112
  >
105
113
  {isMenuOpen ? (
@@ -117,12 +125,12 @@ export function Header() {
117
125
  <div className="flex flex-col gap-4">
118
126
  {/* Mobile Search */}
119
127
  <div className="lg:hidden">
120
- <SearchInput placeholder="Search products..." />
128
+ <SearchInput placeholder={t("search")} />
121
129
  </div>
122
130
 
123
- {/* Mobile Currency Selector */}
131
+ {/* Mobile Language & Currency */}
124
132
  <div className="flex items-center justify-between">
125
- <span className="text-sm font-medium text-muted-foreground">Currency</span>
133
+ <LanguageSwitcher />
126
134
  <CurrencySelector variant="compact" />
127
135
  </div>
128
136
 
@@ -145,7 +153,7 @@ export function Header() {
145
153
  className="text-foreground hover:text-primary transition-colors font-medium"
146
154
  onClick={() => setIsMenuOpen(false)}
147
155
  >
148
- Sign In
156
+ {t("signIn")}
149
157
  </Link>
150
158
  )}
151
159
  </div>
@@ -0,0 +1,54 @@
1
+ "use client";
2
+
3
+ import { useLanguageStore } from "@doswiftly/storefront-sdk/react";
4
+ import { useRouter, usePathname } from "@/i18n/navigation";
5
+ import { useLocale, useTranslations } from "next-intl";
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from "@/components/ui/select";
13
+
14
+ const LANGUAGE_NAMES: Record<string, string> = {
15
+ pl: "Polski",
16
+ en: "English",
17
+ de: "Deutsch",
18
+ fr: "Français",
19
+ es: "Español",
20
+ cs: "Čeština",
21
+ it: "Italiano",
22
+ nl: "Nederlands",
23
+ pt: "Português",
24
+ };
25
+
26
+ export function LanguageSwitcher() {
27
+ const t = useTranslations("language");
28
+ const locale = useLocale();
29
+ const router = useRouter();
30
+ const pathname = usePathname();
31
+ const supportedLanguages = useLanguageStore((s) => s.supportedLanguages);
32
+
33
+ if (supportedLanguages.length < 2) return null;
34
+
35
+ const handleLocaleChange = (newLocale: string) => {
36
+ if (newLocale === locale) return;
37
+ router.replace(pathname, { locale: newLocale });
38
+ };
39
+
40
+ return (
41
+ <Select value={locale} onValueChange={handleLocaleChange}>
42
+ <SelectTrigger className="h-8 w-auto gap-1 border-0 bg-transparent px-2 text-xs font-medium shadow-none" aria-label={t("switchLanguage")}>
43
+ <SelectValue>{locale.toUpperCase()}</SelectValue>
44
+ </SelectTrigger>
45
+ <SelectContent align="end">
46
+ {supportedLanguages.map((lang) => (
47
+ <SelectItem key={lang} value={lang}>
48
+ {lang.toUpperCase()} — {LANGUAGE_NAMES[lang] || lang}
49
+ </SelectItem>
50
+ ))}
51
+ </SelectContent>
52
+ </Select>
53
+ );
54
+ }
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useEffect } from "react";
4
- import Link from "next/link";
5
- import { usePathname } from "next/navigation";
4
+ import { useTranslations } from "next-intl";
5
+ import { Link, usePathname } from "@/i18n/navigation";
6
6
  import { Menu, X, ChevronDown, ChevronRight } from "lucide-react";
7
7
  import { cn } from "@/lib/utils";
8
8
 
@@ -17,26 +17,29 @@ export interface MobileMenuProps {
17
17
  className?: string;
18
18
  }
19
19
 
20
- const defaultItems: MobileMenuItem[] = [
21
- {
22
- label: "Products",
23
- href: "/products",
24
- },
25
- {
26
- label: "Collections",
27
- href: "/collections",
28
- },
29
- {
30
- label: "Categories",
31
- href: "/categories",
32
- },
33
- {
34
- label: "About",
35
- href: "/about",
36
- },
37
- ];
38
-
39
- export function MobileMenu({ items = defaultItems, className }: MobileMenuProps) {
20
+ export function MobileMenu({ items, className }: MobileMenuProps) {
21
+ const t = useTranslations("nav");
22
+
23
+ const defaultItems: MobileMenuItem[] = [
24
+ {
25
+ label: t("products"),
26
+ href: "/products",
27
+ },
28
+ {
29
+ label: t("collections"),
30
+ href: "/collections",
31
+ },
32
+ {
33
+ label: t("categories"),
34
+ href: "/categories",
35
+ },
36
+ {
37
+ label: t("about"),
38
+ href: "/about",
39
+ },
40
+ ];
41
+
42
+ const resolvedItems = items ?? defaultItems;
40
43
  const [isOpen, setIsOpen] = useState(false);
41
44
  const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
42
45
  const pathname = usePathname();
@@ -108,7 +111,7 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
108
111
  toggleExpanded(item.href);
109
112
  }}
110
113
  className="p-3 text-muted-foreground hover:text-foreground"
111
- aria-label={`Toggle ${item.label} submenu`}
114
+ aria-label={t("toggleSubmenu", { label: item.label })}
112
115
  aria-expanded={isExpanded}
113
116
  >
114
117
  {isExpanded ? (
@@ -140,7 +143,7 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
140
143
  "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
141
144
  className
142
145
  )}
143
- aria-label="Toggle mobile menu"
146
+ aria-label={t("toggleMobileMenu")}
144
147
  aria-expanded={isOpen}
145
148
  aria-controls="mobile-menu"
146
149
  >
@@ -163,16 +166,16 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
163
166
  className="fixed inset-y-0 left-0 z-50 w-full max-w-sm bg-background shadow-xl"
164
167
  role="dialog"
165
168
  aria-modal="true"
166
- aria-label="Mobile navigation"
169
+ aria-label={t("mobileNavigation")}
167
170
  >
168
171
  <div className="flex h-full flex-col">
169
172
  {/* Header */}
170
173
  <div className="flex items-center justify-between border-b border-border p-4">
171
- <h2 className="text-lg font-semibold">Menu</h2>
174
+ <h2 className="text-lg font-semibold">{t("menu")}</h2>
172
175
  <button
173
176
  onClick={() => setIsOpen(false)}
174
177
  className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
175
- aria-label="Close mobile menu"
178
+ aria-label={t("closeMobileMenu")}
176
179
  >
177
180
  <X className="h-5 w-5" />
178
181
  </button>
@@ -181,7 +184,7 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
181
184
  {/* Menu Items */}
182
185
  <nav className="flex-1 overflow-y-auto p-4">
183
186
  <div className="space-y-1">
184
- {items.map((item) => renderMenuItem(item))}
187
+ {resolvedItems.map((item) => renderMenuItem(item))}
185
188
  </div>
186
189
  </nav>
187
190
 
@@ -192,13 +195,13 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
192
195
  href="/account"
193
196
  className="rounded-md bg-primary px-4 py-2 text-center text-sm font-medium text-primary-foreground hover:bg-primary/90"
194
197
  >
195
- My Account
198
+ {t("account")}
196
199
  </Link>
197
200
  <Link
198
201
  href="/cart"
199
202
  className="rounded-md border border-border px-4 py-2 text-center text-sm font-medium hover:bg-accent"
200
203
  >
201
- View Cart
204
+ {t("viewCart")}
202
205
  </Link>
203
206
  </div>
204
207
  </div>
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
- import Link from "next/link";
4
- import { usePathname } from "next/navigation";
3
+ import { useTranslations } from "next-intl";
4
+ import { Link, usePathname } from "@/i18n/navigation";
5
5
  import { cn } from "@/lib/utils";
6
6
 
7
7
  export interface NavigationItem {
@@ -15,28 +15,31 @@ export interface NavigationProps {
15
15
  className?: string;
16
16
  }
17
17
 
18
- const defaultItems: NavigationItem[] = [
19
- {
20
- label: "Products",
21
- href: "/products",
22
- },
23
- {
24
- label: "Collections",
25
- href: "/collections",
26
- },
27
- {
28
- label: "Categories",
29
- href: "/categories",
30
- },
31
- {
32
- label: "About",
33
- href: "/about",
34
- },
35
- ];
36
-
37
- export function Navigation({ items = defaultItems, className }: NavigationProps) {
18
+ export function Navigation({ items, className }: NavigationProps) {
19
+ const t = useTranslations("nav");
38
20
  const pathname = usePathname();
39
21
 
22
+ const defaultItems: NavigationItem[] = [
23
+ {
24
+ label: t("products"),
25
+ href: "/products",
26
+ },
27
+ {
28
+ label: t("collections"),
29
+ href: "/collections",
30
+ },
31
+ {
32
+ label: t("categories"),
33
+ href: "/categories",
34
+ },
35
+ {
36
+ label: t("about"),
37
+ href: "/about",
38
+ },
39
+ ];
40
+
41
+ const resolvedItems = items ?? defaultItems;
42
+
40
43
  const isActive = (href: string) => {
41
44
  if (href === "/") {
42
45
  return pathname === "/";
@@ -48,9 +51,9 @@ export function Navigation({ items = defaultItems, className }: NavigationProps)
48
51
  <nav
49
52
  className={cn("flex items-center gap-6", className)}
50
53
  role="navigation"
51
- aria-label="Main navigation"
54
+ aria-label={t("mainNavigation")}
52
55
  >
53
- {items.map((item) => (
56
+ {resolvedItems.map((item) => (
54
57
  <div key={item.href} className="relative group">
55
58
  <Link
56
59
  href={item.href}