diff --git a/index.html b/index.html index 2ee0743..f438cfc 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,12 @@ - + - MEBL - online furniture shop made on React JS + content="[توضیحات فارسی برای فروشگاه آنلاین مبلمان ساخته شده با React. پیاده سازی شده: کاتالوگ، سبد خرید و صفحه سفارش.]"> + [مبل - فروشگاه آنلاین مبلمان ساخته شده با React JS به فارسی] diff --git a/package-lock.json b/package-lock.json index 7937085..0022dc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,14 @@ "@emotion/styled": "^11.11.0", "@reduxjs/toolkit": "^2.0.1", "framer-motion": "^10.18.0", + "i18next": "^25.3.1", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-http-backend": "^3.0.2", "lottie-react": "^2.4.0", "normalize.css": "^8.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^15.6.0", "react-redux": "^9.1.0", "react-router-dom": "^6.21.2", "swiper": "^11.0.5" @@ -224,12 +228,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", - "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -2980,6 +2982,15 @@ "node": ">=10" } }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3880,6 +3891,64 @@ "react-is": "^16.7.0" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "25.3.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.1.tgz", + "integrity": "sha512-S4CPAx8LfMOnURnnJa8jFWvur+UX/LWcl6+61p9VV7SK2m0445JeBJ6tLD0D5SR0H29G4PYfWkEhivKG5p4RDg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", + "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -4499,6 +4568,26 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4897,6 +4986,32 @@ } } }, + "node_modules/react-i18next": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.0.tgz", + "integrity": "sha512-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5070,11 +5185,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", @@ -5520,6 +5630,12 @@ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -5742,6 +5858,31 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index ad8b40e..dbe9eff 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,14 @@ "@emotion/styled": "^11.11.0", "@reduxjs/toolkit": "^2.0.1", "framer-motion": "^10.18.0", + "i18next": "^25.3.1", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-http-backend": "^3.0.2", "lottie-react": "^2.4.0", "normalize.css": "^8.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^15.6.0", "react-redux": "^9.1.0", "react-router-dom": "^6.21.2", "swiper": "^11.0.5" diff --git a/public/locales/fa/translation.json b/public/locales/fa/translation.json new file mode 100644 index 0000000..e3f8395 --- /dev/null +++ b/public/locales/fa/translation.json @@ -0,0 +1,121 @@ +{ + "hero.title": "[چرا در {{mebl}} به فارسی؟]", + "hero.slogan": "[ما مبلمانی را پیدا می کنیم
که می خواهید بخرید! به فارسی]", + "hero.scrollToAriaLabel": "[پیمایش صفحه به پایین به فارسی]", + "navigation.favorites": "[مورد علاقه ها به فارسی]", + "navigation.cart": "[سبد خرید به فارسی]", + "searchForm.placeholder": "[درخواست خود را وارد کنید به فارسی]", + "searchForm.ariaLabel": "[جستجو به فارسی]", + "logo.altText": "[تصویر لوگوی مبل به فارسی]", + "footer.developer": "[توسعه دهنده: به فارسی]", + "footer.copyright": "[© مبل، 2024 به فارسی]", + "contacts.instagramAriaLabel": "[لینک اینستاگرام به فارسی]", + "contacts.youtubeAriaLabel": "[لینک یوتیوب به فارسی]", + "contacts.telegramAriaLabel": "[لینک تلگرام به فارسی]", + "catalog.iconAltText": "[آیکون: {{item}} به فارسی]", + "currencySymbol": "[ریال]", + "currencyNamePlural": "[ریال ها به فارسی]", + "buttonAddToCart.toastMessage": "[محصول به سبد خرید اضافه شد! به فارسی]", + "buttonAddToCart.ariaLabel": "[افزودن به سبد خرید به فارسی]", + "buttonAddToCart.inCart": "[در سبد خرید به فارسی]", + "buttonAddToCart.addToCart": "[به سبد خرید به فارسی]", + "buttonFavorite.addToast": "[محصول به علاقه مندی ها اضافه شد! به فارسی]", + "buttonFavorite.removeToast": "[محصول دیگر در علاقه مندی ها نیست! به فارسی]", + "buttonFavorite.addAriaLabel": "[افزودن به علاقه مندی ها به فارسی]", + "buttonFavorite.removeAriaLabel": "[حذف از علاقه مندی ها به فارسی]", + "cart.title": "[سبد خرید به فارسی]", + "cartProducts.dearCustomer": "[مشتری گرامی به فارسی]", + "cartProducts.emptyCartMessage": "[سبد خرید شما در حال حاضر خالی است.. به فارسی]", + "cartProducts.pleaseGoTo": "[لطفا به به فارسی]", + "cartProducts.catalogLink": "[کاتالوگ به فارسی]", + "cartProducts.andSelectProduct": "[، و محصولی را انتخاب کنید. به فارسی]", + "cartProducts.emptyCartAlt": "[سبد خالی به فارسی]", + "cartProducts.articlePrefix": "[هنر. به فارسی]", + "cartProducts.deleteButton": "[حذف به فارسی]", + "buttonRemoveFromCart.toastMessage": "[محصول از سبد خرید حذف شد! به فارسی]", + "cartPlace.title": "[تسویه حساب: به فارسی]", + "cartPlace.forTheAmountOf": "[به مبلغ: به فارسی]", + "cartPlace.total": "[مجموع: به فارسی]", + "cartPlace.piecesOfFurniture": "[تکه از مبلمان به فارسی]", + "cartPlace.placeOrderButton": "[ثبت سفارش به فارسی]", + "cartPlace.product_one": "[{{count}} محصول به فارسی]", + "cartPlace.product_other": "[{{count}} محصولات به فارسی]", + "cartPlace.unit_one": "[{{count}} واحد به فارسی]", + "cartPlace.unit_other": "[{{count}} واحدها به فارسی]", + "cartDelivery.deliveryLabel": "[تحویل: به فارسی]", + "cartDelivery.tooltipLabel": "[برای مشاهده شرایط تحویل کلیک کنید به فارسی]", + "cartDelivery.conditionsButton": "[شرایط تحویل به فارسی]", + "cartDelivery.drawerHeader": "[شرایط تحویل به فارسی]", + "cartDelivery.drawerBody.paragraph1": "[فروشگاه مبلمان مبل، مبلمان را در سراسر اوکراین تحویل می دهد. ما با شرکت های حمل و نقل پیشرو همکاری می کنیم: نوا پشتا، میست-اکسپرس، Cat، که به ما امکان می دهد تحویل سریع و ایمن سفارشات شما را تضمین کنیم. به فارسی]", + "cartDelivery.drawerBody.standardCostHeader": "[هزینه استاندارد تحویل به فارسی]", + "cartDelivery.drawerBody.standardCostParagraph": "[هزینه استاندارد تحویل مبلمان در سراسر اوکراین 1000 {{currencyName}} است. هزینه تحویل بر اساس ابعاد و وزن مبلمان و همچنین فاصله انبار ما تا محل شما محاسبه می شود به فارسی]", + "cartDelivery.drawerBody.freeDeliveryHeader": "[تحویل رایگان به فارسی]", + "cartDelivery.drawerBody.freeDeliveryParagraph": "[تحویل مبلمان برای سفارشات بالای 9999 {{currencyName}} رایگان است به فارسی]", + "cartDelivery.drawerBody.deliveryTimesHeader": "[زمان تحویل به فارسی]", + "cartDelivery.drawerBody.deliveryTimesParagraph": "[زمان تحویل مبلمان به دور بودن محل شما و مشغله شرکت های حمل و نقل بستگی دارد. به طور متوسط، تحویل مبلمان در سراسر اوکراین از 1 تا 5 روز طول می کشد. به فارسی]", + "cartDelivery.drawerBody.deliveryOptionsHeader": "[گزینه های تحویل به فارسی]", + "cartDelivery.drawerBody.deliveryOptionsParagraph": "[ما گزینه های تحویل مبلمان زیر را ارائه می دهیم: به فارسی]", + "cartDelivery.drawerBody.option1": "[تحویل به انبار شرکت حمل و نقل. به فارسی]", + "cartDelivery.drawerBody.option2": "[تحویل به آدرس توسط پیک. به فارسی]", + "cartDelivery.drawerBody.option3": "[تحویل و مونتاژ مبلمان توسط متخصص شرکت ما (فقط کیف). به فارسی]", + "cartDelivery.drawerBody.option4": "[تحویل حضوری از انبار. به فارسی]", + "cartForm.subtitle": "[اطلاعات تحویل به فارسی]", + "cartForm.fullNamePlaceholder": "[نام خانوادگی نام نام میانی به فارسی]", + "cartForm.phonePlaceholder": "[تلفن به فارسی]", + "cartForm.emailPlaceholder": "[ایمیل به فارسی]", + "cartForm.addressPlaceholder": "[آدرس تحویل به فارسی]", + "cartForm.commentTooltip": "[برای افزودن نظر به سفارش کلیک کنید به فارسی]", + "cartForm.addCommentButton": "[افزودن نظر به فارسی]", + "cartForm.commentPlaceholder": "[نظر برای سفارش به فارسی]", + "cartForm.deliveryLegend": "[تحویل به فارسی]", + "cartForm.deliveryOptionDelivery": "[تحویل به فارسی]", + "cartForm.deliveryOptionPickup": "[تحویل حضوری به فارسی]", + "cartForm.paymentLegend": "[پرداخت به فارسی]", + "cartForm.paymentOptionCard": "[با کارت هنگام تحویل به فارسی]", + "cartForm.paymentOptionCash": "[نقدی هنگام تحویل به فارسی]", + "cartForm.orderToastMessage": "[سفارش شما برای پردازش ارسال شده است به فارسی]", + "favorites.title": "[مورد علاقه ها به فارسی]", + "favorites.emptyImageAlt": "[آیکون با یک تکه مبلمان به فارسی]", + "favorites.emptyOops": "[اوه... اینجا خالی است 😢 به فارسی]", + "favorites.emptyAddPrompt": "[با کلیک روی قلب محصولی اضافه کنید 💚 به فارسی]", + "favorites.emptyWillAppear": "[و تمام محصولاتی که دوست داشتید اینجا ظاهر می شوند 😊 به فارسی]", + "order.title": "[از سفارش شما متشکریم! به فارسی]", + "order.numberPrefix": "[شماره به فارسی] ", + "order.detailsTooltip": "[برای مشاهده محصولات سفارش داده شده کلیک کنید به فارسی]", + "order.detailsButton": "[جزئیات بیشتر در مورد سفارش به فارسی]", + "order.deliveryDetailsSubtitle": "[اطلاعات تحویل به فارسی]", + "order.recipientLabel": "[گیرنده به فارسی]", + "order.phoneLabel": "[تلفن به فارسی]", + "order.emailLabel": "[ایمیل به فارسی]", + "order.addressLabel": "[آدرس تحویل به فارسی]", + "order.paymentMethodLabel": "[روش پرداخت به فارسی]", + "order.deliveryMethodLabel": "[روش تحویل به فارسی]", + "order.paymentCard": "[با کارت به فارسی]", + "order.paymentCash": "[نقدی هنگام تحویل به فارسی]", + "order.deliveryDelivery": "[تحویل به فارسی]", + "order.deliveryPickup": "[تحویل حضوری به فارسی]", + "order.backToHomeTooltip": "[برای بازگشت به صفحه اصلی کلیک کنید به فارسی]", + "order.backToHomeButton": "[به صفحه اصلی به فارسی]", + "breadcrumbs.home": "[خانه به فارسی]", + "breadcrumbs.favorites": "[مورد علاقه ها به فارسی]", + "breadcrumbs.cart": "[سبد خرید به فارسی]", + "breadcrumbs.searchQueryPrefix": "[جستجو بر اساس کوئری: به فارسی]", + "breadcrumbs.separator": " ⬅ ", + "errorMessage.default": "[بارگیری داده ها انجام نشد! به فارسی]", + "errorMessage.auth": "[مدیر، شما باید اطلاعات مجوز را در سرور بررسی کنید. به فارسی]", + "errorMessage.title": "[اوه! خطایی روی داد 😢 به فارسی]", + "errorMessage.tryReload": "[سعی کنید صفحه را پس از مدتی بارگیری مجدد کنید. به فارسی]", + "errorMessage.reloadButtonAriaLabel": "[بارگیری مجدد صفحه به فارسی]", + "errorMessage.reloadButtonText": "[بارگذاری مجدد به فارسی]", + "notFoundProduct.title": "[اوه... به فارسی]", + "notFoundProduct.notFoundForQuery": "[متاسفانه، ما چیزی برای درخواست شما پیدا نکردیم: به فارسی]", + "notFoundProduct.info1": "[شاید چنین محصولی هنوز در کاتالوگ ما موجود نباشد. به فارسی]", + "notFoundProduct.info2": "[همچنین بررسی کنید که آیا نام مبلمان را به درستی وارد کرده اید و درخواست خود را تکرار کنید. به فارسی]", + "page404.title": "[اوه! خطای 404... به فارسی]", + "page404.notFound": "[صفحه ای که به دنبال آن هستید یافت نشد! به فارسی]", + "page404.pleaseReturn": "[لطفا به به فارسی]", + "page404.mainPageLink": "[صفحه اصلی به فارسی]", + "pagination.previousAriaLabel": "[صفحه قبلی به فارسی]", + "pagination.nextAriaLabel": "[صفحه بعدی به فارسی]", + "pagination.of": "[از به فارسی]" +} diff --git a/src/components/Breadcrumbs/Breadcrumbs.jsx b/src/components/Breadcrumbs/Breadcrumbs.jsx index 84e7d76..99fc059 100644 --- a/src/components/Breadcrumbs/Breadcrumbs.jsx +++ b/src/components/Breadcrumbs/Breadcrumbs.jsx @@ -2,17 +2,19 @@ import s from "./Breadcrumbs.module.scss"; import { Link, useLocation, useSearchParams } from "react-router-dom"; import { useSelector } from "react-redux"; import { Container } from "../../views/Container/Container"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function Breadcrumbs() { + const { t } = useTranslation(); // Initialize useTranslation const { data, loading } = useSelector((state) => state.product); const { pathname } = useLocation(); const [searchParam] = useSearchParams(); - const breadcrumbs = [{ name: "Главная", link: "/" }]; + const breadcrumbs = [{ name: t('breadcrumbs.home'), link: "/" }]; if (pathname === "/category") { const category = searchParam.get("category"); breadcrumbs.push({ - name: category, + name: category, // Dynamic, not translated via key link: "", }); } @@ -20,11 +22,11 @@ function Breadcrumbs() { if (pathname.split("/")[1] === "product") { breadcrumbs.push( { - name: data?.category, + name: data?.category, // Dynamic, not translated via key link: `/category?category=${data?.category}`, }, { - name: data?.name, + name: data?.name, // Dynamic, not translated via key link: "", }, ); @@ -32,14 +34,14 @@ function Breadcrumbs() { if (pathname === "/favorite") { breadcrumbs.push({ - name: "Избранное", + name: t('breadcrumbs.favorites'), link: "", }); } if (pathname === "/cart") { breadcrumbs.push({ - name: "Корзина", + name: t('breadcrumbs.cart'), link: "", }); } @@ -47,7 +49,7 @@ function Breadcrumbs() { if (pathname === "/search") { const search = searchParam.get("q"); breadcrumbs.push({ - name: `Поиск по запросу: ${search}`, + name: `${t('breadcrumbs.searchQueryPrefix')} ${search}`, link: `${pathname}`, }); } @@ -68,7 +70,10 @@ function Breadcrumbs() { to={item?.link}> {item?.name} - + {/* Render separator only if it's not the last item */} + {index < breadcrumbs.length - 1 && ( + {t('breadcrumbs.separator')} + )} ))} diff --git a/src/components/ButtonAddToCart/ButtonAddToCart.jsx b/src/components/ButtonAddToCart/ButtonAddToCart.jsx index 118a474..bc63fb8 100644 --- a/src/components/ButtonAddToCart/ButtonAddToCart.jsx +++ b/src/components/ButtonAddToCart/ButtonAddToCart.jsx @@ -3,8 +3,10 @@ import { useDispatch, useSelector } from "react-redux"; import { addProductToCart } from "../../store/cart/cart.slice"; import { useToast } from "@chakra-ui/react"; import { useState } from "react"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function ButtonAddToCart({ className, id }) { + const { t } = useTranslation(); // Initialize useTranslation const dispatch = useDispatch(); const [isInCart, setIsInCart] = useState(false); const { products } = useSelector((state) => state.cart); @@ -12,7 +14,7 @@ function ButtonAddToCart({ className, id }) { const toast = useToast(); const addCartToast = () => { toast({ - description: "Товар добавлен в корзину!", + description: t('buttonAddToCart.toastMessage'), // Use t function for toast message status: "success", variant: "left-accent", duration: 2000, @@ -35,8 +37,8 @@ function ButtonAddToCart({ className, id }) { `${className} ${s.disabled}` : `${className} `} data-id={id} - aria-label="Добавить в корзину"> - {isInCart || isCart ? "В корзине" : "В корзину"} + aria-label={t('buttonAddToCart.ariaLabel')}> {/* Use t function for aria-label */} + {isInCart || isCart ? t('buttonAddToCart.inCart') : t('buttonAddToCart.addToCart')} {/* Use t function for button text */} ); } diff --git a/src/components/ButtonFavorite/ButtonFavorite.jsx b/src/components/ButtonFavorite/ButtonFavorite.jsx index 4edc940..6537fd7 100644 --- a/src/components/ButtonFavorite/ButtonFavorite.jsx +++ b/src/components/ButtonFavorite/ButtonFavorite.jsx @@ -2,8 +2,10 @@ import "./ButtonFavorite.scss"; import { useDispatch, useSelector } from "react-redux"; import { addFavorite, removeFavorite } from "../../store/favorites/favorites.slice"; import { useToast } from "@chakra-ui/react"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function ButtonFavorite({ className, id }) { + const { t } = useTranslation(); // Initialize useTranslation const dispatch = useDispatch(); const favoriteList = useSelector((state) => state.favorite.favoriteList); @@ -13,7 +15,7 @@ function ButtonFavorite({ className, id }) { const addFavoriteToast = () => { toast({ - description: "Товар добавлен в избранное!", + description: t('buttonFavorite.addToast'), // Use t function status: "success", variant: "left-accent", duration: 2000, @@ -23,7 +25,7 @@ function ButtonFavorite({ className, id }) { const removeFavoriteToast = () => { toast({ - description: "Товар больше не в избранном!", + description: t('buttonFavorite.removeToast'), // Use t function variant: "left-accent", duration: 2000, isClosable: true, @@ -44,7 +46,9 @@ function ButtonFavorite({ className, id }) { return ( - Условия доставки + {t('cartDelivery.drawerHeader')} -

Мебельный магазин MEBL осуществляет доставку мебели по всей Украине. Мы работаем с ведущими транспортными компаниями: Новая Почта, Мист-Экспресс, Cat, что позволяет нам гарантировать оперативную и безопасную доставку ваших заказов.

-

Стандартная стоимость доставки

-

Стандартная стоимость доставки мебели по Украине составляет 1000 гривен. Стоимость доставки рассчитывается исходя из габаритов и веса мебели, а также расстояния от нашего склада до вашего населенного пункта

-

Бесплатная доставка

-

Доставка мебели бесплатна при сумме заказа более 9999 гривен

-

Сроки доставки

-

Сроки доставки мебели зависят от удаленности вашего населенного пункта и загруженности транспортных компаний. В среднем, доставка мебели по Украине занимает от 1 до 5 дней.

-

Варианты доставки

-

Мы предлагаем следующие варианты доставки мебели:

+

{t('cartDelivery.drawerBody.paragraph1')}

+

{t('cartDelivery.drawerBody.standardCostHeader')}

+

+

{t('cartDelivery.drawerBody.freeDeliveryHeader')}

+

{t('cartDelivery.drawerBody.freeDeliveryParagraph', { currencyName: t('currencyNamePlural') })}

+

{t('cartDelivery.drawerBody.deliveryTimesHeader')}

+

{t('cartDelivery.drawerBody.deliveryTimesParagraph')}

+

{t('cartDelivery.drawerBody.deliveryOptionsHeader')}

+

{t('cartDelivery.drawerBody.deliveryOptionsParagraph')}

    -
  • Доставка на склад транспортной компании.
  • -
  • Доставка по адресу курьером.
  • -
  • Доставка и сборка мебели специалистом нашей компании (только Киев).
  • -
  • Самовывоз со склада.
  • +
  • {t('cartDelivery.drawerBody.option1')}
  • +
  • {t('cartDelivery.drawerBody.option2')}
  • +
  • {t('cartDelivery.drawerBody.option3')}
  • +
  • {t('cartDelivery.drawerBody.option4')}
diff --git a/src/components/CartDelivery/CartDelivery.module.scss b/src/components/CartDelivery/CartDelivery.module.scss index 91b84a3..728755f 100644 --- a/src/components/CartDelivery/CartDelivery.module.scss +++ b/src/components/CartDelivery/CartDelivery.module.scss @@ -28,7 +28,7 @@ } & ul { - margin-left: 2rem; + margin-inline-start: 2rem; /* Replaces margin-left */ padding-block: 1rem; list-style: disc; // line-height: 1.4; diff --git a/src/components/CartForm/CartForm.jsx b/src/components/CartForm/CartForm.jsx index 5b7ac3f..5f84cfa 100644 --- a/src/components/CartForm/CartForm.jsx +++ b/src/components/CartForm/CartForm.jsx @@ -9,11 +9,12 @@ import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { fetchOrder } from "../../store/order/order.slice"; import { useEffect, useState } from "react"; -// import { fetchCart } from "../../store/cart/cart.slice"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function CartForm() { + const { t } = useTranslation(); // Initialize useTranslation const { isOpen, onToggle } = useDisclosure(); - const [isChecked, setIsChecked] = useState(true); + const [isChecked, setIsChecked] = useState(true); // This state seems to be used for both delivery and payment, which might not be intended. Assuming it's for the default checked state. const navigate = useNavigate(); const dispatch = useDispatch(); const orderStatus = useSelector((state) => state.order); @@ -27,7 +28,7 @@ function CartForm() { const toast = useToast(); const orderToast = () => { toast({ - description: "Ваш заказ добавлен в обработку", + description: t('cartForm.orderToastMessage'), // Use t function position: "top", status: "success", variant: "solid", @@ -52,65 +53,65 @@ function CartForm() { return (
-

Данные для доставки

+

{t('cartForm.subtitle')}

+ type="text" name="name" required placeholder={t('cartForm.fullNamePlaceholder')} /> - +38 + +38 {/* This is a phone prefix, typically not translated but could be made configurable if needed for other regions */} + className={s.input} type="tel" name="phone" required placeholder={t('cartForm.phonePlaceholder')} /> - - + +
- + onClick={onToggle}>{t('cartForm.addCommentButton')}
- Доставка + {t('cartForm.deliveryLegend')}
- Оплата + {t('cartForm.paymentLegend')}
diff --git a/src/components/CartForm/CartForm.module.scss b/src/components/CartForm/CartForm.module.scss index 7605c72..7f8dd1f 100644 --- a/src/components/CartForm/CartForm.module.scss +++ b/src/components/CartForm/CartForm.module.scss @@ -130,7 +130,7 @@ display: flex; align-items: center; margin-bottom: 8px; - padding-left: 1rem; + padding-inline-start: 1rem; /* Replaces padding-left */ font-size: 16px; line-height: 24px; @@ -149,7 +149,7 @@ -moz-appearance: none; appearance: none; padding: 2px; - margin-right: 8px; + margin-inline-end: 8px; /* Replaces margin-right */ &:before { content: ""; diff --git a/src/components/CartPlace/CartPlace.jsx b/src/components/CartPlace/CartPlace.jsx index f6aa252..31d9fd4 100644 --- a/src/components/CartPlace/CartPlace.jsx +++ b/src/components/CartPlace/CartPlace.jsx @@ -1,25 +1,24 @@ import { useSelector } from "react-redux"; -import { getDeclOfNum, titles } from "../../helpers/getDeclOfNum"; import s from "./CartPlace.module.scss"; import { Spinner } from "@chakra-ui/react"; import CartDelivery from "../CartDelivery/CartDelivery"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function CartPlace() { + const { t } = useTranslation(); // Initialize useTranslation const { products, totalPrice, totalCount, loadingFetch: loading } = useSelector((state) => state.cart); const countAllQuantity = (data) => data.reduce((sum, item) => sum + item.quantity, 0); const allQuantity = countAllQuantity(products); - const items = ["единици", "единицы", "единиц"]; - return (
-

Оформление заказа:

+

{t('cartPlace.title')}

-

{getDeclOfNum(totalCount, titles)} на сумму:

+

{t('cartPlace.product', { count: totalCount })} {t('cartPlace.forTheAmountOf')}

{allQuantity > totalCount ? ( -

(всего: {getDeclOfNum(allQuantity, items)} мебели)

+

({t('cartPlace.total')} {t('cartPlace.unit', { count: allQuantity })} {t('cartPlace.piecesOfFurniture')})

) : <>}
@@ -32,11 +31,11 @@ function CartPlace() { color='green.300' size={{ xs: "xs", sm: "sm", md: "sm", lg: "md" }} />) : - totalPrice.toLocaleString()) - } 
+ totalPrice.toLocaleString('fa-IR')) + } {t('currencySymbol')}
- + ); } diff --git a/src/components/CartPlace/CartPlace.module.scss b/src/components/CartPlace/CartPlace.module.scss index f88992f..49506b4 100644 --- a/src/components/CartPlace/CartPlace.module.scss +++ b/src/components/CartPlace/CartPlace.module.scss @@ -116,7 +116,7 @@ content: ''; position: absolute; bottom: 0; - left: 0; + left: 0; // LTR default width: 100%; height: 100%; background-color: var(--color-mebl-dark); @@ -127,7 +127,7 @@ content: ''; position: absolute; bottom: 0; - left: 0; + left: 0; // LTR default width: 0%; height: 100%; background-color: var(--color-mebl); @@ -141,4 +141,15 @@ width: 100%; } } +} + +[dir="rtl"] .placeBtnPro { + &:after { + left: auto; + right: 0; + } + &:before { + left: auto; + right: 0; + } } \ No newline at end of file diff --git a/src/components/CartProducts/CartProducts.jsx b/src/components/CartProducts/CartProducts.jsx index 63be356..9f5b60c 100644 --- a/src/components/CartProducts/CartProducts.jsx +++ b/src/components/CartProducts/CartProducts.jsx @@ -6,8 +6,10 @@ import CartQuantityCounter from "../CartQuantityCounter/CartQuantityCounter"; import { useSelector } from "react-redux"; import emptyCart from "../../assets/emptycart.svg"; import { Center, Spinner } from "@chakra-ui/react"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function CartProducts() { + const { t } = useTranslation(); // Initialize useTranslation const { products, loadingFetch: loading @@ -32,10 +34,10 @@ function CartProducts() { {(products?.length === 0) && !loading ? (<>
-

Уважаемый покупатель, в вашей корзине сейчас пусто..

-

Пожалуйста перейдите в каталог, и выберите какой-нибудь товар.

+

{t('cartProducts.dearCustomer')}, {t('cartProducts.emptyCartMessage')}

+

{t('cartProducts.pleaseGoTo')} {t('cartProducts.catalogLink')}{t('cartProducts.andSelectProduct')}

- Пустая корзина + {t('cartProducts.emptyCartAlt')}
) : @@ -52,17 +54,17 @@ function CartProducts() {

{item.name || ""}

{((item.price * item.quantity) || - 0)?.toLocaleString()} 

- {(item.quantity > 1) ? `(${item.quantity} x ${item.price})` : ""} + 0)?.toLocaleString('fa-IR')} {t('currencySymbol')}

+ {(item.quantity > 1) ? `(${item.quantity.toLocaleString('fa-IR')} x ${item.price.toLocaleString('fa-IR')})` : ""}
-

арт. {item.article || "0"}

+

{t('cartProducts.articlePrefix')} {item.article || "0"}

- + )) } diff --git a/src/components/CartProducts/CartProducts.module.scss b/src/components/CartProducts/CartProducts.module.scss index 1e3d5e4..6ec8e7c 100644 --- a/src/components/CartProducts/CartProducts.module.scss +++ b/src/components/CartProducts/CartProducts.module.scss @@ -22,7 +22,7 @@ gap: 4px 8px; grid-template-columns: 25vw min-content 1fr; grid-template-rows: min-content min-content 1fr; - grid-template-areas: + grid-template-areas: // LTR small "img title title" "img article article" "img control price"; @@ -30,7 +30,7 @@ -webkit-animation: fade-in-left 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both; animation: fade-in-left 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both; - @media (min-width: 480px) { + @media (min-width: 480px) { // LTR large grid-template-columns: 120px 1fr min-content; gap: 6px 12px; grid-template-areas: @@ -40,7 +40,10 @@ } @media (min-width: 880px) { - padding: 0.7em 1.25rem 0.7rem 0.6rem; + padding-block-start: 0.7em; + padding-inline-end: 1.25rem; /* LTR: padding-right */ + padding-block-end: 0.7rem; + padding-inline-start: 0.6rem; /* LTR: padding-left */ &:hover { overflow: hidden; @@ -50,6 +53,42 @@ } } +[dir="rtl"] .product { + grid-template-columns: 1fr min-content 25vw; // RTL small + grid-template-areas: // RTL small + "title title img" + "article article img" + "price control img"; + + animation-name: fade-in-right; // Use RTL animation + + @media (min-width: 480px) { // RTL large + grid-template-columns: min-content 1fr 120px; + grid-template-areas: + "price title img" + "article article img" + ". control img"; + } + + @media (min-width: 880px) { // RTL padding (already logical, but good to confirm if different values were needed) + padding-inline-start: 1.25rem; + padding-inline-end: 0.6rem; + } + + & .price { + text-align: left; /* RTL small screens */ + + @media (min-width: 480px) { + text-align: right; /* RTL large screens */ + justify-self: start; /* RTL large screens */ + } + } + + & .productControl { + justify-self: end; /* RTL */ + } +} + .link { grid-area: img; border: 1px solid transparent; @@ -197,6 +236,31 @@ opacity: 1; } } + +@-webkit-keyframes fade-in-right { + 0% { + -webkit-transform: translateX(50px); + transform: translateX(50px); + opacity: 0; + } + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + opacity: 1; + } +} +@keyframes fade-in-right { + 0% { + -webkit-transform: translateX(50px); + transform: translateX(50px); + opacity: 0; + } + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + opacity: 1; + } +} @keyframes fade-in-left { 0% { -webkit-transform: translateX(-50px); diff --git a/src/components/Catalog/Catalog.jsx b/src/components/Catalog/Catalog.jsx index d1d11fc..87668dd 100644 --- a/src/components/Catalog/Catalog.jsx +++ b/src/components/Catalog/Catalog.jsx @@ -7,8 +7,10 @@ import { fetchCategories } from "../../store/categories/categories.slice"; import ErrorMessage from "../ErrorMessage/ErrorMessage"; import { Link, useLocation } from "react-router-dom"; import { Skeleton } from "@chakra-ui/react"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function Catalog(props) { + const { t } = useTranslation(); // Initialize useTranslation // const defaultData = [ // "Тумбы", "Стулья", "Столы", "Пуфы и банкетки", "Кровати", "Диваны", "Полки", "Стеллажи" // ]; @@ -63,7 +65,7 @@ function Catalog(props) { {`Иконка: {item} diff --git a/src/components/Contacts/Contacts.jsx b/src/components/Contacts/Contacts.jsx index 93f5668..b75cb40 100644 --- a/src/components/Contacts/Contacts.jsx +++ b/src/components/Contacts/Contacts.jsx @@ -1,6 +1,8 @@ import styles from "./Contacts.module.scss"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function Contacts(props) { + const { t } = useTranslation(); // Initialize useTranslation return (
@@ -14,7 +16,7 @@ function Contacts(props) {
  • + aria-label={t('contacts.instagramAriaLabel')}> {/* eslint-disable-next-line max-len */} @@ -26,7 +28,7 @@ function Contacts(props) {
  • + aria-label={t('contacts.youtubeAriaLabel')}> {/* eslint-disable-next-line max-len */} @@ -36,7 +38,7 @@ function Contacts(props) {
  • + aria-label={t('contacts.telegramAriaLabel')}> {/* eslint-disable-next-line max-len */} diff --git a/src/components/ErrorMessage/ErrorMessage.jsx b/src/components/ErrorMessage/ErrorMessage.jsx index e1cd935..2029c9f 100644 --- a/src/components/ErrorMessage/ErrorMessage.jsx +++ b/src/components/ErrorMessage/ErrorMessage.jsx @@ -1,30 +1,39 @@ import styles from "./ErrorMessage.module.scss"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function ErrorMessage({ data }) { - let authErrorMsg = `Не удалось загрузить данные!`; - console.log(`❗ ${data?.errorMsg}`); + const { t } = useTranslation(); // Initialize useTranslation + let specificErrorMsg = ""; + + if (data?.path === "auth") { + specificErrorMsg = t('errorMessage.auth'); + } else { + // Default message or could be based on data?.errorMsg if it were a key + specificErrorMsg = data?.errorMsg ? data.errorMsg : t('errorMessage.default'); + } + console.log(`❗ ${data?.errorMsg}`); // Keep original error for console logging const reloadPage = () => { location.reload(true); }; - if (data?.path === "auth") { - authErrorMsg = "Администратору, необходимо проверить данные для авторизации на сервере."; - } - + // This component seems to have a condition to not render for "categories" error. + // If this needs to be shown, this logic might need adjustment or specific category error handling. if (data?.path === "categories") { return; } return (
    -

    Ой! Возникла ошибка 😢

    +

    -

    {authErrorMsg ? authErrorMsg : ""}

    -

    Попробуйте перезагрузить страницу через некоторое время.

    +

    {specificErrorMsg}

    +

    + aria-label={t('errorMessage.reloadButtonAriaLabel')}> + {t('errorMessage.reloadButtonText')} +

    ); } diff --git a/src/components/Favorites/Favorites.jsx b/src/components/Favorites/Favorites.jsx index a28ea05..e3c43de 100644 --- a/src/components/Favorites/Favorites.jsx +++ b/src/components/Favorites/Favorites.jsx @@ -7,8 +7,10 @@ import { Container } from "../../views/Container/Container"; import { getCatalogIcon } from "../../helpers/getCatalogIcon"; import { useLocation, useSearchParams } from "react-router-dom"; import TitleMain from "../TitleMain/TitleMain"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function Favorites() { + const { t } = useTranslation(); // Initialize useTranslation const dispatch = useDispatch(); const [searchParam] = useSearchParams(); const { pathname } = useLocation(); @@ -32,14 +34,14 @@ function Favorites() { ) : (
    - +
    - Иконка с элементом мебели + {t('favorites.emptyImageAlt')}
    -
    Ой... а тут пусто 😢
    +
    -

    Добавьте какой-нибудь товар, нажав на сердечко 💚

    -

    И здесь появятся все понравившиеся вам товары 😊

    +

    +

    diff --git a/src/components/Logo/Logo.jsx b/src/components/Logo/Logo.jsx index 66c17b7..1c51f4d 100644 --- a/src/components/Logo/Logo.jsx +++ b/src/components/Logo/Logo.jsx @@ -1,10 +1,12 @@ import { Link } from "react-router-dom"; import siteLogo from "/images/logo.png"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function Logo(props) { + const { t } = useTranslation(); // Initialize useTranslation return ( - MEBL logo image + {t('logo.altText')} ); } diff --git a/src/components/Navigation/Navigation.jsx b/src/components/Navigation/Navigation.jsx index fa3730c..7139fed 100644 --- a/src/components/Navigation/Navigation.jsx +++ b/src/components/Navigation/Navigation.jsx @@ -1,8 +1,10 @@ import { Link } from "react-router-dom"; import styles from "./Navigation.module.scss"; import { useSelector } from "react-redux"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function Navigation(props) { + const { t } = useTranslation(); // Initialize useTranslation const FULL_CART = 4; const favoriteList = useSelector((state) => state.favorite.favoriteList); const totalCount = useSelector((state) => state.cart.totalCount); @@ -14,7 +16,7 @@ function Navigation(props) { return (
  • ))} diff --git a/src/components/Page404/Page404.jsx b/src/components/Page404/Page404.jsx index a7a1476..1f8c809 100644 --- a/src/components/Page404/Page404.jsx +++ b/src/components/Page404/Page404.jsx @@ -1,20 +1,22 @@ import { Link } from "react-router-dom"; import { getCatalogIcon } from "../../helpers/getCatalogIcon"; import styles from "./Page404.module.scss"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function Page404() { + const { t } = useTranslation(); // Initialize useTranslation const image = getCatalogIcon(1); return (

    - - Ой! Ошибка 404... + {/* Alt is empty, likely decorative */} + {t('page404.title')}

    -

    Страница, которую вы ищите — не найдена!

    +

    {t('page404.notFound')}

    - Пожалуйста, вернитесь на главную страницу. + {t('page404.pleaseReturn')} {t('page404.mainPageLink')}.

    diff --git a/src/components/Pagination/Pagination.jsx b/src/components/Pagination/Pagination.jsx index 150ed06..0cb8fb3 100644 --- a/src/components/Pagination/Pagination.jsx +++ b/src/components/Pagination/Pagination.jsx @@ -1,7 +1,9 @@ import { Link, useLocation, useSearchParams } from "react-router-dom"; import styles from "./Pagination.module.scss"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function Pagination({ pagination }) { + const { t } = useTranslation(); // Initialize useTranslation const location = useLocation(); const [searchParams] = useSearchParams(); const { currentPage, totalProducts, limit, totalPages } = pagination; @@ -40,18 +42,18 @@ function Pagination({ pagination }) {
    - + {/* eslint-disable-next-line max-len */}
    - {paginationCurrent} -  из  - {totalProducts} + {paginationCurrent.toLocaleString('fa-IR')} +  {t('pagination.of')}  + {totalProducts.toLocaleString('fa-IR')}
    - + {/* eslint-disable-next-line max-len */} diff --git a/src/components/ProductCard/ProductCard.jsx b/src/components/ProductCard/ProductCard.jsx index b56398f..464f8c2 100644 --- a/src/components/ProductCard/ProductCard.jsx +++ b/src/components/ProductCard/ProductCard.jsx @@ -3,8 +3,10 @@ import { API_URL } from "../../store/api"; import styles from "./ProductCard.module.scss"; import ButtonFavorite from "../ButtonFavorite/ButtonFavorite"; import ButtonAddToCart from "../ButtonAddToCart/ButtonAddToCart"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function ProductCard({ id, images: [image], name: title, price }, ...props) { + const { t } = useTranslation(); // Initialize useTranslation return (
    @@ -18,7 +20,7 @@ function ProductCard({ id, images: [image], name: title, price }, ...props) {
    - {parseInt(price).toLocaleString()} ₴ + {parseInt(price).toLocaleString('fa-IR')} {t('currencySymbol')}
    @@ -28,4 +30,3 @@ function ProductCard({ id, images: [image], name: title, price }, ...props) { } export default ProductCard; - diff --git a/src/components/ProductCard/ProductCard.module.scss b/src/components/ProductCard/ProductCard.module.scss index 77f7e8e..00247ff 100644 --- a/src/components/ProductCard/ProductCard.module.scss +++ b/src/components/ProductCard/ProductCard.module.scss @@ -125,7 +125,7 @@ .favorite { position: absolute; top: 12px; - right: 12px; + inset-inline-end: 12px; /* Replaces right for LTR, left for RTL */ color: var(--color-dark); transition: color .3s ease-in-out; diff --git a/src/components/SearchForm/SearchForm.jsx b/src/components/SearchForm/SearchForm.jsx index 30916a1..d56ba7e 100644 --- a/src/components/SearchForm/SearchForm.jsx +++ b/src/components/SearchForm/SearchForm.jsx @@ -1,7 +1,9 @@ import { useNavigate } from "react-router-dom"; import styles from "./SearchForm.module.scss"; +import { useTranslation } from 'react-i18next'; // Import useTranslation function SearchForm(props) { + const { t } = useTranslation(); // Initialize useTranslation const navigate = useNavigate(); const handleSubmit = (event) => { @@ -19,9 +21,9 @@ function SearchForm(props) { -
    -

    Данные доставки

    +

    {t('order.deliveryDetailsSubtitle')}

    - + - + - + - + - + - +
    Получатель{t('order.recipientLabel')} {orderData.name}
    Телефон{t('order.phoneLabel')} {orderData.phone}
    E-mail{t('order.emailLabel')} {orderData.email}
    Адрес доставки{t('order.addressLabel')} {orderData.address ? orderData.address : ""}
    Способ оплаты{t('order.paymentMethodLabel')} {orderData.paymentType === "card" ? - "Картой" : - "Наличными при получении"} + t('order.paymentCard') : + t('order.paymentCash')}
    Способ получения{t('order.deliveryMethodLabel')} {orderData.deliveryType === "delivery" ? - "Доставка" : - "Самовывоз"} + t('order.deliveryDelivery') : + t('order.deliveryPickup')}
    - @@ -162,7 +139,7 @@ function Order() { colorScheme='green' fontWeight='normal' size={{ xs: "sm", sm: "sm", md: "md", lg: "md" }}> - На главную + {t('order.backToHomeButton')}