diff --git a/storefront/app/components/base/footer/index.vue b/storefront/app/components/base/footer/index.vue index 29d1acca..19002d76 100644 --- a/storefront/app/components/base/footer/index.vue +++ b/storefront/app/components/base/footer/index.vue @@ -20,9 +20,9 @@
{{ t('footer.help') }}
{{ t('contact.title') }} {{ t('docs.policy.title') }} - {{ t('docs.terms.title') }} + {{ t('docs.terms.title') }} {{ t('docs.shipping.title') }} - {{ t('docs.return.title') }} + {{ t('docs.return.title') }} {{ t('docs.about.title') }} {{ t('docs.faq.title') }} diff --git a/storefront/app/components/base/header/index.vue b/storefront/app/components/base/header/index.vue index 4465e5cb..31254f53 100644 --- a/storefront/app/components/base/header/index.vue +++ b/storefront/app/components/base/header/index.vue @@ -119,22 +119,35 @@ const appStore = useAppStore(); const userStore = useUserStore(); const wishlistStore = useWishlistStore(); const cartStore = useCartStore(); +const { $appHelpers } = useNuxtApp(); const { uiConfig } = useProjectConfig(); const isAuthenticated = computed(() => userStore.isAuthenticated); const user = computed(() => userStore.user); -const productsInCartQuantity = computed(() => { - let count = 0; - cartStore.currentOrder?.orderProducts?.edges.forEach((el) => { - count = count + el.node.quantity; - }); +const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, { + default: () => [], + path: '/', +}); - return count; +const productsInCartQuantity = computed(() => { + if (isAuthenticated.value) { + let count = 0; + cartStore.currentOrder?.orderProducts?.edges.forEach((el) => { + count = count + el.node.quantity; + }); + return count; + } else { + return cookieCart.value.reduce((acc, item) => acc + item.quantity, 0); + } }); const productsInWishlistQuantity = computed(() => { - return wishlistStore.wishlist ? wishlistStore.wishlist.products.edges.length : 0; + if (isAuthenticated.value) { + return wishlistStore.wishlist ? wishlistStore.wishlist.products.edges.length : 0; + } else { + return cookieWishlist.value.length + } }); const isSearchVisible = ref(false); @@ -302,7 +315,7 @@ const redirectTo = (to) => { } &-avatar { - width: 32px; + width: 28px; border-radius: 50%; border: 1px solid #374151; transition: 0.2s; @@ -317,7 +330,7 @@ const redirectTo = (to) => { } &-profile { - width: 32px; + width: 28px; border-radius: 50%; padding: 5px; border: 1px solid #374151; diff --git a/storefront/app/components/cards/post.vue b/storefront/app/components/cards/post.vue index f9eecc93..0904e3cd 100644 --- a/storefront/app/components/cards/post.vue +++ b/storefront/app/components/cards/post.vue @@ -9,7 +9,7 @@ import type {IPost} from '@types'; const props = defineProps<{ - post: { node: IPost }[]; + post: IPost; }>(); const {t} = useI18n(); diff --git a/storefront/app/components/cards/product.vue b/storefront/app/components/cards/product.vue index 51d24295..8a490c86 100644 --- a/storefront/app/components/cards/product.vue +++ b/storefront/app/components/cards/product.vue @@ -7,11 +7,10 @@ class="card__wishlist" @click="overwriteWishlist({ type: (isProductInWishlist ? 'remove' : 'add'), - productUuid: product.uuid, - productName: product.name + product: product, })" > - +
diff --git a/storefront/app/composables/auth/useLogin.ts b/storefront/app/composables/auth/useLogin.ts index 087c48e7..c4724ba1 100644 --- a/storefront/app/composables/auth/useLogin.ts +++ b/storefront/app/composables/auth/useLogin.ts @@ -43,6 +43,8 @@ export function useLogin() { userStore.setUser(authData.user); cookieAccess.value = authData.accessToken; + await nextTick(); + navigateTo(localePath('/')); useNotification({ diff --git a/storefront/app/composables/breadcrumbs/useBreadcrumbs.ts b/storefront/app/composables/breadcrumbs/useBreadcrumbs.ts index 8b9a702a..1df13a88 100644 --- a/storefront/app/composables/breadcrumbs/useBreadcrumbs.ts +++ b/storefront/app/composables/breadcrumbs/useBreadcrumbs.ts @@ -82,8 +82,6 @@ export function useBreadcrumbs() { return crumbs; }); - console.log(breadcrumbs.value) - return { breadcrumbs, }; diff --git a/storefront/app/composables/orders/index.ts b/storefront/app/composables/orders/index.ts index c0f5c51c..332c179f 100644 --- a/storefront/app/composables/orders/index.ts +++ b/storefront/app/composables/orders/index.ts @@ -1,3 +1,4 @@ export * from './useOrderBuy'; export * from './useOrderOverwrite'; +export * from './useOrderSync'; export * from './useOrders'; diff --git a/storefront/app/composables/orders/useOrderOverwrite.ts b/storefront/app/composables/orders/useOrderOverwrite.ts index 91b20a5e..2be8f5fd 100644 --- a/storefront/app/composables/orders/useOrderOverwrite.ts +++ b/storefront/app/composables/orders/useOrderOverwrite.ts @@ -8,7 +8,7 @@ import { } from '@graphql/mutations/cart'; import type { IAddToOrderResponse, - IBulkOrderResponse, + IBulkOrderResponse, IProduct, IRemoveAllFromOrderResponse, IRemoveFromOrderResponse, IRemoveKindFromOrderResponse, @@ -16,9 +16,9 @@ import type { interface IOverwriteOrderArguments { type: string; - productUuid?: string; - productName?: string; + product: IProduct; bulkAction?: string; + isBulkSync?: boolean; products?: { uuid: string; }[]; @@ -32,6 +32,11 @@ export function useOrderOverwrite() { const isAuthenticated = computed(() => userStore.isAuthenticated); const orderUuid = computed(() => cartStore.currentOrder?.uuid); + const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, { + default: () => [], + path: '/', + }); + const { mutate: addMutate, loading: addLoading, @@ -64,7 +69,7 @@ export function useOrderOverwrite() { case 'add': { const addResult = await addMutate({ orderUuid: orderUuid.value, - productUuid: args.productUuid, + productUuid: args.product.uuid, }); if (addResult?.data?.addOrderProduct?.order) { @@ -72,7 +77,7 @@ export function useOrderOverwrite() { useNotification({ message: t('popup.success.addToCart', { - product: args.productName, + product: args.product.name, }), type: 'success', }); @@ -84,7 +89,7 @@ export function useOrderOverwrite() { case 'remove': { const removeResult = await removeMutate({ orderUuid: orderUuid.value, - productUuid: args.productUuid, + productUuid: args.product.uuid, }); if (removeResult?.data?.removeOrderProduct?.order) { @@ -92,7 +97,7 @@ export function useOrderOverwrite() { useNotification({ message: t('popup.success.removeFromCart', { - product: args.productName, + product: args.product.name, }), type: 'success', }); @@ -104,7 +109,7 @@ export function useOrderOverwrite() { case 'removeKind': { const removeKindResult = await removeKindMutate({ orderUuid: orderUuid.value, - productUuid: args.productUuid, + productUuid: args.product.uuid, }); if (removeKindResult?.data?.removeOrderProductsOfAKind?.order) { @@ -112,7 +117,7 @@ export function useOrderOverwrite() { useNotification({ message: t('popup.success.removeFromCart', { - product: args.productName, + product: args.product.name, }), type: 'success', }); @@ -148,7 +153,7 @@ export function useOrderOverwrite() { if (bulkResult?.data?.bulkOrderAction?.order) { cartStore.setCurrentOrders(bulkResult.data.bulkOrderAction.order); useNotification({ - message: t('popup.success.bulkRemoveWishlist'), + message: t('popup.success.bulkRemoveOrder'), type: 'success', }); } @@ -160,10 +165,119 @@ export function useOrderOverwrite() { console.error('No type provided for overwriteOrder'); } } else { - useNotification({ - message: t('popup.errors.loginFirst'), - type: 'error', - }); + switch (args.type) { + case 'add': { + const currentCart = cookieCart.value || []; + const existingItem = currentCart.find( + (item) => item.product.uuid === args.product.uuid + ); + + if (existingItem) { + existingItem.quantity += 1; + cookieCart.value = [...currentCart]; + } else { + cookieCart.value = [ + ...currentCart, + { product: args.product, quantity: 1 } + ]; + } + + useNotification({ + message: t('popup.success.addToCart', { + product: args.product.name, + }), + type: 'success', + }); + + break; + } + + case 'remove': { + const currentCart = cookieCart.value || []; + const existingItem = currentCart.find( + (item) => item.product.uuid === args.product.uuid + ); + + if (existingItem) { + if (existingItem.quantity > 1) { + existingItem.quantity -= 1; + cookieCart.value = [...currentCart]; + } else { + cookieCart.value = currentCart.filter( + (item) => item.product.uuid !== args.product.uuid + ); + } + + useNotification({ + message: t('popup.success.removeFromCart', { + product: args.product.name, + }), + type: 'success', + }); + } + + break; + } + + case 'removeKind': { + cookieCart.value = cookieCart.value.filter( + (item) => item.product.uuid !== args.product.uuid + ); + + useNotification({ + message: t('popup.success.removeFromCart', { + product: args.product.name, + }), + type: 'success', + }); + + break; + } + + case 'removeAll': { + cookieCart.value = []; + + useNotification({ + message: t('popup.success.removeAllFromCart'), + type: 'success', + }); + + break; + } + + case 'bulk': { + if (args.bulkAction === 'remove' && args.products) { + const uuidsToRemove = args.products.map(p => p.uuid); + cookieCart.value = cookieCart.value.filter( + (item) => !uuidsToRemove.includes(item.product.uuid) + ); + + useNotification({ + message: t('popup.success.bulkRemoveOrder'), + type: 'success', + }); + } else if (args.bulkAction === 'add' && args.products) { + const currentCart = cookieCart.value || []; + + for (const productRef of args.products) { + const existingItem = currentCart.find( + (item) => item.product.uuid === productRef.uuid + ); + + if (existingItem) { + existingItem.quantity += 1; + } + } + + cookieCart.value = [...currentCart]; + } + + break; + } + + default: + console.error('No type provided for overwriteOrder'); + } } } diff --git a/storefront/app/composables/orders/useOrderSync.ts b/storefront/app/composables/orders/useOrderSync.ts new file mode 100644 index 00000000..202450a6 --- /dev/null +++ b/storefront/app/composables/orders/useOrderSync.ts @@ -0,0 +1,68 @@ +import { useOrderOverwrite } from '@composables/orders/useOrderOverwrite'; + +export function useOrderSync() { + const cartStore = useCartStore(); + const userStore = useUserStore(); + const { $appHelpers } = useNuxtApp(); + + const { overwriteOrder } = useOrderOverwrite(); + + const isAuthenticated = computed(() => userStore.isAuthenticated); + const orderUuid = computed(() => cartStore.currentOrder?.uuid); + + const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, { + default: () => [], + path: '/', + }); + + async function syncOrder() { + if (!isAuthenticated.value || !orderUuid.value) { + return; + } + + const cookieCartItems = cookieCart.value || []; + + if (cookieCartItems.length === 0) { + return; + } + + const apiCartProducts = cartStore.currentOrder?.orderProducts?.edges || []; + + const apiProductMap = new Map( + apiCartProducts.map(e => [e.node.product.uuid, e.node.quantity]) + ); + + const productsToSync = []; + + for (const cartItem of cookieCartItems) { + const apiQuantity = apiProductMap.get(cartItem.product.uuid) || 0; + const quantityDifference = cartItem.quantity - apiQuantity; + + if (quantityDifference > 0) { + for (let i = 0; i < quantityDifference; i++) { + productsToSync.push({ uuid: cartItem.product.uuid }); + } + } + } + + if (productsToSync.length === 0) { + cookieCart.value = []; + return; + } + + try { + await overwriteOrder({ + type: 'bulk', + bulkAction: 'add', + isBulkSync: true, + products: productsToSync + }); + } catch (err) { + console.error('Failed to sync cart:', err); + } + } + + return { + syncOrder, + }; +} \ No newline at end of file diff --git a/storefront/app/composables/user/useUserBaseData.ts b/storefront/app/composables/user/useUserBaseData.ts index 30497cc2..d23d866d 100644 --- a/storefront/app/composables/user/useUserBaseData.ts +++ b/storefront/app/composables/user/useUserBaseData.ts @@ -1,4 +1,6 @@ import { orderStatuses } from '@appConstants'; +import { useOrderSync } from '@composables/orders'; +import {useWishlistSync} from "@composables/wishlist"; import { getUserBaseData } from '@graphql/queries/combined/userBaseData'; import type { IUserBaseDataResponse } from '@types'; @@ -7,6 +9,9 @@ export async function useUserBaseData(userEmail: string) { const cartStore = useCartStore(); const promocodeStore = usePromocodeStore(); + const { syncWishlist } = useWishlistSync(); + const { syncOrder } = useOrderSync(); + const { document, variables } = getUserBaseData({ userEmail, status: orderStatuses.PENDING, @@ -19,9 +24,13 @@ export async function useUserBaseData(userEmail: string) { if (data?.wishlists.edges) { wishlistStore.setWishlist(data.wishlists.edges[0].node); + + await syncWishlist(); } if (data?.orders.edges) { cartStore.setCurrentOrders(data.orders.edges[0].node); + + await syncOrder(); } if (data?.promocodes.edges) { promocodeStore.setPromocodes(data.promocodes.edges); diff --git a/storefront/app/composables/wishlist/index.ts b/storefront/app/composables/wishlist/index.ts index ecc23d95..3f65a7cd 100644 --- a/storefront/app/composables/wishlist/index.ts +++ b/storefront/app/composables/wishlist/index.ts @@ -1,2 +1,3 @@ export * from './useWishlist'; export * from './useWishlistOverwrite'; +export * from './useWishlistSync'; diff --git a/storefront/app/composables/wishlist/useWishlistOverwrite.ts b/storefront/app/composables/wishlist/useWishlistOverwrite.ts index f32924fe..5c2c7bad 100644 --- a/storefront/app/composables/wishlist/useWishlistOverwrite.ts +++ b/storefront/app/composables/wishlist/useWishlistOverwrite.ts @@ -7,16 +7,16 @@ import { } from '@graphql/mutations/wishlist'; import type { IAddToWishlistResponse, - IBulkWishlistResponse, + IBulkWishlistResponse, IProduct, IRemoveAllFromWishlistResponse, IRemoveFromWishlistResponse, } from '@types'; interface IOverwriteWishlistArguments { type: string; - productUuid?: string; - productName?: string; + product: IProduct; bulkAction?: string; + isBulkSync?: boolean; products?: { uuid: string; }[]; @@ -26,10 +26,16 @@ export function useWishlistOverwrite() { const { t } = useI18n(); const wishlistStore = useWishlistStore(); const userStore = useUserStore(); + const { $appHelpers } = useNuxtApp(); const isAuthenticated = computed(() => userStore.isAuthenticated); const wishlistUuid = computed(() => wishlistStore.wishlist?.uuid); + const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, { + default: () => [], + path: '/', + }); + const { mutate: addMutate, loading: addLoading, @@ -57,7 +63,7 @@ export function useWishlistOverwrite() { case 'add': { const addResult = await addMutate({ wishlistUuid: wishlistUuid.value, - productUuid: args.productUuid, + productUuid: args.product.uuid, }); if (addResult?.data?.addWishlistProduct?.wishlist) { @@ -65,7 +71,7 @@ export function useWishlistOverwrite() { useNotification({ message: t('popup.success.addToWishlist', { - product: args.productName, + product: args.product.name, }), type: 'success', }); @@ -77,7 +83,7 @@ export function useWishlistOverwrite() { case 'remove': { const removeResult = await removeMutate({ wishlistUuid: wishlistUuid.value, - productUuid: args.productUuid, + productUuid: args.product.uuid, }); if (removeResult?.data?.removeWishlistProduct?.wishlist) { @@ -85,7 +91,7 @@ export function useWishlistOverwrite() { useNotification({ message: t('popup.success.removeFromWishlist', { - product: args.productName, + product: args.product.name, }), type: 'success', }); @@ -97,7 +103,7 @@ export function useWishlistOverwrite() { case 'removeAll': { const removeAllResult = await removeAllMutate({ wishlistUuid: wishlistUuid.value, - productUuid: args.productUuid, + productUuid: args.product.uuid, }); if (removeAllResult?.data?.removeAllWishlistProducts?.wishlist) { @@ -121,10 +127,15 @@ export function useWishlistOverwrite() { if (bulkResult?.data?.bulkWishlistAction?.wishlist) { wishlistStore.setWishlist(bulkResult.data.bulkWishlistAction.wishlist); - useNotification({ - message: t('popup.success.bulkRemoveWishlist'), - type: 'success', - }); + + if (args.isBulkSync) { + cookieWishlist.value = []; + } else { + useNotification({ + message: t('popup.success.bulkRemoveWishlist'), + type: 'success', + }); + } } break; @@ -134,10 +145,85 @@ export function useWishlistOverwrite() { console.error('No type provided for overwriteWishlist'); } } else { - useNotification({ - message: t('popup.errors.loginFirst'), - type: 'error', - }); + switch (args.type) { + case 'add': { + const isAlreadyInWishlist = cookieWishlist.value.some( + (item) => item.uuid === args.product.uuid + ); + + if (isAlreadyInWishlist) { + useNotification({ + message: t('popup.errors.alreadyInWishlist', { + product: args.product.name, + }), + type: 'warning', + }); + } else { + cookieWishlist.value = [...cookieWishlist.value, args.product]; + + useNotification({ + message: t('popup.success.addToWishlist', { + product: args.product.name, + }), + type: 'success', + }); + } + + break; + } + + case 'remove': { + cookieWishlist.value = cookieWishlist.value.filter( + (item) => item.uuid !== args.product.uuid + ); + + useNotification({ + message: t('popup.success.removeFromWishlist', { + product: args.product.name, + }), + type: 'success', + }); + + break; + } + + case 'removeAll': { + cookieWishlist.value = []; + + useNotification({ + message: t('popup.success.removeAllFromWishlist'), + type: 'success', + }); + + break; + } + + case 'bulk': { + const bulkResult = await bulkMutate({ + wishlistUuid: wishlistUuid.value, + action: args.bulkAction, + products: args.products, + }); + + if (bulkResult?.data?.bulkWishlistAction?.wishlist) { + wishlistStore.setWishlist(bulkResult.data.bulkWishlistAction.wishlist); + + if (args.isBulkSync) { + cookieWishlist.value = []; + } else { + useNotification({ + message: t('popup.success.bulkRemoveWishlist'), + type: 'success', + }); + } + } + + break; + } + + default: + console.error('No type provided for overwriteWishlist'); + } } } diff --git a/storefront/app/composables/wishlist/useWishlistSync.ts b/storefront/app/composables/wishlist/useWishlistSync.ts new file mode 100644 index 00000000..d149a9a1 --- /dev/null +++ b/storefront/app/composables/wishlist/useWishlistSync.ts @@ -0,0 +1,66 @@ +import {useWishlistOverwrite} from "@composables/wishlist/useWishlistOverwrite"; + +export function useWishlistSync() { + const wishlistStore = useWishlistStore(); + const userStore = useUserStore(); + const { $appHelpers } = useNuxtApp(); + + const { overwriteWishlist } = useWishlistOverwrite(); + + const isAuthenticated = computed(() => userStore.isAuthenticated); + const wishlistUuid = computed(() => wishlistStore.wishlist?.uuid); + + const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, { + default: () => [], + path: '/', + }); + + async function syncWishlist() { + if (!isAuthenticated.value || !wishlistUuid.value) { + return; + } + + const cookieProducts = cookieWishlist.value || []; + + if (cookieProducts.length === 0) { + return; + } + + const apiProductUuids = wishlistStore.wishlist?.products?.edges.map(e => e.node.uuid) || []; + + const productsToAdd = cookieProducts.filter( + (product) => !apiProductUuids.includes(product.uuid) + ); + + if (productsToAdd.length === 0) { + cookieWishlist.value = []; + return; + } + + try { + await overwriteWishlist({ + type: 'bulk', + bulkAction: 'add', + isBulkSync: true, + products: productsToAdd.map(p => ({ uuid: p.uuid })) + }) + + if (bulkResult?.data?.bulkWishlistAction?.wishlist) { + wishlistStore.setWishlist(bulkResult.data.bulkWishlistAction.wishlist); + + cookieWishlist.value = []; + } + } catch (err) { + console.error('Failed to sync wishlist:', err); + } + } + + watch(syncError, (err) => { + if (!err) return; + console.error('useWishlistSync error:', err); + }); + + return { + syncWishlist, + }; +} \ No newline at end of file diff --git a/storefront/app/constants/cookies.ts b/storefront/app/constants/cookies.ts index 64ce7b36..23e27ffa 100644 --- a/storefront/app/constants/cookies.ts +++ b/storefront/app/constants/cookies.ts @@ -2,6 +2,8 @@ export const COOKIE_KEY_TEMPLATES = { LOCALE: (appNameKey: string) => `${appNameKey}-locale`, REFRESH_TOKEN: (appNameKey: string) => `${appNameKey}-refresh`, ACCESS_TOKEN: (appNameKey: string) => `${appNameKey}-access`, + WISHLIST_TOKEN: (appNameKey: string) => `${appNameKey}-wishlist`, + CART_TOKEN: (appNameKey: string) => `${appNameKey}-cart`, PRODUCT_VIEW: (appNameKey: string) => `${appNameKey}-product-view`, THEME: (appNameKey: string) => `${appNameKey}-theme`, } as const; diff --git a/storefront/app/pages/cart.vue b/storefront/app/pages/cart.vue index de4e37e2..04798ed8 100644 --- a/storefront/app/pages/cart.vue +++ b/storefront/app/pages/cart.vue @@ -4,7 +4,27 @@

{{ t('cart.title') }}

-

({{ t('cart.items', productsInCartQuantity, { count: productsInCartQuantity }) }})

+
+

({{ t('cart.items', productsInCartQuantity, { count: productsInCartQuantity }) }})

+ + + {{ t('buttons.checkout') }} + +
+
+
+
+ +
+

{{ t('cart.empty') }}

@@ -13,23 +33,55 @@ + + \ No newline at end of file diff --git a/storefront/app/plugins/00.app-constants.ts b/storefront/app/plugins/00.app-constants.ts index acf1f406..529ca08d 100644 --- a/storefront/app/plugins/00.app-constants.ts +++ b/storefront/app/plugins/00.app-constants.ts @@ -14,6 +14,8 @@ export default defineNuxtPlugin(() => { COOKIES_LOCALE_KEY: COOKIE_KEY_TEMPLATES.LOCALE(APP_NAME_KEY), COOKIES_REFRESH_TOKEN_KEY: COOKIE_KEY_TEMPLATES.REFRESH_TOKEN(APP_NAME_KEY), COOKIES_ACCESS_TOKEN_KEY: COOKIE_KEY_TEMPLATES.ACCESS_TOKEN(APP_NAME_KEY), + COOKIES_WISHLIST_KEY: COOKIE_KEY_TEMPLATES.WISHLIST_TOKEN(APP_NAME_KEY), + COOKIES_CART_KEY: COOKIE_KEY_TEMPLATES.CART_TOKEN(APP_NAME_KEY), COOKIES_PRODUCT_VIEW_KEY: COOKIE_KEY_TEMPLATES.PRODUCT_VIEW(APP_NAME_KEY), COOKIES_THEME_KEY: COOKIE_KEY_TEMPLATES.THEME(APP_NAME_KEY), }, diff --git a/storefront/app/plugins/01.apollo.ts b/storefront/app/plugins/01.apollo.ts index fa8f59a7..7cb08c80 100644 --- a/storefront/app/plugins/01.apollo.ts +++ b/storefront/app/plugins/01.apollo.ts @@ -1,3 +1,4 @@ + import { ApolloLink, from } from '@apollo/client/core'; import { setContext } from '@apollo/client/link/context'; import { onError } from '@apollo/client/link/error'; @@ -12,25 +13,47 @@ export default defineNuxtPlugin((nuxtApp) => { nuxtApp.callHook('apollo:error', err); }); - const localeCookie = useCookie($appHelpers.COOKIES_LOCALE_KEY); - const accessTokenCookie = useCookie($appHelpers.COOKIES_ACCESS_TOKEN_KEY); - nuxtApp.hook('apollo:error', (error) => { console.error('[Apollo Error]:', error); }); const authLink = setContext((_, { headers }) => { - const acceptLanguage = localeCookie.value || 'en-gb'; + let accessToken = ''; + let locale = 'en-gb'; - return { - headers: { - ...headers, - 'Accept-Language': acceptLanguage, - ...(accessTokenCookie.value && { - 'X-SCHON-AUTH': `Bearer ${accessTokenCookie.value}`, - }), - }, + if (import.meta.client) { + const clientCookies = document.cookie.split(';').reduce((acc, cookie) => { + const [key, value] = cookie.trim().split('='); + acc[key] = decodeURIComponent(value); + return acc; + }, {} as Record); + + accessToken = clientCookies[$appHelpers.COOKIES_ACCESS_TOKEN_KEY] || ''; + locale = clientCookies[$appHelpers.COOKIES_LOCALE_KEY] || 'en-gb'; + } else { + const cookieHeader = nuxtApp.ssrContext?.event?.node?.req?.headers?.cookie || ''; + const serverCookies = cookieHeader.split(';').reduce((acc, cookie) => { + const [key, value] = cookie.trim().split('='); + if (key && value) { + acc[key] = decodeURIComponent(value); + } + return acc; + }, {} as Record); + + accessToken = serverCookies[$appHelpers.COOKIES_ACCESS_TOKEN_KEY] || ''; + locale = serverCookies[$appHelpers.COOKIES_LOCALE_KEY] || 'en-gb'; + } + + const hdrs: Record = { + ...headers, + 'Accept-Language': locale }; + + if (accessToken) { + hdrs['X-SCHON-AUTH'] = `Bearer ${accessToken}`; + } + + return { headers: hdrs }; }); const customLink = new ApolloLink((operation, forward) => { @@ -51,4 +74,4 @@ export default defineNuxtPlugin((nuxtApp) => { ])); provideApolloClient($apollo.defaultClient); -}); +}); \ No newline at end of file diff --git a/storefront/i18n/locales/en-gb.json b/storefront/i18n/locales/en-gb.json index 8fa61298..b08081ab 100644 --- a/storefront/i18n/locales/en-gb.json +++ b/storefront/i18n/locales/en-gb.json @@ -5,6 +5,7 @@ "createAccount": "Create Account", "addToCart": "Add to cart", "removeFromCart": "Remove from cart", + "addAllToCart": "Add all to cart", "addToWishlist": "Add to Wishlist", "removeFromWishlist": "Remove from Wishlist", "send": "Send", @@ -59,7 +60,8 @@ "main": "Error!", "defaultError": "Something went wrong..", "noDataToUpdate": "There is no data to update.", - "loginFirst": "You should be logged in to do this action!" + "loginFirst": "You should be logged in to do this action!", + "alreadyInWishlist": "{product} is already in wishlist!" }, "success": { "login": "Sign in successes", @@ -75,6 +77,7 @@ "removeFromWishlist": "{product} has been removed from the wishlist!", "removeAllFromWishlist": "You have successfully emptied the wishlist!", "bulkRemoveWishlist": "Selected items have been successfully removed from the wishlist!", + "bulkRemoveOrder": "Selected items have been successfully removed from the cart!", "avatarUpload": "You have successfully uploaded an avatar!", "userUpdate": "Profile successfully updated!", "emailUpdate": "Check your inbox for a confirmation link to complete your email update.", @@ -325,8 +328,14 @@ "text": "Discover the latest trends, style inspiration, and fashion insights from our editorial team." }, "cart": { - "title": "Your Cart", - "items": "no items | {count} item | {count} items" + "title": "My Cart", + "items": "no items | {count} item | {count} items", + "empty": "Your cart is empty." + }, + "wishlist": { + "title": "My Wishlist", + "items": "no items | {count} item | {count} items", + "empty": "Your wishlist is empty." }, "shop": { "title": "Shop", diff --git a/storefront/i18n/locales/ro-ro.json b/storefront/i18n/locales/ro-ro.json index fe555ac3..9e26dfee 100644 --- a/storefront/i18n/locales/ro-ro.json +++ b/storefront/i18n/locales/ro-ro.json @@ -1,335 +1 @@ -{ - "buttons": { - "login": "Войти", - "register": "Регистрация", - "createAccount": "Создать аккаунт", - "addToCart": "В корзину", - "removeFromCart": "Удалить из корзины", - "addToWishlist": "В избранное", - "removeFromWishlist": "Удалить из избранного", - "send": "Отправить", - "goEmail": "Перейти к почте", - "logout": "Выйти", - "checkout": "Оформить заказ", - "save": "Сохранить", - "sendLink": "Отправить ссылку для сброса", - "topUp": "Пополнить", - "shopNow": "КУПИТЬ СЕЙЧАС", - "shopTheSale": "Купить по акции", - "readMore": "Читать далее", - "sendMessage": "Отправить сообщение", - "saveChanges": "Сохранить изменения", - "clearAll": "Очистить всё" - }, - "errors": { - "required": "Это поле обязательно!", - "mail": "Введите корректный email!", - "compare": "Пароли не совпадают!", - "needLower": "Добавьте строчную букву.", - "needUpper": "Добавьте заглавную букву.", - "needNumber": "Добавьте цифру.", - "needMin": "Мин. 8 символов", - "needSpecial": "Добавьте спецсимвол: #.?!$%^&*'()_+=:;\"'/>.<,|\\-", - "pageNotFound": "Страница не найдена" - }, - "fields": { - "search": "Поиск", - "searchOrder": "Поиск заказа", - "name": "Имя", - "firstName": "Имя", - "lastName": "Фамилия", - "phoneNumber": "Номер телефона", - "email": "Email", - "subject": "Тема", - "message": "Ваше сообщение", - "password": "Пароль", - "newPassword": "Новый пароль", - "confirmPassword": "Подтвердите пароль", - "confirmNewPassword": "Подтвердите новый пароль", - "brandsSearch": "Поиск брендов по названию..." - }, - "checkboxes": { - "remember": "Запомнить меня", - "chooseAll": "Выбрать все", - "agree": "Я согласен с {terms} и {policy}", - "subscribe": "Подписаться на рассылку новостей об эксклюзивных предложениях и обновлениях" - }, - "popup": { - "errors": { - "main": "Ошибка!", - "defaultError": "Что-то пошло не так...", - "noDataToUpdate": "Нет данных для обновления.", - "loginFirst": "Войдите, чтобы выполнить это действие!" - }, - "success": { - "login": "Вход выполнен", - "register": "Аккаунт успешно создан. Пожалуйста, подтвердите ваш Email перед входом!", - "confirmEmail": "Ссылка для подтверждения E-mail успешно отправлена!", - "reset": "Если указанный email существует в нашей системе, мы отправим на него письмо для восстановления пароля!", - "newPassword": "Вы успешно изменили пароль!", - "contactUs": "Ваше сообщение успешно отправлено!", - "addToCart": "{product} добавлен в корзину!", - "removeFromCart": "{product} удален из корзины!", - "removeAllFromCart": "Корзина успешно очищена!", - "addToWishlist": "{product} добавлен в избранное!", - "removeFromWishlist": "{product} удален из избранного!", - "removeAllFromWishlist": "Список избранного успешно очищен!", - "bulkRemoveWishlist": "Выбранные товары удалены из избранного!", - "avatarUpload": "Аватар успешно загружен!", - "userUpdate": "Профиль успешно обновлен!", - "emailUpdate": "Проверьте вашу почту для перехода по ссылке подтверждения и завершения обновления email.", - "referralCopy": "Реферальная ссылка скопирована!", - "promocodeCopy": "Промокод скопирован!", - "configCopy": "Новая ui конфигурация скопирована!", - "addFeedback": "Ваш отзыв сохранен!" - }, - "addToCartLimit": "Лимит общего количества составляет {quantity}!", - "failAdd": "Пожалуйста, войдите, чтобы совершить покупку", - "activationSuccess": "E-mail успешно подтвержден. Пожалуйста, войдите!", - "successUpdate": "Профиль успешно обновлен!", - "payment": "Ваш платеж обрабатывается! Пожалуйста, подождите", - "successCheckout": "Заказ успешно оплачен!" - }, - "header": { - "nav": { - "shop": "Магазин", - "catalog": "Каталог", - "brands": "Бренды", - "blog": "Блог", - "contact": "Контакты" - }, - "actions": { - "wishlist": "Избранное", - "cart": "Корзина", - "login": "Войти", - "profile": "Профиль" - }, - "search": { - "empty": "Ничего не найдено" - }, - "catalog": { - "title": "Каталог" - } - }, - "footer": { - "address": "Адрес: ", - "email": "Email: ", - "phone": "Телефон: ", - "text": "Курируем вневременную роскошную моду для взыскательных личностей", - "shop": "Магазин", - "allProducts": "Все товары", - "catalog": "Каталог", - "brands": "Бренды", - "help": "Помощь", - "rights": "Все права защищены." - }, - "home": { - "hero": { - "title": "Вневременная элегантность", - "text": "Откройте для себя нашу кураторскую коллекцию люксовой моды и аксессуаров, определяющих утонченный стиль" - }, - "categories": { - "title": "Покупки по категориям" - }, - "ad": { - "title": "Весенняя коллекция", - "text1": "Скидки до 40%", - "text2": "Откройте для себя последние тренды в мире люксовой моды" - }, - "blog": { - "title": "Из журнала" - } - }, - "forms": { - "login": { - "title": "С возвращением", - "subtitle": "Войдите в свой аккаунт, чтобы продолжить", - "forgot": "Забыли пароль?", - "or": "или" - }, - "register": { - "title": "Создать аккаунт", - "subtitle": "Зарегистрируйтесь, чтобы начать ваше стильное путешествие", - "login": "Уже есть аккаунт?" - }, - "reset": { - "title": "Сброс пароля", - "subtitle": "Введите ваш email, и мы отправим ссылку для сброса пароля.", - "backToLogin": "Назад к входу" - }, - "newPassword": { - "title": "Новый пароль" - } - }, - "cards": { - "product": { - "stock": "В наличии: " - } - }, - "breadcrumbs": { - "home": "Главная", - "catalog": "Каталог", - "contact": "Контакты", - "orders": "Заказы", - "wishlist": "Избранное", - "cart": "Корзина", - "settings": "Настройки", - "balance": "Баланс", - "promocodes": "Промокоды", - "login": "Вход", - "register": "Регистрация", - "resetPassword": "Сброс пароля", - "newPassword": "Новый пароль", - "brands": "Бренды", - "blog": "Блог", - "search": "Поиск", - "categories": "Категории", - "shop": "Магазин", - "policy": "Политика конфиденциальности", - "terms": "Условия использования", - "return": "Политика возврата", - "faq": "Часто задаваемые вопросы", - "shipping": "Информация о доставке", - "about": "О нас" - }, - "contact": { - "title": "Свяжитесь с нами", - "text": "Мы будем рады услышать вас. Отправьте нам сообщение, и мы ответим как можно скорее.", - "block": { - "title": "Давайте общаться", - "text": "Есть ли у вас вопросы о наших товарах, нужен совет по стилю или вы хотите сотрудничать с нами — мы здесь, чтобы помочь. Наша команда стремится предоставлять исключительный сервис и поддержку.", - "email": "Написать нам", - "call": "Позвонить нам", - "hours": "Часы работы" - }, - "form": { - "title": "Отправить сообщение" - } - }, - "store": { - "sorting": "Сортировать по:", - "filters": { - "title": "Фильтры", - "apply": "Применить", - "reset": "Сбросить", - "all": "Все" - } - }, - "search": { - "title": "Результаты поиска", - "products": "Товары", - "categories": "Категории", - "brands": "Бренды", - "byRequest": "по запросу" - }, - "product": { - "characteristics": "Все характеристики", - "similar": "Вам также может понравиться" - }, - "profile": { - "settings": { - "title": "Настройки", - "joinData": "Дата регистрации", - "accountInfo": "Информация об аккаунте", - "copyReferral": "Скопировать реферальную ссылку", - "referralTooltip": "Вы получите реферальную ссылку после успешной покупки" - }, - "orders": { - "title": "Заказы", - "chooseStatus": "Выберите статус", - "id": "№", - "price": "Цена", - "total": "Итого", - "empty": "Нет заказов по данным параметрам.", - "statuses": { - "all": "Все", - "failed": "Ошибка", - "payment": "Ожидает оплаты", - "created": "Создан", - "delivering": "Доставляется", - "finished": "Завершен", - "momental": "Моментальный" - }, - "searchTooltip": "Введите номер заказа или название товара" - }, - "wishlist": { - "title": "Избранное", - "total": "{quantity} товаров на сумму {amount}", - "deleteTooltip": "Удалить всё из избранного", - "empty": "Ваш список избранного пуст." - }, - "cart": { - "title": "Корзина", - "quantity": "Количество: ", - "total": "Итого", - "empty": "Ваша корзина пуста." - }, - "balance": { - "title": "Баланс" - }, - "promocodes": { - "title": "Промокоды", - "until": "До", - "empty": "У вас нет промокодов." - }, - "logout": "Выйти" - }, - "demo": { - "settings": { - "title": "Демо-настройки", - "ui": "Настройки интерфейса" - }, - "buttons": { - "reset": "Сбросить на умолчания", - "save": "Сохранить изменения", - "generateCode": "Сгенерировать код для 'app.config.ts'" - }, - "preview": { - "text": "Замените объект UI в 'app.config.ts' на этот код" - }, - "descriptions": { - "showBreadcrumbs": "Показывать цепочку навигации на страницах.", - "showSearchBar": "Показывать строку поиска в шапке сайта." - } - }, - "docs": { - "faq": { - "title": "Часто задаваемые вопросы" - }, - "shipping": { - "title": "Информация о доставке" - }, - "return": { - "title": "Политика возврата" - }, - "policy": { - "title": "Политика конфиденциальности" - }, - "terms": { - "title": "Условия использования" - }, - "about": { - "title": "О нас" - } - }, - "brands": { - "title": "Люксовые бренды", - "text": "Откройте для себя самые престижные мировые модные дома и люксовые бренды, подобранные для взыскательного вкуса." - }, - "catalog": { - "title": "Разнообразные категории", - "text": "Откройте для себя самые престижные мировые модные дома и люксовые категории, подобранные для взыскательного вкуса." - }, - "blog": { - "title": "Модный журнал", - "text": "Узнавайте о последних трендах, источниках вдохновения для стиля и модных инсайтах от нашей редакции." - }, - "cart": { - "title": "Ваша корзина", - "items": "нет товаров | {count} товар | {count} товара | {count} товаров" - }, - "shop": { - "title": "Магазин", - "text": "Откройте для себя нашу кураторскую коллекцию люксовой моды и аксессуаров, определяющих утонченный стиль" - } -} \ No newline at end of file +{} \ No newline at end of file diff --git a/storefront/i18n/locales/ru-ru.json b/storefront/i18n/locales/ru-ru.json index 0967ef42..ea2f0416 100644 --- a/storefront/i18n/locales/ru-ru.json +++ b/storefront/i18n/locales/ru-ru.json @@ -1 +1,344 @@ -{} +{ + "buttons": { + "login": "Войти", + "register": "Регистрация", + "createAccount": "Создать аккаунт", + "addToCart": "В корзину", + "removeFromCart": "Удалить из корзины", + "addAllToCart": "Добавить все в корзину", + "addToWishlist": "В избранное", + "removeFromWishlist": "Удалить из избранного", + "send": "Отправить", + "goEmail": "Перейти к почте", + "logout": "Выйти", + "checkout": "Оформить заказ", + "save": "Сохранить", + "sendLink": "Отправить ссылку для сброса", + "topUp": "Пополнить", + "shopNow": "КУПИТЬ СЕЙЧАС", + "shopTheSale": "Купить по акции", + "readMore": "Читать далее", + "sendMessage": "Отправить сообщение", + "saveChanges": "Сохранить изменения", + "clearAll": "Очистить всё" + }, + "errors": { + "required": "Это поле обязательно!", + "mail": "Введите корректный email!", + "compare": "Пароли не совпадают!", + "needLower": "Добавьте строчную букву.", + "needUpper": "Добавьте заглавную букву.", + "needNumber": "Добавьте цифру.", + "needMin": "Мин. 8 символов", + "needSpecial": "Добавьте спецсимвол: #.?!$%^&*'()_+=:;\"'/>.<,|\\-", + "pageNotFound": "Страница не найдена" + }, + "fields": { + "search": "Поиск", + "searchOrder": "Поиск заказа", + "name": "Имя", + "firstName": "Имя", + "lastName": "Фамилия", + "phoneNumber": "Номер телефона", + "email": "Email", + "subject": "Тема", + "message": "Ваше сообщение", + "password": "Пароль", + "newPassword": "Новый пароль", + "confirmPassword": "Подтвердите пароль", + "confirmNewPassword": "Подтвердите новый пароль", + "brandsSearch": "Поиск брендов по названию..." + }, + "checkboxes": { + "remember": "Запомнить меня", + "chooseAll": "Выбрать все", + "agree": "Я согласен с {terms} и {policy}", + "subscribe": "Подписаться на рассылку новостей об эксклюзивных предложениях и обновлениях" + }, + "popup": { + "errors": { + "main": "Ошибка!", + "defaultError": "Что-то пошло не так...", + "noDataToUpdate": "Нет данных для обновления.", + "loginFirst": "Войдите, чтобы выполнить это действие!", + "alreadyInWishlist": "{product} уже есть в избранном!" + }, + "success": { + "login": "Вход выполнен", + "register": "Аккаунт успешно создан. Пожалуйста, подтвердите ваш Email перед входом!", + "confirmEmail": "Ссылка для подтверждения E-mail успешно отправлена!", + "reset": "Если указанный email существует в нашей системе, мы отправим на него письмо для восстановления пароля!", + "newPassword": "Вы успешно изменили пароль!", + "contactUs": "Ваше сообщение успешно отправлено!", + "addToCart": "{product} добавлен в корзину!", + "removeFromCart": "{product} удален из корзины!", + "removeAllFromCart": "Корзина успешно очищена!", + "addToWishlist": "{product} добавлен в избранное!", + "removeFromWishlist": "{product} удален из избранного!", + "removeAllFromWishlist": "Список избранного успешно очищен!", + "bulkRemoveWishlist": "Выбранные товары удалены из избранного!", + "bulkRemoveOrder": "Выбранные товары удалены из корзины!", + "avatarUpload": "Аватар успешно загружен!", + "userUpdate": "Профиль успешно обновлен!", + "emailUpdate": "Проверьте вашу почту для перехода по ссылке подтверждения и завершения обновления email.", + "referralCopy": "Реферальная ссылка скопирована!", + "promocodeCopy": "Промокод скопирован!", + "configCopy": "Новая ui конфигурация скопирована!", + "addFeedback": "Ваш отзыв сохранен!" + }, + "addToCartLimit": "Лимит общего количества составляет {quantity}!", + "failAdd": "Пожалуйста, войдите, чтобы совершить покупку", + "activationSuccess": "E-mail успешно подтвержден. Пожалуйста, войдите!", + "successUpdate": "Профиль успешно обновлен!", + "payment": "Ваш платеж обрабатывается! Пожалуйста, подождите", + "successCheckout": "Заказ успешно оплачен!" + }, + "header": { + "nav": { + "shop": "Магазин", + "catalog": "Каталог", + "brands": "Бренды", + "blog": "Блог", + "contact": "Контакты" + }, + "actions": { + "wishlist": "Избранное", + "cart": "Корзина", + "login": "Войти", + "profile": "Профиль" + }, + "search": { + "empty": "Ничего не найдено" + }, + "catalog": { + "title": "Каталог" + } + }, + "footer": { + "address": "Адрес: ", + "email": "Email: ", + "phone": "Телефон: ", + "text": "Курируем вневременную роскошную моду для взыскательных личностей", + "shop": "Магазин", + "allProducts": "Все товары", + "catalog": "Каталог", + "brands": "Бренды", + "help": "Помощь", + "rights": "Все права защищены." + }, + "home": { + "hero": { + "title": "Вневременная элегантность", + "text": "Откройте для себя нашу кураторскую коллекцию люксовой моды и аксессуаров, определяющих утонченный стиль" + }, + "categories": { + "title": "Покупки по категориям" + }, + "ad": { + "title": "Весенняя коллекция", + "text1": "Скидки до 40%", + "text2": "Откройте для себя последние тренды в мире люксовой моды" + }, + "blog": { + "title": "Из журнала" + } + }, + "forms": { + "login": { + "title": "С возвращением", + "subtitle": "Войдите в свой аккаунт, чтобы продолжить", + "forgot": "Забыли пароль?", + "or": "или" + }, + "register": { + "title": "Создать аккаунт", + "subtitle": "Зарегистрируйтесь, чтобы начать ваше стильное путешествие", + "login": "Уже есть аккаунт?" + }, + "reset": { + "title": "Сброс пароля", + "subtitle": "Введите ваш email, и мы отправим ссылку для сброса пароля.", + "backToLogin": "Назад к входу" + }, + "newPassword": { + "title": "Новый пароль" + } + }, + "cards": { + "product": { + "stock": "В наличии: " + } + }, + "breadcrumbs": { + "home": "Главная", + "catalog": "Каталог", + "contact": "Контакты", + "orders": "Заказы", + "wishlist": "Избранное", + "cart": "Корзина", + "settings": "Настройки", + "balance": "Баланс", + "promocodes": "Промокоды", + "login": "Вход", + "register": "Регистрация", + "resetPassword": "Сброс пароля", + "newPassword": "Новый пароль", + "brands": "Бренды", + "blog": "Блог", + "search": "Поиск", + "categories": "Категории", + "shop": "Магазин", + "policy": "Политика конфиденциальности", + "terms": "Условия использования", + "return": "Политика возврата", + "faq": "Часто задаваемые вопросы", + "shipping": "Информация о доставке", + "about": "О нас" + }, + "contact": { + "title": "Свяжитесь с нами", + "text": "Мы будем рады услышать вас. Отправьте нам сообщение, и мы ответим как можно скорее.", + "block": { + "title": "Давайте общаться", + "text": "Есть ли у вас вопросы о наших товарах, нужен совет по стилю или вы хотите сотрудничать с нами — мы здесь, чтобы помочь. Наша команда стремится предоставлять исключительный сервис и поддержку.", + "email": "Написать нам", + "call": "Позвонить нам", + "hours": "Часы работы" + }, + "form": { + "title": "Отправить сообщение" + } + }, + "store": { + "sorting": "Сортировать по:", + "filters": { + "title": "Фильтры", + "apply": "Применить", + "reset": "Сбросить", + "all": "Все" + } + }, + "search": { + "title": "Результаты поиска", + "products": "Товары", + "categories": "Категории", + "brands": "Бренды", + "byRequest": "по запросу" + }, + "product": { + "characteristics": "Все характеристики", + "similar": "Вам также может понравиться" + }, + "profile": { + "settings": { + "title": "Настройки", + "joinData": "Дата регистрации", + "accountInfo": "Информация об аккаунте", + "copyReferral": "Скопировать реферальную ссылку", + "referralTooltip": "Вы получите реферальную ссылку после успешной покупки" + }, + "orders": { + "title": "Заказы", + "chooseStatus": "Выберите статус", + "id": "№", + "price": "Цена", + "total": "Итого", + "empty": "Нет заказов по данным параметрам.", + "statuses": { + "all": "Все", + "failed": "Ошибка", + "payment": "Ожидает оплаты", + "created": "Создан", + "delivering": "Доставляется", + "finished": "Завершен", + "momental": "Моментальный" + }, + "searchTooltip": "Введите номер заказа или название товара" + }, + "wishlist": { + "title": "Избранное", + "total": "{quantity} товаров на сумму {amount}", + "deleteTooltip": "Удалить всё из избранного", + "empty": "Ваш список избранного пуст." + }, + "cart": { + "title": "Корзина", + "quantity": "Количество: ", + "total": "Итого", + "empty": "Ваша корзина пуста." + }, + "balance": { + "title": "Баланс" + }, + "promocodes": { + "title": "Промокоды", + "until": "До", + "empty": "У вас нет промокодов." + }, + "logout": "Выйти" + }, + "demo": { + "settings": { + "title": "Демо-настройки", + "ui": "Настройки интерфейса" + }, + "buttons": { + "reset": "Сбросить на умолчания", + "save": "Сохранить изменения", + "generateCode": "Сгенерировать код для 'app.config.ts'" + }, + "preview": { + "text": "Замените объект UI в 'app.config.ts' на этот код" + }, + "descriptions": { + "showBreadcrumbs": "Показывать цепочку навигации на страницах.", + "showSearchBar": "Показывать строку поиска в шапке сайта." + } + }, + "docs": { + "faq": { + "title": "Часто задаваемые вопросы" + }, + "shipping": { + "title": "Информация о доставке" + }, + "return": { + "title": "Политика возврата" + }, + "policy": { + "title": "Политика конфиденциальности" + }, + "terms": { + "title": "Условия использования" + }, + "about": { + "title": "О нас" + } + }, + "brands": { + "title": "Люксовые бренды", + "text": "Откройте для себя самые престижные мировые модные дома и люксовые бренды, подобранные для взыскательного вкуса." + }, + "catalog": { + "title": "Разнообразные категории", + "text": "Откройте для себя самые престижные мировые модные дома и люксовые категории, подобранные для взыскательного вкуса." + }, + "blog": { + "title": "Модный журнал", + "text": "Узнавайте о последних трендах, источниках вдохновения для стиля и модных инсайтах от нашей редакции." + }, + "cart": { + "title": "Моя корзина", + "items": "нет товаров | {count} товар | {count} товара | {count} товаров", + "empty": "Ваша корзина пуста" + }, + "wishlist": { + "title": "Мои Избранные", + "items": "нет товаров | {count} товар | {count} товара | {count} товаров", + "empty": "Список выших избранных пуст" + }, + "shop": { + "title": "Магазин", + "text": "Откройте для себя нашу кураторскую коллекцию люксовой моды и аксессуаров, определяющих утонченный стиль" + } +} \ No newline at end of file