create-magic-storefront 0.4.0 → 0.6.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 +3 -3
- package/template/.claude/skills/storefront-design/SKILL.md +14 -10
- package/template/.env.example +5 -0
- package/template/AGENTS.md +36 -17
- package/template/DESIGN.md +117 -0
- package/template/PAGES.md +116 -29
- package/template/README.md +15 -3
- package/template/app/account/addresses/page.tsx +16 -0
- package/template/app/account/layout.tsx +14 -0
- package/template/app/account/loading.tsx +15 -0
- package/template/app/account/orders/[id]/page.tsx +15 -0
- package/template/app/account/page.tsx +12 -244
- package/template/app/account/profile/page.tsx +16 -0
- package/template/app/blog/(index)/loading.tsx +22 -0
- package/template/app/blog/{page.tsx → (index)/page.tsx} +42 -25
- package/template/app/blog/[handle]/page.tsx +92 -56
- package/template/app/blog/author/[handle]/page.tsx +1 -1
- package/template/app/cart/layout.tsx +14 -0
- package/template/app/cart/page.tsx +49 -101
- package/template/app/checkout/layout.tsx +14 -0
- package/template/app/checkout/page.tsx +98 -281
- package/template/app/collections/[handle]/layout.tsx +20 -0
- package/template/app/collections/[handle]/loading.tsx +23 -0
- package/template/app/collections/[handle]/page.tsx +131 -15
- package/template/app/collections/page.tsx +49 -0
- package/template/app/contacts/page.tsx +196 -0
- package/template/app/error.tsx +19 -8
- package/template/app/fonts/OFL.txt +93 -0
- package/template/app/fonts/index.ts +14 -0
- package/template/app/fonts/onest-variable.woff2 +0 -0
- package/template/app/globals.css +14 -518
- package/template/app/layout.tsx +74 -50
- package/template/app/not-found.tsx +34 -4
- package/template/app/page.tsx +1 -1
- package/template/app/pages/[handle]/page.tsx +1 -1
- package/template/app/products/[handle]/layout.tsx +20 -0
- package/template/app/products/[handle]/loading.tsx +25 -0
- package/template/app/products/[handle]/page.tsx +112 -15
- package/template/app/providers.tsx +20 -7
- package/template/app/search/loading.tsx +12 -0
- package/template/app/search/page.tsx +138 -25
- package/template/app/sitemap.ts +5 -1
- package/template/app/storefront-api/[...path]/route.ts +11 -66
- package/template/app/styles/base.css +138 -0
- package/template/app/styles/components.css +1108 -0
- package/template/app/styles/layout.css +455 -0
- package/template/app/styles/pages.css +1839 -0
- package/template/app/styles/reset.css +55 -0
- package/template/app/styles/sections.css +415 -0
- package/template/app/styles/tokens.css +167 -0
- package/template/app/styles/utilities.css +37 -0
- package/template/app/wishlist/page.tsx +26 -0
- package/template/components/account/account-shell.tsx +100 -0
- package/template/components/account/addresses.tsx +247 -0
- package/template/components/account/order-detail.tsx +281 -0
- package/template/components/account/order-list.tsx +94 -0
- package/template/components/account/order-status.tsx +19 -0
- package/template/components/account/profile.tsx +129 -0
- package/template/components/account/sign-in.tsx +164 -0
- package/template/components/article-body.tsx +3 -2
- package/template/components/article-card.tsx +37 -12
- package/template/components/cart/cart-drawer.tsx +149 -0
- package/template/components/cart/cart-lines.tsx +89 -0
- package/template/components/cart/cart-summary.tsx +191 -0
- package/template/components/catalog/catalog-form.tsx +107 -0
- package/template/components/catalog/catalog-layout.tsx +82 -0
- package/template/components/catalog/facets-shell.tsx +60 -0
- package/template/components/catalog/facets.tsx +216 -0
- package/template/components/checkout/order-summary.tsx +84 -0
- package/template/components/checkout/radio-card.tsx +34 -0
- package/template/components/checkout/step-indicator.tsx +56 -0
- package/template/components/checkout/steps.tsx +264 -0
- package/template/components/checkout/thank-you.tsx +48 -0
- package/template/components/checkout/use-checkout.ts +90 -0
- package/template/components/footer/site-footer.tsx +132 -0
- package/template/components/header/cart-button.tsx +43 -0
- package/template/components/header/desktop-nav.tsx +133 -0
- package/template/components/header/mobile-menu.tsx +102 -0
- package/template/components/header/site-header.tsx +78 -0
- package/template/components/page-loading.tsx +20 -0
- package/template/components/pager.tsx +3 -1
- package/template/components/product/buy-box.tsx +148 -0
- package/template/components/product/gallery.tsx +179 -0
- package/template/components/product/pre-order-form.tsx +93 -0
- package/template/components/product/product-selection.tsx +31 -0
- package/template/components/product/recommendations.tsx +26 -0
- package/template/components/product/review-list.tsx +159 -0
- package/template/components/product/reviews.tsx +80 -0
- package/template/components/product/variant-picker.tsx +75 -0
- package/template/components/product-card.tsx +103 -0
- package/template/components/product-grid.tsx +27 -57
- package/template/components/quick-add.tsx +62 -0
- package/template/components/search/predictive-search.tsx +282 -0
- package/template/components/sections/announcement-bar.tsx +1 -1
- package/template/components/sections/banner.tsx +1 -1
- package/template/components/sections/collections.tsx +50 -13
- package/template/components/sections/deal-of-day.tsx +2 -2
- package/template/components/sections/product-shelves.tsx +13 -5
- package/template/components/sections/shoppable-stories.tsx +2 -2
- package/template/components/sections/store-reviews.tsx +10 -4
- package/template/components/share-button.tsx +42 -0
- package/template/components/ui/accordion.tsx +35 -0
- package/template/components/ui/badge.tsx +20 -0
- package/template/components/ui/breadcrumbs.tsx +31 -0
- package/template/components/ui/button.tsx +90 -0
- package/template/components/ui/drawer.tsx +84 -0
- package/template/components/ui/empty-state.tsx +27 -0
- package/template/components/ui/field.tsx +33 -0
- package/template/components/ui/icon.tsx +112 -0
- package/template/components/ui/price.tsx +35 -0
- package/template/components/ui/quantity-stepper.tsx +91 -0
- package/template/components/ui/rating.tsx +48 -0
- package/template/components/ui/skeleton.tsx +46 -0
- package/template/components/wishlist/wishlist-view.tsx +134 -0
- package/template/lib/article-outline.ts +35 -0
- package/template/lib/catalog-params.ts +170 -0
- package/template/lib/contacts.ts +34 -0
- package/template/lib/errors.ts +29 -0
- package/template/lib/i18n.ts +484 -0
- package/template/lib/menu.ts +70 -0
- package/template/lib/theme-settings.ts +36 -0
- package/template/lib/upstream.ts +21 -0
- package/template/llms.txt +13 -0
- package/template/theme/index.ts +7 -1
- package/template/theme/sections/collapsible-content.tsx +75 -0
- package/template/theme/sections/collection-list.tsx +32 -4
- package/template/theme/sections/featured-collection.tsx +22 -2
- package/template/theme/sections/hero.tsx +94 -7
- package/template/theme/sections/image-with-text.tsx +19 -3
- package/template/theme/sections/multicolumn.tsx +113 -0
- package/template/theme/sections/page-content.tsx +10 -4
- package/template/theme/sections/product-shelf.tsx +26 -4
- package/template/theme/sections/rich-text.tsx +13 -1
- package/template/theme/sections/testimonials.tsx +14 -9
- package/template/theme/settings.ts +85 -1
- package/template/theme/templates/index.json +4 -2
- package/template/.screenshots/home-1280.png +0 -0
- package/template/.screenshots/home-390.png +0 -0
- package/template/.screenshots/home-filled-1280.png +0 -0
- package/template/.screenshots/home-filled-390.png +0 -0
- package/template/.screenshots/page-1280.png +0 -0
- package/template/components/buy-box.tsx +0 -89
- package/template/components/cart-link.tsx +0 -15
package/template/lib/i18n.ts
CHANGED
|
@@ -7,6 +7,9 @@ import type { Schema } from '@magicstoreai/storefront-client';
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
type OrderStatus = Schema<'Order'>['status'];
|
|
10
|
+
type LineIssue = Schema<'CartLine'>['issues'][number];
|
|
11
|
+
type PaymentStatus = NonNullable<Schema<'Order'>['payment']>['status'];
|
|
12
|
+
type DiscountReason = NonNullable<Schema<'CartDiscountCode'>['reasonCode']>;
|
|
10
13
|
|
|
11
14
|
function plural(
|
|
12
15
|
locale: string,
|
|
@@ -59,11 +62,117 @@ const en = {
|
|
|
59
62
|
leaveReview: 'Leave a review',
|
|
60
63
|
shopNow: 'Shop now',
|
|
61
64
|
ratedOutOf: (rating: number) => `Rated ${rating} out of 5`,
|
|
65
|
+
// UI primitives
|
|
66
|
+
// Header
|
|
67
|
+
menu: 'Menu',
|
|
68
|
+
back: 'Back',
|
|
69
|
+
skipToContent: 'Skip to content',
|
|
70
|
+
catalog: 'Catalog',
|
|
71
|
+
wishlist: 'Wishlist',
|
|
72
|
+
cartCount: (count: number) =>
|
|
73
|
+
`Cart, ${count} ${plural('en', count, { one: 'item', other: 'items' })}`,
|
|
74
|
+
submenu: (title: string) => `${title}: more`,
|
|
75
|
+
// Footer
|
|
76
|
+
information: 'Information',
|
|
77
|
+
contacts: 'Contacts',
|
|
78
|
+
email: 'Email',
|
|
79
|
+
openingHours: 'Opening hours',
|
|
80
|
+
allContacts: 'All contacts and addresses',
|
|
81
|
+
headOffice: 'Head office',
|
|
82
|
+
branches: 'Branches',
|
|
83
|
+
showOnMap: 'Show on map',
|
|
84
|
+
landmark: 'Landmark',
|
|
85
|
+
closed: 'Closed',
|
|
86
|
+
requisites: 'Company details',
|
|
87
|
+
contactsEmpty: 'The shop has not added its contacts yet.',
|
|
88
|
+
socialMedia: 'Social media',
|
|
89
|
+
// Cart drawer
|
|
90
|
+
viewCart: 'View cart',
|
|
91
|
+
deliveryAtCheckout: 'Delivery is calculated at checkout.',
|
|
92
|
+
cartEmptyHint: 'Add something from the catalog, it will wait for you here.',
|
|
93
|
+
lineIssues: {
|
|
94
|
+
INVALID_QUANTITY: 'This quantity cannot be ordered.',
|
|
95
|
+
NOT_FOUND: 'This product is no longer sold.',
|
|
96
|
+
UNPUBLISHED: 'This product is no longer sold.',
|
|
97
|
+
VARIANT_MISMATCH: 'This option is no longer available.',
|
|
98
|
+
INSUFFICIENT_STOCK: 'Not enough in stock for this quantity.',
|
|
99
|
+
BUNDLE_UNAVAILABLE: 'This set is no longer available.',
|
|
100
|
+
} satisfies Record<LineIssue, string>,
|
|
101
|
+
// Predictive search
|
|
102
|
+
products: 'Products',
|
|
103
|
+
collections: 'Collections',
|
|
104
|
+
suggestions: 'Suggestions',
|
|
105
|
+
popularSearches: 'Popular searches',
|
|
106
|
+
searchFor: (query: string) => `Search for "${query}"`,
|
|
107
|
+
noSuggestions: (query: string) => `Nothing found for "${query}".`,
|
|
108
|
+
quickAdd: (title: string) => `Add to cart: ${title}`,
|
|
109
|
+
// Catalog
|
|
110
|
+
filters: 'Filters',
|
|
111
|
+
sortBy: 'Sort by',
|
|
112
|
+
sortOptions: {
|
|
113
|
+
default: 'Featured',
|
|
114
|
+
'-createdAt': 'Newest',
|
|
115
|
+
price: 'Price, low to high',
|
|
116
|
+
'-price': 'Price, high to low',
|
|
117
|
+
'-bestSellers': 'Best selling',
|
|
118
|
+
'-rating': 'Top rated',
|
|
119
|
+
},
|
|
120
|
+
priceFrom: 'From',
|
|
121
|
+
priceTo: 'To',
|
|
122
|
+
clearAll: 'Clear all',
|
|
123
|
+
removeFilter: (label: string) => `Remove filter: ${label}`,
|
|
124
|
+
showResults: 'Show results',
|
|
125
|
+
productsCount: (count: number) =>
|
|
126
|
+
`${count} ${plural('en', count, { one: 'product', other: 'products' })}`,
|
|
127
|
+
noMatches: 'No products match these filters',
|
|
128
|
+
noMatchesHint: 'Try removing a filter or two.',
|
|
129
|
+
collectionEmpty: 'Nothing here yet',
|
|
130
|
+
collectionEmptyHint:
|
|
131
|
+
'Products will appear here soon. Meanwhile, take a look at the rest of the catalog.',
|
|
132
|
+
faq: 'Questions and answers',
|
|
133
|
+
// Product page
|
|
134
|
+
description: 'Description',
|
|
135
|
+
inStock: 'In stock',
|
|
136
|
+
lowStock: (count: number) => `Only ${count} left`,
|
|
137
|
+
selectOption: (name: string) => `Choose ${name}`,
|
|
138
|
+
share: 'Share',
|
|
139
|
+
linkCopied: 'Link copied',
|
|
140
|
+
imageOf: (index: number, total: number) => `Image ${index} of ${total}`,
|
|
141
|
+
video: 'Video',
|
|
142
|
+
previousImage: 'Previous image',
|
|
143
|
+
nextImage: 'Next image',
|
|
144
|
+
reviews: 'Reviews',
|
|
145
|
+
noReviewsForRating: 'No reviews with this rating.',
|
|
146
|
+
verifiedBuyer: 'Verified buyer',
|
|
147
|
+
pros: 'Pros',
|
|
148
|
+
cons: 'Cons',
|
|
149
|
+
shopReply: 'Reply from the shop',
|
|
150
|
+
showMoreReviews: 'Show more reviews',
|
|
151
|
+
allRatings: 'All',
|
|
152
|
+
starsFilter: (stars: number) =>
|
|
153
|
+
`${stars} ${plural('en', stars, { one: 'star', other: 'stars' })}`,
|
|
154
|
+
relatedProducts: 'You may also like',
|
|
155
|
+
pairsWellWith: 'Pairs well with',
|
|
156
|
+
preOrderHint: 'Leave your contact and the shop will get in touch when it arrives.',
|
|
157
|
+
preOrderSent: 'Pre-order sent. The shop will contact you.',
|
|
158
|
+
sendPreOrder: 'Send pre-order',
|
|
159
|
+
// Wishlist
|
|
160
|
+
wishlistEmpty: 'Your wishlist is empty',
|
|
161
|
+
wishlistEmptyHint: 'Tap the heart on a product to keep it here.',
|
|
162
|
+
wishlistGuestHint: 'Saved on this device only. Sign in to keep your wishlist everywhere.',
|
|
163
|
+
removeFromWishlist: (title: string) => `Remove ${title} from wishlist`,
|
|
164
|
+
close: 'Close',
|
|
165
|
+
breadcrumbs: 'Breadcrumbs',
|
|
166
|
+
decreaseQuantity: 'Decrease quantity',
|
|
167
|
+
increaseQuantity: 'Increase quantity',
|
|
168
|
+
ratingSummary: (average: number, count: number) =>
|
|
169
|
+
`Rated ${average} out of 5, ${count} ${plural('en', count, { one: 'review', other: 'reviews' })}`,
|
|
62
170
|
// Blog
|
|
63
171
|
blog: 'Blog',
|
|
64
172
|
allArticles: 'All articles',
|
|
65
173
|
featured: "Editor's picks",
|
|
66
174
|
readingTime: (minutes: number) => `${minutes} min read`,
|
|
175
|
+
tableOfContents: 'Contents',
|
|
67
176
|
relatedArticles: 'Read also',
|
|
68
177
|
productsInArticle: 'Products in this article',
|
|
69
178
|
wasHelpful: 'Was this article helpful?',
|
|
@@ -83,6 +192,7 @@ const en = {
|
|
|
83
192
|
saved: 'Saved',
|
|
84
193
|
// Cart
|
|
85
194
|
cartEmpty: 'Your cart is empty',
|
|
195
|
+
loading: 'Loading…',
|
|
86
196
|
loadingCart: 'Loading your cart…',
|
|
87
197
|
quantity: 'Quantity',
|
|
88
198
|
remove: 'Remove',
|
|
@@ -91,6 +201,24 @@ const en = {
|
|
|
91
201
|
apply: 'Apply',
|
|
92
202
|
codeApplied: 'applied',
|
|
93
203
|
codeNotApplied: 'not applied',
|
|
204
|
+
discountReasons: {
|
|
205
|
+
NOT_FOUND: 'There is no such code.',
|
|
206
|
+
EXPIRED: 'This code has expired.',
|
|
207
|
+
NOT_AVAILABLE: 'This code is not available now.',
|
|
208
|
+
USAGE_LIMIT_REACHED: 'This code has been used up.',
|
|
209
|
+
ALREADY_USED: 'You have already used this code.',
|
|
210
|
+
CUSTOMER_NOT_ELIGIBLE: 'This code is not for your account.',
|
|
211
|
+
SEGMENT_NOT_ELIGIBLE: 'This code is not for your account.',
|
|
212
|
+
MINIMUM_SPEND_NOT_MET: 'Your order is below the minimum for this code.',
|
|
213
|
+
MAXIMUM_SPEND_EXCEEDED: 'Your order is above the maximum for this code.',
|
|
214
|
+
PRODUCTS_NOT_IN_CART: 'This code is for products not in your cart.',
|
|
215
|
+
} satisfies Record<DiscountReason, string>,
|
|
216
|
+
removeCode: (code: string) => `Remove code ${code}`,
|
|
217
|
+
welcomeDiscount: 'Welcome discount',
|
|
218
|
+
pointsDiscount: 'Paid with points',
|
|
219
|
+
orderNote: 'Note to the shop',
|
|
220
|
+
noteSaved: 'Note saved',
|
|
221
|
+
summary: 'Order summary',
|
|
94
222
|
checkout: 'Checkout',
|
|
95
223
|
// Checkout
|
|
96
224
|
checkoutTitle: 'Checkout',
|
|
@@ -115,6 +243,12 @@ const en = {
|
|
|
115
243
|
thankYou: 'Thank you!',
|
|
116
244
|
order: 'Order',
|
|
117
245
|
payNow: 'Pay now',
|
|
246
|
+
checkoutSteps: 'Checkout steps',
|
|
247
|
+
stepDone: 'done',
|
|
248
|
+
showOrderSummary: 'Show order summary',
|
|
249
|
+
etaMinutes: (minutes: number) => `about ${minutes} min`,
|
|
250
|
+
orderNumberHint: 'Keep this number: the shop refers to your order by it.',
|
|
251
|
+
backToCart: 'Back to cart',
|
|
118
252
|
// Account
|
|
119
253
|
signIn: 'Sign in',
|
|
120
254
|
sendCode: 'Send code',
|
|
@@ -133,6 +267,41 @@ const en = {
|
|
|
133
267
|
orders: 'Orders',
|
|
134
268
|
loadingOrders: 'Loading orders…',
|
|
135
269
|
noOrders: 'No orders yet.',
|
|
270
|
+
accountNav: 'Account',
|
|
271
|
+
overview: 'Overview',
|
|
272
|
+
addresses: 'Addresses',
|
|
273
|
+
profile: 'Profile',
|
|
274
|
+
orderNumber: (number: string) => `Order ${number}`,
|
|
275
|
+
placedOn: (date: string) => `Placed ${date}`,
|
|
276
|
+
itemsCount: (count: number) => `${count} ${plural('en', count, { one: 'item', other: 'items' })}`,
|
|
277
|
+
backToOrders: 'All orders',
|
|
278
|
+
orderAgain: 'Order again',
|
|
279
|
+
cancelOrder: 'Cancel order',
|
|
280
|
+
cancelOrderConfirm: 'Cancel this order? The shop will not prepare it.',
|
|
281
|
+
keepOrder: 'Keep it',
|
|
282
|
+
orderCancelledNote: 'This order is cancelled.',
|
|
283
|
+
paymentStatus: {
|
|
284
|
+
PENDING: 'Awaiting payment',
|
|
285
|
+
PAID: 'Paid',
|
|
286
|
+
FAILED: 'Not paid',
|
|
287
|
+
NOT_REQUIRED: 'Paid outside the store',
|
|
288
|
+
} satisfies Record<PaymentStatus, string>,
|
|
289
|
+
deliveryAddress: 'Delivery address',
|
|
290
|
+
pickupPoint: 'Pickup point',
|
|
291
|
+
addAddress: 'Add address',
|
|
292
|
+
editAddress: 'Edit',
|
|
293
|
+
deleteAddress: (address: string) => `Delete ${address}`,
|
|
294
|
+
defaultAddress: 'Default',
|
|
295
|
+
makeDefault: 'Make default',
|
|
296
|
+
noAddresses: 'No saved addresses yet.',
|
|
297
|
+
address2: 'Apartment, entrance, floor',
|
|
298
|
+
cancel: 'Cancel',
|
|
299
|
+
saveChanges: 'Save',
|
|
300
|
+
changesSaved: 'Saved',
|
|
301
|
+
gender: 'Gender',
|
|
302
|
+
genderOptions: { MALE: 'Male', FEMALE: 'Female', none: 'Not specified' },
|
|
303
|
+
language: 'Language',
|
|
304
|
+
phoneHint: 'You sign in with this number.',
|
|
136
305
|
savedProducts: 'Saved products',
|
|
137
306
|
orderStatus: {
|
|
138
307
|
NEW: 'New',
|
|
@@ -186,10 +355,109 @@ const ru: Messages = {
|
|
|
186
355
|
leaveReview: 'Оставить отзыв',
|
|
187
356
|
shopNow: 'Перейти в каталог',
|
|
188
357
|
ratedOutOf: (rating: number) => `Оценка ${rating} из 5`,
|
|
358
|
+
menu: 'Меню',
|
|
359
|
+
back: 'Назад',
|
|
360
|
+
skipToContent: 'Перейти к содержимому',
|
|
361
|
+
catalog: 'Каталог',
|
|
362
|
+
wishlist: 'Избранное',
|
|
363
|
+
cartCount: (count) =>
|
|
364
|
+
`Корзина, ${count} ${plural('ru', count, { one: 'товар', few: 'товара', other: 'товаров' })}`,
|
|
365
|
+
submenu: (title) => `${title}: подразделы`,
|
|
366
|
+
information: 'Информация',
|
|
367
|
+
contacts: 'Контакты',
|
|
368
|
+
email: 'Эл. почта',
|
|
369
|
+
openingHours: 'Часы работы',
|
|
370
|
+
allContacts: 'Все контакты и адреса',
|
|
371
|
+
headOffice: 'Главный офис',
|
|
372
|
+
branches: 'Филиалы',
|
|
373
|
+
showOnMap: 'Показать на карте',
|
|
374
|
+
landmark: 'Ориентир',
|
|
375
|
+
closed: 'Выходной',
|
|
376
|
+
requisites: 'Реквизиты',
|
|
377
|
+
contactsEmpty: 'Магазин пока не добавил контакты.',
|
|
378
|
+
socialMedia: 'Мы в соцсетях',
|
|
379
|
+
viewCart: 'Перейти в корзину',
|
|
380
|
+
deliveryAtCheckout: 'Доставка рассчитывается при оформлении.',
|
|
381
|
+
cartEmptyHint: 'Добавьте что-нибудь из каталога, товары подождут вас здесь.',
|
|
382
|
+
lineIssues: {
|
|
383
|
+
INVALID_QUANTITY: 'Такое количество заказать нельзя.',
|
|
384
|
+
NOT_FOUND: 'Этот товар больше не продаётся.',
|
|
385
|
+
UNPUBLISHED: 'Этот товар больше не продаётся.',
|
|
386
|
+
VARIANT_MISMATCH: 'Этот вариант больше недоступен.',
|
|
387
|
+
INSUFFICIENT_STOCK: 'В наличии меньше, чем выбрано.',
|
|
388
|
+
BUNDLE_UNAVAILABLE: 'Этот набор больше недоступен.',
|
|
389
|
+
},
|
|
390
|
+
products: 'Товары',
|
|
391
|
+
collections: 'Коллекции',
|
|
392
|
+
suggestions: 'Подсказки',
|
|
393
|
+
popularSearches: 'Часто ищут',
|
|
394
|
+
searchFor: (query) => `Искать «${query}»`,
|
|
395
|
+
noSuggestions: (query) => `По запросу «${query}» ничего не нашлось.`,
|
|
396
|
+
quickAdd: (title) => `В корзину: ${title}`,
|
|
397
|
+
filters: 'Фильтры',
|
|
398
|
+
sortBy: 'Сортировка',
|
|
399
|
+
sortOptions: {
|
|
400
|
+
default: 'Рекомендуемые',
|
|
401
|
+
'-createdAt': 'Сначала новые',
|
|
402
|
+
price: 'Сначала дешевле',
|
|
403
|
+
'-price': 'Сначала дороже',
|
|
404
|
+
'-bestSellers': 'Хиты продаж',
|
|
405
|
+
'-rating': 'С высоким рейтингом',
|
|
406
|
+
},
|
|
407
|
+
priceFrom: 'От',
|
|
408
|
+
priceTo: 'До',
|
|
409
|
+
clearAll: 'Сбросить всё',
|
|
410
|
+
removeFilter: (label) => `Убрать фильтр: ${label}`,
|
|
411
|
+
showResults: 'Показать товары',
|
|
412
|
+
productsCount: (count) =>
|
|
413
|
+
`${count} ${plural('ru', count, { one: 'товар', few: 'товара', other: 'товаров' })}`,
|
|
414
|
+
noMatches: 'Нет товаров с такими фильтрами',
|
|
415
|
+
noMatchesHint: 'Попробуйте убрать один-два фильтра.',
|
|
416
|
+
collectionEmpty: 'Здесь пока пусто',
|
|
417
|
+
collectionEmptyHint: 'Товары скоро появятся. А пока загляните в остальной каталог.',
|
|
418
|
+
faq: 'Вопросы и ответы',
|
|
419
|
+
description: 'Описание',
|
|
420
|
+
inStock: 'В наличии',
|
|
421
|
+
lowStock: (count) =>
|
|
422
|
+
`${plural('ru', count, { one: 'Остался', few: 'Осталось', other: 'Осталось' })} ${count} шт.`,
|
|
423
|
+
selectOption: (name) => `Выберите: ${name}`,
|
|
424
|
+
share: 'Поделиться',
|
|
425
|
+
linkCopied: 'Ссылка скопирована',
|
|
426
|
+
imageOf: (index, total) => `Фото ${index} из ${total}`,
|
|
427
|
+
video: 'Видео',
|
|
428
|
+
previousImage: 'Предыдущее фото',
|
|
429
|
+
nextImage: 'Следующее фото',
|
|
430
|
+
reviews: 'Отзывы',
|
|
431
|
+
noReviewsForRating: 'Отзывов с такой оценкой нет.',
|
|
432
|
+
verifiedBuyer: 'Подтверждённая покупка',
|
|
433
|
+
pros: 'Достоинства',
|
|
434
|
+
cons: 'Недостатки',
|
|
435
|
+
shopReply: 'Ответ магазина',
|
|
436
|
+
showMoreReviews: 'Показать ещё отзывы',
|
|
437
|
+
allRatings: 'Все',
|
|
438
|
+
starsFilter: (stars) =>
|
|
439
|
+
`${stars} ${plural('ru', stars, { one: 'звезда', few: 'звезды', other: 'звёзд' })}`,
|
|
440
|
+
relatedProducts: 'Вам может понравиться',
|
|
441
|
+
pairsWellWith: 'С этим товаром покупают',
|
|
442
|
+
preOrderHint: 'Оставьте контакт — магазин свяжется с вами, когда товар поступит.',
|
|
443
|
+
preOrderSent: 'Предзаказ отправлен. Магазин свяжется с вами.',
|
|
444
|
+
sendPreOrder: 'Оформить предзаказ',
|
|
445
|
+
wishlistEmpty: 'В избранном пока пусто',
|
|
446
|
+
wishlistEmptyHint: 'Нажмите на сердечко у товара, чтобы сохранить его здесь.',
|
|
447
|
+
wishlistGuestHint:
|
|
448
|
+
'Избранное сохранено только на этом устройстве. Войдите, чтобы оно было с вами везде.',
|
|
449
|
+
removeFromWishlist: (title) => `Убрать «${title}» из избранного`,
|
|
450
|
+
close: 'Закрыть',
|
|
451
|
+
breadcrumbs: 'Навигационная цепочка',
|
|
452
|
+
decreaseQuantity: 'Уменьшить количество',
|
|
453
|
+
increaseQuantity: 'Увеличить количество',
|
|
454
|
+
ratingSummary: (average, count) =>
|
|
455
|
+
`Оценка ${average} из 5, ${count} ${plural('ru', count, { one: 'отзыв', few: 'отзыва', other: 'отзывов' })}`,
|
|
189
456
|
blog: 'Блог',
|
|
190
457
|
allArticles: 'Все статьи',
|
|
191
458
|
featured: 'Выбор редакции',
|
|
192
459
|
readingTime: (minutes) => `${minutes} мин чтения`,
|
|
460
|
+
tableOfContents: 'Содержание',
|
|
193
461
|
relatedArticles: 'Читайте также',
|
|
194
462
|
productsInArticle: 'Товары из статьи',
|
|
195
463
|
wasHelpful: 'Статья была полезной?',
|
|
@@ -207,6 +475,7 @@ const ru: Messages = {
|
|
|
207
475
|
save: 'В избранное',
|
|
208
476
|
saved: 'В избранном',
|
|
209
477
|
cartEmpty: 'Корзина пуста',
|
|
478
|
+
loading: 'Загрузка…',
|
|
210
479
|
loadingCart: 'Загружаем корзину…',
|
|
211
480
|
quantity: 'Количество',
|
|
212
481
|
remove: 'Удалить',
|
|
@@ -215,6 +484,24 @@ const ru: Messages = {
|
|
|
215
484
|
apply: 'Применить',
|
|
216
485
|
codeApplied: 'применён',
|
|
217
486
|
codeNotApplied: 'не применён',
|
|
487
|
+
discountReasons: {
|
|
488
|
+
NOT_FOUND: 'Такого промокода нет.',
|
|
489
|
+
EXPIRED: 'Срок действия промокода истёк.',
|
|
490
|
+
NOT_AVAILABLE: 'Промокод сейчас недоступен.',
|
|
491
|
+
USAGE_LIMIT_REACHED: 'Промокод больше не действует: лимит исчерпан.',
|
|
492
|
+
ALREADY_USED: 'Вы уже использовали этот промокод.',
|
|
493
|
+
CUSTOMER_NOT_ELIGIBLE: 'Промокод не подходит для вашего аккаунта.',
|
|
494
|
+
SEGMENT_NOT_ELIGIBLE: 'Промокод не подходит для вашего аккаунта.',
|
|
495
|
+
MINIMUM_SPEND_NOT_MET: 'Сумма заказа меньше минимальной для промокода.',
|
|
496
|
+
MAXIMUM_SPEND_EXCEEDED: 'Сумма заказа больше максимальной для промокода.',
|
|
497
|
+
PRODUCTS_NOT_IN_CART: 'Промокод действует на товары, которых нет в корзине.',
|
|
498
|
+
},
|
|
499
|
+
removeCode: (code) => `Убрать промокод ${code}`,
|
|
500
|
+
welcomeDiscount: 'Приветственная скидка',
|
|
501
|
+
pointsDiscount: 'Оплачено баллами',
|
|
502
|
+
orderNote: 'Комментарий к заказу',
|
|
503
|
+
noteSaved: 'Комментарий сохранён',
|
|
504
|
+
summary: 'Ваш заказ',
|
|
218
505
|
checkout: 'Оформить заказ',
|
|
219
506
|
checkoutTitle: 'Оформление заказа',
|
|
220
507
|
preparingCheckout: 'Готовим оформление…',
|
|
@@ -238,6 +525,12 @@ const ru: Messages = {
|
|
|
238
525
|
thankYou: 'Спасибо!',
|
|
239
526
|
order: 'Заказ',
|
|
240
527
|
payNow: 'Оплатить',
|
|
528
|
+
checkoutSteps: 'Шаги оформления',
|
|
529
|
+
stepDone: 'готово',
|
|
530
|
+
showOrderSummary: 'Показать заказ',
|
|
531
|
+
etaMinutes: (minutes) => `около ${minutes} мин`,
|
|
532
|
+
orderNumberHint: 'Сохраните этот номер: по нему магазин найдёт ваш заказ.',
|
|
533
|
+
backToCart: 'Вернуться в корзину',
|
|
241
534
|
signIn: 'Войти',
|
|
242
535
|
sendCode: 'Получить код',
|
|
243
536
|
smsCode: 'Код из SMS',
|
|
@@ -253,6 +546,42 @@ const ru: Messages = {
|
|
|
253
546
|
orders: 'Заказы',
|
|
254
547
|
loadingOrders: 'Загружаем заказы…',
|
|
255
548
|
noOrders: 'Заказов пока нет.',
|
|
549
|
+
accountNav: 'Личный кабинет',
|
|
550
|
+
overview: 'Обзор',
|
|
551
|
+
addresses: 'Адреса',
|
|
552
|
+
profile: 'Профиль',
|
|
553
|
+
orderNumber: (number) => `Заказ ${number}`,
|
|
554
|
+
placedOn: (date) => `Оформлен ${date}`,
|
|
555
|
+
itemsCount: (count) =>
|
|
556
|
+
`${count} ${plural('ru', count, { one: 'товар', few: 'товара', other: 'товаров' })}`,
|
|
557
|
+
backToOrders: 'Все заказы',
|
|
558
|
+
orderAgain: 'Повторить заказ',
|
|
559
|
+
cancelOrder: 'Отменить заказ',
|
|
560
|
+
cancelOrderConfirm: 'Отменить этот заказ? Магазин не будет его собирать.',
|
|
561
|
+
keepOrder: 'Не отменять',
|
|
562
|
+
orderCancelledNote: 'Этот заказ отменён.',
|
|
563
|
+
paymentStatus: {
|
|
564
|
+
PENDING: 'Ожидает оплаты',
|
|
565
|
+
PAID: 'Оплачен',
|
|
566
|
+
FAILED: 'Не оплачен',
|
|
567
|
+
NOT_REQUIRED: 'Оплачен вне магазина',
|
|
568
|
+
},
|
|
569
|
+
deliveryAddress: 'Адрес доставки',
|
|
570
|
+
pickupPoint: 'Пункт самовывоза',
|
|
571
|
+
addAddress: 'Добавить адрес',
|
|
572
|
+
editAddress: 'Изменить',
|
|
573
|
+
deleteAddress: (address) => `Удалить адрес ${address}`,
|
|
574
|
+
defaultAddress: 'Основной',
|
|
575
|
+
makeDefault: 'Сделать основным',
|
|
576
|
+
noAddresses: 'Сохранённых адресов пока нет.',
|
|
577
|
+
address2: 'Квартира, подъезд, этаж',
|
|
578
|
+
cancel: 'Отмена',
|
|
579
|
+
saveChanges: 'Сохранить',
|
|
580
|
+
changesSaved: 'Сохранено',
|
|
581
|
+
gender: 'Пол',
|
|
582
|
+
genderOptions: { MALE: 'Мужской', FEMALE: 'Женский', none: 'Не указан' },
|
|
583
|
+
language: 'Язык',
|
|
584
|
+
phoneHint: 'По этому номеру вы входите в аккаунт.',
|
|
256
585
|
savedProducts: 'Избранное',
|
|
257
586
|
orderStatus: {
|
|
258
587
|
NEW: 'Новый',
|
|
@@ -303,10 +632,105 @@ const uz: Messages = {
|
|
|
303
632
|
leaveReview: 'Sharh qoldirish',
|
|
304
633
|
shopNow: "Katalogga o'tish",
|
|
305
634
|
ratedOutOf: (rating: number) => `Baho: ${rating} / 5`,
|
|
635
|
+
menu: 'Menyu',
|
|
636
|
+
back: 'Orqaga',
|
|
637
|
+
skipToContent: "Asosiy qismga o'tish",
|
|
638
|
+
catalog: 'Katalog',
|
|
639
|
+
wishlist: 'Saralanganlar',
|
|
640
|
+
cartCount: (count) => `Savat, ${count} ta mahsulot`,
|
|
641
|
+
submenu: (title) => `${title}: bo'limlar`,
|
|
642
|
+
information: "Ma'lumot",
|
|
643
|
+
contacts: 'Kontaktlar',
|
|
644
|
+
email: 'Elektron pochta',
|
|
645
|
+
openingHours: 'Ish vaqti',
|
|
646
|
+
allContacts: 'Barcha kontaktlar va manzillar',
|
|
647
|
+
headOffice: 'Bosh ofis',
|
|
648
|
+
branches: 'Filiallar',
|
|
649
|
+
showOnMap: "Xaritada ko'rsatish",
|
|
650
|
+
landmark: "Mo'ljal",
|
|
651
|
+
closed: 'Dam olish kuni',
|
|
652
|
+
requisites: 'Rekvizitlar',
|
|
653
|
+
contactsEmpty: "Do'kon hali kontaktlarini qo'shmagan.",
|
|
654
|
+
socialMedia: 'Ijtimoiy tarmoqlar',
|
|
655
|
+
viewCart: "Savatga o'tish",
|
|
656
|
+
deliveryAtCheckout: 'Yetkazib berish narxi rasmiylashtirishda hisoblanadi.',
|
|
657
|
+
cartEmptyHint: "Katalogdan biror narsa qo'shing, u shu yerda sizni kutadi.",
|
|
658
|
+
lineIssues: {
|
|
659
|
+
INVALID_QUANTITY: "Bu miqdorda buyurtma berib bo'lmaydi.",
|
|
660
|
+
NOT_FOUND: 'Bu mahsulot endi sotilmaydi.',
|
|
661
|
+
UNPUBLISHED: 'Bu mahsulot endi sotilmaydi.',
|
|
662
|
+
VARIANT_MISMATCH: 'Bu variant endi mavjud emas.',
|
|
663
|
+
INSUFFICIENT_STOCK: 'Omborda tanlangandan kam qolgan.',
|
|
664
|
+
BUNDLE_UNAVAILABLE: "Bu to'plam endi mavjud emas.",
|
|
665
|
+
},
|
|
666
|
+
products: 'Mahsulotlar',
|
|
667
|
+
collections: "To'plamlar",
|
|
668
|
+
suggestions: 'Takliflar',
|
|
669
|
+
popularSearches: "Ko'p qidirilganlar",
|
|
670
|
+
searchFor: (query) => `«${query}» bo'yicha qidirish`,
|
|
671
|
+
noSuggestions: (query) => `«${query}» bo'yicha hech narsa topilmadi.`,
|
|
672
|
+
quickAdd: (title) => `Savatga qo'shish: ${title}`,
|
|
673
|
+
filters: 'Filtrlar',
|
|
674
|
+
sortBy: 'Saralash',
|
|
675
|
+
sortOptions: {
|
|
676
|
+
default: 'Tavsiya etilgan',
|
|
677
|
+
'-createdAt': 'Avval yangilari',
|
|
678
|
+
price: 'Avval arzonlari',
|
|
679
|
+
'-price': 'Avval qimmatlari',
|
|
680
|
+
'-bestSellers': "Ko'p sotilganlar",
|
|
681
|
+
'-rating': 'Yuqori reytingli',
|
|
682
|
+
},
|
|
683
|
+
priceFrom: 'Dan',
|
|
684
|
+
priceTo: 'Gacha',
|
|
685
|
+
clearAll: 'Hammasini tozalash',
|
|
686
|
+
removeFilter: (label) => `Filtrni olib tashlash: ${label}`,
|
|
687
|
+
showResults: "Mahsulotlarni ko'rsatish",
|
|
688
|
+
productsCount: (count) => `${count} ta mahsulot`,
|
|
689
|
+
noMatches: "Bunday filtrlar bilan mahsulot yo'q",
|
|
690
|
+
noMatchesHint: "Bir-ikkita filtrni olib tashlab ko'ring.",
|
|
691
|
+
collectionEmpty: "Hozircha bu yerda hech narsa yo'q",
|
|
692
|
+
collectionEmptyHint:
|
|
693
|
+
"Mahsulotlar tez orada paydo bo'ladi. Hozircha katalogning qolgan qismini ko'ring.",
|
|
694
|
+
faq: 'Savol va javoblar',
|
|
695
|
+
description: 'Tavsif',
|
|
696
|
+
inStock: 'Sotuvda bor',
|
|
697
|
+
lowStock: (count) => `Faqat ${count} ta qoldi`,
|
|
698
|
+
selectOption: (name) => `Tanlang: ${name}`,
|
|
699
|
+
share: 'Ulashish',
|
|
700
|
+
linkCopied: 'Havola nusxalandi',
|
|
701
|
+
imageOf: (index, total) => `Rasm ${index} / ${total}`,
|
|
702
|
+
video: 'Video',
|
|
703
|
+
previousImage: 'Oldingi rasm',
|
|
704
|
+
nextImage: 'Keyingi rasm',
|
|
705
|
+
reviews: 'Sharhlar',
|
|
706
|
+
noReviewsForRating: "Bunday baholi sharhlar yo'q.",
|
|
707
|
+
verifiedBuyer: 'Tasdiqlangan xarid',
|
|
708
|
+
pros: 'Afzalliklari',
|
|
709
|
+
cons: 'Kamchiliklari',
|
|
710
|
+
shopReply: "Do'kon javobi",
|
|
711
|
+
showMoreReviews: 'Yana sharhlar',
|
|
712
|
+
allRatings: 'Barchasi',
|
|
713
|
+
starsFilter: (stars) => `${stars} yulduz`,
|
|
714
|
+
relatedProducts: 'Sizga yoqishi mumkin',
|
|
715
|
+
pairsWellWith: 'Bu mahsulot bilan olishadi',
|
|
716
|
+
preOrderHint: "Kontaktingizni qoldiring — mahsulot kelganda do'kon siz bilan bog'lanadi.",
|
|
717
|
+
preOrderSent: "Oldindan buyurtma yuborildi. Do'kon siz bilan bog'lanadi.",
|
|
718
|
+
sendPreOrder: 'Oldindan buyurtma berish',
|
|
719
|
+
wishlistEmpty: "Sevimlilar hozircha bo'sh",
|
|
720
|
+
wishlistEmptyHint: 'Mahsulotni shu yerda saqlash uchun yurakchani bosing.',
|
|
721
|
+
wishlistGuestHint:
|
|
722
|
+
"Sevimlilar faqat shu qurilmada saqlangan. Hamma joyda ko'rish uchun tizimga kiring.",
|
|
723
|
+
removeFromWishlist: (title) => `«${title}»ni sevimlilardan olib tashlash`,
|
|
724
|
+
close: 'Yopish',
|
|
725
|
+
breadcrumbs: 'Navigatsiya',
|
|
726
|
+
decreaseQuantity: 'Sonini kamaytirish',
|
|
727
|
+
increaseQuantity: 'Sonini oshirish',
|
|
728
|
+
ratingSummary: (average, count) => `Baho: ${average} / 5, ${count} ta sharh`,
|
|
306
729
|
blog: 'Blog',
|
|
307
730
|
allArticles: 'Barcha maqolalar',
|
|
308
731
|
featured: 'Muharrir tanlovi',
|
|
309
732
|
readingTime: (minutes) => `${minutes} daqiqa o'qish`,
|
|
733
|
+
tableOfContents: 'Mundarija',
|
|
310
734
|
relatedArticles: "Shuningdek o'qing",
|
|
311
735
|
productsInArticle: 'Maqoladagi mahsulotlar',
|
|
312
736
|
wasHelpful: "Maqola foydali bo'ldimi?",
|
|
@@ -324,6 +748,7 @@ const uz: Messages = {
|
|
|
324
748
|
save: 'Saqlash',
|
|
325
749
|
saved: 'Saqlangan',
|
|
326
750
|
cartEmpty: "Savatingiz bo'sh",
|
|
751
|
+
loading: 'Yuklanmoqda…',
|
|
327
752
|
loadingCart: 'Savat yuklanmoqda…',
|
|
328
753
|
quantity: 'Soni',
|
|
329
754
|
remove: "O'chirish",
|
|
@@ -332,6 +757,24 @@ const uz: Messages = {
|
|
|
332
757
|
apply: "Qo'llash",
|
|
333
758
|
codeApplied: "qo'llanildi",
|
|
334
759
|
codeNotApplied: "qo'llanilmadi",
|
|
760
|
+
discountReasons: {
|
|
761
|
+
NOT_FOUND: "Bunday promokod yo'q.",
|
|
762
|
+
EXPIRED: 'Promokod muddati tugagan.',
|
|
763
|
+
NOT_AVAILABLE: 'Promokod hozir mavjud emas.',
|
|
764
|
+
USAGE_LIMIT_REACHED: 'Promokod limiti tugagan.',
|
|
765
|
+
ALREADY_USED: 'Siz bu promokoddan allaqachon foydalangansiz.',
|
|
766
|
+
CUSTOMER_NOT_ELIGIBLE: 'Promokod hisobingiz uchun emas.',
|
|
767
|
+
SEGMENT_NOT_ELIGIBLE: 'Promokod hisobingiz uchun emas.',
|
|
768
|
+
MINIMUM_SPEND_NOT_MET: 'Buyurtma summasi promokod uchun minimaldan kam.',
|
|
769
|
+
MAXIMUM_SPEND_EXCEEDED: "Buyurtma summasi promokod uchun maksimaldan ko'p.",
|
|
770
|
+
PRODUCTS_NOT_IN_CART: "Promokod savatda yo'q mahsulotlar uchun.",
|
|
771
|
+
},
|
|
772
|
+
removeCode: (code) => `${code} promokodini olib tashlash`,
|
|
773
|
+
welcomeDiscount: 'Xush kelibsiz chegirmasi',
|
|
774
|
+
pointsDiscount: "Ballar bilan to'langan",
|
|
775
|
+
orderNote: "Do'konga izoh",
|
|
776
|
+
noteSaved: 'Izoh saqlandi',
|
|
777
|
+
summary: 'Buyurtmangiz',
|
|
335
778
|
checkout: 'Rasmiylashtirish',
|
|
336
779
|
checkoutTitle: 'Buyurtmani rasmiylashtirish',
|
|
337
780
|
preparingCheckout: 'Tayyorlanmoqda…',
|
|
@@ -355,6 +798,12 @@ const uz: Messages = {
|
|
|
355
798
|
thankYou: 'Rahmat!',
|
|
356
799
|
order: 'Buyurtma',
|
|
357
800
|
payNow: "To'lash",
|
|
801
|
+
checkoutSteps: 'Rasmiylashtirish bosqichlari',
|
|
802
|
+
stepDone: 'tayyor',
|
|
803
|
+
showOrderSummary: "Buyurtmani ko'rsatish",
|
|
804
|
+
etaMinutes: (minutes) => `taxminan ${minutes} daqiqa`,
|
|
805
|
+
orderNumberHint: "Bu raqamni saqlang: do'kon buyurtmangizni u orqali topadi.",
|
|
806
|
+
backToCart: 'Savatga qaytish',
|
|
358
807
|
signIn: 'Kirish',
|
|
359
808
|
sendCode: 'Kod olish',
|
|
360
809
|
smsCode: 'SMS dagi kod',
|
|
@@ -371,6 +820,41 @@ const uz: Messages = {
|
|
|
371
820
|
orders: 'Buyurtmalar',
|
|
372
821
|
loadingOrders: 'Buyurtmalar yuklanmoqda…',
|
|
373
822
|
noOrders: "Hozircha buyurtmalar yo'q.",
|
|
823
|
+
accountNav: 'Shaxsiy kabinet',
|
|
824
|
+
overview: 'Umumiy',
|
|
825
|
+
addresses: 'Manzillar',
|
|
826
|
+
profile: 'Profil',
|
|
827
|
+
orderNumber: (number) => `Buyurtma ${number}`,
|
|
828
|
+
placedOn: (date) => `${date} da berilgan`,
|
|
829
|
+
itemsCount: (count) => `${count} ta mahsulot`,
|
|
830
|
+
backToOrders: 'Barcha buyurtmalar',
|
|
831
|
+
orderAgain: 'Qayta buyurtma berish',
|
|
832
|
+
cancelOrder: 'Buyurtmani bekor qilish',
|
|
833
|
+
cancelOrderConfirm: "Buyurtma bekor qilinsinmi? Do'kon uni tayyorlamaydi.",
|
|
834
|
+
keepOrder: 'Bekor qilmaslik',
|
|
835
|
+
orderCancelledNote: 'Bu buyurtma bekor qilingan.',
|
|
836
|
+
paymentStatus: {
|
|
837
|
+
PENDING: "To'lov kutilmoqda",
|
|
838
|
+
PAID: "To'langan",
|
|
839
|
+
FAILED: "To'lanmagan",
|
|
840
|
+
NOT_REQUIRED: "Do'kondan tashqarida to'langan",
|
|
841
|
+
},
|
|
842
|
+
deliveryAddress: 'Yetkazib berish manzili',
|
|
843
|
+
pickupPoint: 'Olib ketish punkti',
|
|
844
|
+
addAddress: "Manzil qo'shish",
|
|
845
|
+
editAddress: "O'zgartirish",
|
|
846
|
+
deleteAddress: (address) => `${address} manzilini o'chirish`,
|
|
847
|
+
defaultAddress: 'Asosiy',
|
|
848
|
+
makeDefault: 'Asosiy qilish',
|
|
849
|
+
noAddresses: "Saqlangan manzillar hozircha yo'q.",
|
|
850
|
+
address2: 'Kvartira, kirish, qavat',
|
|
851
|
+
cancel: 'Bekor qilish',
|
|
852
|
+
saveChanges: 'Saqlash',
|
|
853
|
+
changesSaved: 'Saqlandi',
|
|
854
|
+
gender: 'Jins',
|
|
855
|
+
genderOptions: { MALE: 'Erkak', FEMALE: 'Ayol', none: "Ko'rsatilmagan" },
|
|
856
|
+
language: 'Til',
|
|
857
|
+
phoneHint: 'Siz shu raqam bilan kirasiz.',
|
|
374
858
|
savedProducts: 'Saqlanganlar',
|
|
375
859
|
orderStatus: {
|
|
376
860
|
NEW: 'Yangi',
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { MagicStoreError, type Schema } from '@magicstoreai/storefront-client';
|
|
2
|
+
|
|
3
|
+
type Menu = Schema<'Menu'>;
|
|
4
|
+
type MenuItem = Schema<'MenuItem'>;
|
|
5
|
+
type Collection = Pick<Schema<'Collection'>, 'title' | 'handle'>;
|
|
6
|
+
|
|
7
|
+
/** One navigation link with its children, whatever it came from (a menu or the collections). */
|
|
8
|
+
export type NavItem = { title: string; href: string; children: NavItem[] };
|
|
9
|
+
|
|
10
|
+
/** Where a menu item leads: a collection, or a content page (policies are pages too). */
|
|
11
|
+
export function menuItemHref(item: Pick<MenuItem, 'type' | 'handle'>): string {
|
|
12
|
+
return item.type === 'COLLECTION' ? `/collections/${item.handle}` : `/pages/${item.handle}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function toNav(items: MenuItem[], keep: (item: MenuItem) => boolean): NavItem[] {
|
|
16
|
+
return items.filter(keep).map((item) => ({
|
|
17
|
+
title: item.title,
|
|
18
|
+
href: menuItemHref(item),
|
|
19
|
+
children: toNav(item.items, keep),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The header's navigation: the merchant's `main` menu (the published collection tree), policies
|
|
25
|
+
* left out (they belong in the footer). With no menu, or an empty one, the top-level collections.
|
|
26
|
+
*/
|
|
27
|
+
export function mainNav(menu: Menu | null, collections: Collection[]): NavItem[] {
|
|
28
|
+
const items = menu ? toNav(menu.items, (item) => item.type !== 'POLICY') : [];
|
|
29
|
+
if (items.length > 0) {
|
|
30
|
+
return items;
|
|
31
|
+
}
|
|
32
|
+
return collections.map((collection) => ({
|
|
33
|
+
title: collection.title,
|
|
34
|
+
href: `/collections/${collection.handle}`,
|
|
35
|
+
children: [],
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The footer's links from the `footer` menu: items with children become titled columns, the rest
|
|
41
|
+
* one list of links, and `POLICY` items (offer, privacy, returns) their own row at the bottom.
|
|
42
|
+
*/
|
|
43
|
+
export function footerNav(menu: Menu | null): {
|
|
44
|
+
columns: NavItem[];
|
|
45
|
+
links: NavItem[];
|
|
46
|
+
policies: NavItem[];
|
|
47
|
+
} {
|
|
48
|
+
const items = menu?.items ?? [];
|
|
49
|
+
const nav = (item: MenuItem) => toNav([item], () => true)[0]!;
|
|
50
|
+
return {
|
|
51
|
+
columns: items.filter((item) => item.type !== 'POLICY' && item.items.length > 0).map(nav),
|
|
52
|
+
links: items.filter((item) => item.type !== 'POLICY' && item.items.length === 0).map(nav),
|
|
53
|
+
policies: items.filter((item) => item.type === 'POLICY').map(nav),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A menu by handle, or `null` when the shop has none (`NOT_FOUND`). Any other failure is thrown:
|
|
59
|
+
* the error boundary shows it rather than a header quietly missing its links.
|
|
60
|
+
*/
|
|
61
|
+
export async function readMenu(read: () => Promise<{ data: Menu }>): Promise<Menu | null> {
|
|
62
|
+
try {
|
|
63
|
+
return (await read()).data;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof MagicStoreError && error.code === 'NOT_FOUND') {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|