diff --git a/.env.template b/.env.template index 9ed9bb0a3..79a9c1971 100644 --- a/.env.template +++ b/.env.template @@ -15,3 +15,6 @@ NEXT_PUBLIC_STRIPE_KEY= # Your Next.js revalidation secret. See – https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation REVALIDATE_SECRET=supersecret + +# displays missing translation keys for debugging +NEXT_PUBLIC_TRANSLATION_HELPERS=false \ No newline at end of file diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 000000000..f079a6558 --- /dev/null +++ b/global.d.ts @@ -0,0 +1,19 @@ +import enMessages from '../messages/en/index.json'; +import arMessages from '../messages/ar/index.json'; +import { formats, FormatsType } from './src/lib/i18n/request'; + +type Messages = typeof en; + +declare module 'next-intl' { + type DefaultLocale = 'en'; + type SupportedLocales = DefaultLocale | 'ar'; + + type MessageSchema = typeof enMessages; + + interface IntlConfig { + locale: SupportedLocales; + messages: MessageSchema; + formats: FormatsType; + } + } + \ No newline at end of file diff --git a/messages/ar/index.json b/messages/ar/index.json new file mode 100644 index 000000000..63dc808cc --- /dev/null +++ b/messages/ar/index.json @@ -0,0 +1,240 @@ +{ + "BACK_TO_SHOPPING_CART": "العودة إلى سلة التسوق", + "BACK": "رجوع", + "MEDUSA_STORE": "متجر مدوسا", + "PAGE_NOT_FOUND": "الصفحة غير موجودة", + "THE_PAGE_YOU_TRIED_TO_ACCESS_D": "الصفحة التي حاولت الوصول إليها غير موجودة.", + "GO_TO_FRONTPAGE": "الانتقال إلى الصفحة الرئيسية", + "SHIPPING_ADDRESSES": "عناوين الشحن", + "VIEW_AND_UPDATE_YOUR_SHIPPING": "قم بعرض وتحديث عناوين الشحن الخاصة بك. يمكنك إضافة عناوين متعددة، وسيتم حفظها لاستخدامها أثناء عملية الدفع.", + "ORDERS": "الطلبات", + "VIEW_YOUR_PREVIOUS_ORDERS_AND": "اعرض طلباتك السابقة وحالتها. يمكنك أيضًا طلب استرجاع أو استبدال إذا لزم الأمر.", + "PROFILE": "الملف الشخصي", + "VIEW_AND_UPDATE_YOUR_PROFILE_I": "قم بعرض وتحديث معلومات ملفك الشخصي، بما في ذلك اسمك، بريدك الإلكتروني، ورقم هاتفك. يمكنك أيضًا تحديث عنوان الفواتير أو تغيير كلمة المرور.", + "THE_CART_YOU_TRIED_TO_ACCESS_D": "سلة التسوق التي حاولت الوصول إليها غير موجودة. قم بحذف ملفات تعريف الارتباط وحاول مرة أخرى.", + "NEW_ADDRESS": "عنوان جديد", + "ADD_ADDRESS": "إضافة عنوان", + "CANCEL": "إلغاء", + "SAVE": "حفظ", + "WELCOME_BACK": "مرحباً بعودتك", + "SIGN_IN_TO_ACCESS_AN_ENHANCED": "سجل الدخول للوصول إلى تجربة تسوق أفضل.", + "SIGN_IN": "تسجيل الدخول", + "NOT_A_MEMBER": "لست عضواً؟", + "JOIN_US": "انضم إلينا", + "_": ".", + "_1": "#", + "ITEMS": "عناصر", + "ITEM": "عنصر", + "X": "×", + "_2": "+", + "MORE": "المزيد", + "SEE_DETAILS": "عرض التفاصيل", + "NOTHING_TO_SEE_HERE": "لا يوجد شيء لعرضه هنا", + "YOU_DON_T_HAVE_ANY_ORDERS_YET": "لا توجد لديك أي طلبات بعد، ابدأ التسوق الآن.", + "CONTINUE_SHOPPING": "متابعة التسوق", + "THE_PASSWORD_IS_NOT_SHOWN_FOR": "كلمة المرور غير معروضة لأسباب أمنية.", + "PASSWORD_UPDATE_NOT_IMPLEMENTED": "تحديث كلمة المرور غير متوفر حالياً.", + "BECOME_A_MEDUSA_STORE_MEMBER": "كن عضواً في متجر مدوسا", + "CREATE_YOUR_MEDUSA_STORE_MEMBE": "أنشئ ملفك الشخصي في متجر مدوسا وتمتع بتجربة تسوق مميزة.", + "BY_CREATING_AN_ACCOUNT_YOU_AG": "عند إنشاء حساب، فإنك توافق على", + "PRIVACY_POLICY": "سياسة الخصوصية", + "AND": "و", + "TERMS_OF_USE": "شروط الاستخدام", + "JOIN": "انضم الآن", + "ALREADY_A_MEMBER": "هل لديك حساب بالفعل؟", + "CART": "سلة التسوق", + "YOU_DON_T_HAVE_ANYTHING_IN_YOU": "سلة التسوق الخاصة بك فارغة. دعنا نغير ذلك، استخدم الرابط أدناه لبدء تصفح منتجاتنا.", + "EXPLORE_PRODUCTS": "استكشاف المنتجات", + "ALREADY_HAVE_AN_ACCOUNT": "هل لديك حساب بالفعل؟", + "SIGN_IN_FOR_A_BETTER_EXPERIENC": "سجل الدخول لتجربة أفضل.", + "SUMMARY": "الملخص", + "GO_TO_CHECKOUT": "الانتقال إلى الدفع", + "PROMOTION_S_APPLIED": "تم تطبيق العرض:", + "CODE": "الكود:", + "REMOVE_DISCOUNT_CODE_FROM_ORDER": "إزالة كود الخصم من الطلب", + "DISCOUNT_APPLIED": "تم تطبيق الخصم:", + "_3": "(", + "_4": ")", + "REMOVE_DISCOUNT_CODE_FROM_ORDE": "إزالة كود الخصم من الطلب", + "BUTTON": "زر", + "ADD_PROMOTION_CODE_S": "إضافة كود العرض الترويجي", + "YOU_CAN_ADD_MULTIPLE_GIFT_CARD": "يمكنك إضافة عدة بطاقات هدايا، ولكن كود خصم واحد فقط.", + "PLEASE_ENTER_CODE": "يرجى إدخال الكود", + "CODE1": "الكود", + "TEXT": "نص", + "SECONDARY": "ثانوي", + "APPLY": "تطبيق", + "ATTENTION": "تنبيه:", + "FOR_TESTING_PURPOSES_ONL": "لأغراض الاختبار فقط.", + "REVIEW": "مراجعة", + "PLACE_ORDER": "إتمام الطلب", + "BY_CLICKING_THE_PLACE_ORDER_BU": "عند النقر على زر إتمام الطلب، فإنك تؤكد أنك قد قرأت وفهمت وقبلت شروط الاستخدام، شروط البيع وسياسة الإرجاع، وتقر بأنك قرأت سياسة الخصوصية الخاصة بمتجر مدوسا.", + "IN_YOUR_CART": "في سلة التسوق الخاصة بك", + "SUBTOTAL": "الإجمالي الفرعي", + "SUBTOTAL_EXC_SHIPPING_AND_TAXES": "الإجمالي الفرعي (بدون الشحن والضرائب)", + "DISCOUNT": "الخصم", + "_5": "-", + "GIFT_CARD": "بطاقة الهدايا", + "SHIPPING": "الشحن", + "TAXES": "الضرائب", + "TOTAL": "الإجمالي", + "VARIANT": "النوع:", + "CHECKED": "محدد", + "UNCHECKED": "غير محدد", + "BANCONTACT_ICON": "أيقونة Bancontact", + "IDEAL_ICON": "أيقونة iDEAL", + "ECOMMERCE_STARTER_TEMPLATE": "قالب متجر إلكتروني مبتدئ", + "POWERED_BY_MEDUSA_AND_NEXT_JS": "مدعوم من مدوسا وNext.js", + "VIEW_ON_GITHUB": "عرض على GitHub", + "CART1": "سلة التسوق (", + "SQUARE": "مربع", + "QUANTITY": "\"الكمية\":", + "REMOVE": "إزالة", + "EXCL_TAXES": "(بدون الضرائب)", + "LARGE": "كبير", + "GO_TO_CART": "الذهاب إلى السلة", + "_6": "0", + "CONTINUE_TO_PAYMENT": "الاستمرار إلى الدفع", + "YOUR_SHOPPING_BAG_IS_EMPTY": "حقيبة التسوق الخاصة بك فارغة.", + "GO_TO_ALL_PRODUCTS_PAGE": "الذهاب إلى صفحة جميع المنتجات", + "POWERED_BY": "مدعوم من", + "_7": "&", + "MENU": "القائمة", + "MENU_HOME": "الرئيسية", + "MENU_ACCOUNT": "الحساب", + "MENU_STORE": "المتجر", + "MENU_SEARCH": "بحث", + "MENU_CART": "سلة التسوق", + "_8": "©", + "BILLING_ADDRESS_SAME_AS_DELIVERY_ADDRESS": "عنوان الفواتير هو نفسه عنوان التسليم.", + "MEDUSA_STORE_ALL_RIGHTS": "متجر مدوسا. جميع الحقوق محفوظة.", + "CATEGORIES": "الفئات", + "COLLECTIONS": "المجموعات", + "MEDUSA": "مدوسا", + "GITHUB": "جيتهب", + "DOCUMENTATION": "التوثيق", + "SOURCE_CODE": "الكود المصدري", + "MEDUSA_STORE_ALL_RIGHTS_RESER": "متجر مدوسا. جميع الحقوق محفوظة.", + "SEARCH": "بحث", + "ACCOUNT": "الحساب", + "LL": "ل", + "CART2": "سلة التسوق (0)", + "NEED_HELP": "هل تحتاج إلى مساعدة؟", + "CONTACT": "اتصل بنا", + "RETURNS_EXCHANGES": "الإرجاع والاستبدال", + "YOUR_TEST_ORDER_WAS_SUCCESSFUL": "تم إنشاء طلبك التجريبي بنجاح! 🎉", + "YOU_CAN_NOW_COMPLETE_SETTING_U": "يمكنك الآن إكمال إعداد متجرك في لوحة الإدارة.", + "COMPLETE_SETUP_IN_ADMIN": "إكمال الإعداد في لوحة الإدارة", + "ORDER_SUMMARY": "ملخص الطلب", + "PAYMENT": "الدفع", + "PAYMENT_METHOD": "طريقة الدفع", + "PAYMENT_DETAILS": "تفاصيل الدفع", + "STRIPE": "سترايب", + "_9": "**** **** ****", + "PAID_AT": "تم الدفع في", + "DELIVERY": "التسليم", + "SHIPPING_ADDRESS": "عنوان الشحن", + "_10": "،", + "METHOD": "الطريقة", + "THANK_YOU": "شكراً لك!", + "YOUR_ORDER_WAS_PLACED_SUCCESSF": "تم تقديم طلبك بنجاح.", + "ORDER_DETAILS": "تفاصيل الطلب", + "BACK_TO_OVERVIEW": "العودة إلى النظرة العامة", + "PRODUCT_IMAGE": "صورة المنتج", + "YOUR_DEMO_PRODUCT_WAS_SUCCESSF": "تم إنشاء منتجك التجريبي بنجاح! 🎉", + "YOU_CAN_NOW_CONTINUE_SETTING_U": "يمكنك الآن متابعة إعداد متجرك في لوحة الإدارة.", + "CONTINUE_SETUP_IN_ADMIN": "متابعة الإعداد في لوحة الإدارة", + "MATERIAL": "المادة", + "COUNTRY_OF_ORIGIN": "بلد المنشأ", + "TYPE": "النوع", + "WEIGHT": "الوزن", + "G": "غ", + "DIMENSIONS": "الأبعاد", + "L_X": "ط x", + "W_X": "ع x", + "H": "ا", + "TAGS": "العلامات", + "FAST_DELIVERY": "توصيل سريع", + "YOUR_PACKAGE_WILL_ARRIVE_IN": "سيصل طلبك خلال 3-5 أيام عمل إلى موقع الالتقاط أو إلى منزلك.", + "SIMPLE_EXCHANGES": "تبديل بسيط", + "IS_THE_FIT_NOT_QUITE_RIGHT_NO": "غير متأكد من الملاءمة؟ لا تقلق - سنقوم باستبدال المنتج بمنتج جديد.", + "EASY_RETURNS": "إرجاع سهل", + "JUST_RETURN_YOUR_PRODUCT_AND_W": "قم فقط بإرجاع المنتج وسنرد لك المبلغ. لا أسئلة – سنبذل قصارى جهدنا لجعل عملية الإرجاع خالية من المتاعب.", + "PRODUCT_INFORMATION": "معلومات المنتج", + "SHIPPING_RETURNS": "الشحن والإرجاع", + "NO_RESULTS_FOUND": "لم يتم العثور على نتائج.", + "SHOWING_THE_FIRST": "عرض أول", + "RESULTS": "نتائج.", + "VIEW_ALL": "عرض الكل", + "GOT_QUESTIONS": "لديك أسئلة؟", + "FIND_FAQ": "يمكنك العثور على الأسئلة الشائعة والإجابات على صفحة خدمة العملاء.", + "CUSTOMER_SERVICE": "خدمة العملاء", + "SHIPPING_TO": "الشحن إلى:", + "EMAIL": "البريد الإلكتروني", + "PASS": "كلمة المرور", + "FIRST_NAME": "الاسم الأول", + "LAST_NAME": "الاسم الأخير", + "COMPANY": "الشركة", + "ADDRESS": "العنوان", + "APARTMENT_SUITE_ETC": "شقة، جناح، إلخ.", + "POSTAL_CODE": "الرمز البريدي", + "CITY": "المدينة", + "STATE_PROVINCE": "المقاطعة / الولاية", + "PHONE": "الهاتف", + "BILLING_ADDRESS": "عنوان الفواتير", + "NAME": "الاسم", + "OLD_PASSWORD": "كلمة المرور القديمة", + "NEW_PASSWORD": "كلمة المرور الجديدة", + "CONFIRM_PASSWORD": "تأكيد كلمة المرور", + "BILLING_ADDRESS_SAME_AS_SHIPPING_ADDRESS": "عنوان الفواتير هو نفسه عنوان الشحن", + "UPDATED_SUCCESSFULLY": "تم التحديث بنجاح", + "SAVE_CHANGES": "حفظ التغييرات", + "EDIT": "تعديل", + "EDIT_ADDRESS": "تعديل العنوان", + "NO_BILLING_ADDRESS": "لا يوجد عنوان للفواتير", + + "ENTER_VALID_EMAIL": "أدخل بريدًا إلكترونيًا صالحًا", + "SORT_BY": "ترتيب حسب", + "LATEST_ARRIVALS": "أحدث الإضافات", + "PRICE_LOW_HIGH": "السعر: من الأقل إلى الأعلى", + "PRICE_HIGH_LOW": "السعر: من الأعلى إلى الأقل", + + "HELLO_CUSTOMER": "مرحبًا {firstName}", + "SIGNED_IN_AS": "تم تسجيل الدخول باسم:", + "COMPLETED": "مكتمل", + "ADDRESSES": "العناوين", + "SAVED": "تم الحفظ", + "RECENT_ORDERS": "الطلبات الأخيرة", + "DATE_PLACED": "تاريخ الطلب", + "ORDER_NUMBER": "رقم الطلب", + "ORDER_NUMBER_WITH_COLON": "رقم الطلب:", + "ORDER_CONFIRMATION_SENT_TO": "تم إرسال تفاصيل تأكيد الطلب إلى", + "ORDER_DATE": "تاريخ الطلب", + "ORDER_STATUS": "حالة الطلب", + "PAYMENT_STATUS_WITH_COLON": "حالة الدفع:", + "ORDER": "طلب", + "VIEW_YOUR_ORDER": "عرض طلبك", + "FREE_SHIPPING_UNLOCKED": "تم تفعيل الشحن المجاني!", + "UNLOCK_FREE_SHIPPING": "قم بتفعيل الشحن المجاني", + "ONLY": "فقط", + "AWAY": "بعيد", + "VIEW_CART": "عرض السلة", + "VIEW_PRODUCTS": "عرض المنتجات", + "TOTAL_AMOUNT": "المبلغ الإجمالي", + "SELECT_TITLE": "اختر {title}", + "NO_RECENT_ORDERS": "لا توجد طلبات حديثة", + "GO_TO_ORDER": "انتقل إلى الطلب", + "ACCOUNTS": "الحسابات", + "OVERVIEW": "نظرة عامة", + "LOG_OUT": "تسجيل الخروج", + "ENTER_CARD_DETS": "أدخل تفاصيل بطاقتك:", + "CONTINUE_TO_REVIEW": "تابع إلى المراجعة", + "ENTER_CARD_DETAILS": "أدخل تفاصيل البطاقة", + "NEXT_STEP_APPEARS": "ستظهر خطوة أخرى", + "CONTINUE_TO_DELIVERY": "تابع إلى التسليم", + "PRICE": "السعر", + "FROM": "من", + "RELATED_PRODUCTS": "منتجات ذات صلة", + "MIGHT_ALSO_WANT_CHECK_OUT_PRODUCTS": "قد ترغب أيضًا في الاطلاع على هذه المنتجات.", + "INVALID_DATE": "تاريخ غير صالح" +} \ No newline at end of file diff --git a/messages/en/index.json b/messages/en/index.json new file mode 100644 index 000000000..dc17de050 --- /dev/null +++ b/messages/en/index.json @@ -0,0 +1,240 @@ +{ + "BACK_TO_SHOPPING_CART": "Back to shopping cart", + "BACK": "Back", + "MEDUSA_STORE": "Medusa Store", + "PAGE_NOT_FOUND": "Page not found", + "THE_PAGE_YOU_TRIED_TO_ACCESS_D": "The page you tried to access does not exist.", + "GO_TO_FRONTPAGE": "Go to frontpage", + "SHIPPING_ADDRESSES": "Shipping Addresses", + "VIEW_AND_UPDATE_YOUR_SHIPPING": "View and update your shipping addresses, you can add as many as you like. Saving your addresses will make them available during checkout.", + "ORDERS": "Orders", + "VIEW_YOUR_PREVIOUS_ORDERS_AND": "View your previous orders and their status. You can also create returns or exchanges for your orders if needed.", + "PROFILE": "Profile", + "VIEW_AND_UPDATE_YOUR_PROFILE_I": "View and update your profile information, including your name, email, and phone number. You can also update your billing address, or change your password.", + "THE_CART_YOU_TRIED_TO_ACCESS_D": "The cart you tried to access does not exist. Clear your cookies and try again.", + "NEW_ADDRESS": "New address", + "ADD_ADDRESS": "Add address", + "CANCEL": "Cancel", + "SAVE": "Save", + "WELCOME_BACK": "Welcome back", + "SIGN_IN_TO_ACCESS_AN_ENHANCED": "Sign in to access an enhanced shopping experience.", + "SIGN_IN": "Sign in", + "NOT_A_MEMBER": "Not a member?", + "JOIN_US": "Join us", + "_": ".", + "_1": "#", + "ITEMS": "items", + "ITEM": "item", + "X": "x", + "_2": "+", + "MORE": "more", + "SEE_DETAILS": "See details", + "NOTHING_TO_SEE_HERE": "Nothing to see here", + "YOU_DON_T_HAVE_ANY_ORDERS_YET": "You don't have any orders yet, let us change that", + "CONTINUE_SHOPPING": "Continue shopping", + "THE_PASSWORD_IS_NOT_SHOWN_FOR": "The password is not shown for security reasons", + "PASSWORD_UPDATE_NOT_IMPLEMENTED": "Password update is not implemented", + "BECOME_A_MEDUSA_STORE_MEMBER": "Become a Medusa Store Member", + "CREATE_YOUR_MEDUSA_STORE_MEMBE": "Create your Medusa Store Member profile, and get access to an enhanced shopping experience.", + "BY_CREATING_AN_ACCOUNT_YOU_AG": "By creating an account, you agree to Medusa Store's", + "PRIVACY_POLICY": "Privacy Policy", + "AND": "and", + "TERMS_OF_USE": "Terms of Use", + "JOIN": "Join", + "ALREADY_A_MEMBER": "Already a member?", + "CART": "Cart", + "YOU_DON_T_HAVE_ANYTHING_IN_YOU": "You don't have anything in your cart. Let's change that, use the link below to start browsing our products.", + "EXPLORE_PRODUCTS": "Explore products", + "ALREADY_HAVE_AN_ACCOUNT": "Already have an account?", + "SIGN_IN_FOR_A_BETTER_EXPERIENC": "Sign in for a better experience.", + "SUMMARY": "Summary", + "GO_TO_CHECKOUT": "Go to checkout", + "PROMOTION_S_APPLIED": "Promotion(s) applied:", + "CODE": "Code:", + "REMOVE_DISCOUNT_CODE_FROM_ORDER": "Remove discount code from order", + "DISCOUNT_APPLIED": "Discount applied:", + "_3": "(", + "_4": ")", + "REMOVE_DISCOUNT_CODE_FROM_ORDE": "Remove discount code from order", + "BUTTON": "button", + "ADD_PROMOTION_CODE_S": "Add Promotion Code(s)", + "YOU_CAN_ADD_MULTIPLE_GIFT_CARD": "You can add multiple gift cards, but only one discount code.", + "PLEASE_ENTER_CODE": "Please enter code", + "CODE1": "code", + "TEXT": "text", + "SECONDARY": "secondary", + "APPLY": "Apply", + "ATTENTION": "Attention:", + "FOR_TESTING_PURPOSES_ONL": "For testing purposes only.", + "REVIEW": "Review", + "PLACE_ORDER": "Place order", + "BY_CLICKING_THE_PLACE_ORDER_BU": "By clicking the Place Order button, you confirm that you have read, understand and accept our Terms of Use, Terms of Sale and Returns Policy and acknowledge that you have read Medusa Store\\'s Privacy Policy.", + "IN_YOUR_CART": "In your Cart", + "SUBTOTAL": "Subtotal", + "SUBTOTAL_EXC_SHIPPING_AND_TAXES": "Subtotal (excl. shipping and taxes)", + "DISCOUNT": "Discount", + "_5": "-", + "GIFT_CARD": "Gift card", + "SHIPPING": "Shipping", + "TAXES": "Taxes", + "TOTAL": "Total", + "VARIANT": "Variant:", + "CHECKED": "checked", + "UNCHECKED": "unchecked", + "BANCONTACT_ICON": "Bancontact icon", + "IDEAL_ICON": "iDEAL icon", + "ECOMMERCE_STARTER_TEMPLATE": "Ecommerce Starter Template", + "POWERED_BY_MEDUSA_AND_NEXT_JS": "Powered by Medusa and Next.js", + "VIEW_ON_GITHUB": "View on GitHub", + "CART1": "Cart (", + "SQUARE": "square", + "QUANTITY": "\"Quantity\":", + "REMOVE": "Remove", + "EXCL_TAXES": "(excl. taxes)", + "LARGE": "large", + "GO_TO_CART": "Go to cart", + "_6": "0", + "CONTINUE_TO_PAYMENT": "Continue to Payment", + "YOUR_SHOPPING_BAG_IS_EMPTY": "Your shopping bag is empty.", + "GO_TO_ALL_PRODUCTS_PAGE": "Go to all products page", + "POWERED_BY": "Powered by", + "_7": "&", + "MENU": "Menu", + "MENU_HOME": "Home", + "MENU_ACCOUNT": "Account", + "MENU_STORE": "Store", + "MENU_SEARCH": "Search", + "MENU_CART": "Cart", + "_8": "©", + "BILLING_ADDRESS_SAME_AS_DELIVERY_ADDRESS": "Billing and delivery address are the same.", + "MEDUSA_STORE_ALL_RIGHTS": "Medusa Store. All rights reserved.", + "CATEGORIES": "Categories", + "COLLECTIONS": "Collections", + "MEDUSA": "Medusa", + "GITHUB": "GitHub", + "DOCUMENTATION": "Documentation", + "SOURCE_CODE": "Source code", + "MEDUSA_STORE_ALL_RIGHTS_RESER": "Medusa Store. All rights reserved.", + "SEARCH": "Search", + "ACCOUNT": "Account", + "LL": "lL", + "CART2": "Cart (0)", + "NEED_HELP": "Need help?", + "CONTACT": "Contact", + "RETURNS_EXCHANGES": "Returns & Exchanges", + "YOUR_TEST_ORDER_WAS_SUCCESSFUL": "Your test order was successfully created! 🎉", + "YOU_CAN_NOW_COMPLETE_SETTING_U": "You can now complete setting up your store in the admin.", + "COMPLETE_SETUP_IN_ADMIN": "Complete setup in admin", + "ORDER_SUMMARY": "Order Summary", + "PAYMENT": "Payment", + "PAYMENT_METHOD": "Payment method", + "PAYMENT_DETAILS": "Payment details", + "STRIPE": "stripe", + "_9": "**** **** ****", + "PAID_AT": "paid at", + "DELIVERY": "Delivery", + "SHIPPING_ADDRESS": "Shipping Address", + "_10": ",", + "METHOD": "Method", + "THANK_YOU": "Thank you!", + "YOUR_ORDER_WAS_PLACED_SUCCESSF": "Your order was placed successfully.", + "ORDER_DETAILS": "Order details", + "BACK_TO_OVERVIEW": "Back to overview", + "PRODUCT_IMAGE": "Product image", + "YOUR_DEMO_PRODUCT_WAS_SUCCESSF": "Your demo product was successfully created! 🎉", + "YOU_CAN_NOW_CONTINUE_SETTING_U": "You can now continue setting up your store in the admin.", + "CONTINUE_SETUP_IN_ADMIN": "Continue setup in admin", + "MATERIAL": "Material", + "COUNTRY_OF_ORIGIN": "Country of origin", + "TYPE": "Type", + "WEIGHT": "Weight", + "G": "g", + "DIMENSIONS": "Dimensions", + "L_X": "L x", + "W_X": "W x", + "H": "H", + "TAGS": "Tags", + "FAST_DELIVERY": "Fast delivery", + "YOUR_PACKAGE_WILL_ARRIVE_IN": "Your package will arrive in 3-5 business days at your pick up location or in the comfort of your home.", + "SIMPLE_EXCHANGES": "Simple exchanges", + "IS_THE_FIT_NOT_QUITE_RIGHT_NO": "Is the fit not quite right? No worries - we'll exchange your product for a new one.", + "EASY_RETURNS": "Easy returns", + "JUST_RETURN_YOUR_PRODUCT_AND_W": "Just return your product and we'll refund your money. No questions asked – we'll do our best to make sure your return is hassle-free.", + "PRODUCT_INFORMATION": "Product Information", + "SHIPPING_RETURNS": "Shipping & Returns", + "NO_RESULTS_FOUND": "No results found.", + "SHOWING_THE_FIRST": "Showing the first", + "RESULTS": "results.", + "VIEW_ALL": "View all", + "GOT_QUESTIONS": "Got questions?", + "FIND_FAQ": " You can find frequently asked questions and answers on our customer service page.", + "CUSTOMER_SERVICE": "Customer Service", + "SHIPPING_TO": "Shipping to:", + "EMAIL": "Email", + "PASS": "Password", + "FIRST_NAME": "First name", + "LAST_NAME": "Last name", + "COMPANY": "Company", + "ADDRESS": "Address", + "APARTMENT_SUITE_ETC": "Apartment, suite, etc.", + "POSTAL_CODE": "Postal code", + "CITY": "City", + "STATE_PROVINCE": "Province / State", + "PHONE": "Phone", + "BILLING_ADDRESS": "Billing address", + "NAME": "Name", + "OLD_PASSWORD": "Old password", + "NEW_PASSWORD": "New password", + "CONFIRM_PASSWORD": "Confirm password", + "BILLING_ADDRESS_SAME_AS_SHIPPING_ADDRESS":"Billing address same as shipping address", + "UPDATED_SUCCESSFULLY": "updated succesfully", + "SAVE_CHANGES": "Save changes", + "EDIT": "Edit", + "EDIT_ADDRESS": "Edit address", + "NO_BILLING_ADDRESS": "No billing address", + + "ENTER_VALID_EMAIL": "Enter a valid email", + "SORT_BY": "Sort by", + "LATEST_ARRIVALS": "Latest Arrivals", + "PRICE_LOW_HIGH": "Price: Low -> High", + "PRICE_HIGH_LOW": "Price: High -> Low", + + "HELLO_CUSTOMER": "Hello {firstName}", + "SIGNED_IN_AS": "Signed in as:", + "COMPLETED": "Completed", + "ADDRESSES": "Addresses", + "SAVED": "Saved", + "RECENT_ORDERS": "Recent orders", + "DATE_PLACED": "Date placed", + "ORDER_NUMBER": "Order number", + "ORDER_NUMBER_WITH_COLON": "Order number:", + "ORDER_CONFIRMATION_SENT_TO": "We have sent the order confirmation details to", + "ORDER_DATE": "Order date", + "ORDER_STATUS": "Order status", + "PAYMENT_STATUS_WITH_COLON": "Payment status:", + "ORDER": "Order", + "VIEW_YOUR_ORDER": "View your order", + "FREE_SHIPPING_UNLOCKED": "Free Shipping unlocked!", + "UNLOCK_FREE_SHIPPING": "Unlock Free Shipping", + "ONLY": "Only", + "AWAY": "away", + "VIEW_CART": "View cart", + "VIEW_PRODUCTS": "View products", + "TOTAL_AMOUNT": "Total amount", + "SELECT_TITLE": "Select {title}", + "NO_RECENT_ORDERS": "No recent orders", + "GO_TO_ORDER": "Go to order", + "ACCOUNTS": "Accounts", + "OVERVIEW": "Overview", + "LOG_OUT": "Log out", + "ENTER_CARD_DETS": "Enter your card details:", + "CONTINUE_TO_REVIEW": "Continue to review", + "ENTER_CARD_DETAILS": "Enter card details", + "NEXT_STEP_APPEARS": "Another step will appear", + "CONTINUE_TO_DELIVERY": "Continue to delivery", + "PRICE": "Price", + "FROM": "From", + "RELATED_PRODUCTS": "Related products", + "MIGHT_ALSO_WANT_CHECK_OUT_PRODUCTS": "You might also want to check out these products.", + "INVALID_DATE": "Invalid Date" +} \ No newline at end of file diff --git a/next.config.js b/next.config.js index fd5706a24..6cf7f72d9 100644 --- a/next.config.js +++ b/next.config.js @@ -1,11 +1,16 @@ const checkEnvVariables = require("./check-env-variables") +const nextIntl = require("next-intl/plugin") + checkEnvVariables() /** * @type {import('next').NextConfig} */ -const nextConfig = { + +const withNextIntl = nextIntl("./src/lib/i18n/request.ts"); + +const configOpts = { reactStrictMode: true, logging: { fetches: { @@ -40,4 +45,6 @@ const nextConfig = { }, } +const nextConfig = withNextIntl(configOpts) + module.exports = nextConfig diff --git a/package.json b/package.json index 683e0bf37..7ceae1350 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@stripe/stripe-js": "^1.29.0", "lodash": "^4.17.21", "next": "15.0.3", + "next-intl": "^3.26.3", "pg": "^8.11.3", "qs": "^6.12.1", "react": "19.0.0-rc-66855b96-20241106", diff --git a/src/app/[countryCode]/(checkout)/layout.tsx b/src/app/[countryCode]/(checkout)/layout.tsx deleted file mode 100644 index 53793dbd8..000000000 --- a/src/app/[countryCode]/(checkout)/layout.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import LocalizedClientLink from "@modules/common/components/localized-client-link" -import ChevronDown from "@modules/common/icons/chevron-down" -import MedusaCTA from "@modules/layout/components/medusa-cta" - -export default function CheckoutLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( -
-
- -
-
{children}
-
- -
-
- ) -} diff --git a/src/app/[countryCode]/(checkout)/checkout/page.tsx b/src/app/[locale]/[countryCode]/(checkout)/checkout/page.tsx similarity index 63% rename from src/app/[countryCode]/(checkout)/checkout/page.tsx rename to src/app/[locale]/[countryCode]/(checkout)/checkout/page.tsx index 6244a1d06..2127914b8 100644 --- a/src/app/[countryCode]/(checkout)/checkout/page.tsx +++ b/src/app/[locale]/[countryCode]/(checkout)/checkout/page.tsx @@ -5,6 +5,8 @@ import CheckoutForm from "@modules/checkout/templates/checkout-form" import CheckoutSummary from "@modules/checkout/templates/checkout-summary" import { Metadata } from "next" import { notFound } from "next/navigation" +import { Suspense } from "react" + export const metadata: Metadata = { title: "Checkout", @@ -20,11 +22,13 @@ export default async function Checkout() { const customer = await retrieveCustomer() return ( -
- - - - -
+ }> +
+ + + + +
+
) } diff --git a/src/app/[locale]/[countryCode]/(checkout)/layout.tsx b/src/app/[locale]/[countryCode]/(checkout)/layout.tsx new file mode 100644 index 000000000..1880a7abe --- /dev/null +++ b/src/app/[locale]/[countryCode]/(checkout)/layout.tsx @@ -0,0 +1,49 @@ +import { getTranslations } from "next-intl/server" +import LocalizedClientLink from "@modules/common/components/localized-client-link" +import ChevronDown from "@modules/common/icons/chevron-down" +import MedusaCTA from "@modules/layout/components/medusa-cta" +import { Suspense } from "react" + +export default async function CheckoutLayout({ + children, +}: { + children: React.ReactNode + params: { locale: string } +}) { + const t = await getTranslations() + return ( + }> +
+
+ +
+
{children}
+
+ +
+
+
+ ) +} diff --git a/src/app/[countryCode]/(checkout)/not-found.tsx b/src/app/[locale]/[countryCode]/(checkout)/not-found.tsx similarity index 60% rename from src/app/[countryCode]/(checkout)/not-found.tsx rename to src/app/[locale]/[countryCode]/(checkout)/not-found.tsx index 838c9683e..e552be611 100644 --- a/src/app/[countryCode]/(checkout)/not-found.tsx +++ b/src/app/[locale]/[countryCode]/(checkout)/not-found.tsx @@ -1,3 +1,4 @@ +import { getTranslations } from "next-intl/server" import InteractiveLink from "@modules/common/components/interactive-link" import { Metadata } from "next" @@ -7,13 +8,15 @@ export const metadata: Metadata = { } export default async function NotFound() { + const t = await getTranslations() + return (
-

Page not found

+

{t('PAGE_NOT_FOUND')}

- The page you tried to access does not exist. + {t('THE_PAGE_YOU_TRIED_TO_ACCESS_D')}

- Go to frontpage + {t('GO_TO_FRONTPAGE')}
) } diff --git a/src/app/[countryCode]/(main)/account/@dashboard/addresses/page.tsx b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/addresses/page.tsx similarity index 80% rename from src/app/[countryCode]/(main)/account/@dashboard/addresses/page.tsx rename to src/app/[locale]/[countryCode]/(main)/account/@dashboard/addresses/page.tsx index 18e68651b..498d93a5c 100644 --- a/src/app/[countryCode]/(main)/account/@dashboard/addresses/page.tsx +++ b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/addresses/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from "next-intl/server" + import { Metadata } from "next" import { notFound } from "next/navigation" @@ -18,6 +20,7 @@ export default async function Addresses(props: { const { countryCode } = params const customer = await retrieveCustomer() const region = await getRegion(countryCode) + const t = await getTranslations() if (!customer || !region) { notFound() @@ -26,10 +29,9 @@ export default async function Addresses(props: { return (
-

Shipping Addresses

+

{t('SHIPPING_ADDRESSES')}

- View and update your shipping addresses, you can add as many as you - like. Saving your addresses will make them available during checkout. + {t('VIEW_AND_UPDATE_YOUR_SHIPPING')}

diff --git a/src/app/[countryCode]/(main)/account/@dashboard/loading.tsx b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/loading.tsx similarity index 100% rename from src/app/[countryCode]/(main)/account/@dashboard/loading.tsx rename to src/app/[locale]/[countryCode]/(main)/account/@dashboard/loading.tsx diff --git a/src/app/[countryCode]/(main)/account/@dashboard/orders/details/[id]/page.tsx b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/orders/details/[id]/page.tsx similarity index 79% rename from src/app/[countryCode]/(main)/account/@dashboard/orders/details/[id]/page.tsx rename to src/app/[locale]/[countryCode]/(main)/account/@dashboard/orders/details/[id]/page.tsx index bd158c994..fc656e60c 100644 --- a/src/app/[countryCode]/(main)/account/@dashboard/orders/details/[id]/page.tsx +++ b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/orders/details/[id]/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from "next-intl/server" + import { retrieveOrder } from "@lib/data/orders" import OrderDetailsTemplate from "@modules/order/templates/order-details-template" import { Metadata } from "next" @@ -8,16 +10,19 @@ type Props = { } export async function generateMetadata(props: Props): Promise { + + const t = await getTranslations() + const params = await props.params const order = await retrieveOrder(params.id).catch(() => null) if (!order) { notFound() } - + return { - title: `Order #${order.display_id}`, - description: `View your order`, + title: `${t('ORDER')} #${order.display_id}`, + description: `${t('VIEW_YOUR_ORDER')}`, } } diff --git a/src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/orders/page.tsx similarity index 82% rename from src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx rename to src/app/[locale]/[countryCode]/(main)/account/@dashboard/orders/page.tsx index 5d65e32bd..cd6673bff 100644 --- a/src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx +++ b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/orders/page.tsx @@ -1,3 +1,4 @@ +import { getTranslations } from "next-intl/server" import { Metadata } from "next" import OrderOverview from "@modules/account/components/order-overview" @@ -13,6 +14,7 @@ export const metadata: Metadata = { export default async function Orders() { const orders = await listOrders() + const t = await getTranslations() if (!orders) { notFound() @@ -21,10 +23,9 @@ export default async function Orders() { return (
-

Orders

+

{t('ORDERS')}

- View your previous orders and their status. You can also create - returns or exchanges for your orders if needed. + {t('VIEW_YOUR_PREVIOUS_ORDERS_AND')}

diff --git a/src/app/[countryCode]/(main)/account/@dashboard/page.tsx b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/account/@dashboard/page.tsx rename to src/app/[locale]/[countryCode]/(main)/account/@dashboard/page.tsx diff --git a/src/app/[countryCode]/(main)/account/@dashboard/profile/page.tsx b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/profile/page.tsx similarity index 86% rename from src/app/[countryCode]/(main)/account/@dashboard/profile/page.tsx rename to src/app/[locale]/[countryCode]/(main)/account/@dashboard/profile/page.tsx index 97b05808c..887542dec 100644 --- a/src/app/[countryCode]/(main)/account/@dashboard/profile/page.tsx +++ b/src/app/[locale]/[countryCode]/(main)/account/@dashboard/profile/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from "next-intl/server" + import { Metadata } from "next" import ProfilePhone from "@modules/account//components/profile-phone" @@ -18,6 +20,7 @@ export const metadata: Metadata = { export default async function Profile() { const customer = await retrieveCustomer() const regions = await listRegions() + const t = await getTranslations() if (!customer || !regions) { notFound() @@ -26,11 +29,9 @@ export default async function Profile() { return (
-

Profile

+

{t('PROFILE')}

- View and update your profile information, including your name, email, - and phone number. You can also update your billing address, or change - your password. + {t('VIEW_AND_UPDATE_YOUR_PROFILE_I')}

diff --git a/src/app/[countryCode]/(main)/account/@login/page.tsx b/src/app/[locale]/[countryCode]/(main)/account/@login/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/account/@login/page.tsx rename to src/app/[locale]/[countryCode]/(main)/account/@login/page.tsx diff --git a/src/app/[countryCode]/(main)/account/layout.tsx b/src/app/[locale]/[countryCode]/(main)/account/layout.tsx similarity index 100% rename from src/app/[countryCode]/(main)/account/layout.tsx rename to src/app/[locale]/[countryCode]/(main)/account/layout.tsx diff --git a/src/app/[countryCode]/(main)/account/loading.tsx b/src/app/[locale]/[countryCode]/(main)/account/loading.tsx similarity index 100% rename from src/app/[countryCode]/(main)/account/loading.tsx rename to src/app/[locale]/[countryCode]/(main)/account/loading.tsx diff --git a/src/app/[countryCode]/(main)/cart/loading.tsx b/src/app/[locale]/[countryCode]/(main)/cart/loading.tsx similarity index 100% rename from src/app/[countryCode]/(main)/cart/loading.tsx rename to src/app/[locale]/[countryCode]/(main)/cart/loading.tsx diff --git a/src/app/[countryCode]/(main)/cart/not-found.tsx b/src/app/[locale]/[countryCode]/(main)/cart/not-found.tsx similarity index 60% rename from src/app/[countryCode]/(main)/cart/not-found.tsx rename to src/app/[locale]/[countryCode]/(main)/cart/not-found.tsx index 91af293ef..128c9ae3d 100644 --- a/src/app/[countryCode]/(main)/cart/not-found.tsx +++ b/src/app/[locale]/[countryCode]/(main)/cart/not-found.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import { Metadata } from "next" import InteractiveLink from "@modules/common/components/interactive-link" @@ -8,14 +10,14 @@ export const metadata: Metadata = { } export default function NotFound() { + const t = useTranslations() return (
-

Page not found

+

{t('PAGE_NOT_FOUND')}

- The cart you tried to access does not exist. Clear your cookies and try - again. + {t('THE_CART_YOU_TRIED_TO_ACCESS_D')}

- Go to frontpage + {t('GO_TO_FRONTPAGE')}
) } diff --git a/src/app/[countryCode]/(main)/cart/page.tsx b/src/app/[locale]/[countryCode]/(main)/cart/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/cart/page.tsx rename to src/app/[locale]/[countryCode]/(main)/cart/page.tsx diff --git a/src/app/[countryCode]/(main)/categories/[...category]/page.tsx b/src/app/[locale]/[countryCode]/(main)/categories/[...category]/page.tsx similarity index 92% rename from src/app/[countryCode]/(main)/categories/[...category]/page.tsx rename to src/app/[locale]/[countryCode]/(main)/categories/[...category]/page.tsx index bd851b729..683521ecf 100644 --- a/src/app/[countryCode]/(main)/categories/[...category]/page.tsx +++ b/src/app/[locale]/[countryCode]/(main)/categories/[...category]/page.tsx @@ -6,9 +6,10 @@ import { listRegions } from "@lib/data/regions" import { StoreRegion } from "@medusajs/types" import CategoryTemplate from "@modules/categories/templates" import { SortOptions } from "@modules/store/components/refinement-list/sort-products" +import { setRequestLocale } from "next-intl/server" type Props = { - params: Promise<{ category: string[]; countryCode: string }> + params: Promise<{ category: string[]; countryCode: string; locale: string }> searchParams: Promise<{ sortBy?: SortOptions page?: string @@ -66,6 +67,7 @@ export async function generateMetadata(props: Props): Promise { export default async function CategoryPage(props: Props) { const searchParams = await props.searchParams const params = await props.params + setRequestLocale(params.locale) const { sortBy, page } = searchParams const productCategory = await getCategoryByHandle(params.category) diff --git a/src/app/[countryCode]/(main)/collections/[handle]/page.tsx b/src/app/[locale]/[countryCode]/(main)/collections/[handle]/page.tsx similarity index 92% rename from src/app/[countryCode]/(main)/collections/[handle]/page.tsx rename to src/app/[locale]/[countryCode]/(main)/collections/[handle]/page.tsx index ba237f17f..f8bbf56c4 100644 --- a/src/app/[countryCode]/(main)/collections/[handle]/page.tsx +++ b/src/app/[locale]/[countryCode]/(main)/collections/[handle]/page.tsx @@ -6,9 +6,10 @@ import { listRegions } from "@lib/data/regions" import { StoreCollection, StoreRegion } from "@medusajs/types" import CollectionTemplate from "@modules/collections/templates" import { SortOptions } from "@modules/store/components/refinement-list/sort-products" +import { setRequestLocale } from "next-intl/server" type Props = { - params: Promise<{ handle: string; countryCode: string }> + params: Promise<{ handle: string; countryCode: string; locale: string }> searchParams: Promise<{ page?: string sortBy?: SortOptions @@ -70,6 +71,7 @@ export default async function CollectionPage(props: Props) { const searchParams = await props.searchParams const params = await props.params const { sortBy, page } = searchParams + setRequestLocale(params.locale) const collection = await getCollectionByHandle(params.handle).then( (collection: StoreCollection) => collection diff --git a/src/app/[countryCode]/(main)/layout.tsx b/src/app/[locale]/[countryCode]/(main)/layout.tsx similarity index 100% rename from src/app/[countryCode]/(main)/layout.tsx rename to src/app/[locale]/[countryCode]/(main)/layout.tsx diff --git a/src/app/[countryCode]/(main)/not-found.tsx b/src/app/[locale]/[countryCode]/(main)/not-found.tsx similarity index 58% rename from src/app/[countryCode]/(main)/not-found.tsx rename to src/app/[locale]/[countryCode]/(main)/not-found.tsx index d001053f7..b0cf26e45 100644 --- a/src/app/[countryCode]/(main)/not-found.tsx +++ b/src/app/[locale]/[countryCode]/(main)/not-found.tsx @@ -1,20 +1,26 @@ +"use client" + import { Metadata } from "next" import InteractiveLink from "@modules/common/components/interactive-link" +import { useTranslations } from "next-intl" export const metadata: Metadata = { title: "404", description: "Something went wrong", } + export default function NotFound() { + const t = useTranslations() + return (
-

Page not found

+

{t('PAGE_NOT_FOUND')}

- The page you tried to access does not exist. + {t('THE_PAGE_YOU_TRIED_TO_ACCESS_D') as string}

- Go to frontpage + {t('GO_TO_FRONTPAGE')}
) } diff --git a/src/app/[countryCode]/(main)/order/[id]/confirmed/loading.tsx b/src/app/[locale]/[countryCode]/(main)/order/[id]/confirmed/loading.tsx similarity index 100% rename from src/app/[countryCode]/(main)/order/[id]/confirmed/loading.tsx rename to src/app/[locale]/[countryCode]/(main)/order/[id]/confirmed/loading.tsx diff --git a/src/app/[countryCode]/(main)/order/[id]/confirmed/page.tsx b/src/app/[locale]/[countryCode]/(main)/order/[id]/confirmed/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/order/[id]/confirmed/page.tsx rename to src/app/[locale]/[countryCode]/(main)/order/[id]/confirmed/page.tsx diff --git a/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/accept/page.tsx b/src/app/[locale]/[countryCode]/(main)/order/[id]/transfer/[token]/accept/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/order/[id]/transfer/[token]/accept/page.tsx rename to src/app/[locale]/[countryCode]/(main)/order/[id]/transfer/[token]/accept/page.tsx diff --git a/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/decline/page.tsx b/src/app/[locale]/[countryCode]/(main)/order/[id]/transfer/[token]/decline/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/order/[id]/transfer/[token]/decline/page.tsx rename to src/app/[locale]/[countryCode]/(main)/order/[id]/transfer/[token]/decline/page.tsx diff --git a/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/page.tsx b/src/app/[locale]/[countryCode]/(main)/order/[id]/transfer/[token]/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/order/[id]/transfer/[token]/page.tsx rename to src/app/[locale]/[countryCode]/(main)/order/[id]/transfer/[token]/page.tsx diff --git a/src/app/[countryCode]/(main)/page.tsx b/src/app/[locale]/[countryCode]/(main)/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/page.tsx rename to src/app/[locale]/[countryCode]/(main)/page.tsx diff --git a/src/app/[countryCode]/(main)/products/[handle]/page.tsx b/src/app/[locale]/[countryCode]/(main)/products/[handle]/page.tsx similarity index 93% rename from src/app/[countryCode]/(main)/products/[handle]/page.tsx rename to src/app/[locale]/[countryCode]/(main)/products/[handle]/page.tsx index c2615ff07..f48882930 100644 --- a/src/app/[countryCode]/(main)/products/[handle]/page.tsx +++ b/src/app/[locale]/[countryCode]/(main)/products/[handle]/page.tsx @@ -3,9 +3,11 @@ import { notFound } from "next/navigation" import { listProducts } from "@lib/data/products" import { getRegion, listRegions } from "@lib/data/regions" import ProductTemplate from "@modules/products/templates" +import { setRequestLocale } from "next-intl/server" + type Props = { - params: Promise<{ countryCode: string; handle: string }> + params: Promise<{ countryCode: string; handle: string; locale: string }> } export async function generateStaticParams() { @@ -74,6 +76,7 @@ export async function generateMetadata(props: Props): Promise { export default async function ProductPage(props: Props) { const params = await props.params const region = await getRegion(params.countryCode) + setRequestLocale(params.locale) if (!region) { notFound() diff --git a/src/app/[countryCode]/(main)/store/page.tsx b/src/app/[locale]/[countryCode]/(main)/store/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/store/page.tsx rename to src/app/[locale]/[countryCode]/(main)/store/page.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6db3994cf..0c209e865 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,17 +1,42 @@ import { getBaseURL } from "@lib/util/env" +import { LOCALE_COOKIE, routing } from "@lib/i18n/settings"; import { Metadata } from "next" +import { NextIntlClientProvider } from "next-intl" +import { getLocale, getMessages } from 'next-intl/server'; +import { setRequestLocale } from "next-intl/server" +import { cookies } from "next/headers" +import { Suspense } from "react" import "styles/globals.css" export const metadata: Metadata = { metadataBase: new URL(getBaseURL()), +}; + +export function generateStaticParams() { + return routing.locales.map((locale) => ({ locale })); } -export default function RootLayout(props: { children: React.ReactNode }) { +export default async function RootLayout({ + children, + params: { locale: requestedLocale }, +}: { + children: React.ReactNode; + params: { locale: string }; +}) { + + // Set the request locale (for server-side context) + setRequestLocale(await getLocale()); + + const messages = await getMessages(); + return ( - - -
{props.children}
- + + {/* Provide the intl context */} + + +
{children}
+ +
- ) + ); } diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index ece9db8db..571f7c88c 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,7 +1,8 @@ import { ArrowUpRightMini } from "@medusajs/icons" import { Text } from "@medusajs/ui" import { Metadata } from "next" -import Link from "next/link" +import { Link } from "@lib/i18n/navigation" +import { useTranslations } from "next-intl" export const metadata: Metadata = { title: "404", @@ -9,17 +10,19 @@ export const metadata: Metadata = { } export default function NotFound() { + const t = useTranslations() + return (
-

Page not found

+

{t('PAGE_NOT_FOUND')}

- The page you tried to access does not exist. + {t('THE_PAGE_YOU_TRIED_TO_ACCESS_D')}

- Go to frontpage + {t('GO_TO_FRONTPAGE')} }) => { + // resolve the locale asynchronously + const resolvedRequestLocale = (await requestLocale) || routing.defaultLocale; + + // validate the locale and fallback if necessary + const resolvedLocale = routing.locales.includes(resolvedRequestLocale) + ? resolvedRequestLocale + : routing.defaultLocale; + + const messages = (await import(`@locales/${resolvedLocale}/index.json`)).default; + + return { + locale: resolvedLocale, + messages, + formats, + }; +}); + +export default getI18NRequestConfig; + + \ No newline at end of file diff --git a/src/lib/i18n/settings.ts b/src/lib/i18n/settings.ts new file mode 100644 index 000000000..96b7eab4b --- /dev/null +++ b/src/lib/i18n/settings.ts @@ -0,0 +1,15 @@ +import { defineRouting } from 'next-intl/routing'; + +export const fallbackLng = "en" +export const languages = [ + fallbackLng, + "ar" +] +export const localePrefix = "always" +export const LOCALE_COOKIE = "NEXT_LOCALE" + +export const routing = defineRouting({ + locales: languages, + defaultLocale: fallbackLng, + localeDetection: true, +}) \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index a6cb4e26d..d838d0c3f 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,6 +1,11 @@ import { HttpTypes } from "@medusajs/types" import { NextRequest, NextResponse } from "next/server" +import createIntlMiddleware from "next-intl/middleware" +import { routing } from "./lib/i18n/settings"; + +const intlMiddleware = createIntlMiddleware(routing) + const BACKEND_URL = process.env.MEDUSA_BACKEND_URL const PUBLISHABLE_API_KEY = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY const DEFAULT_REGION = process.env.NEXT_PUBLIC_DEFAULT_REGION || "us" @@ -69,7 +74,8 @@ async function getRegionMap(cacheId: string) { */ async function getCountryCode( request: NextRequest, - regionMap: Map + regionMap: Map, + countryCodePathnameIndex: number ) { try { let countryCode @@ -78,7 +84,9 @@ async function getCountryCode( .get("x-vercel-ip-country") ?.toLowerCase() - const urlCountryCode = request.nextUrl.pathname.split("/")[1]?.toLowerCase() + const urlCountryCode = request.nextUrl.pathname + .split("/") + [countryCodePathnameIndex]?.toLowerCase() if (urlCountryCode && regionMap.has(urlCountryCode)) { countryCode = urlCountryCode @@ -108,20 +116,51 @@ export async function middleware(request: NextRequest) { let response = NextResponse.redirect(redirectUrl, 307) - let cacheIdCookie = request.cookies.get("_medusa_cache_id") + const pathnameArr = request.nextUrl.pathname.split("/"); + const urlHasKnownLocale = routing.locales.includes(pathnameArr[1]); + + // need to redirect manually if we provide a wrong locale when a countryCode is included + const urlHasUnknownLocale = + !urlHasKnownLocale && + pathnameArr?.[1]?.length == 2 && + (pathnameArr?.[2] ? pathnameArr[2].length == 2 : true); + + + const redirectPath = + request.nextUrl.pathname === "/" + ? "" + : urlHasKnownLocale || urlHasUnknownLocale + ? pathnameArr.slice(2).join("/") + : request.nextUrl.pathname + + + const queryString = request.nextUrl.search ? request.nextUrl.search : "" - let cacheId = cacheIdCookie?.value || crypto.randomUUID() + if (urlHasUnknownLocale) { + redirectUrl = `${request.nextUrl.origin}/${routing.defaultLocale}/${redirectPath}${queryString}` + response = NextResponse.redirect(`${redirectUrl}`, 307) + } - const regionMap = await getRegionMap(cacheId) + let cacheIdCookie = request.cookies.get("_medusa_cache_id"); - const countryCode = regionMap && (await getCountryCode(request, regionMap)) + let cacheId = cacheIdCookie?.value || crypto.randomUUID(); + + const regionMap = await getRegionMap(cacheId); + + const countryCodePathnameIndex = urlHasKnownLocale ? 2 : 1; + + const countryCode = + regionMap && + (await getCountryCode(request, regionMap, countryCodePathnameIndex)); + const urlHasCountryCode = - countryCode && request.nextUrl.pathname.split("/")[1].includes(countryCode) + countryCode && + request.nextUrl.pathname.split("/")[countryCodePathnameIndex] == countryCode // if one of the country codes is in the url and the cache id is set, return next - if (urlHasCountryCode && cacheIdCookie) { - return NextResponse.next() + if (!urlHasUnknownLocale && urlHasCountryCode && cacheIdCookie) { + return intlMiddleware(request) } // if one of the country codes is in the url and the cache id is not set, set the cache id and redirect @@ -138,14 +177,13 @@ export async function middleware(request: NextRequest) { return NextResponse.next() } - const redirectPath = - request.nextUrl.pathname === "/" ? "" : request.nextUrl.pathname - - const queryString = request.nextUrl.search ? request.nextUrl.search : "" + if (!urlHasKnownLocale) return intlMiddleware(request) // If no country code is set, we redirect to the relevant region. if (!urlHasCountryCode && countryCode) { - redirectUrl = `${request.nextUrl.origin}/${countryCode}${redirectPath}${queryString}` + redirectUrl = `${request.nextUrl.origin}/${ + urlHasKnownLocale ? pathnameArr[1] + "/" : "" + }${countryCode}/${redirectPath}${queryString}` response = NextResponse.redirect(`${redirectUrl}`, 307) } diff --git a/src/modules/account/components/account-info/index.tsx b/src/modules/account/components/account-info/index.tsx index f0c4937d8..d9c409536 100644 --- a/src/modules/account/components/account-info/index.tsx +++ b/src/modules/account/components/account-info/index.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import { Disclosure } from "@headlessui/react" import { Badge, Button, clx } from "@medusajs/ui" import { useEffect } from "react" @@ -41,6 +43,7 @@ const AccountInfo = ({ } }, [isSuccess, close]) + const t = useTranslations() return (
@@ -63,7 +66,7 @@ const AccountInfo = ({ data-testid="edit-button" data-active={state} > - {state ? "Cancel" : "Edit"} + {state ? t('CANCEL') : t('EDIT')}
@@ -82,7 +85,7 @@ const AccountInfo = ({ data-testid="success-message" > - {label} updated succesfully + {label}{" "}{t('UPDATED_SUCCESSFULLY')} @@ -126,7 +129,7 @@ const AccountInfo = ({ type="submit" data-testid="save-button" > - Save changes + {t('SAVE_CHANGES')}
diff --git a/src/modules/account/components/account-nav/index.tsx b/src/modules/account/components/account-nav/index.tsx index 61dd0c28f..5395079a1 100644 --- a/src/modules/account/components/account-nav/index.tsx +++ b/src/modules/account/components/account-nav/index.tsx @@ -1,8 +1,11 @@ "use client" +import { useTranslations } from "next-intl" + import { clx } from "@medusajs/ui" import { ArrowRightOnRectangle } from "@medusajs/icons" -import { useParams, usePathname } from "next/navigation" +import { useParams } from "next/navigation" +import { usePathname } from "@lib/i18n/navigation" import ChevronDown from "@modules/common/icons/chevron-down" import User from "@modules/common/icons/user" @@ -19,6 +22,7 @@ const AccountNav = ({ }) => { const route = usePathname() const { countryCode } = useParams() as { countryCode: string } + const t = useTranslations() const handleLogout = async () => { await signout(countryCode) @@ -35,13 +39,13 @@ const AccountNav = ({ > <> - Account + {t('ACCOUNT')} ) : ( <>
- Hello {customer?.first_name} + {t('HELLO_CUSTOMER', { firstName: customer?.first_name })}
    @@ -54,7 +58,7 @@ const AccountNav = ({ <>
    - Profile + {t('PROFILE')}
    @@ -69,7 +73,7 @@ const AccountNav = ({ <>
    - Addresses + {t('ADDRESSES')}
    @@ -83,7 +87,7 @@ const AccountNav = ({ >
    - Orders + {t('ORDERS')}
    @@ -97,7 +101,7 @@ const AccountNav = ({ >
    - Log out + {t('LOG_OUT')}
    @@ -110,7 +114,7 @@ const AccountNav = ({
    -

    Account

    +

    {t('ACCOUNT')}

      @@ -120,7 +124,7 @@ const AccountNav = ({ route={route!} data-testid="overview-link" > - Overview + {t('OVERVIEW')}
    • @@ -129,7 +133,7 @@ const AccountNav = ({ route={route!} data-testid="profile-link" > - Profile + {t('PROFILE')}
    • @@ -138,7 +142,7 @@ const AccountNav = ({ route={route!} data-testid="addresses-link" > - Addresses + {t('ADDRESSES')}
    • @@ -147,7 +151,7 @@ const AccountNav = ({ route={route!} data-testid="orders-link" > - Orders + {t('ORDERS')}
    • @@ -156,7 +160,7 @@ const AccountNav = ({ onClick={handleLogout} data-testid="logout-button" > - Log out + {t('LOG_OUT')}
    diff --git a/src/modules/account/components/address-card/add-address.tsx b/src/modules/account/components/address-card/add-address.tsx index 14ad95eeb..2ea03f4af 100644 --- a/src/modules/account/components/address-card/add-address.tsx +++ b/src/modules/account/components/address-card/add-address.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { Plus } from "@medusajs/icons" import { Button, Heading } from "@medusajs/ui" import { useEffect, useState, useActionState } from "react" @@ -46,6 +48,8 @@ const AddAddress = ({ } }, [formState]) + const t = useTranslations() + return ( <> - Add address + {t('ADD_ADDRESS')}
    - Cancel + {t('CANCEL')} - Save + {t('SAVE')}
    diff --git a/src/modules/account/components/address-card/edit-address-modal.tsx b/src/modules/account/components/address-card/edit-address-modal.tsx index 6f08daa91..5c853e852 100644 --- a/src/modules/account/components/address-card/edit-address-modal.tsx +++ b/src/modules/account/components/address-card/edit-address-modal.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import React, { useEffect, useState, useActionState } from "react" import { PencilSquare as Edit, Trash } from "@medusajs/icons" import { Button, Heading, Text, clx } from "@medusajs/ui" @@ -60,6 +62,7 @@ const EditAddress: React.FC = ({ await deleteCustomerAddress(address.id) setRemoving(false) } + const t = useTranslations() return ( <> @@ -108,7 +111,7 @@ const EditAddress: React.FC = ({ data-testid="address-edit-button" > - Edit + {t('EDIT')}
    - Edit address + {t('EDIT_ADDRESS')}
    @@ -225,9 +228,9 @@ const EditAddress: React.FC = ({ className="h-10" data-testid="cancel-button" > - Cancel + {t('CANCEL')} - Save + {t('SAVE')}
    diff --git a/src/modules/account/components/login/index.tsx b/src/modules/account/components/login/index.tsx index c47427bc8..f3e5d5ba5 100644 --- a/src/modules/account/components/login/index.tsx +++ b/src/modules/account/components/login/index.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import { login } from "@lib/data/customer" import { LOGIN_VIEW } from "@modules/account/templates/login-template" import ErrorMessage from "@modules/checkout/components/error-message" @@ -11,29 +13,31 @@ type Props = { const Login = ({ setCurrentView }: Props) => { const [message, formAction] = useActionState(login, null) + + const t = useTranslations() return (
    -

    Welcome back

    +

    {t('WELCOME_BACK')}

    - Sign in to access an enhanced shopping experience. + {t('SIGN_IN_TO_ACCESS_AN_ENHANCED')}

    {
    - Sign in + {t('SIGN_IN')} - Not a member?{" "} + {t('NOT_A_MEMBER')}{" "} - . + {t('_')}
    ) diff --git a/src/modules/account/components/order-card/index.tsx b/src/modules/account/components/order-card/index.tsx index d877231b2..b881bbf57 100644 --- a/src/modules/account/components/order-card/index.tsx +++ b/src/modules/account/components/order-card/index.tsx @@ -1,3 +1,5 @@ +import { useTranslations, useFormatter } from "next-intl" + import { Button } from "@medusajs/ui" import { useMemo } from "react" @@ -11,6 +13,9 @@ type OrderCardProps = { } const OrderCard = ({ order }: OrderCardProps) => { + const t = useTranslations() + const format = useFormatter() + const numberOfLines = useMemo(() => { return ( order.items?.reduce((acc, item) => { @@ -23,14 +28,21 @@ const OrderCard = ({ order }: OrderCardProps) => { return order.items?.length ?? 0 }, [order]) + const formattedDate = format.dateTime(new Date(order.created_at), { + year: 'numeric', + month: 'long', + day: 'numeric', + }) + return (
    - #{order.display_id} + {t('_1')} + {order.display_id}
    - {new Date(order.created_at).toDateString()} + {formattedDate} {convertToLocale({ @@ -39,7 +51,7 @@ const OrderCard = ({ order }: OrderCardProps) => { })} {`${numberOfLines} ${ - numberOfLines > 1 ? "items" : "item" + numberOfLines > 1 ? t('ITEMS') : t('ITEM') }`}
    @@ -58,7 +70,7 @@ const OrderCard = ({ order }: OrderCardProps) => { > {i.title} - x + {t('X')} {i.quantity}
    @@ -67,16 +79,16 @@ const OrderCard = ({ order }: OrderCardProps) => { {numberOfProducts > 4 && (
    - + {numberOfLines - 4} + {t('_2')} {numberOfLines - 4} - more + {t('MORE')}
    )}
diff --git a/src/modules/account/components/order-overview/index.tsx b/src/modules/account/components/order-overview/index.tsx index 35ffe0b49..d5d380ab0 100644 --- a/src/modules/account/components/order-overview/index.tsx +++ b/src/modules/account/components/order-overview/index.tsx @@ -1,7 +1,8 @@ "use client" -import { Button } from "@medusajs/ui" +import { useTranslations } from "next-intl" +import { Button } from "@medusajs/ui" import OrderCard from "../order-card" import LocalizedClientLink from "@modules/common/components/localized-client-link" import { HttpTypes } from "@medusajs/types" @@ -22,19 +23,21 @@ const OrderOverview = ({ orders }: { orders: HttpTypes.StoreOrder[] }) => { ) } + const t = useTranslations() + return (
-

Nothing to see here

+

{t('NOTHING_TO_SEE_HERE')}

- You don't have any orders yet, let us change that {":)"} + {t('YOU_DON_T_HAVE_ANY_ORDERS_YET')} {":)"}

diff --git a/src/modules/account/components/overview/index.tsx b/src/modules/account/components/overview/index.tsx index d807e9755..dd66f72c9 100644 --- a/src/modules/account/components/overview/index.tsx +++ b/src/modules/account/components/overview/index.tsx @@ -1,3 +1,5 @@ +import { useTranslations, useFormatter } from "next-intl" + import { Container } from "@medusajs/ui" import ChevronDown from "@modules/common/icons/chevron-down" @@ -11,15 +13,18 @@ type OverviewProps = { } const Overview = ({ customer, orders }: OverviewProps) => { + const t = useTranslations() + const format = useFormatter() + return (
- Hello {customer?.first_name} + {t('HELLO_CUSTOMER', { firstName: customer?.first_name })} - Signed in as:{" "} + {t('SIGNED_IN_AS')}{" "} {
-

Profile

+

{t('PROFILE')}

{ {getProfileCompletion(customer)}% - Completed + {t('COMPLETED')}
-

Addresses

+

{t('ADDRESSES')}

{ {customer?.addresses?.length || 0} - Saved + {t('SAVED')}
@@ -67,7 +72,7 @@ const Overview = ({ customer, orders }: OverviewProps) => {
-

Recent orders

+

{t('RECENT_ORDERS')}

    { > {orders && orders.length > 0 ? ( orders.slice(0, 5).map((order) => { + // Format order creation date + const formattedDate = format.dateTime(new Date(order.created_at), { + year: 'numeric', + month: 'long', + day: 'numeric', + }) return (
  • { >
    - Date placed + {t('DATE_PLACED')} - Order number + {t('ORDER_NUMBER')} - Total amount + {t('TOTAL_AMOUNT')} - {new Date(order.created_at).toDateString()} + {formattedDate} { data-testid="open-order-button" > - Go to order #{order.display_id} + {t('GO_TO_ORDER')} #{order.display_id} @@ -124,7 +135,7 @@ const Overview = ({ customer, orders }: OverviewProps) => { ) }) ) : ( - No recent orders + {t('NO_RECENT_ORDERS')} )}
diff --git a/src/modules/account/components/profile-billing-address/index.tsx b/src/modules/account/components/profile-billing-address/index.tsx index 95ce947c9..45ebc8ff0 100644 --- a/src/modules/account/components/profile-billing-address/index.tsx +++ b/src/modules/account/components/profile-billing-address/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import React, { useEffect, useMemo, useActionState } from "react" import Input from "@modules/common/components/input" @@ -63,7 +65,7 @@ const ProfileBillingAddress: React.FC = ({ const currentInfo = useMemo(() => { if (!billingAddress) { - return "No billing address" + return t('NO_BILLING_ADDRESS') } const country = @@ -89,11 +91,13 @@ const ProfileBillingAddress: React.FC = ({ ) }, [billingAddress, regionOptions]) + const t = useTranslations() + return (
clearState()} className="w-full"> = ({
= ({ />
= ({ />
= ({ customer }) => { setSuccessState(state.success) }, [state]) + const t = useTranslations() + return ( = ({ customer }) => { >
= ({ customer }) => { setSuccessState(state.success) }, [state]) + const t = useTranslations() + return ( = ({ customer }) => { >
= ({ customer }) => { + const t = useTranslations() + const [successState, setSuccessState] = React.useState(false) // TODO: Add support for password updates const updatePassword = async () => { - toast.info("Password update is not implemented") + toast.info(t('PASSWORD_UPDATE_NOT_IMPLEMENTED')) } const clearState = () => { @@ -31,7 +35,7 @@ const ProfilePassword: React.FC = ({ customer }) => { The password is not shown for security reasons + {t('THE_PASSWORD_IS_NOT_SHOWN_FOR')} } isSuccess={successState} isError={false} @@ -41,21 +45,21 @@ const ProfilePassword: React.FC = ({ customer }) => { >
= ({ customer }) => { + const t = useTranslations() + const [successState, setSuccessState] = React.useState(false) const updateCustomerPhone = async ( @@ -47,7 +51,7 @@ const ProfileEmail: React.FC = ({ customer }) => { return ( = ({ customer }) => { >
{ const [message, formAction] = useActionState(signup, null) + const t = useTranslations() + return (

- Become a Medusa Store Member + {t('BECOME_A_MEDUSA_STORE_MEMBER')}

- Create your Medusa Store Member profile, and get access to an enhanced - shopping experience. + {t('CREATE_YOUR_MEDUSA_STORE_MEMBE')}

{ data-testid="email-input" /> {
- By creating an account, you agree to Medusa Store's{" "} + {t('BY_CREATING_AN_ACCOUNT_YOU_AG')}{" "} - Privacy Policy + {t('PRIVACY_POLICY')} {" "} - and{" "} + {t('AND')}{" "} - Terms of Use + {t('TERMS_OF_USE')} - . + {t('_')} - Join + {t('JOIN')} - Already a member?{" "} + {t('ALREADY_A_MEMBER')}{" "} - . + {t('_')}
) diff --git a/src/modules/account/templates/account-layout.tsx b/src/modules/account/templates/account-layout.tsx index 2d2949063..7c730a8c1 100644 --- a/src/modules/account/templates/account-layout.tsx +++ b/src/modules/account/templates/account-layout.tsx @@ -1,5 +1,7 @@ import React from "react" +import { useTranslations } from "next-intl" + import UnderlineLink from "@modules/common/components/interactive-link" import AccountNav from "../components/account-nav" @@ -14,6 +16,8 @@ const AccountLayout: React.FC = ({ customer, children, }) => { + const t = useTranslations() + return (
@@ -23,15 +27,14 @@ const AccountLayout: React.FC = ({
-

Got questions?

+

{t('GOT_QUESTIONS')}

- You can find frequently asked questions and answers on our - customer service page. + {t('FIND_FAQ')}
- Customer Service + {t('CUSTOMER_SERVICE')}
diff --git a/src/modules/cart/components/empty-cart-message/index.tsx b/src/modules/cart/components/empty-cart-message/index.tsx index e04a1f8c4..4c34ce111 100644 --- a/src/modules/cart/components/empty-cart-message/index.tsx +++ b/src/modules/cart/components/empty-cart-message/index.tsx @@ -1,22 +1,24 @@ +import { useTranslations } from "next-intl" import { Heading, Text } from "@medusajs/ui" import InteractiveLink from "@modules/common/components/interactive-link" const EmptyCartMessage = () => { + const t = useTranslations() + return (
- Cart + {t('CART')} - You don't have anything in your cart. Let's change that, use - the link below to start browsing our products. + {t('YOU_DON_T_HAVE_ANYTHING_IN_YOU')}
- Explore products + {t('EXPLORE_PRODUCTS')}
) diff --git a/src/modules/cart/components/sign-in-prompt/index.tsx b/src/modules/cart/components/sign-in-prompt/index.tsx index b1d169d44..9310f9ba2 100644 --- a/src/modules/cart/components/sign-in-prompt/index.tsx +++ b/src/modules/cart/components/sign-in-prompt/index.tsx @@ -1,21 +1,24 @@ +import { useTranslations } from "next-intl" import { Button, Heading, Text } from "@medusajs/ui" import LocalizedClientLink from "@modules/common/components/localized-client-link" const SignInPrompt = () => { + const t = useTranslations() + return (
- Already have an account? + {t('ALREADY_HAVE_AN_ACCOUNT')} - Sign in for a better experience. + {t('SIGN_IN_FOR_A_BETTER_EXPERIENC')}
diff --git a/src/modules/cart/templates/items.tsx b/src/modules/cart/templates/items.tsx index 71818c3f4..02328ba6d 100644 --- a/src/modules/cart/templates/items.tsx +++ b/src/modules/cart/templates/items.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import repeat from "@lib/util/repeat" import { HttpTypes } from "@medusajs/types" import { Heading, Table } from "@medusajs/ui" @@ -11,22 +13,24 @@ type ItemsTemplateProps = { const ItemsTemplate = ({ cart }: ItemsTemplateProps) => { const items = cart?.items + const t = useTranslations() + return (
- Cart + {t('CART')}
- Item + {t('ITEMS')} - Quantity + {t('QUANTITY')} - Price + {t('PRICE')} - Total + {t('TOTAL')} diff --git a/src/modules/cart/templates/summary.tsx b/src/modules/cart/templates/summary.tsx index 51c1f6dd2..5e4572bcf 100644 --- a/src/modules/cart/templates/summary.tsx +++ b/src/modules/cart/templates/summary.tsx @@ -7,6 +7,7 @@ import Divider from "@modules/common/components/divider" import DiscountCode from "@modules/checkout/components/discount-code" import LocalizedClientLink from "@modules/common/components/localized-client-link" import { HttpTypes } from "@medusajs/types" +import { useTranslations } from "next-intl" type SummaryProps = { cart: HttpTypes.StoreCart & { @@ -26,11 +27,12 @@ function getCheckoutStep(cart: HttpTypes.StoreCart) { const Summary = ({ cart }: SummaryProps) => { const step = getCheckoutStep(cart) + const t = useTranslations() return (
- Summary + {t('SUMMARY')} @@ -39,7 +41,7 @@ const Summary = ({ cart }: SummaryProps) => { href={"/checkout?step=" + step} data-testid="checkout-button" > - +
) diff --git a/src/modules/checkout/components/addresses/index.tsx b/src/modules/checkout/components/addresses/index.tsx index d231acacc..4eeeafde8 100644 --- a/src/modules/checkout/components/addresses/index.tsx +++ b/src/modules/checkout/components/addresses/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { setAddresses } from "@lib/data/cart" import compareAddresses from "@lib/util/compare-addresses" import { CheckCircleSolid } from "@medusajs/icons" @@ -7,7 +9,8 @@ import { HttpTypes } from "@medusajs/types" import { Heading, Text, useToggleState } from "@medusajs/ui" import Divider from "@modules/common/components/divider" import Spinner from "@modules/common/icons/spinner" -import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { useSearchParams } from "next/navigation" +import { usePathname, useRouter } from "@lib/i18n/navigation" import { useActionState } from "react" import BillingAddress from "../billing_address" import ErrorMessage from "../error-message" @@ -24,6 +27,7 @@ const Addresses = ({ const searchParams = useSearchParams() const router = useRouter() const pathname = usePathname() + const t = useTranslations() const isOpen = searchParams.get("step") === "address" @@ -46,7 +50,7 @@ const Addresses = ({ level="h2" className="flex flex-row text-3xl-regular gap-x-2 items-baseline" > - Shipping Address + {t('SHIPPING_ADDRESS')} {!isOpen && } {!isOpen && cart?.shipping_address && ( @@ -56,7 +60,7 @@ const Addresses = ({ className="text-ui-fg-interactive hover:text-ui-fg-interactive-hover" data-testid="edit-address-button" > - Edit + {t('EDIT')} )} @@ -77,14 +81,14 @@ const Addresses = ({ level="h2" className="text-3xl-regular gap-x-4 pb-6 pt-8" > - Billing address + {t('BILLING_ADDRESS')} )} - Continue to delivery + {t('CONTINUE_TO_DELIVERY')} @@ -100,7 +104,7 @@ const Addresses = ({ data-testid="shipping-address-summary" > - Shipping Address + {t('SHIPPING_ADDRESS')} {cart.shipping_address.first_name}{" "} @@ -124,7 +128,7 @@ const Addresses = ({ data-testid="shipping-contact-summary" > - Contact + {t('CONTACT')} {cart.shipping_address.phone} @@ -139,12 +143,12 @@ const Addresses = ({ data-testid="billing-address-summary" > - Billing Address + {t('BILLING_ADDRESS')} {sameAsBilling ? ( - Billing- and delivery address are the same. + {t('BILLING_ADDRESS_SAME_AS_DELIVERY_ADDRESS')} ) : ( <> diff --git a/src/modules/checkout/components/billing_address/index.tsx b/src/modules/checkout/components/billing_address/index.tsx index 4dca2689f..878e5cf9d 100644 --- a/src/modules/checkout/components/billing_address/index.tsx +++ b/src/modules/checkout/components/billing_address/index.tsx @@ -1,3 +1,7 @@ +"use client" + +import { useTranslations } from "next-intl" + import { HttpTypes } from "@medusajs/types" import Input from "@modules/common/components/input" import React, { useState } from "react" @@ -27,11 +31,13 @@ const BillingAddress = ({ cart }: { cart: HttpTypes.StoreCart | null }) => { }) } + const t = useTranslations() + return ( <>
{ data-testid="billing-first-name-input" /> { data-testid="billing-last-name-input" /> { data-testid="billing-address-input" /> { data-testid="billing-company-input" /> { data-testid="billing-postal-input" /> { data-testid="billing-country-select" /> { data-testid="billing-province-input" /> = ({ cart }) => { const [message, formAction] = useActionState(submitPromotionForm, null) + const t = useTranslations() + return (
@@ -62,7 +66,7 @@ const DiscountCode: React.FC = ({ cart }) => { className="txt-medium text-ui-fg-interactive hover:text-ui-fg-interactive-hover" data-testid="add-discount-button" > - Add Promotion Code(s) + {t('ADD_PROMOTION_CODE_S')} {/* @@ -85,7 +89,7 @@ const DiscountCode: React.FC = ({ cart }) => { variant="secondary" data-testid="discount-apply-button" > - Apply + {t('APPLY')}
@@ -101,7 +105,7 @@ const DiscountCode: React.FC = ({ cart }) => {
- Promotion(s) applied: + {t('PROMOTION_S_APPLIED')} {promotions.map((promotion) => { @@ -157,7 +161,7 @@ const DiscountCode: React.FC = ({ cart }) => { > - Remove discount code from order + {t('REMOVE_DISCOUNT_CODE_FROM_ORDER')} )} diff --git a/src/modules/checkout/components/payment-button/index.tsx b/src/modules/checkout/components/payment-button/index.tsx index 493216c1c..d12a4bb16 100644 --- a/src/modules/checkout/components/payment-button/index.tsx +++ b/src/modules/checkout/components/payment-button/index.tsx @@ -7,6 +7,7 @@ import { Button } from "@medusajs/ui" import { useElements, useStripe } from "@stripe/react-stripe-js" import React, { useState } from "react" import ErrorMessage from "../error-message" +import { useTranslations } from "next-intl" type PaymentButtonProps = { cart: HttpTypes.StoreCart @@ -66,6 +67,7 @@ const StripePaymentButton = ({ }) } + const t = useTranslations() const stripe = useStripe() const elements = useElements() const card = elements?.getElement("card") @@ -141,7 +143,7 @@ const StripePaymentButton = ({ isLoading={submitting} data-testid={dataTestId} > - Place order + {t('PLACE_ORDER')} { const [submitting, setSubmitting] = useState(false) const [errorMessage, setErrorMessage] = useState(null) - + const t = useTranslations() + const onPaymentCompleted = async () => { await placeOrder() .catch((err) => { @@ -180,7 +183,7 @@ const ManualTestPaymentButton = ({ notReady }: { notReady: boolean }) => { size="large" data-testid="submit-order-button" > - Place order + {t('PLACE_ORDER')} { + const t = useTranslations() + return ( - Attention: For testing purposes - only. + {t('ATTENTION')}{" "} + {t('FOR_TESTING_PURPOSES_ONL')} ) } diff --git a/src/modules/checkout/components/payment/index.tsx b/src/modules/checkout/components/payment/index.tsx index 8d39ea26d..a65b139b9 100644 --- a/src/modules/checkout/components/payment/index.tsx +++ b/src/modules/checkout/components/payment/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { RadioGroup } from "@headlessui/react" import { isStripe as isStripeFunc, paymentInfoMap } from "@lib/constants" import { initiatePaymentSession } from "@lib/data/cart" @@ -11,7 +13,8 @@ import { StripeContext } from "@modules/checkout/components/payment-wrapper/stri import Divider from "@modules/common/components/divider" import { CardElement } from "@stripe/react-stripe-js" import { StripeCardElementOptions } from "@stripe/stripe-js" -import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { useSearchParams } from "next/navigation" +import { usePathname, useRouter } from "@lib/i18n/navigation" import { useCallback, useContext, useEffect, useMemo, useState } from "react" const Payment = ({ @@ -25,6 +28,8 @@ const Payment = ({ (paymentSession: any) => paymentSession.status === "pending" ) + const t = useTranslations() + const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) const [cardBrand, setCardBrand] = useState(null) @@ -125,7 +130,7 @@ const Payment = ({ } )} > - Payment + {t('PAYMENT')} {!isOpen && paymentReady && } {!isOpen && paymentReady && ( @@ -135,7 +140,7 @@ const Payment = ({ className="text-ui-fg-interactive hover:text-ui-fg-interactive-hover" data-testid="edit-payment-button" > - Edit + {t('EDIT')} )} @@ -162,7 +167,7 @@ const Payment = ({ {isStripe && stripeReady && (
- Enter your card details: + {t('ENTER_CARD_DETS')} - Payment method + {t('PAYMENT_METHOD')} - Gift card + {t('GIFT_CARD')}
)} @@ -212,8 +217,8 @@ const Payment = ({ data-testid="submit-payment-button" > {!activeSession && isStripeFunc(selectedPaymentMethod) - ? " Enter card details" - : "Continue to review"} + ? ` t('ENTER_CARD_DETAILS')` + : t('CONTINUE_TO_REVIEW')}
@@ -222,7 +227,7 @@ const Payment = ({
- Payment method + {t('PAYMENT_METHOD')}
- Payment details + {t('PAYMENT_DETAILS')}
{isStripeFunc(selectedPaymentMethod) && cardBrand ? cardBrand - : "Another step will appear"} + : t('NEXT_STEP_APPEARS')}
@@ -256,13 +261,13 @@ const Payment = ({ ) : paidByGiftcard ? (
- Payment method + {t('PAYMENT_METHOD')} - Gift card + {t('GIFT_CARD')}
) : null} diff --git a/src/modules/checkout/components/review/index.tsx b/src/modules/checkout/components/review/index.tsx index 7730f7a01..0ccf7aee2 100644 --- a/src/modules/checkout/components/review/index.tsx +++ b/src/modules/checkout/components/review/index.tsx @@ -1,11 +1,13 @@ "use client" +import { useTranslations } from "next-intl" import { Heading, Text, clx } from "@medusajs/ui" import PaymentButton from "../payment-button" import { useSearchParams } from "next/navigation" const Review = ({ cart }: { cart: any }) => { + const t = useTranslations() const searchParams = useSearchParams() const isOpen = searchParams.get("step") === "review" @@ -30,7 +32,7 @@ const Review = ({ cart }: { cart: any }) => { } )} > - Review + {t('REVIEW')}
{isOpen && previousStepsCompleted && ( @@ -38,10 +40,7 @@ const Review = ({ cart }: { cart: any }) => {
- By clicking the Place Order button, you confirm that you have - read, understand and accept our Terms of Use, Terms of Sale and - Returns Policy and acknowledge that you have read Medusa - Store's Privacy Policy. + {t('BY_CLICKING_THE_PLACE_ORDER_BU')}
diff --git a/src/modules/checkout/components/shipping-address/index.tsx b/src/modules/checkout/components/shipping-address/index.tsx index ec54269c3..fa373df31 100644 --- a/src/modules/checkout/components/shipping-address/index.tsx +++ b/src/modules/checkout/components/shipping-address/index.tsx @@ -1,3 +1,7 @@ +"use client" + +import { useTranslations } from "next-intl" + import { HttpTypes } from "@medusajs/types" import { Container } from "@medusajs/ui" import Checkbox from "@modules/common/components/checkbox" @@ -91,6 +95,7 @@ const ShippingAddress = ({ [e.target.name]: e.target.value, }) } + const t = useTranslations() return ( <> @@ -112,7 +117,7 @@ const ShippingAddress = ({ )}
= ({ cart.shipping_methods?.at(-1)?.shipping_option_id || null ) + const t = useTranslations() + const searchParams = useSearchParams() const router = useRouter() const pathname = usePathname() @@ -104,7 +109,7 @@ const Shipping: React.FC = ({ } )} > - Delivery + {t('DELIVERY')} {!isOpen && (cart.shipping_methods?.length ?? 0) > 0 && ( )} @@ -119,7 +124,7 @@ const Shipping: React.FC = ({ className="text-ui-fg-interactive hover:text-ui-fg-interactive-hover" data-testid="edit-delivery-button" > - Edit + {t('EDIT')} )} @@ -193,7 +198,7 @@ const Shipping: React.FC = ({ disabled={!cart.shipping_methods?.[0]} data-testid="submit-delivery-option-button" > - Continue to payment + {t('CONTINUE_TO_PAYMENT')}
) : ( @@ -202,7 +207,7 @@ const Shipping: React.FC = ({ {cart && (cart.shipping_methods?.length ?? 0) > 0 && (
- Method + {t('METHOD')} {cart.shipping_methods?.at(-1)?.name}{" "} diff --git a/src/modules/checkout/templates/checkout-summary/index.tsx b/src/modules/checkout/templates/checkout-summary/index.tsx index ca4edbb80..ab6cb3ff4 100644 --- a/src/modules/checkout/templates/checkout-summary/index.tsx +++ b/src/modules/checkout/templates/checkout-summary/index.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import { Heading } from "@medusajs/ui" import ItemsPreviewTemplate from "@modules/cart/templates/preview" @@ -6,6 +8,8 @@ import CartTotals from "@modules/common/components/cart-totals" import Divider from "@modules/common/components/divider" const CheckoutSummary = ({ cart }: { cart: any }) => { + const t = useTranslations() + return (
@@ -14,7 +18,7 @@ const CheckoutSummary = ({ cart }: { cart: any }) => { level="h2" className="flex flex-row text-3xl-regular items-baseline" > - In your Cart + {t('IN_YOUR_CART')} diff --git a/src/modules/common/components/cart-totals/index.tsx b/src/modules/common/components/cart-totals/index.tsx index 9a7c53ea4..1b552bd67 100644 --- a/src/modules/common/components/cart-totals/index.tsx +++ b/src/modules/common/components/cart-totals/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { convertToLocale } from "@lib/util/money" import React from "react" @@ -27,12 +29,14 @@ const CartTotals: React.FC = ({ totals }) => { shipping_subtotal, } = totals + const t = useTranslations() + return (
- Subtotal (excl. shipping and taxes) + {t('SUBTOTAL_EXC_SHIPPING_AND_TAXES')} {convertToLocale({ amount: subtotal ?? 0, currency_code })} @@ -40,32 +44,32 @@ const CartTotals: React.FC = ({ totals }) => {
{!!discount_total && (
- Discount + {t('DISCOUNT')} - -{" "} + {t('_5')}{" "} {convertToLocale({ amount: discount_total ?? 0, currency_code })}
)}
- Shipping + {t('SHIPPING')} {convertToLocale({ amount: shipping_subtotal ?? 0, currency_code })}
- Taxes + {t('TAXES')} {convertToLocale({ amount: tax_total ?? 0, currency_code })}
{!!gift_card_total && (
- Gift card + {t('GIFT_CARD')} = ({ totals }) => {
- Total + {t('TOTAL')} { + const t = useTranslations() + return (
{title} @@ -48,7 +52,7 @@ const FilterRadioGroup = ({ data-testid="radio-label" data-active={i.value === value} > - {i.label} + {t(i.label)}
))} diff --git a/src/modules/common/components/line-item-options/index.tsx b/src/modules/common/components/line-item-options/index.tsx index 69ad5270f..25198ae58 100644 --- a/src/modules/common/components/line-item-options/index.tsx +++ b/src/modules/common/components/line-item-options/index.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import { HttpTypes } from "@medusajs/types" import { Text } from "@medusajs/ui" @@ -12,13 +14,15 @@ const LineItemOptions = ({ "data-testid": dataTestid, "data-value": dataValue, }: LineItemOptionsProps) => { + const t = useTranslations() + return ( - Variant: {variant?.title} + {t('VARIANT')} {variant?.title} ) } diff --git a/src/modules/common/components/radio/index.tsx b/src/modules/common/components/radio/index.tsx index fc52f524d..20e145e14 100644 --- a/src/modules/common/components/radio/index.tsx +++ b/src/modules/common/components/radio/index.tsx @@ -1,18 +1,22 @@ +import { useTranslations } from "next-intl" + const Radio = ({ checked, 'data-testid': dataTestId }: { checked: boolean, 'data-testid'?: string }) => { + const t = useTranslations() + return ( <> diff --git a/src/modules/layout/components/cart-dropdown/index.tsx b/src/modules/layout/components/cart-dropdown/index.tsx index 304b2fe10..9cc819daf 100644 --- a/src/modules/layout/components/cart-dropdown/index.tsx +++ b/src/modules/layout/components/cart-dropdown/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { Popover, PopoverButton, @@ -14,7 +16,7 @@ import LineItemOptions from "@modules/common/components/line-item-options" import LineItemPrice from "@modules/common/components/line-item-price" import LocalizedClientLink from "@modules/common/components/localized-client-link" import Thumbnail from "@modules/products/components/thumbnail" -import { usePathname } from "next/navigation" +import { usePathname } from "@lib/i18n/navigation" import { Fragment, useEffect, useRef, useState } from "react" const CartDropdown = ({ @@ -22,6 +24,8 @@ const CartDropdown = ({ }: { cart?: HttpTypes.StoreCart | null }) => { + const t = useTranslations() + const [activeTimer, setActiveTimer] = useState( undefined ) @@ -85,7 +89,7 @@ const CartDropdown = ({ className="hover:text-ui-fg-base" href="/cart" data-testid="nav-cart-link" - >{`Cart (${totalItems})`} + >{`${t('CART1')}${totalItems}${t('_4')}`}
-

Cart

+

{t('CART')}

{cartState && cartState.items?.length ? ( <> @@ -151,7 +155,7 @@ const CartDropdown = ({ data-testid="cart-item-quantity" data-value={item.quantity} > - Quantity: {item.quantity} + {t('QUANTITY')} {item.quantity}
@@ -168,7 +172,7 @@ const CartDropdown = ({ className="mt-1" data-testid="cart-item-remove-button" > - Remove + {t('REMOVE')}
@@ -177,8 +181,8 @@ const CartDropdown = ({
- Subtotal{" "} - (excl. taxes) + {t('SUBTOTAL')}{" "} + {t('EXCL_TAXES')} - Go to cart + {t('GO_TO_CART')}
@@ -206,14 +210,14 @@ const CartDropdown = ({
- 0 + {t('_6')}
- Your shopping bag is empty. + {t('YOUR_SHOPPING_BAG_IS_EMPTY')}
<> - Go to all products page - + {t('GO_TO_ALL_PRODUCTS_PAGE')} +
diff --git a/src/modules/layout/components/country-select/index.tsx b/src/modules/layout/components/country-select/index.tsx index 52497ab01..1de671746 100644 --- a/src/modules/layout/components/country-select/index.tsx +++ b/src/modules/layout/components/country-select/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { Listbox, ListboxButton, @@ -11,7 +13,8 @@ import { Fragment, useEffect, useMemo, useState } from "react" import ReactCountryFlag from "react-country-flag" import { StateType } from "@lib/hooks/use-toggle-state" -import { useParams, usePathname } from "next/navigation" +import { useParams } from "next/navigation" +import { usePathname } from "@lib/i18n/navigation" import { updateRegion } from "@lib/data/cart" import { HttpTypes } from "@medusajs/types" @@ -62,6 +65,8 @@ const CountrySelect = ({ toggleState, regions }: CountrySelectProps) => { close() } + const t = useTranslations() + return (
{ >
- Shipping to: + {t('SHIPPING_TO')} {current && ( {/* @ts-ignore */} diff --git a/src/modules/layout/components/medusa-cta/index.tsx b/src/modules/layout/components/medusa-cta/index.tsx index d4469471c..657f1eb60 100644 --- a/src/modules/layout/components/medusa-cta/index.tsx +++ b/src/modules/layout/components/medusa-cta/index.tsx @@ -1,16 +1,19 @@ +import { useTranslations } from "next-intl" + import { Text } from "@medusajs/ui" import Medusa from "../../../common/icons/medusa" import NextJs from "../../../common/icons/nextjs" const MedusaCTA = () => { + const t = useTranslations() return ( - Powered by + {t('POWERED_BY')} - & + {t('_7')} diff --git a/src/modules/layout/components/side-menu/index.tsx b/src/modules/layout/components/side-menu/index.tsx index 8cd1cad54..48d26bc5d 100644 --- a/src/modules/layout/components/side-menu/index.tsx +++ b/src/modules/layout/components/side-menu/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { Popover, PopoverPanel, Transition } from "@headlessui/react" import { ArrowRightMini, XMark } from "@medusajs/icons" import { Text, clx, useToggleState } from "@medusajs/ui" @@ -19,6 +21,8 @@ const SideMenuItems = { const SideMenu = ({ regions }: { regions: HttpTypes.StoreRegion[] | null }) => { const toggleState = useToggleState() + const t = useTranslations() + return (
@@ -30,7 +34,7 @@ const SideMenu = ({ regions }: { regions: HttpTypes.StoreRegion[] | null }) => { data-testid="nav-menu-button" className="relative h-full flex items-center transition-all ease-out duration-200 focus:outline-none hover:text-ui-fg-base" > - Menu + {t('MENU')}
@@ -64,7 +68,8 @@ const SideMenu = ({ regions }: { regions: HttpTypes.StoreRegion[] | null }) => { onClick={close} data-testid={`${name.toLowerCase()}-link`} > - {name} + {/* {name} */} + {name && t(`MENU_${name.toUpperCase()}`)} ) @@ -90,8 +95,8 @@ const SideMenu = ({ regions }: { regions: HttpTypes.StoreRegion[] | null }) => { />
- © {new Date().getFullYear()} Medusa Store. All rights - reserved. + {t('_8')} {new Date().getFullYear()} {" "} + {t('MEDUSA_STORE_ALL_RIGHTS')}
diff --git a/src/modules/layout/templates/footer/index.tsx b/src/modules/layout/templates/footer/index.tsx index 1a5505833..a829658a8 100644 --- a/src/modules/layout/templates/footer/index.tsx +++ b/src/modules/layout/templates/footer/index.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from "next-intl/server" + import { listCategories } from "@lib/data/categories" import { listCollections } from "@lib/data/collections" import { Text, clx } from "@medusajs/ui" @@ -10,6 +12,7 @@ export default async function Footer() { fields: "*products", }) const productCategories = await listCategories() + const t = await getTranslations() return (
@@ -20,14 +23,14 @@ export default async function Footer() { href="/" className="txt-compact-xlarge-plus text-ui-fg-subtle hover:text-ui-fg-base uppercase" > - Medusa Store + {t('MEDUSA_STORE')}
{productCategories && productCategories?.length > 0 && (
- Categories + {t('CATEGORIES')}
    0 && (
    - Collections + {t('COLLECTIONS')}
      )}
      - Medusa + {t('MEDUSA')}
      • - GitHub + {t('GITHUB')}
      • @@ -128,7 +131,7 @@ export default async function Footer() { rel="noreferrer" className="hover:text-ui-fg-base" > - Documentation + {t('DOCUMENTATION')}
      • @@ -138,7 +141,7 @@ export default async function Footer() { rel="noreferrer" className="hover:text-ui-fg-base" > - Source code + {t('SOURCE_CODE')}
      @@ -147,7 +150,8 @@ export default async function Footer() {
      - © {new Date().getFullYear()} Medusa Store. All rights reserved. + {t('_8')} {new Date().getFullYear()}{" "} + {t('MEDUSA_STORE_ALL_RIGHTS_RESER')}
      diff --git a/src/modules/layout/templates/nav/index.tsx b/src/modules/layout/templates/nav/index.tsx index d8d763ae4..60d00f655 100644 --- a/src/modules/layout/templates/nav/index.tsx +++ b/src/modules/layout/templates/nav/index.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from "next-intl/server" + import { Suspense } from "react" import { listRegions } from "@lib/data/regions" @@ -8,6 +10,7 @@ import SideMenu from "@modules/layout/components/side-menu" export default async function Nav() { const regions = await listRegions().then((regions: StoreRegion[]) => regions) + const t = await getTranslations() return (
      @@ -25,7 +28,7 @@ export default async function Nav() { className="txt-compact-xlarge-plus hover:text-ui-fg-base uppercase" data-testid="nav-store-link" > - Medusa Store + {t('MEDUSA_STORE')}
      @@ -36,7 +39,7 @@ export default async function Nav() { href="/account" data-testid="nav-account-link" > - Account + {t('ACCOUNT')}
    - Cart (0) + {t('CART2')} } > diff --git a/src/modules/order/components/help/index.tsx b/src/modules/order/components/help/index.tsx index 45106d054..f154beea3 100644 --- a/src/modules/order/components/help/index.tsx +++ b/src/modules/order/components/help/index.tsx @@ -1,19 +1,23 @@ +import { useTranslations } from "next-intl" + import { Heading } from "@medusajs/ui" import LocalizedClientLink from "@modules/common/components/localized-client-link" import React from "react" const Help = () => { + const t = useTranslations() + return (
    - Need help? + {t('NEED_HELP')}
    • - Contact + {t('CONTACT')}
    • - Returns & Exchanges + {t('RETURNS_EXCHANGES')}
    diff --git a/src/modules/order/components/onboarding-cta/index.tsx b/src/modules/order/components/onboarding-cta/index.tsx index 497f1fd89..fd4ccca39 100644 --- a/src/modules/order/components/onboarding-cta/index.tsx +++ b/src/modules/order/components/onboarding-cta/index.tsx @@ -1,24 +1,28 @@ "use client" +import { useTranslations } from "next-intl" + import { resetOnboardingState } from "@lib/data/onboarding" import { Button, Container, Text } from "@medusajs/ui" const OnboardingCta = ({ orderId }: { orderId: string }) => { + const t = useTranslations() + return (
    - Your test order was successfully created! 🎉 + {t('YOUR_TEST_ORDER_WAS_SUCCESSFUL')} - You can now complete setting up your store in the admin. + {t('YOU_CAN_NOW_COMPLETE_SETTING_U')}
    diff --git a/src/modules/order/components/order-details/index.tsx b/src/modules/order/components/order-details/index.tsx index 99e9639c7..7f6381e3f 100644 --- a/src/modules/order/components/order-details/index.tsx +++ b/src/modules/order/components/order-details/index.tsx @@ -1,3 +1,4 @@ +import { useTranslations, useFormatter } from "next-intl" import { HttpTypes } from "@medusajs/types" import { Text } from "@medusajs/ui" @@ -7,16 +8,25 @@ type OrderDetailsProps = { } const OrderDetails = ({ order, showStatus }: OrderDetailsProps) => { + const t = useTranslations() + const format = useFormatter() + const formatStatus = (str: string) => { const formatted = str.split("_").join(" ") return formatted.slice(0, 1).toUpperCase() + formatted.slice(1) } + const formattedDate = format.dateTime(new Date(order.created_at), { + year: 'numeric', + month: 'long', + day: 'numeric', + }) + return (
    - We have sent the order confirmation details to{" "} + {t('ORDER_CONFIRMATION_SENT_TO')}{" "} { . - Order date:{" "} + {t('ORDER_DATE')}{" "} - {new Date(order.created_at).toDateString()} + {formattedDate} - Order number: {order.display_id} + {t('ORDER_NUMBER_WITH_COLON')} {order.display_id}
    {showStatus && ( <> - Order status:{" "} + {t('ORDER_STATUS')}{" "} {/* TODO: Check where the statuses should come from */} {/* {formatStatus(order.fulfillment_status)} */} - Payment status:{" "} + {t('PAYMENT_STATUS_WITH_COLON')}{" "} { }) } + const t = useTranslations() + return (
    -

    Order Summary

    +

    {t('ORDER_SUMMARY')}

    - Subtotal + {t('SUBTOTAL')} {getAmount(order.subtotal)}
    {order.discount_total > 0 && (
    - Discount + {t('DISCOUNT')} - {getAmount(order.discount_total)}
    )} {order.gift_card_total > 0 && (
    - Discount + {t('DISCOUNT')} - {getAmount(order.gift_card_total)}
    )}
    - Shipping + {t('SHIPPING')} {getAmount(order.shipping_total)}
    - Taxes + {t('TAXES')} {getAmount(order.tax_total)}
    - Total + {t('TOTAL')} {getAmount(order.total)}
    diff --git a/src/modules/order/components/payment-details/index.tsx b/src/modules/order/components/payment-details/index.tsx index 83bd92508..f48f465e7 100644 --- a/src/modules/order/components/payment-details/index.tsx +++ b/src/modules/order/components/payment-details/index.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import { Container, Heading, Text } from "@medusajs/ui" import { isStripe, paymentInfoMap } from "@lib/constants" @@ -10,19 +12,22 @@ type PaymentDetailsProps = { } const PaymentDetails = ({ order }: PaymentDetailsProps) => { + + const t = useTranslations() + const payment = order.payment_collections?.[0].payments?.[0] return (
    - Payment + {t('PAYMENT')}
    {payment && (
    - Payment method + {t('PAYMENT_METHOD')} {
    - Payment details + {t('PAYMENT_DETAILS')}
    @@ -41,11 +46,11 @@ const PaymentDetails = ({ order }: PaymentDetailsProps) => { {isStripe(payment.provider_id) && payment.data?.card_last4 - ? `**** **** **** ${payment.data.card_last4}` + ? `${t('_9')} ${payment.data.card_last4}` : `${convertToLocale({ amount: payment.amount, currency_code: order.currency_code, - })} paid at ${new Date( + })} ${t('PAID_AT')} ${new Date( payment.created_at ?? "" ).toLocaleString()}`} diff --git a/src/modules/order/components/shipping-details/index.tsx b/src/modules/order/components/shipping-details/index.tsx index e83564c2f..8a0353017 100644 --- a/src/modules/order/components/shipping-details/index.tsx +++ b/src/modules/order/components/shipping-details/index.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import { convertToLocale } from "@lib/util/money" import { HttpTypes } from "@medusajs/types" import { Heading, Text } from "@medusajs/ui" @@ -9,10 +11,12 @@ type ShippingDetailsProps = { } const ShippingDetails = ({ order }: ShippingDetailsProps) => { + const t = useTranslations() + return (
    - Delivery + {t('DELIVERY')}
    { data-testid="shipping-address-summary" > - Shipping Address + {t('SHIPPING_ADDRESS')} {order.shipping_address?.first_name}{" "} @@ -31,7 +35,7 @@ const ShippingDetails = ({ order }: ShippingDetailsProps) => { {order.shipping_address?.address_2} - {order.shipping_address?.postal_code},{" "} + {order.shipping_address?.postal_code}{t('_10')} {" "} {order.shipping_address?.city} @@ -43,7 +47,7 @@ const ShippingDetails = ({ order }: ShippingDetailsProps) => { className="flex flex-col w-1/3 " data-testid="shipping-contact-summary" > - Contact + {t('CONTACT')} {order.shipping_address?.phone} @@ -54,16 +58,16 @@ const ShippingDetails = ({ order }: ShippingDetailsProps) => { className="flex flex-col w-1/3" data-testid="shipping-method-summary" > - Method + {t('METHOD')} - {(order as any).shipping_methods[0]?.name} ( + {(order as any).shipping_methods[0]?.name} {t('_3')} {convertToLocale({ amount: order.shipping_methods?.[0].total ?? 0, currency_code: order.currency_code, }) .replace(/,/g, "") .replace(/\./g, ",")} - ) + {t('_4')}
    diff --git a/src/modules/order/templates/order-completed-template.tsx b/src/modules/order/templates/order-completed-template.tsx index 26001ba11..5b76b2037 100644 --- a/src/modules/order/templates/order-completed-template.tsx +++ b/src/modules/order/templates/order-completed-template.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from "next-intl/server" + import { Heading } from "@medusajs/ui" import { cookies as nextCookies } from "next/headers" @@ -19,6 +21,8 @@ export default async function OrderCompletedTemplate({ }: OrderCompletedTemplateProps) { const cookies = await nextCookies() + const t = await getTranslations() + const isOnboarding = cookies.get("_medusa_onboarding")?.value === "true" return ( @@ -33,12 +37,12 @@ export default async function OrderCompletedTemplate({ level="h1" className="flex flex-col gap-y-3 text-ui-fg-base text-3xl mb-4" > - Thank you! - Your order was placed successfully. + {t('THANK_YOU')} + {t('YOUR_ORDER_WAS_PLACED_SUCCESSF')} - Summary + {t('SUMMARY')} diff --git a/src/modules/order/templates/order-details-template.tsx b/src/modules/order/templates/order-details-template.tsx index c74b95f93..820cc0dfc 100644 --- a/src/modules/order/templates/order-details-template.tsx +++ b/src/modules/order/templates/order-details-template.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { XMark } from "@medusajs/icons" import { HttpTypes } from "@medusajs/types" import LocalizedClientLink from "@modules/common/components/localized-client-link" @@ -17,16 +19,18 @@ type OrderDetailsTemplateProps = { const OrderDetailsTemplate: React.FC = ({ order, }) => { + const t = useTranslations() + return (
    -

    Order details

    +

    {t('ORDER_DETAILS')}

    - Back to overview + {t('BACK_TO_OVERVIEW')}
    { + const t = useTranslations() + return (
    @@ -22,7 +26,7 @@ const ImageGallery = ({ images }: ImageGalleryProps) => { src={image.url} priority={index <= 2 ? true : false} className="absolute inset-0 rounded-rounded" - alt={`Product image ${index + 1}`} + alt={`${t('PRODUCT_IMAGE')} ${index + 1}`} fill sizes="(max-width: 576px) 280px, (max-width: 768px) 360px, (max-width: 992px) 480px, 800px" style={{ diff --git a/src/modules/products/components/product-actions/option-select.tsx b/src/modules/products/components/product-actions/option-select.tsx index 6ccaeaec3..51f8eda3d 100644 --- a/src/modules/products/components/product-actions/option-select.tsx +++ b/src/modules/products/components/product-actions/option-select.tsx @@ -1,6 +1,7 @@ import { HttpTypes } from "@medusajs/types" import { clx } from "@medusajs/ui" import React from "react" +import { useTranslations } from "next-intl" type OptionSelectProps = { option: HttpTypes.StoreProductOption @@ -20,10 +21,11 @@ const OptionSelect: React.FC = ({ disabled, }) => { const filteredOptions = (option.values ?? []).map((v) => v.value) + const t = useTranslations() return (
    - Select {title} + {t('SELECT_TITLE', { title: title })}
    - Your demo product was successfully created! 🎉 + {t('YOUR_DEMO_PRODUCT_WAS_SUCCESSF')} - You can now continue setting up your store in the admin. + {t('YOU_CAN_NOW_CONTINUE_SETTING_U')} - +
    diff --git a/src/modules/products/components/product-price/index.tsx b/src/modules/products/components/product-price/index.tsx index c5a7a8b20..ac3718222 100644 --- a/src/modules/products/components/product-price/index.tsx +++ b/src/modules/products/components/product-price/index.tsx @@ -2,6 +2,7 @@ import { clx } from "@medusajs/ui" import { getProductPrice } from "@lib/util/get-product-price" import { HttpTypes } from "@medusajs/types" +import { useTranslations } from "next-intl" export default function ProductPrice({ product, @@ -10,6 +11,8 @@ export default function ProductPrice({ product: HttpTypes.StoreProduct variant?: HttpTypes.StoreProductVariant }) { + const t = useTranslations() + const { cheapestPrice, variantPrice } = getProductPrice({ product, variantId: variant?.id, @@ -28,7 +31,7 @@ export default function ProductPrice({ "text-ui-fg-interactive": selectedPrice.price_type === "sale", })} > - {!variant && "From "} + {!variant && t('FROM')}{" "} { + const t = useTranslations() + const tabs = [ { - label: "Product Information", + label: t('PRODUCT_INFORMATION'), component: , }, { - label: "Shipping & Returns", + label: t('SHIPPING_RETURNS'), component: , }, ] @@ -42,34 +46,38 @@ const ProductTabs = ({ product }: ProductTabsProps) => { } const ProductInfoTab = ({ product }: ProductTabsProps) => { + const t = useTranslations() + return (
    - Material -

    {product.material ? product.material : "-"}

    + {t('MATERIAL')} +

    {product.material ? product.material : t('_5')}

    - Country of origin -

    {product.origin_country ? product.origin_country : "-"}

    + {t('COUNTRY_OF_ORIGIN')} +

    {product.origin_country ? product.origin_country : t('_5')}

    - Type -

    {product.type ? product.type.value : "-"}

    + {t('TYPE')} +

    {product.type ? product.type.value : t('_5')}

    - Weight -

    {product.weight ? `${product.weight} g` : "-"}

    + {t('WEIGHT')} +

    {product.weight ? `${product.weight} g` : t('_5')}

    - Dimensions + {t('DIMENSIONS')}

    {product.length && product.width && product.height - ? `${product.length}L x ${product.width}W x ${product.height}H` - : "-"} + ? `${product.length}${t('L_X')} ${product.width}${t('W_X')} ${ + product.height + }${t('H')}` + : t('_5')}

    @@ -79,37 +87,35 @@ const ProductInfoTab = ({ product }: ProductTabsProps) => { } const ShippingInfoTab = () => { + const t = useTranslations() + return (
    - Fast delivery + {t('FAST_DELIVERY')}

    - Your package will arrive in 3-5 business days at your pick up - location or in the comfort of your home. + {t('YOUR_PACKAGE_WILL_ARRIVE_IN')}

    - Simple exchanges + {t('SIMPLE_EXCHANGES')}

    - Is the fit not quite right? No worries - we'll exchange your - product for a new one. + {t('IS_THE_FIT_NOT_QUITE_RIGHT_NO')}

    - Easy returns + {t('EASY_RETURNS')}

    - Just return your product and we'll refund your money. No - questions asked – we'll do our best to make sure your return - is hassle-free. + {t('IS_THE_FIT_NOT_QUITE_RIGHT_NO')}

    diff --git a/src/modules/products/components/related-products/index.tsx b/src/modules/products/components/related-products/index.tsx index 72107eedc..75fd475fe 100644 --- a/src/modules/products/components/related-products/index.tsx +++ b/src/modules/products/components/related-products/index.tsx @@ -2,6 +2,7 @@ import { listProducts } from "@lib/data/products" import { getRegion } from "@lib/data/regions" import { HttpTypes } from "@medusajs/types" import Product from "../product-preview" +import { getTranslations } from "next-intl/server" type RelatedProductsProps = { product: HttpTypes.StoreProduct @@ -12,6 +13,8 @@ export default async function RelatedProducts({ product, countryCode, }: RelatedProductsProps) { + const t = await getTranslations() + const region = await getRegion(countryCode) if (!region) { @@ -50,10 +53,10 @@ export default async function RelatedProducts({
    - Related products + {t('RELATED_PRODUCTS')}

    - You might also want to check out these products. + {t('MIGHT_ALSO_WANT_CHECK_OUT_PRODUCTS')}

    diff --git a/src/modules/shipping/components/free-shipping-price-nudge/index.tsx b/src/modules/shipping/components/free-shipping-price-nudge/index.tsx index c057b0e4b..91f9744e8 100644 --- a/src/modules/shipping/components/free-shipping-price-nudge/index.tsx +++ b/src/modules/shipping/components/free-shipping-price-nudge/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import { convertToLocale } from "@lib/util/money" import { CheckCircleSolid, XMark } from "@medusajs/icons" import { @@ -141,6 +143,8 @@ function FreeShippingInline({ remaining_percentage: number } }) { + const t = useTranslations() + return (
    @@ -149,10 +153,10 @@ function FreeShippingInline({ {price.target_reached ? (
    {" "} - Free Shipping unlocked! + {t('FREE_SHIPPING_UNLOCKED')}
    ) : ( - `Unlock Free Shipping` + `${t('UNLOCK_FREE_SHIPPING')}` )}
    @@ -161,14 +165,14 @@ function FreeShippingInline({ "opacity-0 invisible": price.target_reached, })} > - Only{" "} + {t('ONLY')}{" "} {convertToLocale({ amount: price.target_remaining, currency_code: cart.currency_code, })} {" "} - away + {t('AWAY')}
    @@ -196,6 +200,7 @@ function FreeShippingPopup({ price: StoreFreeShippingPrice }) { const [isClosed, setIsClosed] = useState(false) + const t = useTranslations() return (
    {" "} - Free Shipping unlocked! + {t('FREE_SHIPPING_UNLOCKED')}
    ) : ( - `Unlock Free Shipping` + `${t('UNLOCK_FREE_SHIPPING')}` )}
    @@ -237,14 +242,14 @@ function FreeShippingPopup({ "opacity-0 invisible": price.target_reached, })} > - Only{" "} + {t('ONLY')}{" "} {convertToLocale({ amount: price.target_remaining, currency_code: cart.currency_code, })} {" "} - away + {t('ONLY')}
    @@ -267,14 +272,14 @@ function FreeShippingPopup({ className="rounded-2xl bg-transparent shadow-none outline-none border-[1px] border-white text-[15px] py-2.5 px-4" href="/cart" > - View cart + {t('VIEW_CART')} - View products + {t('VIEW_PRODUCTS')}
    diff --git a/src/modules/store/components/pagination/index.tsx b/src/modules/store/components/pagination/index.tsx index 6b827c5d6..3ac4cc650 100644 --- a/src/modules/store/components/pagination/index.tsx +++ b/src/modules/store/components/pagination/index.tsx @@ -1,7 +1,8 @@ "use client" import { clx } from "@medusajs/ui" -import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { useSearchParams } from "next/navigation" +import { usePathname, useRouter } from "@lib/i18n/navigation" export function Pagination({ page, diff --git a/src/modules/store/components/refinement-list/index.tsx b/src/modules/store/components/refinement-list/index.tsx index 47d03eb09..1fb701c99 100644 --- a/src/modules/store/components/refinement-list/index.tsx +++ b/src/modules/store/components/refinement-list/index.tsx @@ -1,6 +1,7 @@ "use client" -import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { useSearchParams } from "next/navigation" +import { usePathname, useRouter } from "@lib/i18n/navigation" import { useCallback } from "react" import SortProducts, { SortOptions } from "./sort-products" diff --git a/src/modules/store/components/refinement-list/sort-products/index.tsx b/src/modules/store/components/refinement-list/sort-products/index.tsx index 384896aa1..7829e3be2 100644 --- a/src/modules/store/components/refinement-list/sort-products/index.tsx +++ b/src/modules/store/components/refinement-list/sort-products/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useTranslations } from "next-intl" + import FilterRadioGroup from "@modules/common/components/filter-radio-group" export type SortOptions = "price_asc" | "price_desc" | "created_at" @@ -13,15 +15,15 @@ type SortProductsProps = { const sortOptions = [ { value: "created_at", - label: "Latest Arrivals", + label: 'LATEST_ARRIVALS' }, { value: "price_asc", - label: "Price: Low -> High", + label: 'PRICE_LOW_HIGH', }, { value: "price_desc", - label: "Price: High -> Low", + label: 'PRICE_HIGH_LOW', }, ] @@ -34,9 +36,11 @@ const SortProducts = ({ setQueryParams("sortBy", value) } + const t = useTranslations() + return (