diff --git a/messages/de.json b/messages/de.json new file mode 100644 index 00000000..32c333c1 --- /dev/null +++ b/messages/de.json @@ -0,0 +1,503 @@ +{ + "common": { + "subtotal": "Zwischensumme", + "shipping": "Versand", + "tax": "Steuer", + "discount": "Rabatt", + "total": "Gesamt", + "adjustments": "Anpassungen", + "edit": "Bearbeiten", + "remove": "Entfernen", + "cancel": "Abbrechen", + "back": "Zurück", + "continue": "Weiter", + "loading": "Laden...", + "apply": "Anwenden", + "saving": "Speichern...", + "processing": "Verarbeitung...", + "continueShopping": "Weiter einkaufen", + "orderSummary": "Bestellübersicht", + "selectCountry": "Land auswählen", + "myAccount": "Mein Konto", + "giftCard": "Geschenkkarte", + "storeCredit": "Guthaben", + "amountDue": "Fälliger Betrag" + }, + "header": { + "categories": "Kategorien", + "openCart": "Warenkorb öffnen", + "account": "Konto", + "closeMenu": "Menü schließen", + "openMenu": "Menü öffnen", + "menu": "Menü", + "selectCountry": "Land auswählen", + "home": "Startseite", + "allProducts": "Alle Produkte", + "contact": "Kontakt", + "viewAllCategory": "Alle {category} anzeigen", + "openSearch": "Suche öffnen", + "closeSearch": "Suche schließen", + "myAccount": "Mein Konto" + }, + "footer": { + "description": "Ein moderner E-Commerce-Shop, betrieben von Spree Commerce und Next.js.", + "shop": "Shop", + "allProducts": "Alle Produkte", + "categories": "Kategorien", + "account": "Konto", + "myAccount": "Mein Konto", + "orderHistory": "Bestellverlauf", + "cart": "Warenkorb", + "policies": "Richtlinien", + "poweredBy": "Betrieben von", + "copyright": "© {year} {storeName}. Unterstützt durch Spree Commerce." + }, + "home": { + "welcome": "Willkommen bei {storeName}", + "heroDescription": "Entdecken Sie großartige Produkte mit unserem modernen E-Commerce-Erlebnis, betrieben von Spree Commerce.", + "shopNow": "Jetzt einkaufen", + "browseCategories": "Kategorien durchsuchen", + "featuredProducts": "Ausgewählte Produkte", + "viewAll": "Alle ansehen", + "qualityProducts": "Qualitätsprodukte", + "qualityDescription": "Sorgfältig kuratierte Auswahl der besten Produkte.", + "fastShipping": "Schneller Versand", + "shippingDescription": "Schnelle und zuverlässige Lieferung bis an Ihre Haustür.", + "support": "24/7 Support", + "supportDescription": "Unser Team ist jederzeit für Sie da." + }, + "cart": { + "cart": "Warenkorb", + "closeCart": "Warenkorb schließen", + "emptyCart": "Ihr Warenkorb ist leer", + "emptyCartDescription": "Es sieht so aus, als hätten Sie noch nichts in Ihren Warenkorb gelegt.", + "removeItem": "Artikel entfernen", + "decreaseQuantity": "Menge verringern", + "increaseQuantity": "Menge erhöhen", + "shippingNote": "Versand und Steuern werden an der Kasse berechnet", + "checkout": "Zur Kasse", + "viewCart": "Warenkorb anzeigen", + "shoppingCart": "Warenkorb", + "proceedToCheckout": "Zur Kasse gehen", + "itemCount": "({count, plural, one {# Artikel} other {# Artikel}})", + "shippingCalculatedAtCheckout": "Wird an der Kasse berechnet", + "removeItemLabel": "{name} entfernen", + "giftCard": "Geschenkkarte", + "storeCredit": "Guthaben", + "amountDue": "Fälliger Betrag" + }, + "products": { + "allProducts": "Alle Produkte", + "browseCollection": "Entdecken Sie unsere gesamte Kollektion", + "searchResultsFor": "Suchergebnisse für \"{query}\"", + "productsFound": "{count, plural, one {# Produkt} other {# Produkte}} gefunden", + "noProductsFound": "Keine Produkte gefunden", + "tryAdjustingFilters": "Versuchen Sie, Ihre Filter anzupassen", + "noMatchingProducts": "Wir konnten keine Produkte finden, die zu \"{query}\" passen", + "showingProducts": "{shown} von {total} Produkten werden angezeigt", + "loadingMore": "Weitere werden geladen...", + "noMoreProducts": "Keine weiteren Produkte zum Laden", + "filters": "Filter", + "sortBy": "Sortieren nach", + "resetFilters": "Filter zurücksetzen", + "price": "Preis", + "availability": "Verfügbarkeit", + "inStock": "Auf Lager", + "outOfStock": "Nicht auf Lager", + "min": "Min", + "max": "Max", + "range": "Bereich: {min} - {max}", + "clear": "Löschen", + "productCount": "{count, plural, one {# Produkt} other {# Produkte}}", + "sale": "Sale", + "outOfStockVariant": "(Nicht auf Lager)", + "addToCart": "In den Warenkorb", + "adding": "Wird hinzugefügt...", + "description": "Beschreibung", + "details": "Details", + "sku": "Artikelnr.", + "options": "Optionen", + "search": "Suche...", + "searching": "Suche läuft...", + "viewAllResultsFor": "Alle Ergebnisse für \"{query}\" anzeigen", + "manual": "Manuell", + "bestSelling": "Bestseller", + "priceLowHigh": "Preis (niedrig-hoch)", + "priceHighLow": "Preis (hoch-niedrig)", + "newest": "Neueste", + "oldest": "Älteste", + "nameAZ": "Name (A-Z)", + "nameZA": "Name (Z-A)", + "sortUnknown": "Sonstige", + "categories": "Kategorien", + "noCategoriesFound": "Keine Kategorien gefunden.", + "noCategoriesInGroup": "Keine Kategorien in dieser Gruppe.", + "subcategories": "{count, plural, one {# Unterkategorie} other {# Unterkategorien}}", + "noImageAvailable": "Kein Bild verfügbar", + "clickToZoom": "Zum Vergrößern klicken", + "noProductsMatchingFilters": "Keine Produkte gefunden, die Ihren Filtern entsprechen.", + "decreaseQuantity": "Menge verringern", + "increaseQuantity": "Menge erhöhen", + "carouselPrev": "Vorherige Produkte", + "carouselNext": "Nächste Produkte", + "lightboxClose": "Lightbox schließen", + "lightboxPrev": "Vorheriges Bild", + "lightboxNext": "Nächstes Bild", + "subcategoriesHeading": "Unterkategorien", + "sort": "Sortieren", + "clearAll": "Alle löschen", + "clearAllFiltersCount": "Alle Filter löschen ({count})", + "showResults": "Ergebnisse anzeigen", + "closeFilters": "Filter schließen", + "anyAvailability": "Beliebige Verfügbarkeit", + "customPrice": "Benutzerdefiniert", + "priceLabel": "Preis: {value}", + "clearFilter": "{label} löschen", + "openImageZoom": "Bildzoom öffnen", + "priceUnder": "Unter {price}", + "priceAbove": "{price}+", + "priceRangeBucket": "{min} – {max}", + "properties": "Eigenschaften", + "yes": "Ja", + "no": "Nein", + "optionFilterChip": "{filter}: {value}" + }, + "navigation": { + "home": "Startseite", + "categories": "Kategorien", + "breadcrumb": "Brotkrümelnavigation" + }, + "checkout": { + "checkout": "Kasse", + "orderNumber": "Bestellung #{number}", + "checkoutError": "Kassenfehler", + "returnToCart": "Zurück zum Warenkorb", + "emptyCart": "Ihr Warenkorb ist leer", + "emptyCartDescription": "Fügen Sie einige Artikel zu Ihrem Warenkorb hinzu, bevor Sie zur Kasse gehen.", + "stepShipping": "Versand", + "stepDelivery": "Lieferung", + "stepPayment": "Zahlung", + "contactInformation": "Kontaktinformationen", + "emailAddress": "E-Mail-Adresse", + "emailPlaceholder": "sie@beispiel.de", + "usingAccountEmail": "Verwendung Ihrer Konto-E-Mail-Adresse", + "signInPrompt": "Haben Sie bereits ein Konto?", + "signIn": "Anmelden", + "signInDescription": "um auf Ihre gespeicherten Adressen und Bestellhistorie zuzugreifen.", + "shippingAddress": "Lieferadresse", + "continueToDelivery": "Weiter zur Lieferung", + "shippingMethod": "Versandmethode", + "loadingShippingOptions": "Versandoptionen werden geladen...", + "enterShippingAddressForMethods": "Geben Sie Ihre Lieferadresse ein, um verfügbare Versandmethoden anzuzeigen.", + "shipmentOf": "Sendung {current} von {total}", + "shipsFrom": "Versand von {location}", + "phone": "Telefon: {phone}", + "billingAddress": "Rechnungsadresse", + "sameAsShipping": "Wie Lieferadresse", + "paymentMethod": "Zahlungsmethode", + "loadingPaymentForm": "Zahlungsformular wird geladen...", + "addNewPaymentMethod": "Neue Zahlungsmethode hinzufügen", + "endingIn": "endet auf", + "exp": "Gültig bis", + "savedCardLabel": "{brand} endet auf {digits}", + "cardExpiry": "Gültig bis {month}/{year}", + "default": "Standard", + "noPaymentMethods": "Für diese Bestellung sind keine Zahlungsmethoden verfügbar.", + "secureTransactions": "Alle Transaktionen sind sicher und verschlüsselt.", + "creditCard": "Kreditkarte", + "enterShippingAddress": "Lieferadresse eingeben", + "payNow": "Jetzt bezahlen", + "failedToInitPayment": "Zahlung konnte nicht initialisiert werden. Bitte versuchen Sie es erneut.", + "failedToCreateSession": "Zahlungssitzung konnte nicht erstellt werden.", + "paymentError": "Bei der Zahlung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", + "orderNotFound": "Bestellung nicht gefunden oder Sie haben keinen Zugriff darauf.", + "failedToLoadCheckout": "Kasse konnte nicht geladen werden. Bitte versuchen Sie es erneut.", + "orderNotFoundAfterPayment": "Bestellung nach Zahlung nicht gefunden. Bitte kontaktieren Sie den Support.", + "failedToSaveAddress": "Adresse konnte nicht gespeichert werden", + "failedToProceed": "Weiter zum nächsten Schritt fehlgeschlagen", + "generalError": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.", + "failedToSelectRate": "Versandrate konnte nicht ausgewählt werden", + "failedToSaveBilling": "Rechnungsadresse konnte nicht gespeichert werden", + "failedToSaveBillingRetry": "Rechnungsadresse konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.", + "failedToCompleteOrder": "Bestellung konnte nicht abgeschlossen werden", + "stripeNotLoaded": "Stripe wurde noch nicht geladen", + "paymentProcessingError": "Bei der Zahlung ist ein Fehler aufgetreten.", + "failedToCompletePaymentSession": "Zahlungssitzung konnte nicht abgeschlossen werden", + "noOrder": "Keine Bestellung", + "confirmingPayment": "Ihre Zahlung wird bestätigt...", + "policyConsentRequired": "Sie müssen den Geschäftsrichtlinien zustimmen, bevor Sie Ihre Bestellung aufgeben", + "iAgreeToThe": "Ich stimme zu", + "policySeparatorComma": ", ", + "policySeparatorAnd": " und " + }, + "address": { + "firstName": "Vorname", + "lastName": "Nachname", + "company": "Firma (optional)", + "address": "Adresse", + "streetAddress": "Straßenadresse", + "apartment": "Wohnung, Suite usw. (optional)", + "city": "Stadt", + "country": "Land", + "selectCountry": "Land auswählen", + "stateProvince": "Bundesland / Provinz", + "selectState": "Bundesland auswählen", + "stateOrProvince": "Bundesland oder Provinz", + "zipCode": "Postleitzahl", + "phone": "Telefon (optional)", + "selectAnAddress": "Adresse auswählen", + "useDifferentAddress": "Andere Adresse verwenden", + "editAddress": "Adresse bearbeiten", + "addNewAddress": "Neue Adresse hinzufügen", + "saveAddress": "Adresse speichern", + "failedToSave": "Adresse konnte nicht gespeichert werden", + "deleteAddressTitle": "Adresse löschen?", + "deleteAddressConfirmation": "Diese Adresse wird dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", + "delete": "Löschen", + "deleting": "Wird gelöscht...", + "cancel": "Abbrechen", + "edit": "Bearbeiten", + "failedToDeleteAddress": "Adresse konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "addFirstAddress": "Erste Adresse hinzufügen", + "addAddress": "Adresse hinzufügen" + }, + "account": { + "myAccount": "Mein Konto", + "signInDescription": "Melden Sie sich an, um auf Ihr Konto und Ihre Bestellhistorie zuzugreifen.", + "email": "E-Mail", + "password": "Passwort", + "signIn": "Anmelden", + "signingIn": "Anmeldung...", + "invalidCredentials": "Ungültige E-Mail oder Passwort", + "dontHaveAccount": "Noch kein Konto?", + "signUp": "Registrieren", + "accountOverview": "Kontoübersicht", + "orderHistory": "Bestellverlauf", + "orderHistoryDescription": "Frühere Bestellungen ansehen und Sendungen verfolgen", + "addresses": "Adressen", + "addressesDescription": "Versand- und Rechnungsadressen verwalten", + "paymentMethods": "Zahlungsmethoden", + "paymentMethodsDescription": "Gespeicherte Kreditkarten verwalten", + "profile": "Profil", + "profileDescription": "Persönliche Daten aktualisieren", + "signOut": "Abmelden", + "overview": "Übersicht", + "orders": "Bestellungen", + "giftCards": "Geschenkkarten", + "addNewAddress": "Neue Adresse hinzufügen", + "noAddresses": "Keine gespeicherten Adressen", + "noAddressesDescription": "Adressen, die Sie beim Checkout hinzufügen, erscheinen hier.", + "defaultAddress": "Standard", + "deleteConfirm": "Sind Sie sicher, dass Sie diese Adresse löschen möchten?", + "setAsDefault": "Als Standard festlegen", + "showPassword": "Passwort anzeigen", + "hidePassword": "Passwort verbergen", + "forgotPassword": "Passwort vergessen?", + "policyConsentRequired": "Sie müssen den Geschäftsrichtlinien zustimmen, um ein Konto zu erstellen" + }, + "register": { + "createAccount": "Konto erstellen", + "signUpDescription": "Registrieren Sie sich, um mit dem Einkaufen zu beginnen.", + "confirmPassword": "Passwort bestätigen", + "creatingAccount": "Konto wird erstellt...", + "passwordsDontMatch": "Passwörter stimmen nicht überein", + "passwordTooShort": "Das Passwort muss mindestens 6 Zeichen lang sein", + "registrationFailed": "Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut.", + "alreadyHaveAccount": "Haben Sie bereits ein Konto?", + "signIn": "Anmelden", + "unexpectedError": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.", + "firstName": "Vorname", + "lastName": "Nachname", + "firstNamePlaceholder": "Max", + "lastNamePlaceholder": "Mustermann", + "emailPlaceholder": "sie@beispiel.de" + }, + "orders": { + "orderHistory": "Bestellverlauf", + "noOrders": "Noch keine Bestellungen", + "noOrdersDescription": "Wenn Sie Bestellungen aufgeben, erscheinen sie hier.", + "startShopping": "Einkaufen starten", + "order": "Bestellung", + "date": "Datum", + "status": "Status", + "actions": "Aktionen", + "view": "Ansehen", + "orderNotFound": "Bestellung nicht gefunden", + "backToOrders": "Zurück zu Bestellungen", + "placedOn": "Bestellt am {date}", + "orderItems": "Bestellartikel", + "qty": "Menge: {quantity}", + "orderAgain": "Erneut bestellen", + "deliveryAddress": "Lieferadresse", + "shippingMethod": "Versandmethode", + "shippedFrom": "Versand von {location}", + "trackItems": "Sendung verfolgen", + "shipmentCanceled": "Diese Sendung wurde storniert.", + "noTrackingInfo": "Keine Tracking-Informationen vorhanden.", + "paymentInformation": "Zahlungsinformationen", + "cardEndingIn": "{label} endet auf {digits}", + "cardExpires": "Gültig bis {month}/{year}", + "storeCreditApplied": "Angewendet {amount} — {remaining} verbleibend", + "billingAddress": "Rechnungsadresse", + "orderNotFoundDescription": "Die Bestellung, die Sie suchen, existiert nicht.", + "storeCredit": "Store-Guthaben", + "specialInstructions": "Besondere Hinweise", + "payment": "Zahlung", + "shipment": "Sendung", + "shipped": "Versendet", + "partiallyShipped": "Teilweise versendet", + "delivered": "Zugestellt", + "ready": "Bereit", + "pending": "Ausstehend", + "canceled": "Storniert", + "backorder": "Nachbestellung", + "paid": "Bezahlt", + "balanceDue": "Offener Betrag", + "creditOwed": "Guthaben geschuldet", + "failed": "Fehlgeschlagen", + "void": "Storniert", + "notAvailable": "k. A.", + "unknownShipmentStatus": "Unbekannt", + "orderTitle": "Bestellung #{number}", + "shipmentCanceledRefund": "Sendung storniert — eine Rückerstattung wurde veranlasst.", + "shippingMethodUnavailable": "Nicht verfügbar", + "totalColumn": "Gesamt" + }, + "orderPlaced": { + "thanksForOrder": "Vielen Dank für Ihre Bestellung, {name}!", + "thanksForOrderAnonymous": "Vielen Dank für Ihre Bestellung!", + "orderNumber": "Bestellung #{number}", + "emailConfirmation": "Sie erhalten in Kürze eine E-Mail-Bestätigung.", + "orderItems": "Bestellartikel", + "contactInformation": "Kontaktinformationen", + "shippingAddress": "Lieferadresse", + "billingAddress": "Rechnungsadresse", + "orderNotFound": "Bestellung nicht gefunden", + "failedToLoad": "Bestelldetails konnten nicht geladen werden.", + "qty": "Menge: {quantity}", + "shippingMethod": "Versandart", + "standardShipping": "Standardversand", + "payment": "Zahlung", + "giftCard": "Geschenkkarte", + "confirmationSentTo": "Bestätigung gesendet an" + }, + "coupon": { + "placeholder": "Geschenkkarte oder Rabattcode", + "apply": "Anwenden", + "remove": "Entfernen", + "invalidCode": "Ungültiger Gutscheincode", + "applyFailed": "Gutschein konnte nicht angewendet werden. Bitte versuchen Sie es erneut.", + "failedToRemove": "Gutscheincode konnte nicht entfernt werden", + "failedToRemoveGiftCard": "Geschenkkarte konnte nicht entfernt werden", + "removeCoupon": "Gutschein {code} entfernen", + "removeGiftCard": "Geschenkkarte entfernen", + "giftCardCode": "Geschenkkarte {code}", + "applying": "Wird angewendet...", + "removing": "Wird entfernt..." + }, + "checkoutLayout": { + "backToStore": "Zurück zum Shop", + "showOrderSummary": "Bestellübersicht anzeigen", + "hideOrderSummary": "Bestellübersicht ausblenden", + "allRightsReserved": "© {year} {storeName}. Alle Rechte vorbehalten." + }, + "profile": { + "profile": "Profil", + "firstName": "Vorname", + "lastName": "Nachname", + "emailAddress": "E-Mail-Adresse", + "saveChanges": "Änderungen speichern", + "saving": "Speichern...", + "profileUpdated": "Profil erfolgreich aktualisiert!", + "accountInformation": "Kontoinformationen", + "accountId": "Konto-ID", + "loadingProfile": "Profil wird geladen...", + "failedToUpdate": "Profil konnte nicht aktualisiert werden", + "email": "E-Mail" + }, + "creditCards": { + "paymentMethods": "Zahlungsmethoden", + "noCards": "Keine Zahlungsmethoden gespeichert", + "noCardsDescription": "Zahlungsmethoden werden automatisch gespeichert, wenn Sie einen Kauf tätigen.", + "expires": "Gültig bis", + "default": "Standard", + "removing": "Wird entfernt...", + "secureInfo": "Ihre Zahlungsinformationen werden sicher gespeichert.", + "deleteConfirm": "Sind Sie sicher, dass Sie diese Karte entfernen möchten?", + "cardMaskedLabel": "{label} endet auf {digits}, gültig bis {month}/{year}", + "removePaymentMethodTitle": "Zahlungsmethode entfernen?", + "endingIn": "endet auf", + "cardEndingIn": "{label} endet auf {digits}", + "cardExpires": "Gültig bis {month}/{year}", + "remove": "Entfernen", + "cancel": "Abbrechen", + "confirmRemoveDescription": "Die {label} mit Endung {digits} wird von Ihrem Konto entfernt. Diese Aktion kann nicht rückgängig gemacht werden.", + "failedToRemove": "Karte konnte nicht entfernt werden. Bitte versuchen Sie es erneut." + }, + "giftCards": { + "giftCards": "Geschenkkarten", + "noGiftCards": "Keine Geschenkkarten", + "noGiftCardsDescription": "Sie haben noch keine Geschenkkarten.", + "active": "Aktiv", + "partiallyUsed": "Teilweise eingelöst", + "fullyRedeemed": "Vollständig eingelöst", + "canceled": "Storniert", + "expired": "Abgelaufen", + "noExpiration": "Kein Ablaufdatum", + "remaining": "verbleibend", + "usedAmount": "Verwendet: {amount}", + "totalAmountWithValue": "Gesamt: {amount}", + "addedOnDate": "Hinzugefügt am {date}", + "fullyRedeemedOnDate": "Vollständig eingelöst am {date}", + "activeGiftCards": "Aktive Geschenkkarten", + "expiredRedeemed": "Abgelaufen / Eingelöst", + "copy": "Kopieren", + "copied": "Kopiert!", + "copyCodeToClipboard": "Code in die Zwischenablage kopieren", + "expiresOn": "Läuft ab am {date}", + "percentUsed": "{percent}% verwendet", + "activeGiftCardsCount": "Aktive Geschenkkarten ({count})", + "expiredRedeemedCount": "Abgelaufen / Eingelöst ({count})", + "giftCardsHelpText": "Geschenkkarten können beim Checkout zum Bezahlen Ihrer Bestellungen verwendet werden. Das verbleibende Guthaben wird für zukünftige Einkäufe gespeichert." + }, + "forgotPassword": { + "title": "Passwort zurücksetzen", + "description": "Geben Sie Ihre E-Mail-Adresse ein und wir senden Ihnen einen Link zum Zurücksetzen Ihres Passworts.", + "email": "E-Mail", + "sending": "Wird gesendet...", + "sendResetLink": "Link zum Zurücksetzen senden", + "backToSignIn": "Zurück zur Anmeldung", + "checkYourEmail": "Überprüfen Sie Ihre E-Mail", + "resetEmailSent": "Wenn ein Konto für {email} existiert, haben wir Anweisungen zum Zurücksetzen des Passworts gesendet.", + "linkExpiry": "Der Link in der E-Mail läuft in 15 Minuten ab. Überprüfen Sie Ihren Spam-Ordner, wenn Sie ihn nicht sehen.", + "tryDifferentEmail": "Andere E-Mail-Adresse versuchen", + "genericError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut." + }, + "resetPassword": { + "title": "Neues Passwort festlegen", + "description": "Geben Sie unten Ihr neues Passwort ein.", + "newPassword": "Neues Passwort", + "confirmPassword": "Neues Passwort bestätigen", + "resetting": "Wird zurückgesetzt...", + "resetPassword": "Passwort zurücksetzen", + "backToSignIn": "Zurück zur Anmeldung", + "invalidLink": "Ungültiger Link", + "invalidLinkDescription": "Dieser Link zum Zurücksetzen des Passworts ist ungültig oder abgelaufen.", + "requestNewLink": "Neuen Link anfordern", + "passwordsDontMatch": "Passwörter stimmen nicht überein", + "passwordTooShort": "Das Passwort muss mindestens 6 Zeichen lang sein", + "linkExpired": "Dieser Link ist abgelaufen. Bitte fordern Sie einen neuen an.", + "genericError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.", + "success": "Passwort erfolgreich zurückgesetzt", + "successDescription": "Ihr Passwort wurde aktualisiert. Sie können sich jetzt mit Ihrem neuen Passwort anmelden.", + "signIn": "Anmelden" + }, + "policies": { + "policyNotFound": "Richtlinie nicht gefunden", + "noContent": "Für diese Richtlinie sind keine Inhalte verfügbar.", + "shippingPolicy": "Versandrichtlinie", + "privacyPolicy": "Datenschutzrichtlinie", + "returnsPolicy": "Rückgaberichtlinie", + "termsOfService": "Nutzungsbedingungen" + } +} diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 00000000..99182e4c --- /dev/null +++ b/messages/en.json @@ -0,0 +1,503 @@ +{ + "common": { + "subtotal": "Subtotal", + "shipping": "Shipping", + "tax": "Tax", + "discount": "Discount", + "total": "Total", + "adjustments": "Adjustments", + "edit": "Edit", + "remove": "Remove", + "cancel": "Cancel", + "back": "Back", + "continue": "Continue", + "loading": "Loading...", + "apply": "Apply", + "saving": "Saving...", + "processing": "Processing...", + "continueShopping": "Continue Shopping", + "orderSummary": "Order Summary", + "selectCountry": "Select Country", + "myAccount": "My Account", + "giftCard": "Gift card", + "storeCredit": "Store credit", + "amountDue": "Amount due" + }, + "header": { + "categories": "Categories", + "openCart": "Open cart", + "account": "Account", + "closeMenu": "Close menu", + "openMenu": "Open menu", + "menu": "Menu", + "selectCountry": "Select Country", + "home": "Home", + "allProducts": "All Products", + "contact": "Contact", + "viewAllCategory": "View all {category}", + "openSearch": "Open search", + "closeSearch": "Close search", + "myAccount": "My Account" + }, + "footer": { + "description": "A modern e-commerce storefront powered by Spree Commerce and Next.js.", + "shop": "Shop", + "allProducts": "All Products", + "categories": "Categories", + "account": "Account", + "myAccount": "My Account", + "orderHistory": "Order History", + "cart": "Cart", + "policies": "Policies", + "poweredBy": "Powered by", + "copyright": "© {year} {storeName}. Powered by Spree Commerce." + }, + "home": { + "welcome": "Welcome to {storeName}", + "heroDescription": "Discover amazing products with our modern e-commerce experience powered by Spree Commerce.", + "shopNow": "Shop Now", + "browseCategories": "Browse Categories", + "featuredProducts": "Featured Products", + "viewAll": "View all", + "qualityProducts": "Quality Products", + "qualityDescription": "Carefully curated selection of the best products.", + "fastShipping": "Fast Shipping", + "shippingDescription": "Quick and reliable delivery to your doorstep.", + "support": "24/7 Support", + "supportDescription": "Our team is here to help you anytime." + }, + "cart": { + "cart": "Cart", + "closeCart": "Close cart", + "emptyCart": "Your cart is empty", + "emptyCartDescription": "Looks like you haven't added anything to your cart yet.", + "removeItem": "Remove item", + "decreaseQuantity": "Decrease quantity", + "increaseQuantity": "Increase quantity", + "shippingNote": "Shipping and taxes calculated at checkout", + "checkout": "Checkout", + "viewCart": "View Cart", + "shoppingCart": "Shopping Cart", + "proceedToCheckout": "Proceed to Checkout", + "itemCount": "({count, plural, one {# item} other {# items}})", + "shippingCalculatedAtCheckout": "Calculated at checkout", + "removeItemLabel": "Remove {name}", + "giftCard": "Gift card", + "storeCredit": "Store credit", + "amountDue": "Amount due" + }, + "products": { + "allProducts": "All Products", + "browseCollection": "Browse our complete collection", + "searchResultsFor": "Search results for \"{query}\"", + "productsFound": "{count, plural, one {# product} other {# products}} found", + "noProductsFound": "No products found", + "tryAdjustingFilters": "Try adjusting your filters", + "noMatchingProducts": "We couldn't find any products matching \"{query}\"", + "showingProducts": "Showing {shown} of {total} products", + "loadingMore": "Loading more...", + "noMoreProducts": "No more products to load", + "filters": "Filters", + "sortBy": "Sort by", + "resetFilters": "Reset filters", + "price": "Price", + "availability": "Availability", + "inStock": "In Stock", + "outOfStock": "Out of Stock", + "min": "Min", + "max": "Max", + "range": "Range: {min} - {max}", + "clear": "Clear", + "productCount": "{count, plural, one {# product} other {# products}}", + "sale": "Sale", + "outOfStockVariant": "(Out of stock)", + "addToCart": "Add to Cart", + "adding": "Adding...", + "description": "Description", + "details": "Details", + "sku": "SKU", + "options": "Options", + "search": "Search...", + "searching": "Searching...", + "viewAllResultsFor": "View all results for \"{query}\"", + "manual": "Manual", + "bestSelling": "Best Selling", + "priceLowHigh": "Price (low-high)", + "priceHighLow": "Price (high-low)", + "newest": "Newest", + "oldest": "Oldest", + "nameAZ": "Name (A-Z)", + "nameZA": "Name (Z-A)", + "sortUnknown": "Other", + "categories": "Categories", + "noCategoriesFound": "No categories found.", + "noCategoriesInGroup": "No categories in this group.", + "subcategories": "{count, plural, one {# subcategory} other {# subcategories}}", + "noImageAvailable": "No image available", + "clickToZoom": "Click to zoom", + "noProductsMatchingFilters": "No products found matching your filters.", + "decreaseQuantity": "Decrease quantity", + "increaseQuantity": "Increase quantity", + "carouselPrev": "Previous products", + "carouselNext": "Next products", + "lightboxClose": "Close lightbox", + "lightboxPrev": "Previous image", + "lightboxNext": "Next image", + "subcategoriesHeading": "Subcategories", + "sort": "Sort", + "clearAll": "Clear all", + "clearAllFiltersCount": "Clear all filters ({count})", + "showResults": "Show results", + "closeFilters": "Close filters", + "anyAvailability": "Any availability", + "customPrice": "Custom", + "priceLabel": "Price: {value}", + "clearFilter": "Clear {label}", + "openImageZoom": "Open image zoom", + "priceUnder": "Under {price}", + "priceAbove": "{price}+", + "priceRangeBucket": "{min} – {max}", + "properties": "Properties", + "yes": "Yes", + "no": "No", + "optionFilterChip": "{filter}: {value}" + }, + "navigation": { + "home": "Home", + "categories": "Categories", + "breadcrumb": "Breadcrumb" + }, + "checkout": { + "checkout": "Checkout", + "orderNumber": "Order #{number}", + "checkoutError": "Checkout Error", + "returnToCart": "Return to Cart", + "emptyCart": "Your Cart is Empty", + "emptyCartDescription": "Add some items to your cart before checking out.", + "stepShipping": "Shipping", + "stepDelivery": "Delivery", + "stepPayment": "Payment", + "contactInformation": "Contact Information", + "emailAddress": "Email address", + "emailPlaceholder": "you@example.com", + "usingAccountEmail": "Using your account email address", + "signInPrompt": "Already have an account?", + "signIn": "Sign in", + "signInDescription": "to access your saved addresses and order history.", + "shippingAddress": "Shipping Address", + "continueToDelivery": "Continue to Delivery", + "shippingMethod": "Shipping Method", + "loadingShippingOptions": "Loading shipping options...", + "enterShippingAddressForMethods": "Enter your shipping address to view available shipping methods.", + "shipmentOf": "Shipment {current} of {total}", + "shipsFrom": "Ships from {location}", + "phone": "Phone: {phone}", + "billingAddress": "Billing Address", + "sameAsShipping": "Same as shipping address", + "paymentMethod": "Payment Method", + "loadingPaymentForm": "Loading payment form...", + "addNewPaymentMethod": "Add new payment method", + "endingIn": "ending in", + "exp": "Exp", + "savedCardLabel": "{brand} ending in {digits}", + "cardExpiry": "Exp {month}/{year}", + "default": "Default", + "noPaymentMethods": "No payment methods available for this order.", + "secureTransactions": "All transactions are secure and encrypted.", + "creditCard": "Credit card", + "enterShippingAddress": "Enter shipping address", + "payNow": "Pay Now", + "failedToInitPayment": "Failed to initialize payment. Please try again.", + "failedToCreateSession": "Failed to create payment session.", + "paymentError": "An error occurred during payment. Please try again.", + "orderNotFound": "Order not found or you don't have access to it.", + "failedToLoadCheckout": "Failed to load checkout. Please try again.", + "orderNotFoundAfterPayment": "Order not found after payment. Please contact support.", + "failedToSaveAddress": "Failed to save address", + "failedToProceed": "Failed to proceed to next step", + "generalError": "An error occurred. Please try again.", + "failedToSelectRate": "Failed to select shipping rate", + "failedToSaveBilling": "Failed to save billing address", + "failedToSaveBillingRetry": "Failed to save billing address. Please try again.", + "failedToCompleteOrder": "Failed to complete order", + "stripeNotLoaded": "Stripe has not loaded yet", + "paymentProcessingError": "An error occurred during payment.", + "failedToCompletePaymentSession": "Failed to complete payment session", + "noOrder": "No order", + "confirmingPayment": "Confirming your payment...", + "policyConsentRequired": "You must agree to the store policies before placing your order", + "iAgreeToThe": "I agree to the", + "policySeparatorComma": ", ", + "policySeparatorAnd": " and " + }, + "address": { + "firstName": "First name", + "lastName": "Last name", + "company": "Company (optional)", + "address": "Address", + "streetAddress": "Street address", + "apartment": "Apartment, suite, etc. (optional)", + "city": "City", + "country": "Country", + "selectCountry": "Select a country", + "stateProvince": "State / Province", + "selectState": "Select a state", + "stateOrProvince": "State or province", + "zipCode": "ZIP / Postal code", + "phone": "Phone (optional)", + "selectAnAddress": "Select an address", + "useDifferentAddress": "Use a different address", + "editAddress": "Edit Address", + "addNewAddress": "Add New Address", + "saveAddress": "Save Address", + "failedToSave": "Failed to save address", + "deleteAddressTitle": "Delete address?", + "deleteAddressConfirmation": "This will permanently delete this address. This action cannot be undone.", + "delete": "Delete", + "deleting": "Deleting...", + "cancel": "Cancel", + "edit": "Edit", + "failedToDeleteAddress": "Failed to delete address. Please try again.", + "addFirstAddress": "Add Your First Address", + "addAddress": "Add Address" + }, + "account": { + "myAccount": "My Account", + "signInDescription": "Sign in to access your account and order history.", + "email": "Email", + "password": "Password", + "signIn": "Sign In", + "signingIn": "Signing in...", + "invalidCredentials": "Invalid email or password", + "dontHaveAccount": "Don't have an account?", + "signUp": "Sign up", + "accountOverview": "Account Overview", + "orderHistory": "Order History", + "orderHistoryDescription": "View your past orders and track shipments", + "addresses": "Addresses", + "addressesDescription": "Manage your shipping and billing addresses", + "paymentMethods": "Payment Methods", + "paymentMethodsDescription": "Manage your saved credit cards", + "profile": "Profile", + "profileDescription": "Update your personal information", + "signOut": "Sign Out", + "overview": "Overview", + "orders": "Orders", + "giftCards": "Gift Cards", + "addNewAddress": "Add New Address", + "noAddresses": "No saved addresses", + "noAddressesDescription": "Addresses you add during checkout will appear here.", + "defaultAddress": "Default", + "deleteConfirm": "Are you sure you want to delete this address?", + "setAsDefault": "Set as default", + "showPassword": "Show password", + "hidePassword": "Hide password", + "forgotPassword": "Forgot password?", + "policyConsentRequired": "You must agree to the store policies to create an account" + }, + "register": { + "createAccount": "Create Account", + "signUpDescription": "Sign up to start shopping with us.", + "confirmPassword": "Confirm Password", + "creatingAccount": "Creating account...", + "passwordsDontMatch": "Passwords do not match", + "passwordTooShort": "Password must be at least 6 characters", + "registrationFailed": "Registration failed. Please try again.", + "alreadyHaveAccount": "Already have an account?", + "signIn": "Sign in", + "unexpectedError": "An unexpected error occurred. Please try again.", + "firstName": "First name", + "lastName": "Last name", + "firstNamePlaceholder": "John", + "lastNamePlaceholder": "Doe", + "emailPlaceholder": "you@example.com" + }, + "orders": { + "orderHistory": "Order History", + "noOrders": "No orders yet", + "noOrdersDescription": "When you place orders, they will appear here.", + "startShopping": "Start Shopping", + "order": "Order", + "date": "Date", + "status": "Status", + "actions": "Actions", + "view": "View", + "orderNotFound": "Order not found", + "backToOrders": "Back to orders", + "placedOn": "Placed on {date}", + "orderItems": "Order Items", + "qty": "Qty: {quantity}", + "orderAgain": "Order again", + "deliveryAddress": "Delivery Address", + "shippingMethod": "Shipping Method", + "shippedFrom": "Shipped from {location}", + "trackItems": "Track Items", + "shipmentCanceled": "This shipment has been canceled.", + "noTrackingInfo": "No tracking information present.", + "paymentInformation": "Payment Information", + "cardEndingIn": "{label} ending in {digits}", + "cardExpires": "Expires {month}/{year}", + "storeCreditApplied": "Applied {amount} — {remaining} remaining", + "billingAddress": "Billing Address", + "orderNotFoundDescription": "The order you're looking for doesn't exist.", + "storeCredit": "Store Credit", + "specialInstructions": "Special Instructions", + "payment": "Payment", + "shipment": "Shipment", + "shipped": "Shipped", + "partiallyShipped": "Partially Shipped", + "delivered": "Delivered", + "ready": "Ready", + "pending": "Pending", + "canceled": "Canceled", + "backorder": "Backorder", + "paid": "Paid", + "balanceDue": "Balance Due", + "creditOwed": "Credit Owed", + "failed": "Failed", + "void": "Void", + "notAvailable": "N/A", + "unknownShipmentStatus": "Unknown", + "orderTitle": "Order #{number}", + "shipmentCanceledRefund": "Shipment canceled — a refund has been issued.", + "shippingMethodUnavailable": "Unavailable", + "totalColumn": "Total" + }, + "orderPlaced": { + "thanksForOrder": "Thanks for your order, {name}!", + "thanksForOrderAnonymous": "Thanks for your order!", + "orderNumber": "Order #{number}", + "emailConfirmation": "You will receive an email confirmation shortly.", + "orderItems": "Order Items", + "contactInformation": "Contact Information", + "shippingAddress": "Shipping Address", + "billingAddress": "Billing Address", + "orderNotFound": "Order not found", + "failedToLoad": "Failed to load order details.", + "qty": "Qty: {quantity}", + "shippingMethod": "Shipping Method", + "standardShipping": "Standard Shipping", + "payment": "Payment", + "giftCard": "Gift Card", + "confirmationSentTo": "Confirmation sent to" + }, + "coupon": { + "placeholder": "Gift card or discount code", + "apply": "Apply", + "remove": "Remove", + "invalidCode": "Invalid coupon code", + "applyFailed": "Could not apply coupon. Please try again.", + "failedToRemove": "Failed to remove coupon code", + "failedToRemoveGiftCard": "Failed to remove gift card", + "removeCoupon": "Remove coupon {code}", + "removeGiftCard": "Remove gift card", + "giftCardCode": "Gift card {code}", + "applying": "Applying...", + "removing": "Removing..." + }, + "checkoutLayout": { + "backToStore": "Back to store", + "showOrderSummary": "Show order summary", + "hideOrderSummary": "Hide order summary", + "allRightsReserved": "© {year} {storeName}. All rights reserved." + }, + "profile": { + "profile": "Profile", + "firstName": "First Name", + "lastName": "Last Name", + "emailAddress": "Email Address", + "saveChanges": "Save Changes", + "saving": "Saving...", + "profileUpdated": "Profile updated successfully!", + "accountInformation": "Account Information", + "accountId": "Account ID", + "loadingProfile": "Loading profile...", + "failedToUpdate": "Failed to update profile", + "email": "Email" + }, + "creditCards": { + "paymentMethods": "Payment Methods", + "noCards": "No payment methods saved", + "noCardsDescription": "Payment methods are saved automatically when you make a purchase.", + "expires": "Expires", + "default": "Default", + "removing": "Removing...", + "secureInfo": "Your payment information is securely stored.", + "deleteConfirm": "Are you sure you want to remove this card?", + "cardMaskedLabel": "{label} ending in {digits}, expires {month}/{year}", + "removePaymentMethodTitle": "Remove payment method?", + "endingIn": "ending in", + "cardEndingIn": "{label} ending in {digits}", + "cardExpires": "Expires {month}/{year}", + "remove": "Remove", + "cancel": "Cancel", + "confirmRemoveDescription": "This will remove the {label} ending in {digits} from your account. This action cannot be undone.", + "failedToRemove": "Failed to remove card. Please try again." + }, + "giftCards": { + "giftCards": "Gift Cards", + "noGiftCards": "No gift cards", + "noGiftCardsDescription": "You don't have any gift cards yet.", + "active": "Active", + "partiallyUsed": "Partially Used", + "fullyRedeemed": "Fully Redeemed", + "canceled": "Canceled", + "expired": "Expired", + "noExpiration": "No expiration", + "remaining": "remaining", + "usedAmount": "Used: {amount}", + "totalAmountWithValue": "Total: {amount}", + "addedOnDate": "Added on {date}", + "fullyRedeemedOnDate": "Fully redeemed on {date}", + "activeGiftCards": "Active Gift Cards", + "expiredRedeemed": "Expired / Redeemed", + "copy": "Copy", + "copied": "Copied!", + "copyCodeToClipboard": "Copy code to clipboard", + "expiresOn": "Expires {date}", + "percentUsed": "{percent}% used", + "activeGiftCardsCount": "Active Gift Cards ({count})", + "expiredRedeemedCount": "Expired / Redeemed ({count})", + "giftCardsHelpText": "Gift cards can be used during checkout to pay for your orders. The remaining balance will be saved for future purchases." + }, + "forgotPassword": { + "title": "Reset your password", + "description": "Enter your email and we'll send you a link to reset your password.", + "email": "Email", + "sending": "Sending...", + "sendResetLink": "Send reset link", + "backToSignIn": "Back to sign in", + "checkYourEmail": "Check your email", + "resetEmailSent": "If an account exists for {email}, we've sent password reset instructions.", + "linkExpiry": "The link in the email will expire in 15 minutes. Check your spam folder if you don't see it.", + "tryDifferentEmail": "Try a different email", + "genericError": "Something went wrong. Please try again." + }, + "resetPassword": { + "title": "Set a new password", + "description": "Enter your new password below.", + "newPassword": "New password", + "confirmPassword": "Confirm new password", + "resetting": "Resetting...", + "resetPassword": "Reset password", + "backToSignIn": "Back to sign in", + "invalidLink": "Invalid link", + "invalidLinkDescription": "This password reset link is invalid or has expired.", + "requestNewLink": "Request a new reset link", + "passwordsDontMatch": "Passwords do not match", + "passwordTooShort": "Password must be at least 6 characters", + "linkExpired": "This reset link has expired. Please request a new one.", + "genericError": "Something went wrong. Please try again.", + "success": "Password reset successful", + "successDescription": "Your password has been updated. You can now sign in with your new password.", + "signIn": "Sign in" + }, + "policies": { + "policyNotFound": "Policy Not Found", + "noContent": "No content available for this policy.", + "shippingPolicy": "Shipping Policy", + "privacyPolicy": "Privacy Policy", + "returnsPolicy": "Returns Policy", + "termsOfService": "Terms of Service" + } +} diff --git a/messages/es.json b/messages/es.json new file mode 100644 index 00000000..876b61f8 --- /dev/null +++ b/messages/es.json @@ -0,0 +1,503 @@ +{ + "common": { + "subtotal": "Subtotal", + "shipping": "Envio", + "tax": "Impuestos", + "discount": "Descuento", + "total": "Total", + "adjustments": "Ajustes", + "edit": "Editar", + "remove": "Eliminar", + "cancel": "Cancelar", + "back": "Volver", + "continue": "Continuar", + "loading": "Cargando...", + "apply": "Aplicar", + "saving": "Guardando...", + "processing": "Procesando...", + "continueShopping": "Seguir comprando", + "orderSummary": "Resumen del pedido", + "selectCountry": "Seleccionar pais", + "myAccount": "Mi cuenta", + "giftCard": "Tarjeta regalo", + "storeCredit": "Credito de la tienda", + "amountDue": "Importe pendiente" + }, + "header": { + "categories": "Categorias", + "openCart": "Abrir carrito", + "account": "Cuenta", + "closeMenu": "Cerrar menu", + "openMenu": "Abrir menu", + "menu": "Menu", + "selectCountry": "Seleccionar pais", + "home": "Inicio", + "allProducts": "Todos los productos", + "contact": "Contacto", + "viewAllCategory": "Ver todo en {category}", + "openSearch": "Abrir busqueda", + "closeSearch": "Cerrar busqueda", + "myAccount": "Mi cuenta" + }, + "footer": { + "description": "Una tienda en linea moderna impulsada por Spree Commerce y Next.js.", + "shop": "Tienda", + "allProducts": "Todos los productos", + "categories": "Categorias", + "account": "Cuenta", + "myAccount": "Mi cuenta", + "orderHistory": "Historial de pedidos", + "cart": "Carrito", + "policies": "Politicas", + "poweredBy": "Impulsado por", + "copyright": "\u00a9 {year} {storeName}. Impulsado por Spree Commerce." + }, + "home": { + "welcome": "Bienvenido a {storeName}", + "heroDescription": "Descubre productos increibles con nuestra experiencia de comercio electronico moderna impulsada por Spree Commerce.", + "shopNow": "Comprar ahora", + "browseCategories": "Explorar categorias", + "featuredProducts": "Productos destacados", + "viewAll": "Ver todo", + "qualityProducts": "Productos de calidad", + "qualityDescription": "Una seleccion cuidadosamente curada de los mejores productos.", + "fastShipping": "Envio rapido", + "shippingDescription": "Entrega rapida y fiable hasta tu puerta.", + "support": "Soporte 24/7", + "supportDescription": "Nuestro equipo esta aqui para ayudarte en cualquier momento." + }, + "cart": { + "cart": "Carrito", + "closeCart": "Cerrar carrito", + "emptyCart": "Tu carrito esta vacio", + "emptyCartDescription": "Parece que aun no has agregado nada a tu carrito.", + "removeItem": "Eliminar articulo", + "decreaseQuantity": "Disminuir cantidad", + "increaseQuantity": "Aumentar cantidad", + "shippingNote": "Envio e impuestos calculados en el checkout", + "checkout": "Pagar", + "viewCart": "Ver carrito", + "shoppingCart": "Carrito de compras", + "proceedToCheckout": "Proceder al pago", + "itemCount": "({count, plural, one {# articulo} other {# articulos}})", + "shippingCalculatedAtCheckout": "Calculado en el checkout", + "removeItemLabel": "Eliminar {name}", + "giftCard": "Tarjeta regalo", + "storeCredit": "Credito de la tienda", + "amountDue": "Importe pendiente" + }, + "products": { + "allProducts": "Todos los productos", + "browseCollection": "Explora nuestra coleccion completa", + "searchResultsFor": "Resultados de busqueda para \"{query}\"", + "productsFound": "{count, plural, one {# producto} other {# productos}} encontrados", + "noProductsFound": "No se encontraron productos", + "tryAdjustingFilters": "Intenta ajustar tus filtros", + "noMatchingProducts": "No encontramos productos que coincidan con \"{query}\"", + "showingProducts": "Mostrando {shown} de {total} productos", + "loadingMore": "Cargando mas...", + "noMoreProducts": "No hay mas productos para cargar", + "filters": "Filtros", + "sortBy": "Ordenar por", + "resetFilters": "Restablecer filtros", + "price": "Precio", + "availability": "Disponibilidad", + "inStock": "En stock", + "outOfStock": "Agotado", + "min": "Min", + "max": "Max", + "range": "Rango: {min} - {max}", + "clear": "Limpiar", + "productCount": "{count, plural, one {# producto} other {# productos}}", + "sale": "Oferta", + "outOfStockVariant": "(Agotado)", + "addToCart": "Agregar al carrito", + "adding": "Agregando...", + "description": "Descripcion", + "details": "Detalles", + "sku": "SKU", + "options": "Opciones", + "search": "Buscar...", + "searching": "Buscando...", + "viewAllResultsFor": "Ver todos los resultados para \"{query}\"", + "manual": "Manual", + "bestSelling": "Mas vendidos", + "priceLowHigh": "Precio (menor a mayor)", + "priceHighLow": "Precio (mayor a menor)", + "newest": "Mas recientes", + "oldest": "Mas antiguos", + "nameAZ": "Nombre (A-Z)", + "nameZA": "Nombre (Z-A)", + "sortUnknown": "Otro", + "categories": "Categorias", + "noCategoriesFound": "No se encontraron categorias.", + "noCategoriesInGroup": "No hay categorias en este grupo.", + "subcategories": "{count, plural, one {# subcategoria} other {# subcategorias}}", + "noImageAvailable": "Imagen no disponible", + "clickToZoom": "Clic para ampliar", + "noProductsMatchingFilters": "No se encontraron productos que coincidan con tus filtros.", + "decreaseQuantity": "Disminuir cantidad", + "increaseQuantity": "Aumentar cantidad", + "carouselPrev": "Productos anteriores", + "carouselNext": "Productos siguientes", + "lightboxClose": "Cerrar visor", + "lightboxPrev": "Imagen anterior", + "lightboxNext": "Imagen siguiente", + "subcategoriesHeading": "Subcategorias", + "sort": "Ordenar", + "clearAll": "Limpiar todo", + "clearAllFiltersCount": "Limpiar todos los filtros ({count})", + "showResults": "Mostrar resultados", + "closeFilters": "Cerrar filtros", + "anyAvailability": "Cualquier disponibilidad", + "customPrice": "Personalizado", + "priceLabel": "Precio: {value}", + "clearFilter": "Limpiar {label}", + "openImageZoom": "Abrir zoom de imagen", + "priceUnder": "Menos de {price}", + "priceAbove": "{price}+", + "priceRangeBucket": "{min} \u2013 {max}", + "properties": "Propiedades", + "yes": "Si", + "no": "No", + "optionFilterChip": "{filter}: {value}" + }, + "navigation": { + "home": "Inicio", + "categories": "Categorias", + "breadcrumb": "Migas de pan" + }, + "checkout": { + "checkout": "Pagar", + "orderNumber": "Pedido #{number}", + "checkoutError": "Error en el pago", + "returnToCart": "Volver al carrito", + "emptyCart": "Tu carrito esta vacio", + "emptyCartDescription": "Agrega algunos articulos a tu carrito antes de pagar.", + "stepShipping": "Envio", + "stepDelivery": "Entrega", + "stepPayment": "Pago", + "contactInformation": "Informacion de contacto", + "emailAddress": "Correo electronico", + "emailPlaceholder": "tu@ejemplo.com", + "usingAccountEmail": "Usando el correo electronico de tu cuenta", + "signInPrompt": "\u00bfYa tienes una cuenta?", + "signIn": "Iniciar sesion", + "signInDescription": "para acceder a tus direcciones guardadas e historial de pedidos.", + "shippingAddress": "Direccion de envio", + "continueToDelivery": "Continuar a entrega", + "shippingMethod": "Metodo de envio", + "loadingShippingOptions": "Cargando opciones de envio...", + "enterShippingAddressForMethods": "Ingresa tu direccion de envio para ver los metodos de envio disponibles.", + "shipmentOf": "Envio {current} de {total}", + "shipsFrom": "Se envia desde {location}", + "phone": "Telefono: {phone}", + "billingAddress": "Direccion de facturacion", + "sameAsShipping": "Igual que la direccion de envio", + "paymentMethod": "Metodo de pago", + "loadingPaymentForm": "Cargando formulario de pago...", + "addNewPaymentMethod": "Agregar nuevo metodo de pago", + "endingIn": "terminada en", + "exp": "Venc.", + "savedCardLabel": "{brand} terminada en {digits}", + "cardExpiry": "Venc. {month}/{year}", + "default": "Predeterminado", + "noPaymentMethods": "No hay metodos de pago disponibles para este pedido.", + "secureTransactions": "Todas las transacciones son seguras y estan encriptadas.", + "creditCard": "Tarjeta de credito", + "enterShippingAddress": "Ingresa la direccion de envio", + "payNow": "Pagar ahora", + "failedToInitPayment": "Error al inicializar el pago. Por favor, intenta de nuevo.", + "failedToCreateSession": "Error al crear la sesion de pago.", + "paymentError": "Ocurrio un error durante el pago. Por favor, intenta de nuevo.", + "orderNotFound": "Pedido no encontrado o no tienes acceso.", + "failedToLoadCheckout": "Error al cargar el checkout. Por favor, intenta de nuevo.", + "orderNotFoundAfterPayment": "Pedido no encontrado despues del pago. Por favor, contacta a soporte.", + "failedToSaveAddress": "Error al guardar la direccion", + "failedToProceed": "Error al continuar al siguiente paso", + "generalError": "Ocurrio un error. Por favor, intenta de nuevo.", + "failedToSelectRate": "Error al seleccionar la tarifa de envio", + "failedToSaveBilling": "Error al guardar la direccion de facturacion", + "failedToSaveBillingRetry": "Error al guardar la direccion de facturacion. Por favor, intenta de nuevo.", + "failedToCompleteOrder": "Error al completar el pedido", + "stripeNotLoaded": "Stripe aun no se ha cargado", + "paymentProcessingError": "Ocurrio un error durante el pago.", + "failedToCompletePaymentSession": "Error al completar la sesion de pago", + "noOrder": "Sin pedido", + "confirmingPayment": "Confirmando tu pago...", + "policyConsentRequired": "Debes aceptar las politicas de la tienda antes de realizar tu pedido", + "iAgreeToThe": "Acepto las", + "policySeparatorComma": ", ", + "policySeparatorAnd": " y " + }, + "address": { + "firstName": "Nombre", + "lastName": "Apellido", + "company": "Empresa (opcional)", + "address": "Direccion", + "streetAddress": "Direccion", + "apartment": "Apartamento, suite, etc. (opcional)", + "city": "Ciudad", + "country": "Pais", + "selectCountry": "Selecciona un pais", + "stateProvince": "Estado / Provincia", + "selectState": "Selecciona un estado", + "stateOrProvince": "Estado o provincia", + "zipCode": "Codigo postal", + "phone": "Telefono (opcional)", + "selectAnAddress": "Selecciona una direccion", + "useDifferentAddress": "Usar una direccion diferente", + "editAddress": "Editar direccion", + "addNewAddress": "Agregar nueva direccion", + "saveAddress": "Guardar direccion", + "failedToSave": "Error al guardar la direccion", + "deleteAddressTitle": "\u00bfEliminar direccion?", + "deleteAddressConfirmation": "Esto eliminara permanentemente esta direccion. Esta accion no se puede deshacer.", + "delete": "Eliminar", + "deleting": "Eliminando...", + "cancel": "Cancelar", + "edit": "Editar", + "failedToDeleteAddress": "Error al eliminar la direccion. Por favor, intenta de nuevo.", + "addFirstAddress": "Agrega tu primera direccion", + "addAddress": "Agregar direccion" + }, + "account": { + "myAccount": "Mi cuenta", + "signInDescription": "Inicia sesion para acceder a tu cuenta e historial de pedidos.", + "email": "Correo electronico", + "password": "Contrasena", + "signIn": "Iniciar sesion", + "signingIn": "Iniciando sesion...", + "invalidCredentials": "Correo electronico o contrasena invalidos", + "dontHaveAccount": "\u00bfNo tienes una cuenta?", + "signUp": "Registrate", + "accountOverview": "Resumen de la cuenta", + "orderHistory": "Historial de pedidos", + "orderHistoryDescription": "Consulta tus pedidos anteriores y rastrea envios", + "addresses": "Direcciones", + "addressesDescription": "Administra tus direcciones de envio y facturacion", + "paymentMethods": "Metodos de pago", + "paymentMethodsDescription": "Administra tus tarjetas de credito guardadas", + "profile": "Perfil", + "profileDescription": "Actualiza tu informacion personal", + "signOut": "Cerrar sesion", + "overview": "Resumen", + "orders": "Pedidos", + "giftCards": "Tarjetas regalo", + "addNewAddress": "Agregar nueva direccion", + "noAddresses": "No hay direcciones guardadas", + "noAddressesDescription": "Las direcciones que agregues durante el checkout apareceran aqui.", + "defaultAddress": "Predeterminada", + "deleteConfirm": "\u00bfEstas seguro de que deseas eliminar esta direccion?", + "setAsDefault": "Establecer como predeterminada", + "showPassword": "Mostrar contrasena", + "hidePassword": "Ocultar contrasena", + "forgotPassword": "\u00bfOlvidaste tu contrasena?", + "policyConsentRequired": "Debes aceptar las politicas de la tienda para crear una cuenta" + }, + "register": { + "createAccount": "Crear cuenta", + "signUpDescription": "Registrate para empezar a comprar con nosotros.", + "confirmPassword": "Confirmar contrasena", + "creatingAccount": "Creando cuenta...", + "passwordsDontMatch": "Las contrasenas no coinciden", + "passwordTooShort": "La contrasena debe tener al menos 6 caracteres", + "registrationFailed": "Error en el registro. Por favor, intenta de nuevo.", + "alreadyHaveAccount": "\u00bfYa tienes una cuenta?", + "signIn": "Iniciar sesion", + "unexpectedError": "Ocurrio un error inesperado. Por favor, intenta de nuevo.", + "firstName": "Nombre", + "lastName": "Apellido", + "firstNamePlaceholder": "Juan", + "lastNamePlaceholder": "Garcia", + "emailPlaceholder": "tu@ejemplo.com" + }, + "orders": { + "orderHistory": "Historial de pedidos", + "noOrders": "Aun no hay pedidos", + "noOrdersDescription": "Cuando realices pedidos, apareceran aqui.", + "startShopping": "Empezar a comprar", + "order": "Pedido", + "date": "Fecha", + "status": "Estado", + "actions": "Acciones", + "view": "Ver", + "orderNotFound": "Pedido no encontrado", + "backToOrders": "Volver a pedidos", + "placedOn": "Realizado el {date}", + "orderItems": "Articulos del pedido", + "qty": "Cant.: {quantity}", + "orderAgain": "Pedir de nuevo", + "deliveryAddress": "Direccion de entrega", + "shippingMethod": "Metodo de envio", + "shippedFrom": "Enviado desde {location}", + "trackItems": "Rastrear articulos", + "shipmentCanceled": "Este envio ha sido cancelado.", + "noTrackingInfo": "No hay informacion de rastreo disponible.", + "paymentInformation": "Informacion de pago", + "cardEndingIn": "{label} terminada en {digits}", + "cardExpires": "Vence {month}/{year}", + "storeCreditApplied": "Aplicado {amount} \u2014 {remaining} restante", + "billingAddress": "Direccion de facturacion", + "orderNotFoundDescription": "El pedido que buscas no existe.", + "storeCredit": "Credito de la tienda", + "specialInstructions": "Instrucciones especiales", + "payment": "Pago", + "shipment": "Envio", + "shipped": "Enviado", + "partiallyShipped": "Parcialmente enviado", + "delivered": "Entregado", + "ready": "Listo", + "pending": "Pendiente", + "canceled": "Cancelado", + "backorder": "Pedido pendiente", + "paid": "Pagado", + "balanceDue": "Saldo pendiente", + "creditOwed": "Credito pendiente", + "failed": "Fallido", + "void": "Anulado", + "notAvailable": "N/D", + "unknownShipmentStatus": "Desconocido", + "orderTitle": "Pedido #{number}", + "shipmentCanceledRefund": "Envio cancelado \u2014 se ha emitido un reembolso.", + "shippingMethodUnavailable": "No disponible", + "totalColumn": "Total" + }, + "orderPlaced": { + "thanksForOrder": "\u00a1Gracias por tu pedido, {name}!", + "thanksForOrderAnonymous": "\u00a1Gracias por tu pedido!", + "orderNumber": "Pedido #{number}", + "emailConfirmation": "Recibiras una confirmacion por correo electronico en breve.", + "orderItems": "Articulos del pedido", + "contactInformation": "Informacion de contacto", + "shippingAddress": "Direccion de envio", + "billingAddress": "Direccion de facturacion", + "orderNotFound": "Pedido no encontrado", + "failedToLoad": "Error al cargar los detalles del pedido.", + "qty": "Cant.: {quantity}", + "shippingMethod": "Metodo de envio", + "standardShipping": "Envio estandar", + "payment": "Pago", + "giftCard": "Tarjeta regalo", + "confirmationSentTo": "Confirmacion enviada a" + }, + "coupon": { + "placeholder": "Tarjeta regalo o codigo de descuento", + "apply": "Aplicar", + "remove": "Eliminar", + "invalidCode": "Codigo de cupon invalido", + "applyFailed": "No se pudo aplicar el cupon. Por favor, intenta de nuevo.", + "failedToRemove": "Error al eliminar el codigo de cupon", + "failedToRemoveGiftCard": "Error al eliminar la tarjeta regalo", + "removeCoupon": "Eliminar cupon {code}", + "removeGiftCard": "Eliminar tarjeta regalo", + "giftCardCode": "Tarjeta regalo {code}", + "applying": "Aplicando...", + "removing": "Eliminando..." + }, + "checkoutLayout": { + "backToStore": "Volver a la tienda", + "showOrderSummary": "Mostrar resumen del pedido", + "hideOrderSummary": "Ocultar resumen del pedido", + "allRightsReserved": "\u00a9 {year} {storeName}. Todos los derechos reservados." + }, + "profile": { + "profile": "Perfil", + "firstName": "Nombre", + "lastName": "Apellido", + "emailAddress": "Correo electronico", + "saveChanges": "Guardar cambios", + "saving": "Guardando...", + "profileUpdated": "\u00a1Perfil actualizado con exito!", + "accountInformation": "Informacion de la cuenta", + "accountId": "ID de cuenta", + "loadingProfile": "Cargando perfil...", + "failedToUpdate": "Error al actualizar el perfil", + "email": "Correo electronico" + }, + "creditCards": { + "paymentMethods": "Metodos de pago", + "noCards": "No hay metodos de pago guardados", + "noCardsDescription": "Los metodos de pago se guardan automaticamente cuando realizas una compra.", + "expires": "Vence", + "default": "Predeterminado", + "removing": "Eliminando...", + "secureInfo": "Tu informacion de pago esta almacenada de forma segura.", + "deleteConfirm": "\u00bfEstas seguro de que deseas eliminar esta tarjeta?", + "cardMaskedLabel": "{label} terminada en {digits}, vence {month}/{year}", + "removePaymentMethodTitle": "\u00bfEliminar metodo de pago?", + "endingIn": "terminada en", + "cardEndingIn": "{label} terminada en {digits}", + "cardExpires": "Vence {month}/{year}", + "remove": "Eliminar", + "cancel": "Cancelar", + "confirmRemoveDescription": "Esto eliminara la {label} terminada en {digits} de tu cuenta. Esta accion no se puede deshacer.", + "failedToRemove": "Error al eliminar la tarjeta. Por favor, intenta de nuevo." + }, + "giftCards": { + "giftCards": "Tarjetas regalo", + "noGiftCards": "No hay tarjetas regalo", + "noGiftCardsDescription": "Aun no tienes tarjetas regalo.", + "active": "Activa", + "partiallyUsed": "Parcialmente usada", + "fullyRedeemed": "Totalmente canjeada", + "canceled": "Cancelada", + "expired": "Expirada", + "noExpiration": "Sin fecha de expiracion", + "remaining": "restante", + "usedAmount": "Usado: {amount}", + "totalAmountWithValue": "Total: {amount}", + "addedOnDate": "Agregada el {date}", + "fullyRedeemedOnDate": "Totalmente canjeada el {date}", + "activeGiftCards": "Tarjetas regalo activas", + "expiredRedeemed": "Expiradas / Canjeadas", + "copy": "Copiar", + "copied": "\u00a1Copiado!", + "copyCodeToClipboard": "Copiar codigo al portapapeles", + "expiresOn": "Expira el {date}", + "percentUsed": "{percent}% usado", + "activeGiftCardsCount": "Tarjetas regalo activas ({count})", + "expiredRedeemedCount": "Expiradas / Canjeadas ({count})", + "giftCardsHelpText": "Las tarjetas regalo se pueden usar durante el checkout para pagar tus pedidos. El saldo restante se guardara para futuras compras." + }, + "forgotPassword": { + "title": "Restablecer tu contrasena", + "description": "Ingresa tu correo electronico y te enviaremos un enlace para restablecer tu contrasena.", + "email": "Correo electronico", + "sending": "Enviando...", + "sendResetLink": "Enviar enlace de restablecimiento", + "backToSignIn": "Volver a iniciar sesion", + "checkYourEmail": "Revisa tu correo electronico", + "resetEmailSent": "Si existe una cuenta para {email}, hemos enviado instrucciones para restablecer la contrasena.", + "linkExpiry": "El enlace en el correo expirara en 15 minutos. Revisa tu carpeta de spam si no lo encuentras.", + "tryDifferentEmail": "Probar con otro correo electronico", + "genericError": "Algo salio mal. Por favor, intenta de nuevo." + }, + "resetPassword": { + "title": "Establecer una nueva contrasena", + "description": "Ingresa tu nueva contrasena a continuacion.", + "newPassword": "Nueva contrasena", + "confirmPassword": "Confirmar nueva contrasena", + "resetting": "Restableciendo...", + "resetPassword": "Restablecer contrasena", + "backToSignIn": "Volver a iniciar sesion", + "invalidLink": "Enlace invalido", + "invalidLinkDescription": "Este enlace de restablecimiento de contrasena es invalido o ha expirado.", + "requestNewLink": "Solicitar un nuevo enlace de restablecimiento", + "passwordsDontMatch": "Las contrasenas no coinciden", + "passwordTooShort": "La contrasena debe tener al menos 6 caracteres", + "linkExpired": "Este enlace de restablecimiento ha expirado. Por favor, solicita uno nuevo.", + "genericError": "Algo salio mal. Por favor, intenta de nuevo.", + "success": "Contrasena restablecida con exito", + "successDescription": "Tu contrasena ha sido actualizada. Ahora puedes iniciar sesion con tu nueva contrasena.", + "signIn": "Iniciar sesion" + }, + "policies": { + "policyNotFound": "Politica no encontrada", + "noContent": "No hay contenido disponible para esta politica.", + "shippingPolicy": "Politica de envio", + "privacyPolicy": "Politica de privacidad", + "returnsPolicy": "Politica de devoluciones", + "termsOfService": "Terminos de servicio" + } +} diff --git a/messages/fr.json b/messages/fr.json new file mode 100644 index 00000000..3d315c95 --- /dev/null +++ b/messages/fr.json @@ -0,0 +1,503 @@ +{ + "common": { + "subtotal": "Sous-total", + "shipping": "Livraison", + "tax": "Taxes", + "discount": "Remise", + "total": "Total", + "adjustments": "Ajustements", + "edit": "Modifier", + "remove": "Supprimer", + "cancel": "Annuler", + "back": "Retour", + "continue": "Continuer", + "loading": "Chargement...", + "apply": "Appliquer", + "saving": "Enregistrement...", + "processing": "Traitement...", + "continueShopping": "Continuer les achats", + "orderSummary": "Recapitulatif de la commande", + "selectCountry": "Choisir le pays", + "myAccount": "Mon compte", + "giftCard": "Carte cadeau", + "storeCredit": "Credit boutique", + "amountDue": "Montant du" + }, + "header": { + "categories": "Categories", + "openCart": "Ouvrir le panier", + "account": "Compte", + "closeMenu": "Fermer le menu", + "openMenu": "Ouvrir le menu", + "menu": "Menu", + "selectCountry": "Choisir le pays", + "home": "Accueil", + "allProducts": "Tous les produits", + "contact": "Contact", + "viewAllCategory": "Voir tout dans {category}", + "openSearch": "Ouvrir la recherche", + "closeSearch": "Fermer la recherche", + "myAccount": "Mon compte" + }, + "footer": { + "description": "Une boutique en ligne moderne propulsee par Spree Commerce et Next.js.", + "shop": "Boutique", + "allProducts": "Tous les produits", + "categories": "Categories", + "account": "Compte", + "myAccount": "Mon compte", + "orderHistory": "Historique des commandes", + "cart": "Panier", + "policies": "Politiques", + "poweredBy": "Propulse par", + "copyright": "\u00a9 {year} {storeName}. Propulse par Spree Commerce." + }, + "home": { + "welcome": "Bienvenue chez {storeName}", + "heroDescription": "Decouvrez des produits exceptionnels avec notre experience e-commerce moderne propulsee par Spree Commerce.", + "shopNow": "Acheter maintenant", + "browseCategories": "Parcourir les categories", + "featuredProducts": "Produits en vedette", + "viewAll": "Voir tout", + "qualityProducts": "Produits de qualite", + "qualityDescription": "Une selection soigneusement curee des meilleurs produits.", + "fastShipping": "Livraison rapide", + "shippingDescription": "Livraison rapide et fiable jusqu'a votre porte.", + "support": "Support 24/7", + "supportDescription": "Notre equipe est la pour vous aider a tout moment." + }, + "cart": { + "cart": "Panier", + "closeCart": "Fermer le panier", + "emptyCart": "Votre panier est vide", + "emptyCartDescription": "Il semble que vous n'ayez encore rien ajoute a votre panier.", + "removeItem": "Supprimer l'article", + "decreaseQuantity": "Diminuer la quantite", + "increaseQuantity": "Augmenter la quantite", + "shippingNote": "Livraison et taxes calculees lors du paiement", + "checkout": "Passer la commande", + "viewCart": "Voir le panier", + "shoppingCart": "Panier d'achat", + "proceedToCheckout": "Passer au paiement", + "itemCount": "({count, plural, one {# article} other {# articles}})", + "shippingCalculatedAtCheckout": "Calcule lors du paiement", + "removeItemLabel": "Supprimer {name}", + "giftCard": "Carte cadeau", + "storeCredit": "Credit boutique", + "amountDue": "Montant du" + }, + "products": { + "allProducts": "Tous les produits", + "browseCollection": "Parcourez notre collection complete", + "searchResultsFor": "Resultats de recherche pour \"{query}\"", + "productsFound": "{count, plural, one {# produit} other {# produits}} trouve(s)", + "noProductsFound": "Aucun produit trouve", + "tryAdjustingFilters": "Essayez d'ajuster vos filtres", + "noMatchingProducts": "Nous n'avons trouve aucun produit correspondant a \"{query}\"", + "showingProducts": "Affichage de {shown} sur {total} produits", + "loadingMore": "Chargement...", + "noMoreProducts": "Plus de produits a charger", + "filters": "Filtres", + "sortBy": "Trier par", + "resetFilters": "Reinitialiser les filtres", + "price": "Prix", + "availability": "Disponibilite", + "inStock": "En stock", + "outOfStock": "Rupture de stock", + "min": "Min", + "max": "Max", + "range": "Fourchette : {min} - {max}", + "clear": "Effacer", + "productCount": "{count, plural, one {# produit} other {# produits}}", + "sale": "Promo", + "outOfStockVariant": "(Rupture de stock)", + "addToCart": "Ajouter au panier", + "adding": "Ajout...", + "description": "Description", + "details": "Details", + "sku": "UGS", + "options": "Options", + "search": "Rechercher...", + "searching": "Recherche...", + "viewAllResultsFor": "Voir tous les resultats pour \"{query}\"", + "manual": "Manuel", + "bestSelling": "Meilleures ventes", + "priceLowHigh": "Prix (croissant)", + "priceHighLow": "Prix (decroissant)", + "newest": "Plus recents", + "oldest": "Plus anciens", + "nameAZ": "Nom (A-Z)", + "nameZA": "Nom (Z-A)", + "sortUnknown": "Autre", + "categories": "Categories", + "noCategoriesFound": "Aucune categorie trouvee.", + "noCategoriesInGroup": "Aucune categorie dans ce groupe.", + "subcategories": "{count, plural, one {# sous-categorie} other {# sous-categories}}", + "noImageAvailable": "Aucune image disponible", + "clickToZoom": "Cliquez pour agrandir", + "noProductsMatchingFilters": "Aucun produit ne correspond a vos filtres.", + "decreaseQuantity": "Diminuer la quantite", + "increaseQuantity": "Augmenter la quantite", + "carouselPrev": "Produits precedents", + "carouselNext": "Produits suivants", + "lightboxClose": "Fermer la visionneuse", + "lightboxPrev": "Image precedente", + "lightboxNext": "Image suivante", + "subcategoriesHeading": "Sous-categories", + "sort": "Trier", + "clearAll": "Tout effacer", + "clearAllFiltersCount": "Effacer tous les filtres ({count})", + "showResults": "Afficher les resultats", + "closeFilters": "Fermer les filtres", + "anyAvailability": "Toute disponibilite", + "customPrice": "Personnalise", + "priceLabel": "Prix : {value}", + "clearFilter": "Effacer {label}", + "openImageZoom": "Ouvrir le zoom de l'image", + "priceUnder": "Moins de {price}", + "priceAbove": "{price}+", + "priceRangeBucket": "{min} \u2013 {max}", + "properties": "Proprietes", + "yes": "Oui", + "no": "Non", + "optionFilterChip": "{filter} : {value}" + }, + "navigation": { + "home": "Accueil", + "categories": "Categories", + "breadcrumb": "Fil d'Ariane" + }, + "checkout": { + "checkout": "Paiement", + "orderNumber": "Commande #{number}", + "checkoutError": "Erreur de paiement", + "returnToCart": "Retourner au panier", + "emptyCart": "Votre panier est vide", + "emptyCartDescription": "Ajoutez des articles a votre panier avant de passer commande.", + "stepShipping": "Expedition", + "stepDelivery": "Livraison", + "stepPayment": "Paiement", + "contactInformation": "Coordonnees", + "emailAddress": "Adresse e-mail", + "emailPlaceholder": "vous@exemple.com", + "usingAccountEmail": "Utilisation de l'adresse e-mail de votre compte", + "signInPrompt": "Vous avez deja un compte ?", + "signIn": "Se connecter", + "signInDescription": "pour acceder a vos adresses enregistrees et a votre historique de commandes.", + "shippingAddress": "Adresse de livraison", + "continueToDelivery": "Continuer vers la livraison", + "shippingMethod": "Mode de livraison", + "loadingShippingOptions": "Chargement des options de livraison...", + "enterShippingAddressForMethods": "Entrez votre adresse de livraison pour voir les modes de livraison disponibles.", + "shipmentOf": "Expedition {current} sur {total}", + "shipsFrom": "Expedie depuis {location}", + "phone": "Telephone : {phone}", + "billingAddress": "Adresse de facturation", + "sameAsShipping": "Identique a l'adresse de livraison", + "paymentMethod": "Mode de paiement", + "loadingPaymentForm": "Chargement du formulaire de paiement...", + "addNewPaymentMethod": "Ajouter un nouveau mode de paiement", + "endingIn": "se terminant par", + "exp": "Exp.", + "savedCardLabel": "{brand} se terminant par {digits}", + "cardExpiry": "Exp. {month}/{year}", + "default": "Par defaut", + "noPaymentMethods": "Aucun mode de paiement disponible pour cette commande.", + "secureTransactions": "Toutes les transactions sont securisees et chiffrees.", + "creditCard": "Carte de credit", + "enterShippingAddress": "Entrer l'adresse de livraison", + "payNow": "Payer maintenant", + "failedToInitPayment": "Echec de l'initialisation du paiement. Veuillez reessayer.", + "failedToCreateSession": "Echec de la creation de la session de paiement.", + "paymentError": "Une erreur est survenue lors du paiement. Veuillez reessayer.", + "orderNotFound": "Commande introuvable ou vous n'y avez pas acces.", + "failedToLoadCheckout": "Echec du chargement du paiement. Veuillez reessayer.", + "orderNotFoundAfterPayment": "Commande introuvable apres le paiement. Veuillez contacter le support.", + "failedToSaveAddress": "Echec de l'enregistrement de l'adresse", + "failedToProceed": "Echec du passage a l'etape suivante", + "generalError": "Une erreur est survenue. Veuillez reessayer.", + "failedToSelectRate": "Echec de la selection du tarif de livraison", + "failedToSaveBilling": "Echec de l'enregistrement de l'adresse de facturation", + "failedToSaveBillingRetry": "Echec de l'enregistrement de l'adresse de facturation. Veuillez reessayer.", + "failedToCompleteOrder": "Echec de la finalisation de la commande", + "stripeNotLoaded": "Stripe n'est pas encore charge", + "paymentProcessingError": "Une erreur est survenue lors du paiement.", + "failedToCompletePaymentSession": "Echec de la finalisation de la session de paiement", + "noOrder": "Aucune commande", + "confirmingPayment": "Confirmation de votre paiement...", + "policyConsentRequired": "Vous devez accepter les politiques du magasin avant de passer votre commande", + "iAgreeToThe": "J'accepte les", + "policySeparatorComma": ", ", + "policySeparatorAnd": " et " + }, + "address": { + "firstName": "Prenom", + "lastName": "Nom", + "company": "Entreprise (facultatif)", + "address": "Adresse", + "streetAddress": "Adresse", + "apartment": "Appartement, suite, etc. (facultatif)", + "city": "Ville", + "country": "Pays", + "selectCountry": "Choisir un pays", + "stateProvince": "Etat / Province", + "selectState": "Choisir un etat", + "stateOrProvince": "Etat ou province", + "zipCode": "Code postal", + "phone": "Telephone (facultatif)", + "selectAnAddress": "Choisir une adresse", + "useDifferentAddress": "Utiliser une adresse differente", + "editAddress": "Modifier l'adresse", + "addNewAddress": "Ajouter une nouvelle adresse", + "saveAddress": "Enregistrer l'adresse", + "failedToSave": "Echec de l'enregistrement de l'adresse", + "deleteAddressTitle": "Supprimer l'adresse ?", + "deleteAddressConfirmation": "Cela supprimera definitivement cette adresse. Cette action est irreversible.", + "delete": "Supprimer", + "deleting": "Suppression...", + "cancel": "Annuler", + "edit": "Modifier", + "failedToDeleteAddress": "Echec de la suppression de l'adresse. Veuillez reessayer.", + "addFirstAddress": "Ajoutez votre premiere adresse", + "addAddress": "Ajouter une adresse" + }, + "account": { + "myAccount": "Mon compte", + "signInDescription": "Connectez-vous pour acceder a votre compte et a votre historique de commandes.", + "email": "E-mail", + "password": "Mot de passe", + "signIn": "Se connecter", + "signingIn": "Connexion...", + "invalidCredentials": "E-mail ou mot de passe invalide", + "dontHaveAccount": "Vous n'avez pas de compte ?", + "signUp": "S'inscrire", + "accountOverview": "Apercu du compte", + "orderHistory": "Historique des commandes", + "orderHistoryDescription": "Consultez vos commandes passees et suivez vos expeditions", + "addresses": "Adresses", + "addressesDescription": "Gerez vos adresses de livraison et de facturation", + "paymentMethods": "Modes de paiement", + "paymentMethodsDescription": "Gerez vos cartes de credit enregistrees", + "profile": "Profil", + "profileDescription": "Mettez a jour vos informations personnelles", + "signOut": "Se deconnecter", + "overview": "Apercu", + "orders": "Commandes", + "giftCards": "Cartes cadeaux", + "addNewAddress": "Ajouter une nouvelle adresse", + "noAddresses": "Aucune adresse enregistree", + "noAddressesDescription": "Les adresses que vous ajoutez lors du paiement apparaitront ici.", + "defaultAddress": "Par defaut", + "deleteConfirm": "Etes-vous sur de vouloir supprimer cette adresse ?", + "setAsDefault": "Definir par defaut", + "showPassword": "Afficher le mot de passe", + "hidePassword": "Masquer le mot de passe", + "forgotPassword": "Mot de passe oublie ?", + "policyConsentRequired": "Vous devez accepter les politiques du magasin pour creer un compte" + }, + "register": { + "createAccount": "Creer un compte", + "signUpDescription": "Inscrivez-vous pour commencer a faire vos achats chez nous.", + "confirmPassword": "Confirmer le mot de passe", + "creatingAccount": "Creation du compte...", + "passwordsDontMatch": "Les mots de passe ne correspondent pas", + "passwordTooShort": "Le mot de passe doit contenir au moins 6 caracteres", + "registrationFailed": "Echec de l'inscription. Veuillez reessayer.", + "alreadyHaveAccount": "Vous avez deja un compte ?", + "signIn": "Se connecter", + "unexpectedError": "Une erreur inattendue est survenue. Veuillez reessayer.", + "firstName": "Prenom", + "lastName": "Nom", + "firstNamePlaceholder": "Jean", + "lastNamePlaceholder": "Dupont", + "emailPlaceholder": "vous@exemple.com" + }, + "orders": { + "orderHistory": "Historique des commandes", + "noOrders": "Aucune commande", + "noOrdersDescription": "Lorsque vous passerez des commandes, elles apparaitront ici.", + "startShopping": "Commencer les achats", + "order": "Commande", + "date": "Date", + "status": "Statut", + "actions": "Actions", + "view": "Voir", + "orderNotFound": "Commande introuvable", + "backToOrders": "Retour aux commandes", + "placedOn": "Passee le {date}", + "orderItems": "Articles de la commande", + "qty": "Qte : {quantity}", + "orderAgain": "Commander a nouveau", + "deliveryAddress": "Adresse de livraison", + "shippingMethod": "Mode de livraison", + "shippedFrom": "Expedie depuis {location}", + "trackItems": "Suivre les articles", + "shipmentCanceled": "Cette expedition a ete annulee.", + "noTrackingInfo": "Aucune information de suivi disponible.", + "paymentInformation": "Informations de paiement", + "cardEndingIn": "{label} se terminant par {digits}", + "cardExpires": "Expire le {month}/{year}", + "storeCreditApplied": "{amount} applique \u2014 {remaining} restant", + "billingAddress": "Adresse de facturation", + "orderNotFoundDescription": "La commande que vous recherchez n'existe pas.", + "storeCredit": "Credit boutique", + "specialInstructions": "Instructions speciales", + "payment": "Paiement", + "shipment": "Expedition", + "shipped": "Expedie", + "partiallyShipped": "Partiellement expedie", + "delivered": "Livre", + "ready": "Pret", + "pending": "En attente", + "canceled": "Annule", + "backorder": "En commande", + "paid": "Paye", + "balanceDue": "Solde du", + "creditOwed": "Credit du", + "failed": "Echoue", + "void": "Annule", + "notAvailable": "N/D", + "unknownShipmentStatus": "Inconnu", + "orderTitle": "Commande #{number}", + "shipmentCanceledRefund": "Expedition annulee \u2014 un remboursement a ete emis.", + "shippingMethodUnavailable": "Indisponible", + "totalColumn": "Total" + }, + "orderPlaced": { + "thanksForOrder": "Merci pour votre commande, {name} !", + "thanksForOrderAnonymous": "Merci pour votre commande !", + "orderNumber": "Commande #{number}", + "emailConfirmation": "Vous recevrez une confirmation par e-mail sous peu.", + "orderItems": "Articles de la commande", + "contactInformation": "Coordonnees", + "shippingAddress": "Adresse de livraison", + "billingAddress": "Adresse de facturation", + "orderNotFound": "Commande introuvable", + "failedToLoad": "Echec du chargement des details de la commande.", + "qty": "Qte : {quantity}", + "shippingMethod": "Mode de livraison", + "standardShipping": "Livraison standard", + "payment": "Paiement", + "giftCard": "Carte cadeau", + "confirmationSentTo": "Confirmation envoyee a" + }, + "coupon": { + "placeholder": "Carte cadeau ou code de reduction", + "apply": "Appliquer", + "remove": "Supprimer", + "invalidCode": "Code de coupon invalide", + "applyFailed": "Impossible d'appliquer le coupon. Veuillez reessayer.", + "failedToRemove": "Echec de la suppression du code de coupon", + "failedToRemoveGiftCard": "Echec de la suppression de la carte cadeau", + "removeCoupon": "Supprimer le coupon {code}", + "removeGiftCard": "Supprimer la carte cadeau", + "giftCardCode": "Carte cadeau {code}", + "applying": "Application...", + "removing": "Suppression..." + }, + "checkoutLayout": { + "backToStore": "Retour a la boutique", + "showOrderSummary": "Afficher le recapitulatif de la commande", + "hideOrderSummary": "Masquer le recapitulatif de la commande", + "allRightsReserved": "\u00a9 {year} {storeName}. Tous droits reserves." + }, + "profile": { + "profile": "Profil", + "firstName": "Prenom", + "lastName": "Nom", + "emailAddress": "Adresse e-mail", + "saveChanges": "Enregistrer les modifications", + "saving": "Enregistrement...", + "profileUpdated": "Profil mis a jour avec succes !", + "accountInformation": "Informations du compte", + "accountId": "ID du compte", + "loadingProfile": "Chargement du profil...", + "failedToUpdate": "Echec de la mise a jour du profil", + "email": "E-mail" + }, + "creditCards": { + "paymentMethods": "Modes de paiement", + "noCards": "Aucun mode de paiement enregistre", + "noCardsDescription": "Les modes de paiement sont enregistres automatiquement lorsque vous effectuez un achat.", + "expires": "Expire", + "default": "Par defaut", + "removing": "Suppression...", + "secureInfo": "Vos informations de paiement sont stockees en toute securite.", + "deleteConfirm": "Etes-vous sur de vouloir supprimer cette carte ?", + "cardMaskedLabel": "{label} se terminant par {digits}, expire {month}/{year}", + "removePaymentMethodTitle": "Supprimer le mode de paiement ?", + "endingIn": "se terminant par", + "cardEndingIn": "{label} se terminant par {digits}", + "cardExpires": "Expire {month}/{year}", + "remove": "Supprimer", + "cancel": "Annuler", + "confirmRemoveDescription": "Cela supprimera la {label} se terminant par {digits} de votre compte. Cette action est irreversible.", + "failedToRemove": "Echec de la suppression de la carte. Veuillez reessayer." + }, + "giftCards": { + "giftCards": "Cartes cadeaux", + "noGiftCards": "Aucune carte cadeau", + "noGiftCardsDescription": "Vous n'avez pas encore de cartes cadeaux.", + "active": "Active", + "partiallyUsed": "Partiellement utilisee", + "fullyRedeemed": "Entierement utilisee", + "canceled": "Annulee", + "expired": "Expiree", + "noExpiration": "Sans date d'expiration", + "remaining": "restant", + "usedAmount": "Utilise : {amount}", + "totalAmountWithValue": "Total : {amount}", + "addedOnDate": "Ajoutee le {date}", + "fullyRedeemedOnDate": "Entierement utilisee le {date}", + "activeGiftCards": "Cartes cadeaux actives", + "expiredRedeemed": "Expirees / Utilisees", + "copy": "Copier", + "copied": "Copie !", + "copyCodeToClipboard": "Copier le code dans le presse-papiers", + "expiresOn": "Expire le {date}", + "percentUsed": "{percent}% utilise", + "activeGiftCardsCount": "Cartes cadeaux actives ({count})", + "expiredRedeemedCount": "Expirees / Utilisees ({count})", + "giftCardsHelpText": "Les cartes cadeaux peuvent etre utilisees lors du paiement pour regler vos commandes. Le solde restant sera conserve pour de futurs achats." + }, + "forgotPassword": { + "title": "Reinitialiser votre mot de passe", + "description": "Entrez votre e-mail et nous vous enverrons un lien pour reinitialiser votre mot de passe.", + "email": "E-mail", + "sending": "Envoi...", + "sendResetLink": "Envoyer le lien de reinitialisation", + "backToSignIn": "Retour a la connexion", + "checkYourEmail": "Consultez votre e-mail", + "resetEmailSent": "Si un compte existe pour {email}, nous avons envoye les instructions de reinitialisation du mot de passe.", + "linkExpiry": "Le lien dans l'e-mail expirera dans 15 minutes. Verifiez votre dossier spam si vous ne le trouvez pas.", + "tryDifferentEmail": "Essayer un autre e-mail", + "genericError": "Une erreur est survenue. Veuillez reessayer." + }, + "resetPassword": { + "title": "Definir un nouveau mot de passe", + "description": "Entrez votre nouveau mot de passe ci-dessous.", + "newPassword": "Nouveau mot de passe", + "confirmPassword": "Confirmer le nouveau mot de passe", + "resetting": "Reinitialisation...", + "resetPassword": "Reinitialiser le mot de passe", + "backToSignIn": "Retour a la connexion", + "invalidLink": "Lien invalide", + "invalidLinkDescription": "Ce lien de reinitialisation de mot de passe est invalide ou a expire.", + "requestNewLink": "Demander un nouveau lien de reinitialisation", + "passwordsDontMatch": "Les mots de passe ne correspondent pas", + "passwordTooShort": "Le mot de passe doit contenir au moins 6 caracteres", + "linkExpired": "Ce lien de reinitialisation a expire. Veuillez en demander un nouveau.", + "genericError": "Une erreur est survenue. Veuillez reessayer.", + "success": "Mot de passe reinitialise avec succes", + "successDescription": "Votre mot de passe a ete mis a jour. Vous pouvez maintenant vous connecter avec votre nouveau mot de passe.", + "signIn": "Se connecter" + }, + "policies": { + "policyNotFound": "Politique introuvable", + "noContent": "Aucun contenu disponible pour cette politique.", + "shippingPolicy": "Politique de livraison", + "privacyPolicy": "Politique de confidentialite", + "returnsPolicy": "Politique de retour", + "termsOfService": "Conditions generales" + } +} diff --git a/messages/pl.json b/messages/pl.json new file mode 100644 index 00000000..4cdd449c --- /dev/null +++ b/messages/pl.json @@ -0,0 +1,503 @@ +{ + "common": { + "subtotal": "Suma częściowa", + "shipping": "Wysyłka", + "tax": "Podatek", + "discount": "Rabat", + "total": "Suma", + "adjustments": "Korekty", + "edit": "Edytuj", + "remove": "Usuń", + "cancel": "Anuluj", + "back": "Wstecz", + "continue": "Kontynuuj", + "loading": "Ładowanie...", + "apply": "Zastosuj", + "saving": "Zapisywanie...", + "processing": "Przetwarzanie...", + "continueShopping": "Kontynuuj zakupy", + "orderSummary": "Podsumowanie zamówienia", + "selectCountry": "Wybierz kraj", + "myAccount": "Moje konto", + "giftCard": "Karta podarunkowa", + "storeCredit": "Kredyt sklepowy", + "amountDue": "Do zapłaty" + }, + "header": { + "categories": "Kategorie", + "openCart": "Otwórz koszyk", + "account": "Konto", + "closeMenu": "Zamknij menu", + "openMenu": "Otwórz menu", + "menu": "Menu", + "selectCountry": "Wybierz kraj", + "home": "Strona główna", + "allProducts": "Wszystkie produkty", + "contact": "Kontakt", + "viewAllCategory": "Zobacz wszystkie {category}", + "openSearch": "Otwórz wyszukiwanie", + "closeSearch": "Zamknij wyszukiwanie", + "myAccount": "Moje konto" + }, + "footer": { + "description": "Nowoczesny sklep internetowy oparty na Spree Commerce i Next.js.", + "shop": "Sklep", + "allProducts": "Wszystkie produkty", + "categories": "Kategorie", + "account": "Konto", + "myAccount": "Moje konto", + "orderHistory": "Historia zamówień", + "cart": "Koszyk", + "policies": "Regulaminy", + "poweredBy": "Zasilany przez", + "copyright": "© {year} {storeName}. Zasilany przez Spree Commerce." + }, + "home": { + "welcome": "Witaj w {storeName}", + "heroDescription": "Odkryj niesamowite produkty dzięki nowoczesnemu sklepowi internetowemu opartemu na Spree Commerce.", + "shopNow": "Kup teraz", + "browseCategories": "Przeglądaj kategorie", + "featuredProducts": "Polecane produkty", + "viewAll": "Zobacz wszystkie", + "qualityProducts": "Produkty wysokiej jakości", + "qualityDescription": "Starannie wyselekcjonowana kolekcja najlepszych produktów.", + "fastShipping": "Szybka wysyłka", + "shippingDescription": "Szybka i niezawodna dostawa pod Twoje drzwi.", + "support": "Wsparcie 24/7", + "supportDescription": "Nasz zespół jest do Twojej dyspozycji o każdej porze." + }, + "cart": { + "cart": "Koszyk", + "closeCart": "Zamknij koszyk", + "emptyCart": "Twój koszyk jest pusty", + "emptyCartDescription": "Wygląda na to, że nie dodałeś jeszcze nic do koszyka.", + "removeItem": "Usuń produkt", + "decreaseQuantity": "Zmniejsz ilość", + "increaseQuantity": "Zwiększ ilość", + "shippingNote": "Koszty wysyłki i podatki zostaną obliczone przy kasie", + "checkout": "Do kasy", + "viewCart": "Zobacz koszyk", + "shoppingCart": "Koszyk zakupowy", + "proceedToCheckout": "Przejdź do kasy", + "itemCount": "({count, plural, one {# produkt} few {# produkty} many {# produktów} other {# produktów}})", + "shippingCalculatedAtCheckout": "Obliczona przy kasie", + "removeItemLabel": "Usuń {name}", + "giftCard": "Karta podarunkowa", + "storeCredit": "Kredyt sklepowy", + "amountDue": "Do zapłaty" + }, + "products": { + "allProducts": "Wszystkie produkty", + "browseCollection": "Przeglądaj naszą pełną kolekcję", + "searchResultsFor": "Wyniki wyszukiwania dla \"{query}\"", + "productsFound": "{count, plural, one {# produkt} few {# produkty} other {# produktów}}", + "noProductsFound": "Nie znaleziono produktów", + "tryAdjustingFilters": "Spróbuj zmienić filtry", + "noMatchingProducts": "Nie znaleźliśmy produktów pasujących do \"{query}\"", + "showingProducts": "Wyświetlanie {shown} z {total} produktów", + "loadingMore": "Ładowanie kolejnych...", + "noMoreProducts": "Brak kolejnych produktów", + "filters": "Filtry", + "sortBy": "Sortuj wg", + "resetFilters": "Resetuj filtry", + "price": "Cena", + "availability": "Dostępność", + "inStock": "Dostępny", + "outOfStock": "Niedostępny", + "min": "Min", + "max": "Maks", + "range": "Zakres: {min} - {max}", + "clear": "Wyczyść", + "productCount": "{count, plural, =0 {0 produktów} one {# produkt} few {# produkty} many {# produktów} other {# produktów}}", + "sale": "Wyprzedaż", + "outOfStockVariant": "(Niedostępny)", + "addToCart": "Dodaj do koszyka", + "adding": "Dodawanie...", + "description": "Opis", + "details": "Szczegóły", + "sku": "SKU", + "options": "Opcje", + "search": "Szukaj...", + "searching": "Wyszukiwanie...", + "viewAllResultsFor": "Zobacz wszystkie wyniki dla \"{query}\"", + "manual": "Ręcznie", + "bestSelling": "Bestsellery", + "priceLowHigh": "Cena (rosnąco)", + "priceHighLow": "Cena (malejąco)", + "newest": "Najnowsze", + "oldest": "Najstarsze", + "nameAZ": "Nazwa (A-Z)", + "nameZA": "Nazwa (Z-A)", + "sortUnknown": "Inne", + "categories": "Kategorie", + "noCategoriesFound": "Nie znaleziono kategorii.", + "noCategoriesInGroup": "Brak kategorii w tej grupie.", + "subcategories": "{count, plural, =0 {0 podkategorii} one {# podkategoria} few {# podkategorie} many {# podkategorii} other {# podkategorii}}", + "noImageAvailable": "Brak zdjęcia", + "clickToZoom": "Kliknij aby powiększyć", + "noProductsMatchingFilters": "Nie znaleziono produktów pasujących do wybranych filtrów.", + "decreaseQuantity": "Zmniejsz ilość", + "increaseQuantity": "Zwiększ ilość", + "carouselPrev": "Poprzednie produkty", + "carouselNext": "Następne produkty", + "lightboxClose": "Zamknij podgląd", + "lightboxPrev": "Poprzednie zdjęcie", + "lightboxNext": "Następne zdjęcie", + "subcategoriesHeading": "Podkategorie", + "sort": "Sortuj", + "clearAll": "Wyczyść wszystko", + "clearAllFiltersCount": "Wyczyść wszystkie filtry ({count})", + "showResults": "Pokaż wyniki", + "closeFilters": "Zamknij filtry", + "anyAvailability": "Dowolna dostępność", + "customPrice": "Niestandardowa", + "priceLabel": "Cena: {value}", + "clearFilter": "Wyczyść {label}", + "openImageZoom": "Otwórz powiększenie zdjęcia", + "priceUnder": "Poniżej {price}", + "priceAbove": "{price}+", + "priceRangeBucket": "{min} – {max}", + "properties": "Właściwości", + "yes": "Tak", + "no": "Nie", + "optionFilterChip": "{filter}: {value}" + }, + "navigation": { + "home": "Strona główna", + "categories": "Kategorie", + "breadcrumb": "Ścieżka nawigacji" + }, + "checkout": { + "checkout": "Kasa", + "orderNumber": "Zamówienie #{number}", + "checkoutError": "Błąd kasy", + "returnToCart": "Wróć do koszyka", + "emptyCart": "Twój koszyk jest pusty", + "emptyCartDescription": "Dodaj produkty do koszyka przed złożeniem zamówienia.", + "stepShipping": "Wysyłka", + "stepDelivery": "Dostawa", + "stepPayment": "Płatność", + "contactInformation": "Dane kontaktowe", + "emailAddress": "Adres e-mail", + "emailPlaceholder": "ty@przyklad.pl", + "usingAccountEmail": "Używasz adresu e-mail Twojego konta", + "signInPrompt": "Masz już konto?", + "signIn": "Zaloguj się", + "signInDescription": "aby uzyskać dostęp do zapisanych adresów i historii zamówień.", + "shippingAddress": "Adres wysyłki", + "continueToDelivery": "Przejdź do dostawy", + "shippingMethod": "Metoda wysyłki", + "loadingShippingOptions": "Ładowanie opcji wysyłki...", + "enterShippingAddressForMethods": "Podaj adres wysyłki, aby zobaczyć dostępne metody dostawy.", + "shipmentOf": "Przesyłka {current} z {total}", + "shipsFrom": "Wysyłka z {location}", + "phone": "Telefon: {phone}", + "billingAddress": "Adres rozliczeniowy", + "sameAsShipping": "Taki sam jak adres wysyłki", + "paymentMethod": "Metoda płatności", + "loadingPaymentForm": "Ładowanie formularza płatności...", + "addNewPaymentMethod": "Dodaj nową metodę płatności", + "endingIn": "kończąca się na", + "exp": "Ważna do", + "savedCardLabel": "{brand} kończąca się na {digits}", + "cardExpiry": "Ważna do {month}/{year}", + "default": "Domyślna", + "noPaymentMethods": "Brak dostępnych metod płatności dla tego zamówienia.", + "secureTransactions": "Wszystkie transakcje są bezpieczne i szyfrowane.", + "creditCard": "Karta kredytowa", + "enterShippingAddress": "Podaj adres wysyłki", + "payNow": "Zapłać teraz", + "failedToInitPayment": "Nie udało się zainicjować płatności. Spróbuj ponownie.", + "failedToCreateSession": "Nie udało się utworzyć sesji płatności.", + "paymentError": "Wystąpił błąd podczas płatności. Spróbuj ponownie.", + "orderNotFound": "Zamówienie nie zostało znalezione lub nie masz do niego dostępu.", + "failedToLoadCheckout": "Nie udało się załadować kasy. Spróbuj ponownie.", + "orderNotFoundAfterPayment": "Zamówienie nie zostało znalezione po płatności. Skontaktuj się z obsługą.", + "failedToSaveAddress": "Nie udało się zapisać adresu", + "failedToProceed": "Nie udało się przejść do następnego kroku", + "generalError": "Wystąpił błąd. Spróbuj ponownie.", + "failedToSelectRate": "Nie udało się wybrać stawki wysyłki", + "failedToSaveBilling": "Nie udało się zapisać adresu rozliczeniowego", + "failedToSaveBillingRetry": "Nie udało się zapisać adresu rozliczeniowego. Spróbuj ponownie.", + "failedToCompleteOrder": "Nie udało się złożyć zamówienia", + "stripeNotLoaded": "Stripe nie został jeszcze załadowany", + "paymentProcessingError": "Wystąpił błąd podczas przetwarzania płatności.", + "failedToCompletePaymentSession": "Nie udało się zakończyć sesji płatności", + "noOrder": "Brak zamówienia", + "confirmingPayment": "Potwierdzanie płatności...", + "policyConsentRequired": "Musisz zaakceptować regulamin sklepu przed złożeniem zamówienia", + "iAgreeToThe": "Zgadzam się z", + "policySeparatorComma": ", ", + "policySeparatorAnd": " i " + }, + "address": { + "firstName": "Imię", + "lastName": "Nazwisko", + "company": "Firma (opcjonalnie)", + "address": "Adres", + "streetAddress": "Ulica i numer", + "apartment": "Mieszkanie, lokal itp. (opcjonalnie)", + "city": "Miasto", + "country": "Kraj", + "selectCountry": "Wybierz kraj", + "stateProvince": "Województwo", + "selectState": "Wybierz województwo", + "stateOrProvince": "Województwo", + "zipCode": "Kod pocztowy", + "phone": "Telefon (opcjonalnie)", + "selectAnAddress": "Wybierz adres", + "useDifferentAddress": "Użyj innego adresu", + "editAddress": "Edytuj adres", + "addNewAddress": "Dodaj nowy adres", + "saveAddress": "Zapisz adres", + "failedToSave": "Nie udało się zapisać adresu", + "deleteAddressTitle": "Usunąć adres?", + "deleteAddressConfirmation": "Ten adres zostanie trwale usunięty. Tej operacji nie można cofnąć.", + "delete": "Usuń", + "deleting": "Usuwanie...", + "cancel": "Anuluj", + "edit": "Edytuj", + "failedToDeleteAddress": "Nie udało się usunąć adresu. Spróbuj ponownie.", + "addFirstAddress": "Dodaj swój pierwszy adres", + "addAddress": "Dodaj adres" + }, + "account": { + "myAccount": "Moje konto", + "signInDescription": "Zaloguj się, aby uzyskać dostęp do konta i historii zamówień.", + "email": "E-mail", + "password": "Hasło", + "signIn": "Zaloguj się", + "signingIn": "Logowanie...", + "invalidCredentials": "Nieprawidłowy e-mail lub hasło", + "dontHaveAccount": "Nie masz konta?", + "signUp": "Zarejestruj się", + "accountOverview": "Przegląd konta", + "orderHistory": "Historia zamówień", + "orderHistoryDescription": "Przeglądaj poprzednie zamówienia i śledź przesyłki", + "addresses": "Adresy", + "addressesDescription": "Zarządzaj adresami wysyłki i rozliczeń", + "paymentMethods": "Metody płatności", + "paymentMethodsDescription": "Zarządzaj zapisanymi kartami płatniczymi", + "profile": "Profil", + "profileDescription": "Zaktualizuj swoje dane osobowe", + "signOut": "Wyloguj się", + "overview": "Przegląd", + "orders": "Zamówienia", + "giftCards": "Karty podarunkowe", + "addNewAddress": "Dodaj nowy adres", + "noAddresses": "Brak zapisanych adresów", + "noAddressesDescription": "Adresy dodane podczas składania zamówienia pojawią się tutaj.", + "defaultAddress": "Domyślny", + "deleteConfirm": "Czy na pewno chcesz usunąć ten adres?", + "setAsDefault": "Ustaw jako domyślny", + "showPassword": "Pokaż hasło", + "hidePassword": "Ukryj hasło", + "forgotPassword": "Zapomniałeś hasła?", + "policyConsentRequired": "Musisz zaakceptować regulamin sklepu, aby utworzyć konto" + }, + "register": { + "createAccount": "Utwórz konto", + "signUpDescription": "Zarejestruj się, aby rozpocząć zakupy.", + "confirmPassword": "Potwierdź hasło", + "creatingAccount": "Tworzenie konta...", + "passwordsDontMatch": "Hasła nie są takie same", + "passwordTooShort": "Hasło musi mieć co najmniej 6 znaków", + "registrationFailed": "Rejestracja nie powiodła się. Spróbuj ponownie.", + "alreadyHaveAccount": "Masz już konto?", + "signIn": "Zaloguj się", + "unexpectedError": "Wystąpił nieoczekiwany błąd. Spróbuj ponownie.", + "firstName": "Imię", + "lastName": "Nazwisko", + "firstNamePlaceholder": "Jan", + "lastNamePlaceholder": "Kowalski", + "emailPlaceholder": "ty@przyklad.pl" + }, + "orders": { + "orderHistory": "Historia zamówień", + "noOrders": "Brak zamówień", + "noOrdersDescription": "Gdy złożysz zamówienie, pojawi się tutaj.", + "startShopping": "Zacznij zakupy", + "order": "Zamówienie", + "date": "Data", + "status": "Status", + "actions": "Akcje", + "view": "Zobacz", + "orderNotFound": "Zamówienie nie zostało znalezione", + "backToOrders": "Wróć do zamówień", + "placedOn": "Złożone {date}", + "orderItems": "Pozycje zamówienia", + "qty": "Ilość: {quantity}", + "orderAgain": "Zamów ponownie", + "deliveryAddress": "Adres dostawy", + "shippingMethod": "Metoda wysyłki", + "shippedFrom": "Wysłane z {location}", + "trackItems": "Śledź przesyłkę", + "shipmentCanceled": "Ta przesyłka została anulowana.", + "noTrackingInfo": "Brak informacji o śledzeniu.", + "paymentInformation": "Informacje o płatności", + "cardEndingIn": "{label} kończąca się na {digits}", + "cardExpires": "Ważna do {month}/{year}", + "storeCreditApplied": "Zastosowano {amount} — {remaining} pozostało", + "billingAddress": "Adres rozliczeniowy", + "orderNotFoundDescription": "Zamówienie, którego szukasz, nie istnieje.", + "storeCredit": "Kredyt sklepowy", + "specialInstructions": "Specjalne instrukcje", + "payment": "Płatność", + "shipment": "Przesyłka", + "shipped": "Wysłano", + "partiallyShipped": "Częściowo wysłano", + "delivered": "Dostarczono", + "ready": "Gotowe", + "pending": "Oczekujące", + "canceled": "Anulowane", + "backorder": "Zamówienie wsteczne", + "paid": "Opłacone", + "balanceDue": "Do zapłaty", + "creditOwed": "Należny kredyt", + "failed": "Nieudane", + "void": "Unieważnione", + "notAvailable": "Brak", + "unknownShipmentStatus": "Nieznany", + "orderTitle": "Zamówienie #{number}", + "shipmentCanceledRefund": "Przesyłka anulowana — zwrot został wystawiony.", + "shippingMethodUnavailable": "Niedostępna", + "totalColumn": "Suma" + }, + "orderPlaced": { + "thanksForOrder": "Dziękujemy za zamówienie, {name}!", + "thanksForOrderAnonymous": "Dziękujemy za zamówienie!", + "orderNumber": "Zamówienie #{number}", + "emailConfirmation": "Wkrótce otrzymasz potwierdzenie e-mailem.", + "orderItems": "Pozycje zamówienia", + "contactInformation": "Dane kontaktowe", + "shippingAddress": "Adres wysyłki", + "billingAddress": "Adres rozliczeniowy", + "orderNotFound": "Zamówienie nie zostało znalezione", + "failedToLoad": "Nie udało się załadować szczegółów zamówienia.", + "qty": "Ilość: {quantity}", + "shippingMethod": "Metoda wysyłki", + "standardShipping": "Wysyłka standardowa", + "payment": "Płatność", + "giftCard": "Karta podarunkowa", + "confirmationSentTo": "Potwierdzenie wysłane do" + }, + "coupon": { + "placeholder": "Karta podarunkowa lub kod rabatowy", + "apply": "Zastosuj", + "remove": "Usuń", + "invalidCode": "Nieprawidłowy kod kuponu", + "applyFailed": "Nie udało się zastosować kuponu. Spróbuj ponownie.", + "failedToRemove": "Nie udało się usunąć kodu kuponu", + "failedToRemoveGiftCard": "Nie udało się usunąć karty podarunkowej", + "removeCoupon": "Usuń kupon {code}", + "removeGiftCard": "Usuń kartę podarunkową", + "giftCardCode": "Karta podarunkowa {code}", + "applying": "Stosowanie...", + "removing": "Usuwanie..." + }, + "checkoutLayout": { + "backToStore": "Wróć do sklepu", + "showOrderSummary": "Pokaż podsumowanie zamówienia", + "hideOrderSummary": "Ukryj podsumowanie zamówienia", + "allRightsReserved": "© {year} {storeName}. Wszelkie prawa zastrzeżone." + }, + "profile": { + "profile": "Profil", + "firstName": "Imię", + "lastName": "Nazwisko", + "emailAddress": "Adres e-mail", + "saveChanges": "Zapisz zmiany", + "saving": "Zapisywanie...", + "profileUpdated": "Profil został zaktualizowany!", + "accountInformation": "Informacje o koncie", + "accountId": "ID konta", + "loadingProfile": "Ładowanie profilu...", + "failedToUpdate": "Nie udało się zaktualizować profilu", + "email": "E-mail" + }, + "creditCards": { + "paymentMethods": "Metody płatności", + "noCards": "Brak zapisanych metod płatności", + "noCardsDescription": "Metody płatności są zapisywane automatycznie podczas zakupów.", + "expires": "Ważna do", + "default": "Domyślna", + "removing": "Usuwanie...", + "secureInfo": "Twoje dane płatnicze są bezpiecznie przechowywane.", + "deleteConfirm": "Czy na pewno chcesz usunąć tę kartę?", + "cardMaskedLabel": "{label} kończąca się na {digits}, ważna do {month}/{year}", + "removePaymentMethodTitle": "Usunąć metodę płatności?", + "endingIn": "kończąca się na", + "cardEndingIn": "{label} kończąca się na {digits}", + "cardExpires": "Ważna do {month}/{year}", + "remove": "Usuń", + "cancel": "Anuluj", + "confirmRemoveDescription": "Karta {label} kończąca się na {digits} zostanie usunięta z Twojego konta. Tej operacji nie można cofnąć.", + "failedToRemove": "Nie udało się usunąć karty. Spróbuj ponownie." + }, + "giftCards": { + "giftCards": "Karty podarunkowe", + "noGiftCards": "Brak kart podarunkowych", + "noGiftCardsDescription": "Nie masz jeszcze żadnych kart podarunkowych.", + "active": "Aktywna", + "partiallyUsed": "Częściowo wykorzystana", + "fullyRedeemed": "W pełni wykorzystana", + "canceled": "Anulowana", + "expired": "Wygasła", + "noExpiration": "Bez daty ważności", + "remaining": "pozostało", + "usedAmount": "Wykorzystano: {amount}", + "totalAmountWithValue": "Łącznie: {amount}", + "addedOnDate": "Dodano {date}", + "fullyRedeemedOnDate": "W pełni wykorzystano {date}", + "activeGiftCards": "Aktywne karty podarunkowe", + "expiredRedeemed": "Wygasłe / Wykorzystane", + "copy": "Kopiuj", + "copied": "Skopiowano!", + "copyCodeToClipboard": "Skopiuj kod do schowka", + "expiresOn": "Wygasa {date}", + "percentUsed": "{percent}% wykorzystano", + "activeGiftCardsCount": "Aktywne karty podarunkowe ({count})", + "expiredRedeemedCount": "Wygasłe / Wykorzystane ({count})", + "giftCardsHelpText": "Karty podarunkowe mogą być używane podczas składania zamówienia do opłacenia zakupów. Pozostałe saldo zostanie zapisane na przyszłe zakupy." + }, + "forgotPassword": { + "title": "Zresetuj hasło", + "description": "Podaj swój adres e-mail, a wyślemy Ci link do zresetowania hasła.", + "email": "E-mail", + "sending": "Wysyłanie...", + "sendResetLink": "Wyślij link resetujący", + "backToSignIn": "Wróć do logowania", + "checkYourEmail": "Sprawdź swoją skrzynkę", + "resetEmailSent": "Jeśli konto istnieje dla {email}, wysłaliśmy instrukcje resetowania hasła.", + "linkExpiry": "Link w wiadomości wygaśnie za 15 minut. Sprawdź folder spam, jeśli go nie widzisz.", + "tryDifferentEmail": "Spróbuj inny adres e-mail", + "genericError": "Coś poszło nie tak. Spróbuj ponownie." + }, + "resetPassword": { + "title": "Ustaw nowe hasło", + "description": "Wpisz poniżej nowe hasło.", + "newPassword": "Nowe hasło", + "confirmPassword": "Potwierdź nowe hasło", + "resetting": "Resetowanie...", + "resetPassword": "Zresetuj hasło", + "backToSignIn": "Wróć do logowania", + "invalidLink": "Nieprawidłowy link", + "invalidLinkDescription": "Ten link do resetowania hasła jest nieprawidłowy lub wygasł.", + "requestNewLink": "Poproś o nowy link resetujący", + "passwordsDontMatch": "Hasła nie są takie same", + "passwordTooShort": "Hasło musi mieć co najmniej 6 znaków", + "linkExpired": "Ten link resetujący wygasł. Poproś o nowy.", + "genericError": "Coś poszło nie tak. Spróbuj ponownie.", + "success": "Hasło zostało zresetowane", + "successDescription": "Twoje hasło zostało zaktualizowane. Możesz się teraz zalogować nowym hasłem.", + "signIn": "Zaloguj się" + }, + "policies": { + "policyNotFound": "Nie znaleziono regulaminu", + "noContent": "Brak treści dla tego regulaminu.", + "shippingPolicy": "Polityka wysyłki", + "privacyPolicy": "Polityka prywatności", + "returnsPolicy": "Polityka zwrotów", + "termsOfService": "Regulamin" + } +} diff --git a/next.config.ts b/next.config.ts index 2608f981..c891ca1f 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,5 +1,8 @@ import { withSentryConfig } from "@sentry/nextjs"; import type { NextConfig } from "next"; +import createNextIntlPlugin from "next-intl/plugin"; + +const withNextIntl = createNextIntlPlugin(); const nextConfig: NextConfig = { env: { @@ -35,8 +38,10 @@ const nextConfig: NextConfig = { }, }; +const configWithIntl = withNextIntl(nextConfig); + export default process.env.SENTRY_DSN - ? withSentryConfig(nextConfig, { + ? withSentryConfig(configWithIntl, { org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, authToken: process.env.SENTRY_AUTH_TOKEN, @@ -54,4 +59,4 @@ export default process.env.SENTRY_DSN // Disables the Sentry SDK build-time telemetry telemetry: false, }) - : nextConfig; + : configWithIntl; diff --git a/package-lock.json b/package-lock.json index 132f7e44..0fa3b800 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,13 @@ "@spree/sdk": "^0.20.2", "@stripe/react-stripe-js": "^5.6.0", "@stripe/stripe-js": "^8.7.0", + "@swc/helpers": "^0.5.21", "@vercel/analytics": "^2.0.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.577.0", "next": "^16", + "next-intl": "^4.8.3", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "^19", @@ -46,6 +48,7 @@ "lefthook": "^2.1.2", "react-email": "^5.2.10", "tailwindcss": "^4", + "tsx": "^4.19.0", "typescript": "^5", "vitest": "^4.0.18" } @@ -184,6 +187,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -867,6 +871,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -907,6 +912,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -1095,6 +1101,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1653,6 +1660,57 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, + "node_modules/@formatjs/bigdecimal": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/bigdecimal/-/bigdecimal-0.2.0.tgz", + "integrity": "sha512-GeaxHZbUoYvHL9tC5eltHLs+1zU70aPw0s7LwqgktIzF5oMhNY4o4deEtusJMsq7WFJF3Ye2zQEzdG8beVk73w==", + "license": "MIT" + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.2.0.tgz", + "integrity": "sha512-dHnqHgBo6GXYGRsepaE1wmsC2etaivOWd5VaJstZd+HI2zR3DCUjbDVZRtoPGkkXZmyHvBwrdEUuqfvzhF/DtQ==", + "license": "MIT", + "dependencies": { + "@formatjs/bigdecimal": "0.2.0", + "@formatjs/fast-memoize": "3.1.1", + "@formatjs/intl-localematcher": "0.8.2" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.1.tgz", + "integrity": "sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg==", + "license": "MIT" + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.3.tgz", + "integrity": "sha512-HJWZ9S6JWey6iY5+YXE3Kd0ofWU1sC2KTTp56e1168g/xxWvVvr8k9G4fexIgwYV9wbtjY7kGYK5FjoWB3B2OQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "3.2.0", + "@formatjs/icu-skeleton-parser": "2.1.3" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.3.tgz", + "integrity": "sha512-9mFp8TJ166ZM2pcjKwsBWXrDnOJGT7vMEScVgLygUODPOsE8S6f/FHoacvrlHK1B4dYZk8vSCNruyPU64AfgJQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "3.2.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.2.tgz", + "integrity": "sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "3.1.1" + } + }, "node_modules/@hono/node-server": { "version": "1.19.13", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", @@ -1682,6 +1740,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1704,6 +1763,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1726,6 +1786,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1742,6 +1803,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1758,6 +1820,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1774,6 +1837,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1790,6 +1854,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1806,6 +1871,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1822,6 +1888,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1838,6 +1905,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1854,6 +1922,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1870,6 +1939,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1886,6 +1956,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1908,6 +1979,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1930,6 +2002,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1952,6 +2025,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1974,6 +2048,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1996,6 +2071,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2018,6 +2094,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2040,6 +2117,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2062,6 +2140,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -2081,6 +2160,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -2100,6 +2180,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -2119,6 +2200,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -2248,7 +2330,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -2496,6 +2577,7 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -2592,6 +2674,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2613,6 +2696,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.1.tgz", "integrity": "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -2625,6 +2709,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.1.tgz", "integrity": "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3048,6 +3133,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.1.tgz", "integrity": "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3064,6 +3150,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.1.tgz", "integrity": "sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/resources": "2.5.1", @@ -3081,6 +3168,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -3100,6 +3188,301 @@ "@opentelemetry/api": "^1.1.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@prisma/instrumentation": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.2.0.tgz", @@ -4644,6 +5027,7 @@ "resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.3.0.tgz", "integrity": "sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -4656,6 +5040,7 @@ "resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.2.1.tgz", "integrity": "sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -4668,6 +5053,7 @@ "resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.2.1.tgz", "integrity": "sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==", "license": "MIT", + "peer": true, "dependencies": { "prismjs": "^1.30.0" }, @@ -4683,6 +5069,7 @@ "resolved": "https://registry.npmjs.org/@react-email/code-inline/-/code-inline-0.0.6.tgz", "integrity": "sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -4741,6 +5128,7 @@ "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.16.tgz", "integrity": "sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -4777,6 +5165,7 @@ "resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.16.tgz", "integrity": "sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -4789,6 +5178,7 @@ "resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.12.tgz", "integrity": "sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -4813,6 +5203,7 @@ "resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.12.tgz", "integrity": "sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -4825,6 +5216,7 @@ "resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.13.tgz", "integrity": "sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -4852,6 +5244,7 @@ "resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.14.tgz", "integrity": "sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5013,6 +5406,16 @@ "node": ">= 10" } }, + "node_modules/@react-email/preview-server/node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@react-email/preview-server/node_modules/next": { "version": "16.1.7", "resolved": "https://registry.npmjs.org/next/-/next-16.1.7.tgz", @@ -5206,6 +5609,7 @@ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz", "integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5593,6 +5997,12 @@ "win32" ] }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -6103,51 +6513,260 @@ } } }, - "node_modules/@stablelib/base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", - "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", - "license": "MIT" + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stripe/react-stripe-js": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-5.6.0.tgz", + "integrity": "sha512-tucu/vTGc+5NXbo2pUiaVjA4ENdRBET8qGS00BM4BAU8J4Pi3eY6BHollsP2+VSuzzlvXwMg0it3ZLhbCj2fPg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@stripe/stripe-js": ">=8.0.0 <9.0.0", + "react": ">=16.8.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@stripe/stripe-js": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.8.0.tgz", + "integrity": "sha512-NNYuyW8qmLjyHnpyFgs/23wUrjB8k0xN9YIZFOMLewCa/pIkIji9e9aY/EgdNryEDDRptc6TcPIHRvG1R0ClFw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.16" + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.24.tgz", + "integrity": "sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.24.tgz", + "integrity": "sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.24.tgz", + "integrity": "sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.24.tgz", + "integrity": "sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.24.tgz", + "integrity": "sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.24.tgz", + "integrity": "sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.24.tgz", + "integrity": "sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.24.tgz", + "integrity": "sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.24.tgz", + "integrity": "sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.24.tgz", + "integrity": "sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@stripe/react-stripe-js": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-5.6.0.tgz", - "integrity": "sha512-tucu/vTGc+5NXbo2pUiaVjA4ENdRBET8qGS00BM4BAU8J4Pi3eY6BHollsP2+VSuzzlvXwMg0it3ZLhbCj2fPg==", - "license": "MIT", - "dependencies": { - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "@stripe/stripe-js": ">=8.0.0 <9.0.0", - "react": ">=16.8.0 <20.0.0", - "react-dom": ">=16.8.0 <20.0.0" + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.24.tgz", + "integrity": "sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@stripe/stripe-js": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.8.0.tgz", - "integrity": "sha512-NNYuyW8qmLjyHnpyFgs/23wUrjB8k0xN9YIZFOMLewCa/pIkIji9e9aY/EgdNryEDDRptc6TcPIHRvG1R0ClFw==", - "license": "MIT", + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.24.tgz", + "integrity": "sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12.16" + "node": ">=10" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.8.0" } }, + "node_modules/@swc/types": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@tailwindcss/node": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", @@ -6589,8 +7208,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6679,7 +7297,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -6690,7 +7307,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -6706,8 +7322,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/mysql": { "version": "2.15.27", @@ -6753,6 +7368,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6763,6 +7379,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6987,7 +7604,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -6997,29 +7613,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -7030,15 +7642,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7051,7 +7661,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -7061,7 +7670,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -7070,15 +7678,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7095,7 +7701,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -7109,7 +7714,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7122,7 +7726,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -7137,7 +7740,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -7147,15 +7749,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/accepts": { "version": "2.0.0", @@ -7200,6 +7800,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7221,7 +7822,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -7246,6 +7846,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7262,7 +7863,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -7280,7 +7880,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -7303,7 +7902,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -7489,6 +8087,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7507,8 +8106,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bundle-name": { "version": "4.1.0", @@ -7635,7 +8233,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -8227,7 +8824,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -8253,8 +8849,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-serializer": { "version": "2.0.0", @@ -8611,7 +9206,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -8638,7 +9232,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -8651,7 +9244,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -8661,7 +9253,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -8686,7 +9277,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -9187,6 +9777,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -9220,8 +9823,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/globals": { "version": "11.12.0", @@ -9265,7 +9867,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -9305,6 +9906,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -9439,6 +10041,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/icu-minify": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.0.tgz", + "integrity": "sha512-9ev7MqkN29jcIelUAqJRfNCxzGOEkBJPnr+scYATMp2bfpU4Bm1eIwYU0/o5xRy8BBnSWMUjK58WTB3132P0bg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/icu-messageformat-parser": "^3.4.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -9492,6 +10109,17 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/intl-messageformat": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.0.tgz", + "integrity": "sha512-IhghAA8n4KSlXuWKzYsWyWb82JoYTzShfyvdSF85oJPnNOjvv4kAo7S7Jtkm3/vJ53C7dQNRO+Gpnj3iWgTjBQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "3.2.0", + "@formatjs/fast-memoize": "3.1.1", + "@formatjs/icu-messageformat-parser": "3.5.3" + } + }, "node_modules/ip-address": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", @@ -9726,7 +10354,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -10345,7 +10972,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" }, @@ -10433,7 +11059,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -10745,14 +11370,14 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/next": { "version": "16.2.1", "resolved": "https://registry.npmjs.org/next/-/next-16.2.1.tgz", "integrity": "sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.2.1", "@swc/helpers": "0.5.15", @@ -10801,6 +11426,83 @@ } } }, + "node_modules/next-intl": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.9.0.tgz", + "integrity": "sha512-MMNAjewHUw9Ke93E5/Yzhf8lqesesaXJTPlrK3FwECgn4EXG9m7Tuzy4rnDes0ogjDhQIa/Ksj/qmFnHJAOluw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.8.1", + "@parcel/watcher": "^2.4.1", + "@swc/core": "^1.15.2", + "icu-minify": "^4.9.0", + "negotiator": "^1.0.0", + "next-intl-swc-plugin-extractor": "^4.9.0", + "po-parser": "^2.1.1", + "use-intl": "^4.9.0" + }, + "peerDependencies": { + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/next-intl-swc-plugin-extractor": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.9.0.tgz", + "integrity": "sha512-CAu6Qy6XiCenKsvzyCPm2cZFkGfcvhJi8N93TCnOowmzD4Br3ked7QdROusRRp4MQ1iG9u+KCLgVcM9CLDUOIQ==", + "license": "MIT" + }, + "node_modules/next-intl/node_modules/@swc/core": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.24.tgz", + "integrity": "sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.26" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.24", + "@swc/core-darwin-x64": "1.15.24", + "@swc/core-linux-arm-gnueabihf": "1.15.24", + "@swc/core-linux-arm64-gnu": "1.15.24", + "@swc/core-linux-arm64-musl": "1.15.24", + "@swc/core-linux-ppc64-gnu": "1.15.24", + "@swc/core-linux-s390x-gnu": "1.15.24", + "@swc/core-linux-x64-gnu": "1.15.24", + "@swc/core-linux-x64-musl": "1.15.24", + "@swc/core-win32-arm64-msvc": "1.15.24", + "@swc/core-win32-ia32-msvc": "1.15.24", + "@swc/core-win32-x64-msvc": "1.15.24" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, "node_modules/next-themes": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", @@ -10811,6 +11513,15 @@ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/next/node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -10839,6 +11550,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -11358,6 +12075,12 @@ "pathe": "^2.0.3" } }, + "node_modules/po-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz", + "integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==", + "license": "MIT" + }, "node_modules/postal-mime": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.3.tgz", @@ -11477,7 +12200,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -11729,6 +12451,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11871,8 +12594,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.18.0", @@ -12068,6 +12790,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -12105,6 +12837,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -12235,7 +12968,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -12745,7 +13477,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -12954,7 +13685,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -13061,7 +13791,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -13080,7 +13809,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -13113,8 +13841,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/third-party-capital": { "version": "1.0.20", @@ -13265,6 +13992,27 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tw-animate-css": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", @@ -13328,6 +14076,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13455,6 +14204,27 @@ } } }, + "node_modules/use-intl": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.9.0.tgz", + "integrity": "sha512-GehJvP7gu8SvmaDHNDNrRHt2TCNSZt4l1cGJMpUX77TGeZPAQKVQokAVvoYkeTT1UWPtv9RJ6N16UJNButzrgg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^3.1.0", + "@schummar/icu-type-parser": "1.21.5", + "icu-minify": "^4.9.0", + "intl-messageformat": "^11.1.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", @@ -13529,6 +14299,7 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13694,7 +14465,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -13727,7 +14497,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -13776,7 +14545,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -13785,8 +14553,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/whatwg-mimetype": { "version": "5.0.0", @@ -14098,6 +14865,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 94ab71e0..9ca041c1 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "check": "biome check .", "test": "vitest run", "test:watch": "vitest", + "check:locales": "tsx scripts/check-locale-parity.ts", "email:dev": "email dev --port 4000" }, "dependencies": { @@ -20,11 +21,13 @@ "@spree/sdk": "^0.20.2", "@stripe/react-stripe-js": "^5.6.0", "@stripe/stripe-js": "^8.7.0", + "@swc/helpers": "^0.5.21", "@vercel/analytics": "^2.0.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.577.0", "next": "^16", + "next-intl": "^4.8.3", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "^19", @@ -55,6 +58,7 @@ "lefthook": "^2.1.2", "react-email": "^5.2.10", "tailwindcss": "^4", + "tsx": "^4.19.0", "typescript": "^5", "vitest": "^4.0.18" } diff --git a/scripts/check-locale-parity.ts b/scripts/check-locale-parity.ts new file mode 100644 index 00000000..3c80850c --- /dev/null +++ b/scripts/check-locale-parity.ts @@ -0,0 +1,84 @@ +/** + * Checks that all locale JSON files have the same set of keys as en.json. + * Run: tsx scripts/check-locale-parity.ts + */ +import fs from "node:fs"; +import path from "node:path"; + +const MESSAGES_DIR = path.resolve(process.cwd(), "messages"); +const BASE_LOCALE = "en"; + +if (!fs.existsSync(MESSAGES_DIR) || !fs.statSync(MESSAGES_DIR).isDirectory()) { + console.error(`Messages directory not found: ${MESSAGES_DIR}`); + process.exit(1); +} + +function getNestedKeys(obj: Record, prefix = ""): string[] { + const keys: string[] = []; + for (const key of Object.keys(obj)) { + const fullKey = prefix ? `${prefix}.${key}` : key; + const value = obj[key]; + if (typeof value === "object" && value !== null && !Array.isArray(value)) { + keys.push(...getNestedKeys(value as Record, fullKey)); + } else { + keys.push(fullKey); + } + } + return keys; +} + +const baseFile = path.join(MESSAGES_DIR, `${BASE_LOCALE}.json`); +let baseMessages: Record; +try { + baseMessages = JSON.parse(fs.readFileSync(baseFile, "utf-8")); +} catch (err) { + console.error(`Failed to parse base locale file ${baseFile}:`, err); + process.exit(1); +} +const baseKeys = new Set(getNestedKeys(baseMessages)); + +const localeFiles = fs + .readdirSync(MESSAGES_DIR) + .filter((f) => f.endsWith(".json") && f !== `${BASE_LOCALE}.json`); + +let hasError = false; + +for (const file of localeFiles) { + const locale = file.replace(".json", ""); + let messages: Record; + try { + messages = JSON.parse( + fs.readFileSync(path.join(MESSAGES_DIR, file), "utf-8"), + ); + } catch (err) { + console.error(`Failed to parse locale file ${file}:`, err); + hasError = true; + continue; + } + const localeKeys = new Set(getNestedKeys(messages)); + + const missing = [...baseKeys].filter((k) => !localeKeys.has(k)); + const extra = [...localeKeys].filter((k) => !baseKeys.has(k)); + + if (missing.length > 0) { + console.error(`[${locale}] Missing ${missing.length} key(s):`); + for (const k of missing) console.error(` - ${k}`); + hasError = true; + } + + if (extra.length > 0) { + console.error(`[${locale}] Extra ${extra.length} key(s):`); + for (const k of extra) console.error(` + ${k}`); + hasError = true; + } + + if (missing.length === 0 && extra.length === 0) { + console.log(`[${locale}] OK — all keys match ${BASE_LOCALE}.json`); + } +} + +if (hasError) { + process.exit(1); +} else { + console.log("\nAll locale files are in sync."); +} diff --git a/src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx b/src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx index 2f48a5b1..e3e485d7 100644 --- a/src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx +++ b/src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx @@ -4,7 +4,8 @@ import type { Address, AddressParams, Cart, Country } from "@spree/sdk"; import { CircleAlert, Loader2 } from "lucide-react"; import Link from "next/link"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { use, useCallback, useEffect, useRef, useState } from "react"; +import { useTranslations } from "next-intl"; +import { Suspense, use, useCallback, useEffect, useRef, useState } from "react"; import { AddressSection } from "@/components/checkout/AddressSection"; import { CouponCode } from "@/components/checkout/CouponCode"; import { DeliveryMethodSection } from "@/components/checkout/DeliveryMethodSection"; @@ -79,13 +80,15 @@ function CheckoutSidebar({ ); } -export default function CheckoutPage({ params }: CheckoutPageProps) { +function CheckoutPageContent({ params }: CheckoutPageProps) { const { id: cartId, country: urlCountry } = use(params); const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const basePath = extractBasePath(pathname); const { setSummaryContent } = useCheckout(); + const t = useTranslations("checkout"); + const tc = useTranslations("common"); const { user, loading: authLoading } = useAuth(); // Pick up payment errors from the confirm-payment redirect @@ -109,13 +112,18 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { const cartRef = useRef(cart); cartRef.current = cart; + const routerRef = useRef(router); + routerRef.current = router; + const tRef = useRef(t); + tRef.current = t; const beginCheckoutFiredRef = useRef(false); const paymentRef = useRef(null); // Handle code application (discount code or gift card — single input field) const handleApplyCode = useCallback(async (code: string) => { const currentOrder = cartRef.current; - if (!currentOrder) return { success: false, error: "No cart" }; + if (!currentOrder) + return { success: false, error: tRef.current("noOrder") }; const result = await applyCode(currentOrder.id, code); if (result.success && result.cart) { @@ -126,7 +134,8 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { const handleRemoveDiscount = useCallback(async (discountCode: string) => { const currentOrder = cartRef.current; - if (!currentOrder) return { success: false, error: "No cart" }; + if (!currentOrder) + return { success: false, error: tRef.current("noOrder") }; const result = await removeDiscountCode(currentOrder.id, discountCode); if (result.success && result.cart) { @@ -137,7 +146,8 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { const handleRemoveGiftCard = useCallback(async (giftCardId: string) => { const currentOrder = cartRef.current; - if (!currentOrder) return { success: false, error: "No cart" }; + if (!currentOrder) + return { success: false, error: tRef.current("noOrder") }; const result = await removeGiftCard(currentOrder.id, giftCardId); if (result.success && result.cart) { @@ -202,13 +212,13 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { : { data: [] as Country[] }; if (!cartData) { - setError("Order not found or you don't have access to it."); + setError(tRef.current("orderNotFound")); setLoading(false); return; } if (cartData.current_step === "complete") { - router.push(`${basePath}/order-placed/${cartId}`); + routerRef.current.push(`${basePath}/order-placed/${cartId}`); return; } @@ -228,12 +238,12 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { return cartData; } catch { - setError("Failed to load checkout. Please try again."); + setError(tRef.current("failedToLoadCheckout")); return null; } finally { setLoading(false); } - }, [cartId, urlCountry, basePath, router, paymentError]); + }, [cartId, urlCountry, basePath, paymentError]); useEffect(() => { loadOrder(); @@ -282,7 +292,7 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { }); if (!updateResult.success) { - setError(updateResult.error || "Failed to save address"); + setError(updateResult.error || tRef.current("failedToSaveAddress")); return; } @@ -290,7 +300,7 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { setCart(updateResult.cart); } } catch { - setError("An error occurred. Please try again."); + setError(tRef.current("generalError")); } finally { setSaving(false); } @@ -317,7 +327,7 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { rateId, ); if (!result.success) { - setError(result.error || "Failed to select delivery rate"); + setError(result.error || tRef.current("failedToSelectRate")); } else if (result.cart) { setCart(result.cart); @@ -328,7 +338,7 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { trackingRateName = selectedRate?.name; } } catch { - setError("An error occurred. Please try again."); + setError(tRef.current("generalError")); } finally { setProcessing(false); } @@ -364,13 +374,13 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { }); if (!updateResult.success) { - setError(updateResult.error || "Failed to save billing address"); + setError(updateResult.error || tRef.current("failedToSaveBilling")); return false; } return true; } catch { - setError("Failed to save billing address. Please try again."); + setError(tRef.current("failedToSaveBillingRetry")); return false; } }, @@ -392,7 +402,10 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { ); if (!sessionResult.success) { - setError(sessionResult.error || "Failed to complete payment session"); + setError( + sessionResult.error || + tRef.current("failedToCompletePaymentSession"), + ); setProcessing(false); return; } @@ -407,7 +420,9 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { // session completion, completeCheckoutOrder handles 403/422 gracefully. const completeResult = await completeCheckoutOrder(currentOrder.id); if (!completeResult.success) { - setError(completeResult.error || "Failed to complete order"); + setError( + completeResult.error || tRef.current("failedToCompleteOrder"), + ); setProcessing(false); return; } @@ -420,13 +435,13 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { cacheCompletedOrder(currentOrder.id, completeResult.order); } - router.push(`${basePath}/order-placed/${currentOrder.id}`); + routerRef.current.push(`${basePath}/order-placed/${currentOrder.id}`); } catch { - setError("An error occurred. Please try again."); + setError(tRef.current("generalError")); setProcessing(false); } }, - [basePath, router], + [basePath], ); // Fetch states for a country @@ -445,11 +460,11 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { const result = await updateAddress(id, data); if (!result.success) { - throw new Error(result.error || "Failed to update address"); + throw new Error(result.error || tRef.current("failedToSaveAddress")); } if (!result.address) { - throw new Error("Update succeeded but address payload is missing"); + throw new Error(tRef.current("generalError")); } return result.address; @@ -466,9 +481,7 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { if (!isAuthenticated && !policyConsent) { setPolicyError(true); - setError( - "You must agree to the store policies before placing your order", - ); + setError(t("policyConsentRequired")); document .getElementById("policy-consent") ?.scrollIntoView({ behavior: "smooth", block: "center" }); @@ -479,7 +492,7 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { // Refresh cart to get latest requirements const freshOrder = await getCheckoutOrder(cart.id); if (!freshOrder) { - setError("Failed to load cart. Please try again."); + setError(t("failedToLoadCheckout")); return; } setCart(freshOrder); @@ -521,7 +534,7 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { // All requirements met — submit payment if (!paymentRef.current) { - setError("Payment is not ready. Please wait and try again."); + setError(t("failedToInitPayment")); return; } @@ -550,14 +563,14 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { return (

- Checkout Error + {t("checkoutError")}

{error}

- Return to Cart + {t("returnToCart")}
); @@ -570,16 +583,14 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { return (

- Your Cart is Empty + {t("emptyCart")}

-

- Add some items to your cart before checking out. -

+

{t("emptyCartDescription")}

- Continue Shopping + {tc("continueShopping")}
); @@ -666,12 +677,26 @@ export default function CheckoutPage({ params }: CheckoutPageProps) { {processing ? ( <> - Processing... + {tc("processing")} ) : ( - "Pay now" + t("payNow") )} ); } + +export default function CheckoutPage({ params }: CheckoutPageProps) { + return ( + + + + } + > + + + ); +} diff --git a/src/app/[country]/[locale]/(checkout)/confirm-payment/[id]/page.tsx b/src/app/[country]/[locale]/(checkout)/confirm-payment/[id]/page.tsx index 5587d679..ad89b41d 100644 --- a/src/app/[country]/[locale]/(checkout)/confirm-payment/[id]/page.tsx +++ b/src/app/[country]/[locale]/(checkout)/confirm-payment/[id]/page.tsx @@ -2,6 +2,7 @@ import { Loader2 } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useTranslations } from "next-intl"; import { use, useEffect, useRef } from "react"; import { confirmPaymentAndCompleteCart } from "@/lib/data/payment"; import { extractBasePath } from "@/lib/utils/path"; @@ -27,6 +28,7 @@ export default function ConfirmPaymentPage({ params, }: ConfirmPaymentPageProps) { const { id: cartId } = use(params); + const t = useTranslations("checkout"); const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); @@ -57,7 +59,7 @@ export default function ConfirmPaymentPage({ router.replace(`${basePath}/order-placed/${cartId}`); } else { const errorMessage = encodeURIComponent( - result.error || "Payment could not be confirmed. Please try again.", + result.error || t("paymentError"), ); router.replace( `${basePath}/checkout/${cartId}?payment_error=${errorMessage}`, @@ -66,12 +68,12 @@ export default function ConfirmPaymentPage({ } confirmAndRedirect(); - }, [cartId, searchParams, basePath, router]); + }, [cartId, searchParams, basePath, router, t]); return (
-

Confirming your payment...

+

{t("confirmingPayment")}

); } diff --git a/src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx b/src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx index 5f95645a..68134e81 100644 --- a/src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx +++ b/src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx @@ -5,6 +5,14 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const mockReplace = vi.fn(); let mockSearchParams = new URLSearchParams(); +vi.mock("next-intl", async () => { + const actual = await vi.importActual("next-intl"); + return { + ...actual, + useTranslations: () => (key: string) => key, + }; +}); + vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn(), @@ -56,7 +64,7 @@ describe("ConfirmPaymentPage", () => { renderPage(); }); - expect(screen.getByText("Confirming your payment...")).toBeInTheDocument(); + expect(screen.getByText("confirmingPayment")).toBeInTheDocument(); }); it("redirects to order-placed on success", async () => { @@ -124,7 +132,7 @@ describe("ConfirmPaymentPage", () => { await waitFor(() => { expect(mockReplace).toHaveBeenCalledWith( expect.stringContaining( - "payment_error=Payment%20could%20not%20be%20confirmed", + "/us/en/checkout/cart-1?payment_error=paymentError", ), ); }); diff --git a/src/app/[country]/[locale]/(checkout)/layout.tsx b/src/app/[country]/[locale]/(checkout)/layout.tsx index b926dcf0..860dd624 100644 --- a/src/app/[country]/[locale]/(checkout)/layout.tsx +++ b/src/app/[country]/[locale]/(checkout)/layout.tsx @@ -4,16 +4,19 @@ import { ArrowLeft, ChevronDown, ShoppingBag } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { CheckoutProvider, CheckoutSummary } from "@/contexts/CheckoutContext"; import { POLICY_LINKS } from "@/lib/constants/policies"; +import { getStoreName } from "@/lib/seo"; import { extractBasePath } from "@/lib/utils/path"; -const storeName = process.env.NEXT_PUBLIC_STORE_NAME || "Spree Store"; +const storeName = getStoreName(); function CheckoutHeader() { const pathname = usePathname(); const basePath = extractBasePath(pathname); + const t = useTranslations("checkoutLayout"); return (
@@ -29,10 +32,10 @@ function CheckoutHeader() {
); @@ -41,12 +44,13 @@ function CheckoutHeader() { function CheckoutFooter() { const pathname = usePathname(); const basePath = extractBasePath(pathname); - const currentYear = new Date().getFullYear(); + const t = useTranslations("checkoutLayout"); + const tp = useTranslations("policies"); return (

- © {currentYear} {storeName}. All rights reserved. + {t("allRightsReserved", { year: new Date().getFullYear(), storeName })}

{POLICY_LINKS.map((policy) => ( - {policy.name} + {tp(policy.nameKey)} ))}
@@ -64,6 +68,7 @@ function CheckoutFooter() { function MobileSummaryToggle() { const [isOpen, setIsOpen] = useState(false); + const t = useTranslations("checkoutLayout"); return (
@@ -71,17 +76,19 @@ function MobileSummaryToggle() { type="button" onClick={() => setIsOpen(!isOpen)} className="w-full px-5 py-4 flex items-center justify-between text-left" + aria-expanded={isOpen} + aria-controls="checkout-summary-panel" > - {isOpen ? "Hide order summary" : "Show order summary"} + {isOpen ? t("hideOrderSummary") : t("showOrderSummary")} {isOpen && ( -
+
)} diff --git a/src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx b/src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx index ef594760..1559cd22 100644 --- a/src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx +++ b/src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx @@ -4,6 +4,7 @@ import type { Cart } from "@spree/sdk"; import { CircleCheckBig, Package } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useTranslations } from "next-intl"; import { use, useEffect, useRef, useState } from "react"; import { AddressBlock } from "@/components/order/AddressBlock"; import { OrderTotals } from "@/components/order/OrderTotals"; @@ -29,10 +30,14 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { const pathname = usePathname(); const basePath = extractBasePath(pathname); const { setSummaryContent } = useCheckout(); + const t = useTranslations("orderPlaced"); + const tc = useTranslations("common"); const [order, setOrder] = useState(null); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [error, setError] = useState<"orderNotFound" | "failedToLoad" | null>( + null, + ); // Clear sidebar summary useEffect(() => { @@ -65,13 +70,13 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { // Analytics failure must not break the order confirmation UX } } else { - setError("Order not found."); + setError("orderNotFound"); } setLoading(false); } catch { if (!cancelled) { loadedRef.current = true; - setError("Failed to load order details."); + setError("failedToLoad"); setLoading(false); } } @@ -99,10 +104,10 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { return (

- {error || "Order not found"} + {t(error || "orderNotFound")}

); @@ -117,19 +122,22 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) {

- Thanks for your order - {customerName ? `, ${customerName.split(" ")[0]}` : ""}! + {customerName + ? t("thanksForOrder", { name: customerName.split(" ")[0] }) + : t("thanksForOrderAnonymous")}

-

Order #{order.number}

-

- You will receive an email confirmation shortly. +

+ {t("orderNumber", { number: order.number })}

+

{t("emailConfirmation")}

{/* Order Items */}
-

Order Items

+

+ {t("orderItems")} +

    {order.items?.map((item) => ( @@ -150,7 +158,9 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { {item.options_text && (

    {item.options_text}

    )} -

    Qty: {item.quantity}

    +

    + {t("qty", { quantity: item.quantity })} +

{item.display_total} @@ -172,7 +182,7 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { {order.fulfillments && order.fulfillments.length > 0 && (

- Shipping Method + {t("shippingMethod")}

{order.fulfillments.map((fulfillment) => (

- {fulfillment.delivery_method?.name || "Standard Shipping"} + {fulfillment.delivery_method?.name || + t("standardShipping")}

{fulfillment.display_cost} @@ -197,7 +208,7 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { {order.payments && order.payments.length > 0 && (

- Payment + {t("payment")}

{order.payments .filter((p) => p.status !== "void" && p.status !== "invalid") @@ -206,7 +217,7 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) {
@@ -222,7 +233,7 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { {order.shipping_address && (

- Shipping Address + {t("shippingAddress")}

@@ -231,7 +242,7 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { {order.billing_address && (

- Billing Address + {t("billingAddress")}

@@ -241,7 +252,7 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { {order.email && (

- Confirmation sent to{" "} + {t("confirmationSentTo")}{" "} {order.email}

@@ -251,7 +262,7 @@ export default function OrderPlacedPage({ params }: OrderPlacedPageProps) { {/* Actions */}
diff --git a/src/app/[country]/[locale]/(storefront)/account/addresses/page.tsx b/src/app/[country]/[locale]/(storefront)/account/addresses/page.tsx index 6b138455..a1805763 100644 --- a/src/app/[country]/[locale]/(storefront)/account/addresses/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/addresses/page.tsx @@ -1,13 +1,23 @@ import { MapPin } from "lucide-react"; import { connection } from "next/server"; +import { getTranslations } from "next-intl/server"; import { AddressManagement } from "@/components/addresses/AddressManagement"; import type { User } from "@/contexts/AuthContext"; import { getAddresses } from "@/lib/data/addresses"; import { getCountries } from "@/lib/data/countries"; import { getCustomer } from "@/lib/data/customer"; -export default async function AddressesPage() { +interface AddressesPageProps { + params: Promise<{ country: string; locale: string }>; +} + +export default async function AddressesPage({ params }: AddressesPageProps) { await connection(); + const { locale } = await params; + const t = await getTranslations({ + locale: locale as Locale, + namespace: "account", + }); const [addressResponse, countriesResponse, customer] = await Promise.all([ getAddresses(), getCountries(), @@ -28,18 +38,16 @@ export default async function AddressesPage() { return (
-

Addresses

+

{t("addresses")}

{addresses.length === 0 ? (

- No addresses saved + {t("noAddresses")}

-

- Add an address for faster checkout. -

+

{t("noAddressesDescription")}

; +} + +export default async function CreditCardsPage({ + params, +}: CreditCardsPageProps) { await connection(); + const { locale } = await params; + const t = await getTranslations({ + locale: locale as Locale, + namespace: "creditCards", + }); const response = await getCreditCards(); const cards = response.data; return (
-

Payment Methods

+

+ {t("paymentMethods")} +

{cards.length === 0 ? (

- No payment methods saved + {t("noCards")}

-

- Payment methods are saved automatically when you make a purchase. -

+

{t("noCardsDescription")}

) : ( @@ -29,8 +41,7 @@ export default async function CreditCardsPage() {

- Your payment information is securely stored and encrypted. We never - store your full card number. + {t("secureInfo")}

diff --git a/src/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsx b/src/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsx index 0a601d04..e34bc043 100644 --- a/src/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsx @@ -3,6 +3,7 @@ import { CircleAlert, CircleCheck, Mail } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; @@ -20,6 +21,7 @@ import { requestPasswordReset } from "@/lib/data/customer"; import { extractBasePath } from "@/lib/utils/path"; export default function ForgotPasswordPage() { + const t = useTranslations("forgotPassword"); const pathname = usePathname(); const basePath = extractBasePath(pathname); @@ -40,10 +42,10 @@ export default function ForgotPasswordPage() { if (result?.message) { setSubmitted(true); } else { - setError("Something went wrong. Please try again."); + setError(t("genericError")); } } catch { - setError("Something went wrong. Please try again."); + setError(t("genericError")); } finally { setSubmitting(false); } @@ -57,20 +59,19 @@ export default function ForgotPasswordPage() {
- Check your email + {t("checkYourEmail")} - If an account exists for {email}, we've sent - password reset instructions. + {t.rich("resetEmailSent", { + email, + strong: (chunks) => {chunks}, + })}
-

- The link in the email will expire in 15 minutes. Check your spam - folder if you don't see it. -

+

{t("linkExpiry")}

@@ -90,7 +91,7 @@ export default function ForgotPasswordPage() { href={`${basePath}/account`} className="text-sm text-primary hover:text-primary/70 font-medium" > - Back to sign in + {t("backToSignIn")} @@ -102,11 +103,8 @@ export default function ForgotPasswordPage() {
- Reset your password - - Enter your email and we'll send you a link to reset your - password. - + {t("title")} + {t("description")} @@ -119,7 +117,7 @@ export default function ForgotPasswordPage() { )} - Email + {t("email")} - {submitting ? "Sending..." : "Send reset link"} + {submitting ? t("sending") : t("sendResetLink")}
@@ -150,7 +148,7 @@ export default function ForgotPasswordPage() { href={`${basePath}/account`} className="text-sm text-primary hover:text-primary/70 font-medium" > - Back to sign in + {t("backToSignIn")} diff --git a/src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx b/src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx index 8fa4a0e1..7331b23e 100644 --- a/src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx @@ -1,26 +1,36 @@ import { Gift, Info } from "lucide-react"; import { connection } from "next/server"; +import { getTranslations } from "next-intl/server"; import { GiftCardList } from "@/components/account/GiftCardList"; import { getGiftCards } from "@/lib/data/gift-cards"; -export default async function GiftCardsPage() { +interface GiftCardsPageProps { + params: Promise<{ country: string; locale: string }>; +} + +export default async function GiftCardsPage({ params }: GiftCardsPageProps) { await connection(); + const { locale } = await params; + const t = await getTranslations({ + locale: locale as Locale, + namespace: "giftCards", + }); const response = await getGiftCards(); const cards = response.data; return (
-

Gift Cards

+

+ {t("giftCards")} +

{cards.length === 0 ? (

- No gift cards + {t("noGiftCards")}

-

- You don't have any gift cards associated with your account yet. -

+

{t("noGiftCardsDescription")}

) : ( @@ -29,8 +39,7 @@ export default async function GiftCardsPage() {

- Gift cards can be used during checkout to pay for your orders. The - remaining balance will be saved for future purchases. + {t("giftCardsHelpText")}

diff --git a/src/app/[country]/[locale]/(storefront)/account/layout.tsx b/src/app/[country]/[locale]/(storefront)/account/layout.tsx index 155e1f0f..407c631b 100644 --- a/src/app/[country]/[locale]/(storefront)/account/layout.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/layout.tsx @@ -12,27 +12,30 @@ import { } from "lucide-react"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useEffect } from "react"; import { Button } from "@/components/ui/button"; import { useAuth } from "@/contexts/AuthContext"; import { extractBasePath } from "@/lib/utils/path"; -const navItems: { +function getNavItems(t: ReturnType>): { href: string; label: string; icon: LucideIcon; -}[] = [ - { href: "/account", label: "Overview", icon: Home }, - { href: "/account/orders", label: "Orders", icon: ShoppingBag }, - { href: "/account/addresses", label: "Addresses", icon: MapPin }, - { - href: "/account/credit-cards", - label: "Payment Methods", - icon: CreditCard, - }, - { href: "/account/gift-cards", label: "Gift Cards", icon: Gift }, - { href: "/account/profile", label: "Profile", icon: User }, -]; +}[] { + return [ + { href: "/account", label: t("overview"), icon: Home }, + { href: "/account/orders", label: t("orders"), icon: ShoppingBag }, + { href: "/account/addresses", label: t("addresses"), icon: MapPin }, + { + href: "/account/credit-cards", + label: t("paymentMethods"), + icon: CreditCard, + }, + { href: "/account/gift-cards", label: t("giftCards"), icon: Gift }, + { href: "/account/profile", label: t("profile"), icon: User }, + ]; +} function ContentSkeleton() { return ( @@ -66,6 +69,8 @@ function AccountShell({ onLogout, isLoading, }: AccountShellProps) { + const t = useTranslations("account"); + const navItems = getNavItems(t); return (
@@ -84,7 +89,7 @@ function AccountShell({

{user?.first_name ? `${user.first_name} ${user.last_name || ""}`.trim() - : "My Account"} + : t("myAccount")}

{user?.email} @@ -125,7 +130,7 @@ function AccountShell({

diff --git a/src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx b/src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx index decc552b..72d24331 100644 --- a/src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; import { connection } from "next/server"; +import { getTranslations } from "next-intl/server"; import { OrderDetail } from "@/components/account/OrderDetail"; import { getOrder } from "@/lib/data/orders"; @@ -16,6 +17,10 @@ export default async function OrderDetailPage({ }: OrderDetailPageProps) { await connection(); const { country, locale, id } = await params; + const t = await getTranslations({ + locale: locale as Locale, + namespace: "orders", + }); const basePath = `/${country}/${locale}`; const order = await getOrder(id); @@ -23,20 +28,18 @@ export default async function OrderDetailPage({ return (

- Order not found + {t("orderNotFound")}

-

- The order you're looking for doesn't exist. -

+

{t("orderNotFoundDescription")}

- Back to orders + {t("backToOrders")}
); } - return ; + return ; } diff --git a/src/app/[country]/[locale]/(storefront)/account/orders/page.tsx b/src/app/[country]/[locale]/(storefront)/account/orders/page.tsx index baaceef9..d89bb56c 100644 --- a/src/app/[country]/[locale]/(storefront)/account/orders/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/orders/page.tsx @@ -1,6 +1,7 @@ import { ShoppingBag } from "lucide-react"; import Link from "next/link"; import { connection } from "next/server"; +import { getTranslations } from "next-intl/server"; import { OrderList } from "@/components/account/OrderList"; import { Button } from "@/components/ui/button"; import { getOrders } from "@/lib/data/orders"; @@ -12,6 +13,10 @@ interface OrdersPageProps { export default async function OrdersPage({ params }: OrdersPageProps) { await connection(); const { country, locale } = await params; + const t = await getTranslations({ + locale: locale as Locale, + namespace: "orders", + }); const basePath = `/${country}/${locale}`; const response = await getOrders({ limit: 50 }); @@ -19,23 +24,23 @@ export default async function OrdersPage({ params }: OrdersPageProps) { return (
-

Order History

+

+ {t("orderHistory")} +

{orders.length === 0 ? (

- No orders yet + {t("noOrders")}

-

- When you place orders, they will appear here. -

+

{t("noOrdersDescription")}

) : ( - + )}
); diff --git a/src/app/[country]/[locale]/(storefront)/account/page.tsx b/src/app/[country]/[locale]/(storefront)/account/page.tsx index 9e9a3710..539605f6 100644 --- a/src/app/[country]/[locale]/(storefront)/account/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/page.tsx @@ -11,6 +11,7 @@ import { } from "lucide-react"; import Link from "next/link"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; @@ -32,6 +33,7 @@ export default function AccountPage() { const pathname = usePathname(); const searchParams = useSearchParams(); const basePath = extractBasePath(pathname); + const t = useTranslations("account"); const { login, isAuthenticated, loading: authLoading } = useAuth(); // Get redirect URL from query params (e.g., from checkout) @@ -55,7 +57,7 @@ export default function AccountPage() { router.push(redirectUrl); } } else { - setError(result.error || "Invalid email or password"); + setError(result.error || t("invalidCredentials")); } setLoading(false); }; @@ -79,10 +81,8 @@ export default function AccountPage() {
- My Account - - Sign in to access your account and order history. - + {t("myAccount")} + {t("signInDescription")} @@ -95,7 +95,7 @@ export default function AccountPage() { )} - Email + {t("email")} - Password + {t("password")}
setShowPassword(!showPassword)} aria-label={ - showPassword ? "Hide password" : "Show password" + showPassword ? t("hidePassword") : t("showPassword") } > {showPassword ? ( @@ -147,7 +147,7 @@ export default function AccountPage() { href={`${basePath}/account/forgot-password`} className="text-sm text-primary hover:text-primary/70 font-medium" > - Forgot password? + {t("forgotPassword")}
@@ -158,7 +158,7 @@ export default function AccountPage() { size="lg" className="w-full" > - {loading ? "Signing in..." : "Sign In"} + {loading ? t("signingIn") : t("signIn")}
@@ -166,12 +166,12 @@ export default function AccountPage() {

- Don't have an account?{" "} + {t("dontHaveAccount")}{" "} - Sign up + {t("signUp")}

@@ -184,7 +184,7 @@ export default function AccountPage() { return (

- Account Overview + {t("accountOverview")}

@@ -196,10 +196,10 @@ export default function AccountPage() {

- Order History + {t("orderHistory")}

- View your past orders and track deliveries + {t("orderHistoryDescription")}

@@ -213,9 +213,11 @@ export default function AccountPage() {
-

Addresses

+

+ {t("addresses")} +

- Manage your shipping and billing addresses + {t("addressesDescription")}

@@ -230,10 +232,10 @@ export default function AccountPage() {

- Payment Methods + {t("paymentMethods")}

- Manage your saved credit cards + {t("paymentMethodsDescription")}

@@ -247,9 +249,11 @@ export default function AccountPage() {
-

Profile

+

+ {t("profile")} +

- Update your personal information + {t("profileDescription")}

diff --git a/src/app/[country]/[locale]/(storefront)/account/profile/page.tsx b/src/app/[country]/[locale]/(storefront)/account/profile/page.tsx index 044d84c0..5ffad034 100644 --- a/src/app/[country]/[locale]/(storefront)/account/profile/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/profile/page.tsx @@ -1,6 +1,7 @@ "use client"; import { CircleAlert } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; import { Alert, AlertDescription } from "@/components/ui/alert"; @@ -23,6 +24,7 @@ function ProfileForm({ }; refreshUser: () => Promise; }) { + const t = useTranslations("profile"); // Initialize form data from user props - no useEffect needed const [formData, setFormData] = useState({ first_name: user.first_name || "", @@ -40,10 +42,10 @@ function ProfileForm({ const result = await updateCustomer(formData); if (result.success) { - toast.success("Profile updated successfully!"); + toast.success(t("profileUpdated")); await refreshUser(); } else { - setError(result.error || "Failed to update profile"); + setError(result.error || t("failedToUpdate")); } setSaving(false); @@ -51,7 +53,7 @@ function ProfileForm({ return (
-

Profile

+

{t("profile")}

@@ -64,7 +66,7 @@ function ProfileForm({ )}
- First Name + {t("firstName")} - Last Name + {t("lastName")} - Email Address + {t("emailAddress")}
@@ -114,17 +116,21 @@ function ProfileForm({

- Account Information + {t("accountInformation")}

-
Account ID
+
+ {t("accountId")} +
{user.id}
-
Email
+
+ {t("email")} +
{user.email}
@@ -136,12 +142,13 @@ function ProfileForm({ // Main page component - uses key prop to reset form when user changes export default function ProfilePage() { + const t = useTranslations("profile"); const { user, refreshUser } = useAuth(); if (!user) { return (
-

Loading profile...

+

{t("loadingProfile")}

); } diff --git a/src/app/[country]/[locale]/(storefront)/account/register/page.tsx b/src/app/[country]/[locale]/(storefront)/account/register/page.tsx index ce447bb7..79fae3f3 100644 --- a/src/app/[country]/[locale]/(storefront)/account/register/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/register/page.tsx @@ -3,6 +3,7 @@ import { CircleAlert, Eye, EyeOff } from "lucide-react"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; import { PolicyConsent } from "@/components/policy/PolicyConsent"; import { Alert, AlertDescription } from "@/components/ui/alert"; @@ -24,6 +25,8 @@ export default function RegisterPage() { const router = useRouter(); const pathname = usePathname(); const basePath = extractBasePath(pathname); + const t = useTranslations("register"); + const ta = useTranslations("account"); const { register, isAuthenticated, loading: authLoading } = useAuth(); const [firstName, setFirstName] = useState(""); @@ -55,18 +58,18 @@ export default function RegisterPage() { setError(null); if (password !== passwordConfirmation) { - setError("Passwords do not match"); + setError(t("passwordsDontMatch")); return; } if (password.length < 6) { - setError("Password must be at least 6 characters"); + setError(t("passwordTooShort")); return; } if (!policyConsent) { setPolicyError(true); - setError("You must agree to the store policies to create an account"); + setError(ta("policyConsentRequired")); document .getElementById("policy-consent") ?.scrollIntoView({ behavior: "smooth", block: "center" }); @@ -87,10 +90,10 @@ export default function RegisterPage() { if (result.success) { router.push(`${basePath}/account`); } else { - setError(result.error || "Registration failed. Please try again."); + setError(result.error || t("registrationFailed")); } } catch { - setError("An unexpected error occurred. Please try again."); + setError(t("unexpectedError")); } finally { setSubmitting(false); } @@ -100,8 +103,8 @@ export default function RegisterPage() {
- Create Account - Sign up to start shopping with us. + {t("createAccount")} + {t("signUpDescription")} @@ -115,44 +118,44 @@ export default function RegisterPage() {
- First name + {t("firstName")} setFirstName(e.target.value)} required - placeholder="John" + placeholder={t("firstNamePlaceholder")} /> - Last name + {t("lastName")} setLastName(e.target.value)} required - placeholder="Doe" + placeholder={t("lastNamePlaceholder")} />
- Email + {ta("email")} setEmail(e.target.value)} required - placeholder="you@example.com" + placeholder={t("emailPlaceholder")} /> - Password + {ta("password")}
setShowPassword(!showPassword)} aria-label={ - showPassword ? "Hide password" : "Show password" + showPassword ? ta("hidePassword") : ta("showPassword") } > {showPassword ? ( @@ -186,7 +189,7 @@ export default function RegisterPage() { - Confirm Password + {t("confirmPassword")}
{showPasswordConfirmation ? ( @@ -239,7 +242,7 @@ export default function RegisterPage() { size="lg" className="w-full" > - {submitting ? "Creating account..." : "Create Account"} + {submitting ? t("creatingAccount") : t("createAccount")}
@@ -247,12 +250,12 @@ export default function RegisterPage() {

- Already have an account?{" "} + {t("alreadyHaveAccount")}{" "} - Sign in + {t("signIn")}

diff --git a/src/app/[country]/[locale]/(storefront)/account/reset-password/page.tsx b/src/app/[country]/[locale]/(storefront)/account/reset-password/page.tsx index 2197096b..fbc558d8 100644 --- a/src/app/[country]/[locale]/(storefront)/account/reset-password/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/reset-password/page.tsx @@ -3,6 +3,7 @@ import { CircleAlert, CircleCheck, Eye, EyeOff } from "lucide-react"; import Link from "next/link"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; @@ -20,6 +21,8 @@ import { resetPassword } from "@/lib/data/customer"; import { extractBasePath } from "@/lib/utils/path"; export default function ResetPasswordPage() { + const t = useTranslations("resetPassword"); + const ta = useTranslations("account"); const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); @@ -41,10 +44,8 @@ export default function ResetPasswordPage() {
- Invalid link - - This password reset link is invalid or has expired. - + {t("invalidLink")} + {t("invalidLinkDescription")} @@ -52,7 +53,7 @@ export default function ResetPasswordPage() { href={`${basePath}/account/forgot-password`} className="text-sm text-primary hover:text-primary/70 font-medium" > - Request a new reset link + {t("requestNewLink")} @@ -65,12 +66,12 @@ export default function ResetPasswordPage() { setError(null); if (password !== passwordConfirmation) { - setError("Passwords do not match"); + setError(t("passwordsDontMatch")); return; } if (password.length < 6) { - setError("Password must be at least 6 characters"); + setError(t("passwordTooShort")); return; } @@ -81,13 +82,10 @@ export default function ResetPasswordPage() { if (result.success) { setSuccess(true); } else { - setError( - result.error || - "This reset link has expired. Please request a new one.", - ); + setError(result.error || t("linkExpired")); } } catch { - setError("Something went wrong. Please try again."); + setError(t("genericError")); } finally { setSubmitting(false); } @@ -101,11 +99,8 @@ export default function ResetPasswordPage() {
- Password reset successful - - Your password has been updated. You can now sign in with your new - password. - + {t("success")} + {t("successDescription")} @@ -114,7 +109,7 @@ export default function ResetPasswordPage() { className="w-full" onClick={() => router.push(`${basePath}/account`)} > - Sign in + {t("signIn")} @@ -126,8 +121,8 @@ export default function ResetPasswordPage() {
- Set a new password - Enter your new password below. + {t("title")} + {t("description")} @@ -140,7 +135,7 @@ export default function ResetPasswordPage() { )} - New password + {t("newPassword")}
setShowPassword(!showPassword)} aria-label={ - showPassword ? "Hide password" : "Show password" + showPassword ? ta("hidePassword") : ta("showPassword") } > {showPassword ? ( @@ -175,7 +170,7 @@ export default function ResetPasswordPage() { - Confirm new password + {t("confirmPassword")}
{showPasswordConfirmation ? ( @@ -220,7 +215,7 @@ export default function ResetPasswordPage() { size="lg" className="w-full" > - {submitting ? "Resetting..." : "Reset password"} + {submitting ? t("resetting") : t("resetPassword")}
@@ -231,7 +226,7 @@ export default function ResetPasswordPage() { href={`${basePath}/account`} className="text-sm text-primary hover:text-primary/70 font-medium" > - Back to sign in + {t("backToSignIn")} diff --git a/src/app/[country]/[locale]/(storefront)/c/[...permalink]/CategoryProductsContent.tsx b/src/app/[country]/[locale]/(storefront)/c/[...permalink]/CategoryProductsContent.tsx index e4648620..4c6cebee 100644 --- a/src/app/[country]/[locale]/(storefront)/c/[...permalink]/CategoryProductsContent.tsx +++ b/src/app/[country]/[locale]/(storefront)/c/[...permalink]/CategoryProductsContent.tsx @@ -1,6 +1,7 @@ "use client"; import type { ProductListParams } from "@spree/sdk"; +import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef } from "react"; import { ProductListingLayout } from "@/components/products/ProductListingLayout"; import { useStore } from "@/contexts/StoreContext"; @@ -19,6 +20,7 @@ export function CategoryProductsContent({ categoryName, basePath, }: CategoryProductsContentProps) { + const t = useTranslations("products"); const { currency } = useStore(); const fetchFn = useCallback( @@ -65,7 +67,7 @@ export function CategoryProductsContent({ onFilterChange={listing.handleFilterChange} listId={listId} listName={listName} - emptyMessage="No products found matching your filters." + emptyMessage={t("noProductsMatchingFilters")} /> ); } diff --git a/src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx b/src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx index 0fd9679f..c7528990 100644 --- a/src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx @@ -55,7 +55,11 @@ export default async function CategoryPage({ params }: CategoryPageProps) { style={{ backgroundImage: `url(${category.image_url})` }} >
- +

diff --git a/src/app/[country]/[locale]/(storefront)/cart/page.tsx b/src/app/[country]/[locale]/(storefront)/cart/page.tsx index a9353f0b..9ef8e6da 100644 --- a/src/app/[country]/[locale]/(storefront)/cart/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/cart/page.tsx @@ -4,6 +4,7 @@ import type { LineItem } from "@spree/sdk"; import { ShoppingBag } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useEffect, useRef } from "react"; import { Button } from "@/components/ui/button"; import { ProductImage } from "@/components/ui/product-image"; @@ -17,6 +18,8 @@ export default function CartPage() { const pathname = usePathname(); const basePath = extractBasePath(pathname); const viewCartFiredRef = useRef(false); + const t = useTranslations("cart"); + const tc = useTranslations("common"); // Track view_cart when cart loads with items useEffect(() => { @@ -62,14 +65,14 @@ export default function CartPage() { strokeWidth={1} />

- Your cart is empty + {t("emptyCart")}

-

- Looks like you haven't added anything to your cart yet. -

+

{t("emptyCartDescription")}

@@ -79,7 +82,9 @@ export default function CartPage() { return (
-

Shopping Cart

+

+ {t("shoppingCart")} +

{/* Cart Items */} @@ -125,10 +130,10 @@ export default function CartPage() {
@@ -139,22 +144,24 @@ export default function CartPage() { {/* Order Summary */}
-

Order Summary

+

+ {tc("orderSummary")} +

-
Subtotal
+
{tc("subtotal")}
{cart.display_item_total}
{cart.discount_total && parseFloat(cart.discount_total) < 0 && (
-
Discount
+
{tc("discount")}
{cart.display_discount_total}
)} {cart.delivery_total && parseFloat(cart.delivery_total) > 0 && (
-
Shipping
+
{tc("shipping")}
{cart.display_delivery_total}
@@ -162,12 +169,14 @@ export default function CartPage() { )} {cart.tax_total && parseFloat(cart.tax_total) > 0 && (
-
Tax
+
{tc("tax")}
{cart.display_tax_total}
)}
-
Total
+
+ {tc("total")} +
{cart.display_total}
@@ -175,13 +184,13 @@ export default function CartPage() { {cart.gift_card && parseFloat(cart.gift_card_total) > 0 ? (
-
Gift card
+
{t("giftCard")}
-{cart.display_gift_card_total}
) : cart.store_credit_total && parseFloat(cart.store_credit_total) > 0 ? (
-
Store credit
+
{t("storeCredit")}
-{cart.display_store_credit_total}
) : null} @@ -191,7 +200,7 @@ export default function CartPage() { parseFloat(cart.amount_due) > 0 && (
- Amount due + {t("amountDue")}
{cart.display_amount_due} @@ -203,11 +212,13 @@ export default function CartPage() {
diff --git a/src/app/[country]/[locale]/(storefront)/layout.tsx b/src/app/[country]/[locale]/(storefront)/layout.tsx index 14c25316..3b7729cd 100644 --- a/src/app/[country]/[locale]/(storefront)/layout.tsx +++ b/src/app/[country]/[locale]/(storefront)/layout.tsx @@ -45,14 +45,22 @@ export default async function StorefrontLayout({ return ( <> -
+
{rootCategories.length > 0 && ( )}
{children}
-
+
); } diff --git a/src/app/[country]/[locale]/(storefront)/page.tsx b/src/app/[country]/[locale]/(storefront)/page.tsx index a4e68029..893a79f8 100644 --- a/src/app/[country]/[locale]/(storefront)/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/page.tsx @@ -1,9 +1,10 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { getTranslations } from "next-intl/server"; import { ProductCarousel } from "@/components/products/ProductCarousel"; import { Button } from "@/components/ui/button"; import { generateHomeMetadata } from "@/lib/metadata/home"; -import { getStoreDescription, getStoreName } from "@/lib/seo"; +import { getStoreName } from "@/lib/seo"; interface HomePageProps { params: Promise<{ @@ -22,6 +23,11 @@ export async function generateMetadata({ export default async function HomePage({ params }: HomePageProps) { const { country, locale } = await params; const basePath = `/${country}/${locale}`; + const t = await getTranslations({ + locale: locale as Locale, + namespace: "home", + }); + const storeName = getStoreName(); return (
@@ -30,17 +36,19 @@ export default async function HomePage({ params }: HomePageProps) {

- Welcome to {getStoreName()} + {t("welcome", { storeName })}

- {getStoreDescription()} + {t("heroDescription")}

@@ -51,10 +59,10 @@ export default async function HomePage({ params }: HomePageProps) {

- Featured Products + {t("featuredProducts")}

diff --git a/src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx b/src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx index 12d52e30..659eb9fc 100644 --- a/src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; +import { getTranslations } from "next-intl/server"; import { getPolicy } from "@/lib/data/policies"; import { getStoreName } from "@/lib/seo"; @@ -14,23 +15,40 @@ interface PolicyPageProps { export async function generateMetadata({ params, }: PolicyPageProps): Promise { - const { slug } = await params; + const { slug, locale } = await params; const policy = await getPolicy(slug); + const storeName = getStoreName(); + if (!policy) { - return { title: "Policy Not Found" }; + const t = await getTranslations({ + locale: locale as Locale, + namespace: "policies", + }); + return { + title: t("policyNotFound"), + description: t("noContent"), + }; } - const storeName = getStoreName(); - return { title: storeName ? `${policy.name} | ${storeName}` : policy.name, + description: `${policy.name} — ${storeName}`, + openGraph: { + title: policy.name, + description: `${policy.name} — ${storeName}`, + }, }; } -export default async function PolicyPage({ params }: PolicyPageProps) { - const { slug } = await params; - const policy = await getPolicy(slug); +export default async function PolicyPage({ + params, +}: PolicyPageProps): Promise { + const { slug, locale } = await params; + const [policy, t] = await Promise.all([ + getPolicy(slug), + getTranslations({ locale: locale as Locale, namespace: "policies" }), + ]); if (!policy) { notFound(); @@ -49,7 +67,7 @@ export default async function PolicyPage({ params }: PolicyPageProps) { {policy.body}
) : ( -

No content available for this policy.

+

{t("noContent")}

)}
); diff --git a/src/app/[country]/[locale]/(storefront)/products/ProductsContent.tsx b/src/app/[country]/[locale]/(storefront)/products/ProductsContent.tsx index e94a4a4f..df8f7648 100644 --- a/src/app/[country]/[locale]/(storefront)/products/ProductsContent.tsx +++ b/src/app/[country]/[locale]/(storefront)/products/ProductsContent.tsx @@ -2,6 +2,7 @@ import type { ProductListParams } from "@spree/sdk"; import { useSearchParams } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef } from "react"; import { ProductListingLayout } from "@/components/products/ProductListingLayout"; import { useStore } from "@/contexts/StoreContext"; @@ -16,6 +17,7 @@ interface ProductsContentProps { export function ProductsContent({ basePath }: ProductsContentProps) { const searchParams = useSearchParams(); const { currency } = useStore(); + const t = useTranslations("products"); const query = searchParams.get("q") || ""; const fetchFn = useCallback( @@ -68,17 +70,18 @@ export function ProductsContent({ basePath }: ProductsContentProps) { {query ? ( <>

- Search results for “{query}” + {t("searchResultsFor", { query })}

- {listing.totalCount}{" "} - {listing.totalCount === 1 ? "product" : "products"} found + {t("productsFound", { count: listing.totalCount })}

) : ( <> -

All Products

-

Browse our complete collection

+

+ {t("allProducts")} +

+

{t("browseCollection")}

)}
@@ -90,9 +93,7 @@ export function ProductsContent({ basePath }: ProductsContentProps) { listId={listId} listName={listName} emptyMessage={ - query - ? `We couldn't find any products matching "${query}"` - : "Try adjusting your filters" + query ? t("noMatchingProducts", { query }) : t("tryAdjustingFilters") } />
diff --git a/src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx b/src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx index df35119b..bc1bb89c 100644 --- a/src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx +++ b/src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx @@ -2,6 +2,7 @@ import type { Media, Product, Variant } from "@spree/sdk"; import { CircleCheckBig, CircleX, Loader2, ShoppingBag } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useEffect, useMemo, useState } from "react"; import { MediaGallery } from "@/components/products/MediaGallery"; import { ProductCustomFields } from "@/components/products/ProductCustomFields"; @@ -20,6 +21,7 @@ interface ProductDetailsProps { export function ProductDetails({ product, basePath }: ProductDetailsProps) { const { addItem } = useCart(); const { currency } = useStore(); + const t = useTranslations("products"); // Filter variants list const variants = useMemo(() => { @@ -143,7 +145,7 @@ export function ProductDetails({ product, basePath }: ProductDetailsProps) { {strikethroughPrice} - Sale + {t("sale")} )} @@ -154,12 +156,12 @@ export function ProductDetails({ product, basePath }: ProductDetailsProps) { {inStock ? ( - In Stock + {t("inStock")} ) : ( - Out of Stock + {t("outOfStock")} )}
@@ -195,15 +197,15 @@ export function ProductDetails({ product, basePath }: ProductDetailsProps) { {loading ? ( <> - Adding... + {t("adding")} ) : isPurchasable ? ( <> - Add to Cart + {t("addToCart")} ) : ( - "Out of Stock" + t("outOfStock") )}
@@ -213,7 +215,7 @@ export function ProductDetails({ product, basePath }: ProductDetailsProps) { {product.description && (

- Description + {t("description")}

{/* Description is admin-authored HTML from the Spree CMS backend (trusted source) */}
-

Details

+

+ {t("details")} +

{selectedVariant?.sku && (
-
SKU
+
{t("sku")}
{selectedVariant.sku}
@@ -240,7 +244,7 @@ export function ProductDetails({ product, basePath }: ProductDetailsProps) { )} {selectedVariant?.options_text && (
-
Options
+
{t("options")}
{selectedVariant.options_text}
diff --git a/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx b/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx index a7ae860b..79874581 100644 --- a/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx @@ -90,6 +90,7 @@ export default async function ProductPage({ category={breadcrumbCategory} basePath={basePath} productName={product.name} + locale={locale} /> )}
diff --git a/src/app/[country]/[locale]/layout.tsx b/src/app/[country]/[locale]/layout.tsx index 519095f2..53b37c14 100644 --- a/src/app/[country]/[locale]/layout.tsx +++ b/src/app/[country]/[locale]/layout.tsx @@ -1,15 +1,31 @@ import type { Metadata } from "next"; import { redirect } from "next/navigation"; +import { NextIntlClientProvider } from "next-intl"; +import { CartDrawer } from "@/components/cart/CartDrawer"; import { JsonLd } from "@/components/seo/JsonLd"; +import { Toaster } from "@/components/ui/sonner"; import { AuthProvider } from "@/contexts/AuthContext"; import { StoreProvider } from "@/contexts/StoreContext"; import { getMarkets } from "@/lib/data/markets"; import { generateStoreMetadata } from "@/lib/metadata/store"; import { buildOrganizationJsonLd } from "@/lib/seo"; +import deMessages from "../../../../messages/de.json"; +import enMessages from "../../../../messages/en.json"; +import esMessages from "../../../../messages/es.json"; +import frMessages from "../../../../messages/fr.json"; +import plMessages from "../../../../messages/pl.json"; const DEFAULT_COUNTRY = process.env.NEXT_PUBLIC_DEFAULT_COUNTRY || "us"; const DEFAULT_LOCALE = process.env.NEXT_PUBLIC_DEFAULT_LOCALE || "en"; +const messagesMap: Record = { + en: enMessages, + de: deMessages, + es: esMessages, + fr: frMessages, + pl: plMessages, +}; + interface CountryLocaleLayoutProps { children: React.ReactNode; params: Promise<{ @@ -52,16 +68,26 @@ export default async function CountryLocaleLayout({ redirect(`/${fallbackCountry}/${fallbackLocale}`); } + // Load messages statically (no runtime data access) to avoid blocking prerender + const messages = messagesMap[locale] || messagesMap.en; + return ( - - - - {children} - - + + + + {children} + + + + + ); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 24d52358..b9767518 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,8 +4,6 @@ import type { Metadata } from "next"; import { Geist } from "next/font/google"; import "./globals.css"; import { Suspense } from "react"; -import { CartDrawer } from "@/components/cart/CartDrawer"; -import { Toaster } from "@/components/ui/sonner"; import { CartProvider } from "@/contexts/CartContext"; const gtmId = process.env.GTM_ID; @@ -38,12 +36,8 @@ export default function RootLayout({ - - - {children} - - - + + {children} diff --git a/src/components/account/CreditCardList.tsx b/src/components/account/CreditCardList.tsx index 044f0520..62095f0d 100644 --- a/src/components/account/CreditCardList.tsx +++ b/src/components/account/CreditCardList.tsx @@ -1,6 +1,7 @@ "use client"; import type { CreditCard as SpreeCreditCard } from "@spree/sdk"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { PaymentIcon } from "react-svg-credit-card-payment-icons"; import { @@ -25,6 +26,7 @@ function CreditCardItem({ card: SpreeCreditCard; onDelete: () => void; }) { + const t = useTranslations("creditCards"); const [deleting, setDeleting] = useState(false); const handleDelete = async () => { @@ -47,10 +49,16 @@ function CreditCardItem({ />

- {getCardLabel(card.brand)} ending in {card.last4} + {t("cardEndingIn", { + label: getCardLabel(card.brand), + digits: card.last4, + })}

- Exp {String(card.month).padStart(2, "0")}/{card.year} + {t("cardExpires", { + month: String(card.month).padStart(2, "0"), + year: String(card.year), + })}

{card.name && (

{card.name}

@@ -60,27 +68,31 @@ function CreditCardItem({
{card.default && ( - Default + {t("default")} )} - Remove payment method? + + {t("removePaymentMethodTitle")} + - This will remove the {getCardLabel(card.brand)} ending in{" "} - {card.last4} from your account. This action cannot be undone. + {t("confirmRemoveDescription", { + label: getCardLabel(card.brand), + digits: card.last4, + })} - Cancel + {t("cancel")} - Remove + {t("remove")} @@ -96,6 +108,7 @@ interface CreditCardListProps { } export function CreditCardList({ initialCards }: CreditCardListProps) { + const t = useTranslations("creditCards"); const [cards, setCards] = useState(initialCards); const handleDelete = async (id: string) => { @@ -103,7 +116,7 @@ export function CreditCardList({ initialCards }: CreditCardListProps) { if (result.success) { setCards((prev) => prev.filter((card) => card.id !== id)); } else { - alert(`Failed to remove card: ${result.error}`); + alert(t("failedToRemove")); } }; diff --git a/src/components/account/GiftCardList.tsx b/src/components/account/GiftCardList.tsx index 987873c5..c36f7a15 100644 --- a/src/components/account/GiftCardList.tsx +++ b/src/components/account/GiftCardList.tsx @@ -2,6 +2,7 @@ import type { GiftCard } from "@spree/sdk"; import { Check, ClipboardCopy } from "lucide-react"; +import { useLocale, useTranslations } from "next-intl"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { formatDate } from "@/lib/utils/format"; @@ -22,24 +23,28 @@ function getStateColor(state: string, expired: boolean): string { } } -function getStateLabel(state: string): string { +function getStateLabel( + state: string, + t: ReturnType>, +): string { switch (state) { case "active": - return "Active"; + return t("active"); case "partially_redeemed": - return "Partially Used"; + return t("partiallyUsed"); case "redeemed": - return "Fully Redeemed"; + return t("fullyRedeemed"); case "canceled": - return "Canceled"; + return t("canceled"); case "expired": - return "Expired"; + return t("expired"); default: return state; } } function CopyButton({ code }: { code: string }) { + const t = useTranslations("giftCards"); const [copied, setCopied] = useState(false); useEffect(() => { @@ -67,17 +72,17 @@ function CopyButton({ code }: { code: string }) { variant="ghost" size="sm" onClick={handleCopy} - title="Copy code to clipboard" + title={t("copyCodeToClipboard")} > {copied ? ( <> - Copied! + {t("copied")} ) : ( <> - Copy + {t("copy")} )} @@ -85,6 +90,8 @@ function CopyButton({ code }: { code: string }) { } function GiftCardItem({ card }: { card: GiftCard }) { + const t = useTranslations("giftCards"); + const locale = useLocale(); const usagePercentage = Number(card.amount) > 0 ? Math.round((Number(card.amount_used) / Number(card.amount)) * 100) @@ -102,20 +109,22 @@ function GiftCardItem({ card }: { card: GiftCard }) { - {getStateLabel(card.status)} + {getStateLabel(card.status, t)}

{card.expires_at - ? `Expires ${formatDate(card.expires_at)}` - : "No expiration date"} + ? t("expiresOn", { + date: formatDate(card.expires_at, "-", locale), + }) + : t("noExpiration")}

{card.display_amount_remaining}

-

remaining

+

{t("remaining")}

@@ -123,9 +132,11 @@ function GiftCardItem({ card }: { card: GiftCard }) {
- Used: {card.display_amount_used} + {t("usedAmount", { amount: card.display_amount_used })} + + + {t("totalAmountWithValue", { amount: card.display_amount })} - Total: {card.display_amount}
-

{usagePercentage}% used

+

+ {t("percentUsed", { percent: usagePercentage })} +

{/* Additional info */} {card.redeemed_at && (

- Fully redeemed on {formatDate(card.redeemed_at)} + {t("fullyRedeemedOnDate", { + date: formatDate(card.redeemed_at, "-", locale), + })}

)} @@ -155,6 +170,7 @@ interface GiftCardListProps { } export function GiftCardList({ cards }: GiftCardListProps) { + const t = useTranslations("giftCards"); const activeCards = cards.filter((c) => c.active && !c.expired); const inactiveCards = cards.filter((c) => !c.active || c.expired); @@ -164,7 +180,7 @@ export function GiftCardList({ cards }: GiftCardListProps) { {activeCards.length > 0 && (

- Active Gift Cards ({activeCards.length}) + {t("activeGiftCardsCount", { count: activeCards.length })}

{activeCards.map((card) => ( @@ -178,7 +194,7 @@ export function GiftCardList({ cards }: GiftCardListProps) { {inactiveCards.length > 0 && (

- Expired / Redeemed ({inactiveCards.length}) + {t("expiredRedeemedCount", { count: inactiveCards.length })}

{inactiveCards.map((card) => ( diff --git a/src/components/account/OrderDetail.tsx b/src/components/account/OrderDetail.tsx index ee3a9c9c..9d4c8ede 100644 --- a/src/components/account/OrderDetail.tsx +++ b/src/components/account/OrderDetail.tsx @@ -1,159 +1,29 @@ -import type { Address, Fulfillment, Order } from "@spree/sdk"; -import { ChevronLeft, CircleAlert } from "lucide-react"; +import type { Order } from "@spree/sdk"; +import { ChevronLeft } from "lucide-react"; import Link from "next/link"; +import { getTranslations } from "next-intl/server"; import { AddressBlock } from "@/components/order/AddressBlock"; +import { FulfillmentBlock } from "@/components/order/FulfillmentBlock"; +import { LineItemCard } from "@/components/order/LineItemCard"; import { OrderTotals } from "@/components/order/OrderTotals"; import { PaymentInfo } from "@/components/order/PaymentInfo"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { ProductImage } from "@/components/ui/product-image"; -import { formatDateTime, getFulfillmentStatusColor } from "@/lib/utils/format"; - -function LineItemCard({ - item, - basePath, -}: { - item: Order["items"][number]; - basePath: string; -}) { - return ( -
- - - - -
- - {item.name} - -
{item.display_price}
- {item.options_text && ( -

{item.options_text}

- )} -

Qty: {item.quantity}

- - Order again - -
- -
- {item.display_total} -
-
- ); -} - -function FulfillmentBlock({ - fulfillment, - shipAddress, - basePath, - lineItems, -}: { - fulfillment: Fulfillment; - shipAddress: Address | null; - basePath: string; - lineItems: Order["items"]; -}) { - return ( -
-
-
- {shipAddress && ( -
-

- Delivery Address -

- -
- )} -
-
-

- Shipping Method -

-

- {fulfillment.delivery_method?.name || "Canceled"} -

- {fulfillment.stock_location && ( -

- Shipped from {fulfillment.stock_location.name} -

- )} - - {fulfillment.status} - -
-
- {fulfillment.status === "shipped" && fulfillment.tracking_url ? ( - - ) : ( - - )} -
-
-
- - {fulfillment.status === "canceled" && !fulfillment.fulfilled_at && ( - - - - Fulfillment canceled — a refund has been issued. - - - )} - {fulfillment.status !== "canceled" && - fulfillment.status !== "shipped" && - !fulfillment.tracking && ( -
- No tracking information present -
- )} -
- -
- {lineItems.map((item) => ( -
- -
- ))} -
-
- ); -} +import { formatDateTime } from "@/lib/utils/format"; interface OrderDetailProps { order: Order; basePath: string; + locale: string; } -export function OrderDetail({ order, basePath }: OrderDetailProps) { +export async function OrderDetail({ + order, + basePath, + locale, +}: OrderDetailProps) { + const t = await getTranslations({ + locale: locale as Locale, + namespace: "orders", + }); const hasFulfillments = order.fulfillments && order.fulfillments.length > 0; return ( @@ -163,14 +33,14 @@ export function OrderDetail({ order, basePath }: OrderDetailProps) { className="text-sm text-gray-500 hover:text-gray-700 mb-4 inline-flex items-center gap-1" > - Back to orders + {t("backToOrders")}

- Order #{order.number} + {t("orderTitle", { number: order.number })}

- Placed on {formatDateTime(order.completed_at)} + {t("placedOn", { date: formatDateTime(order.completed_at, locale) })}

{hasFulfillments ? ( @@ -210,7 +80,7 @@ export function OrderDetail({ order, basePath }: OrderDetailProps) { {order.customer_note && (

- Customer Note + {t("specialInstructions")}

{order.customer_note}

@@ -221,7 +91,7 @@ export function OrderDetail({ order, basePath }: OrderDetailProps) { {order.billing_address && (

- Billing Address + {t("billingAddress")}

@@ -229,7 +99,7 @@ export function OrderDetail({ order, basePath }: OrderDetailProps) { {order.payments && order.payments.length > 0 && (

- Payment Information + {t("paymentInformation")}

{order.payments .filter((p) => p.status !== "void" && p.status !== "invalid") diff --git a/src/components/account/OrderList.tsx b/src/components/account/OrderList.tsx index e91965d9..9aab79a4 100644 --- a/src/components/account/OrderList.tsx +++ b/src/components/account/OrderList.tsx @@ -1,5 +1,6 @@ import type { Order } from "@spree/sdk"; import Link from "next/link"; +import { getTranslations } from "next-intl/server"; import { Button } from "@/components/ui/button"; import { formatDate, @@ -7,12 +8,32 @@ import { getPaymentStatusColor, } from "@/lib/utils/format"; +function getStatusLabel( + status: string | null, + t: (key: string) => string, +): string { + if (!status) return t("notAvailable"); + // Map API statuses like "balance_due" to translation keys like "balanceDue" + const key = status.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase()); + try { + return t(key); + } catch { + return status.replace(/_/g, " "); + } +} + interface OrderListProps { orders: Order[]; basePath: string; + locale: string; } -export function OrderList({ orders, basePath }: OrderListProps) { +export async function OrderList({ orders, basePath, locale }: OrderListProps) { + const t = await getTranslations({ + locale: locale as Locale, + namespace: "orders", + }); + return (
@@ -20,22 +41,22 @@ export function OrderList({ orders, basePath }: OrderListProps) { - Order + {t("order")} - Date + {t("date")} - Payment + {t("payment")} - Delivery + {t("shipment")} - Total + {t("totalColumn")} - Actions + {t("actions")} @@ -53,21 +74,21 @@ export function OrderList({ orders, basePath }: OrderListProps) { - {formatDate(order.completed_at)} + {formatDate(order.completed_at, "-", locale)} - {order.payment_status?.replace("_", " ") || "N/A"} + {getStatusLabel(order.payment_status ?? null, t)} - {order.fulfillment_status || "N/A"} + {getStatusLabel(order.fulfillment_status ?? null, t)} @@ -78,7 +99,7 @@ export function OrderList({ orders, basePath }: OrderListProps) { diff --git a/src/components/addresses/AddressManagement.tsx b/src/components/addresses/AddressManagement.tsx index 6f58930b..16b06b78 100644 --- a/src/components/addresses/AddressManagement.tsx +++ b/src/components/addresses/AddressManagement.tsx @@ -2,6 +2,7 @@ import type { Address, AddressParams, Country } from "@spree/sdk"; import { Plus } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useCallback, useState } from "react"; import { AddressEditModal } from "@/components/checkout/AddressEditModal"; import { @@ -31,6 +32,7 @@ interface AddressCardProps { } function AddressCard({ address, onEdit, onDelete }: AddressCardProps) { + const t = useTranslations("address"); const [deleting, setDeleting] = useState(false); const handleDelete = async () => { @@ -58,26 +60,25 @@ function AddressCard({ address, onEdit, onDelete }: AddressCardProps) {
- Delete address? + {t("deleteAddressTitle")} - This will permanently delete this address. This action cannot - be undone. + {t("deleteAddressConfirmation")} - Cancel + {t("cancel")} - Delete + {t("delete")} @@ -103,6 +104,7 @@ export function AddressManagement({ emptyState, user, }: AddressManagementProps) { + const t = useTranslations("address"); const [addresses, setAddresses] = useState(initialAddresses); const [modalOpen, setModalOpen] = useState(false); const [editingAddress, setEditingAddress] = useState
(null); @@ -155,14 +157,14 @@ export function AddressManagement({ if (result.success) { setAddresses((prev) => prev.filter((addr) => addr.id !== id)); } else { - alert(`Failed to delete address: ${result.error}`); + alert(t("failedToDeleteAddress")); } }; if (emptyState && addresses.length === 0) { return ( <> - + {modalOpen && (
)} diff --git a/src/components/cart/CartDrawer.tsx b/src/components/cart/CartDrawer.tsx index a499f9ad..525399bc 100644 --- a/src/components/cart/CartDrawer.tsx +++ b/src/components/cart/CartDrawer.tsx @@ -3,6 +3,7 @@ import { ShoppingBag, Trash, X } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useEffect, useRef } from "react"; import { Button } from "@/components/ui/button"; import { ProductImage } from "@/components/ui/product-image"; @@ -29,6 +30,8 @@ export function CartDrawer() { removeItem, itemCount, } = useCart(); + const t = useTranslations("cart"); + const tc = useTranslations("common"); const pathname = usePathname(); const basePath = extractBasePath(pathname); const viewCartFiredRef = useRef(false); @@ -77,16 +80,18 @@ export function CartDrawer() { - Cart + {t("cart")} {itemCount > 0 && ( - ({itemCount} items) + + {t("itemCount", { count: itemCount })} + )} @@ -110,13 +115,13 @@ export function CartDrawer() { className="w-16 h-16 text-gray-300 mb-4" strokeWidth={1} /> -

Your cart is empty

+

{t("emptyCart")}

- Continue Shopping + {tc("continueShopping")}
) : ( @@ -159,7 +164,7 @@ export function CartDrawer() { } }} disabled={updating} - aria-label="Remove item" + aria-label={t("removeItemLabel", { name: item.name })} > @@ -218,18 +223,20 @@ export function CartDrawer() { {/* Summary */}
- Subtotal + {tc("subtotal")} {cart?.display_item_total}
{cart?.discount_total && parseFloat(cart.discount_total) < 0 && (
- Discount + {tc("discount")} {cart.display_discount_total}
)}
- Shipping - Calculated on checkout + {tc("shipping")} + + {t("shippingCalculatedAtCheckout")} +
@@ -240,12 +247,12 @@ export function CartDrawer() { href={`${basePath}/checkout/${cart?.id}`} onClick={closeCart} > - Checkout + {t("checkout")}
diff --git a/src/components/checkout/AddressEditModal.tsx b/src/components/checkout/AddressEditModal.tsx index 4c620df1..d9ab09cc 100644 --- a/src/components/checkout/AddressEditModal.tsx +++ b/src/components/checkout/AddressEditModal.tsx @@ -2,6 +2,7 @@ import type { Country, State } from "@spree/sdk"; import { CircleAlert } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; @@ -52,6 +53,8 @@ export function AddressEditModal({ title, user, }: AddressEditModalProps) { + const t = useTranslations("address"); + const tc = useTranslations("common"); const [formData, setFormData] = useState(() => { if (address) return addressToFormData(address); return { @@ -80,14 +83,14 @@ export function AddressEditModal({ await onSave(formDataToAddress(formData), address?.id); onClose(); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to save address"); + setError(err instanceof Error ? err.message : t("failedToSave")); } finally { setSaving(false); } }; const modalTitle = - title ?? (address?.id ? "Edit Address" : "Add New Address"); + title ?? (address?.id ? t("editAddress") : t("addNewAddress")); return (
diff --git a/src/components/checkout/AddressFormFields.tsx b/src/components/checkout/AddressFormFields.tsx index ec5b30f2..b4dd0d60 100644 --- a/src/components/checkout/AddressFormFields.tsx +++ b/src/components/checkout/AddressFormFields.tsx @@ -1,6 +1,7 @@ "use client"; import type { Country, State } from "@spree/sdk"; +import { useTranslations } from "next-intl"; import { Input } from "@/components/ui/input"; import { NativeSelect, @@ -25,6 +26,8 @@ export function AddressFormFields({ onChange, idPrefix, }: AddressFormFieldsProps) { + const t = useTranslations("address"); + const tc = useTranslations("common"); const hasStates = states.length > 0; return ( @@ -33,14 +36,14 @@ export function AddressFormFields({
onChange("country_iso", e.target.value)} required > - Country/Region + {t("selectCountry")} {countries.map((country) => ( @@ -55,19 +58,19 @@ export function AddressFormFields({ onChange("first_name", e.target.value)} - placeholder="First name (optional)" + placeholder={t("firstName")} /> onChange("last_name", e.target.value)} - placeholder="Last name" + placeholder={t("lastName")} />
@@ -75,31 +78,31 @@ export function AddressFormFields({ onChange("company", e.target.value)} - placeholder="Company (optional)" + placeholder={t("company")} /> {/* Address */} onChange("address1", e.target.value)} - placeholder="Address" + placeholder={t("address")} /> {/* Apartment */} onChange("address2", e.target.value)} - placeholder="Apartment, suite, etc. (optional)" + placeholder={t("apartment")} /> {/* City / State / ZIP — 3 columns */} @@ -107,32 +110,32 @@ export function AddressFormFields({ onChange("city", e.target.value)} - placeholder="City" + placeholder={t("city")} /> {loadingStates ? ( - Loading... + {tc("loading")} ) : hasStates ? ( onChange("state_abbr", e.target.value)} required > - State + {t("selectState")} {states.map((state) => ( @@ -144,20 +147,20 @@ export function AddressFormFields({ onChange("state_name", e.target.value)} - placeholder="State" + placeholder={t("stateProvince")} /> )} onChange("postal_code", e.target.value)} - placeholder="ZIP code" + placeholder={t("zipCode")} />
@@ -165,10 +168,10 @@ export function AddressFormFields({ onChange("phone", e.target.value)} - placeholder="Phone (optional)" + placeholder={t("phone")} />
); diff --git a/src/components/checkout/AddressSection.tsx b/src/components/checkout/AddressSection.tsx index 53d9ac4d..b595df54 100644 --- a/src/components/checkout/AddressSection.tsx +++ b/src/components/checkout/AddressSection.tsx @@ -3,6 +3,7 @@ import type { Address, AddressParams, Cart, Country, State } from "@spree/sdk"; import { Loader2 } from "lucide-react"; import Link from "next/link"; +import { useTranslations } from "next-intl"; import { useCallback, useEffect, useRef, useState } from "react"; import { AddressEditModal } from "@/components/checkout/AddressEditModal"; import { AddressFormFields } from "@/components/checkout/AddressFormFields"; @@ -81,6 +82,10 @@ export function AddressSection({ processing, user, }: AddressSectionProps) { + const t = useTranslations("checkout"); + const tc = useTranslations("common"); + const ta = useTranslations("address"); + // Determine initial saved address: use the first saved address when the // cart doesn't have a shipping address yet (authenticated users). const initialSavedAddress = @@ -244,13 +249,15 @@ export function AddressSection({ {/* Contact section */}
-

Contact

+

+ {t("contactInformation")} +

{!isAuthenticated && ( - Sign in + {t("signIn")} )}
@@ -262,11 +269,11 @@ export function AddressSection({ onChange={(e) => setEmail(e.target.value)} onBlur={handleEmailBlur} disabled={isAuthenticated} - placeholder="Email" + placeholder={t("emailAddress")} /> {isAuthenticated && (

- Using your account email address + {t("usingAccountEmail")}

)}
@@ -274,11 +281,13 @@ export function AddressSection({ {/* Delivery section */}
-

Delivery

+

+ {t("shippingAddress")} +

{saving && ( - Saving + {tc("saving")} )}
@@ -322,7 +331,7 @@ export function AddressSection({ fetchStates={fetchStates} onSave={handleSaveEditedAddress} onClose={() => setEditingAddress(null)} - title="Edit Address" + title={ta("editAddress")} /> )} diff --git a/src/components/checkout/AddressSelector.tsx b/src/components/checkout/AddressSelector.tsx index 542a9bd6..2cb78d2e 100644 --- a/src/components/checkout/AddressSelector.tsx +++ b/src/components/checkout/AddressSelector.tsx @@ -2,6 +2,7 @@ import type { Address, Country, State } from "@spree/sdk"; import { MapPin } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useCallback, useMemo } from "react"; import { AddressFormFields } from "@/components/checkout/AddressFormFields"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; @@ -35,6 +36,8 @@ export function AddressSelector({ idPrefix, user, }: AddressSelectorProps) { + const t = useTranslations("address"); + const tc = useTranslations("common"); // Derive selected address from current form data — no useEffect needed const selectedAddressId = useMemo((): string => { if (savedAddresses.length === 0) return "new"; @@ -132,7 +135,7 @@ export function AddressSelector({ }} className="text-xs text-gray-500 underline underline-offset-2 hover:text-gray-900 flex-shrink-0" > - Edit + {tc("edit")} )} @@ -149,7 +152,7 @@ export function AddressSelector({ - Use a different address + {t("useDifferentAddress")} diff --git a/src/components/checkout/CouponCode.tsx b/src/components/checkout/CouponCode.tsx index 68911ac5..ab90c1c7 100644 --- a/src/components/checkout/CouponCode.tsx +++ b/src/components/checkout/CouponCode.tsx @@ -2,6 +2,7 @@ import type { Cart } from "@spree/sdk"; import { X } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -23,6 +24,7 @@ export function CouponCode({ onRemoveDiscount, onRemoveGiftCard, }: CouponCodeProps) { + const t = useTranslations("coupon"); const [code, setCode] = useState(""); const [applying, setApplying] = useState(false); const [removing, setRemoving] = useState(null); @@ -41,26 +43,34 @@ export function CouponCode({ setApplying(true); setError(null); - const result = await onApply(code.trim()); - if (result.success) { - setCode(""); - } else { - setError(result.error || "Invalid code"); + try { + const result = await onApply(code.trim()); + if (result.success) { + setCode(""); + } else { + setError(result.error || t("invalidCode")); + } + } catch { + setError(t("invalidCode")); + } finally { + setApplying(false); } - - setApplying(false); }; const handleRemoveDiscount = async (discountCode: string) => { setRemoving(discountCode); setError(null); - const result = await onRemoveDiscount(discountCode); - if (!result.success) { - setError(result.error || "Failed to remove discount code"); + try { + const result = await onRemoveDiscount(discountCode); + if (!result.success) { + setError(result.error || t("failedToRemove")); + } + } catch { + setError(t("failedToRemove")); + } finally { + setRemoving(null); } - - setRemoving(null); }; const handleRemoveGiftCard = async () => { @@ -69,15 +79,20 @@ export function CouponCode({ setRemoving(appliedGiftCard.id); setError(null); - const result = await onRemoveGiftCard(appliedGiftCard.id); - if (!result.success) { - setError(result.error || "Failed to remove gift card"); + try { + const result = await onRemoveGiftCard(appliedGiftCard.id); + if (!result.success) { + setError(result.error || t("failedToRemoveGiftCard")); + } + } catch { + setError(t("failedToRemoveGiftCard")); + } finally { + setRemoving(null); } - - setRemoving(null); }; - const hasAppliedCode = couponPromotions.length > 0 && !!appliedGiftCard; + // Hide the input only when both a coupon and a gift card are applied + const hasAllCodesApplied = couponPromotions.length > 0 && !!appliedGiftCard; return (
@@ -101,7 +116,9 @@ export function CouponCode({ )} diff --git a/src/components/checkout/DeliveryMethodSection.tsx b/src/components/checkout/DeliveryMethodSection.tsx index 1abc969d..0f89a401 100644 --- a/src/components/checkout/DeliveryMethodSection.tsx +++ b/src/components/checkout/DeliveryMethodSection.tsx @@ -1,6 +1,7 @@ "use client"; import type { Fulfillment } from "@spree/sdk"; +import { useTranslations } from "next-intl"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; interface DeliveryMethodSectionProps { @@ -19,9 +20,13 @@ export function DeliveryMethodSection({ processing, errors, }: DeliveryMethodSectionProps) { + const t = useTranslations("checkout"); + return (
-

Shipping method

+

+ {t("shippingMethod")} +

{errors && errors.length > 0 && (
@@ -35,7 +40,7 @@ export function DeliveryMethodSection({ {fulfillments.length === 0 ? (
- Enter your shipping address to view available shipping methods. + {t("enterShippingAddressForMethods")}
) : (
@@ -47,11 +52,17 @@ export function DeliveryMethodSection({
{fulfillments.length > 1 && (

- Package {index + 1} of {fulfillments.length} + {t("shipmentOf", { + current: index + 1, + total: fulfillments.length, + })} {fulfillment.stock_location?.name && ( {" "} - — Fulfilled from {fulfillment.stock_location.name} + —{" "} + {t("shipsFrom", { + location: fulfillment.stock_location.name, + })} )}

diff --git a/src/components/checkout/PaymentSection.tsx b/src/components/checkout/PaymentSection.tsx index 2c450c5a..ec92294a 100644 --- a/src/components/checkout/PaymentSection.tsx +++ b/src/components/checkout/PaymentSection.tsx @@ -8,6 +8,7 @@ import type { State, } from "@spree/sdk"; import { CircleAlert, CreditCard, Loader2 } from "lucide-react"; +import { useTranslations } from "next-intl"; import { forwardRef, useCallback, @@ -74,6 +75,8 @@ export const PaymentSection = forwardRef< }, ref, ) { + const t = useTranslations("checkout"); + // Initialize billing address from cart, check if it matches shipping const shipAddressData = useMemo( () => addressToFormData(cart.shipping_address), @@ -146,21 +149,21 @@ export const PaymentSection = forwardRef< setClientSecret(secret); setPaymentSessionId(result.session.id); } else { - setGatewayError("Failed to initialize payment. Please try again."); + setGatewayError(t("failedToInitPayment")); } } else if (!result.success) { - setGatewayError(result.error || "Failed to create payment session."); + setGatewayError(result.error || t("failedToCreateSession")); } } catch { if (requestId !== sessionRequestIdRef.current) return; - setGatewayError("Failed to initialize payment. Please try again."); + setGatewayError(t("failedToInitPayment")); } finally { if (requestId === sessionRequestIdRef.current) { setLoading(false); } } }, - [sessionPaymentMethod, cart.id], + [sessionPaymentMethod, cart.id, t], ); // Track the cart total so we can recreate the session when it changes @@ -247,10 +250,10 @@ export const PaymentSection = forwardRef< () => ({ submit: async () => { if (!paymentSessionId || !clientSecret) { - return { error: "Payment not ready" }; + return { error: t("failedToInitPayment") }; } if (!selectedCardId && !gatewayHandleRef.current) { - return { error: "Payment not ready" }; + return { error: t("failedToInitPayment") }; } setProcessing(true); @@ -272,7 +275,7 @@ export const PaymentSection = forwardRef< if (!addressSuccess) { setProcessing(false); - return { error: "Failed to save billing address" }; + return { error: t("failedToSaveBilling") }; } // 2. Confirm payment with gateway @@ -307,7 +310,7 @@ export const PaymentSection = forwardRef< await onPaymentComplete(paymentSessionId); return {}; } catch { - const msg = "An error occurred during payment. Please try again."; + const msg = t("paymentError"); setGatewayError(msg); setProcessing(false); return { error: msg }; @@ -324,6 +327,7 @@ export const PaymentSection = forwardRef< onPaymentComplete, cart.id, setProcessing, + t, ], ); @@ -332,9 +336,9 @@ export const PaymentSection = forwardRef< return (
{/* Section Header */} -

Payment

+

{t("paymentMethod")}

- All transactions are secure and encrypted. + {t("secureTransactions")}

{/* Inline requirement errors from parent */} @@ -377,14 +381,20 @@ export const PaymentSection = forwardRef< width={34} /> - {getCardLabel(card.brand)} ending in {card.last4} + {t("savedCardLabel", { + brand: getCardLabel(card.brand), + digits: card.last4, + })} - Exp {String(card.month).padStart(2, "0")}/{card.year} + {t("cardExpiry", { + month: String(card.month).padStart(2, "0"), + year: String(card.year), + })} {card.default && ( - Default + {t("default")} )} @@ -399,7 +409,7 @@ export const PaymentSection = forwardRef< - Add new payment method + {t("addNewPaymentMethod")} @@ -411,7 +421,7 @@ export const PaymentSection = forwardRef<
- Credit card + {t("creditCard")}
@@ -423,7 +433,7 @@ export const PaymentSection = forwardRef<
- Loading payment form... + {t("loadingPaymentForm")}
)} @@ -455,9 +465,7 @@ export const PaymentSection = forwardRef< className="w-10 h-10 text-gray-300 mx-auto mb-3" strokeWidth={1.5} /> -

- No payment methods available for this cart. -

+

{t("noPaymentMethods")}

)}
@@ -472,9 +480,7 @@ export const PaymentSection = forwardRef< handleUseShippingChange(checked === true) } /> - - Use shipping address as billing address - + {t("sameAsShipping")} {!useShippingForBilling && ( diff --git a/src/components/checkout/Summary.tsx b/src/components/checkout/Summary.tsx index fe640716..84fcc5f3 100644 --- a/src/components/checkout/Summary.tsx +++ b/src/components/checkout/Summary.tsx @@ -1,6 +1,7 @@ "use client"; import type { Cart } from "@spree/sdk"; +import { useTranslations } from "next-intl"; import { ProductImage } from "@/components/ui/product-image"; interface SummaryProps { @@ -8,6 +9,8 @@ interface SummaryProps { } export function Summary({ cart }: SummaryProps) { + const tc = useTranslations("common"); + const t = useTranslations("checkout"); const items = cart.items || []; const hasShipping = (cart.fulfillments?.length ?? 0) > 0; @@ -50,24 +53,24 @@ export function Summary({ cart }: SummaryProps) { {/* Totals — Shopify style */}
- Subtotal + {tc("subtotal")} {cart.display_item_total}
- Shipping + {tc("shipping")} {hasShipping ? ( {cart.display_delivery_total} ) : ( - Enter shipping address + {t("enterShippingAddress")} )}
{cart.discount_total && parseFloat(cart.discount_total) !== 0 && (
- Discount + {tc("discount")} {cart.display_discount_total} @@ -76,14 +79,16 @@ export function Summary({ cart }: SummaryProps) { {parseFloat(cart.tax_total) > 0 && (
- Tax + {tc("tax")} {cart.display_tax_total}
)} {/* Total row */}
- Total + + {tc("total")} +
{cart.currency} @@ -98,7 +103,7 @@ export function Summary({ cart }: SummaryProps) { Gift cards use store credits under the hood, so only show one. */} {cart.gift_card && parseFloat(cart.gift_card_total) > 0 ? (
- Gift card + {tc("giftCard")} -{cart.display_gift_card_total} @@ -106,7 +111,7 @@ export function Summary({ cart }: SummaryProps) { ) : cart.store_credit_total && parseFloat(cart.store_credit_total) > 0 ? (
- Store credit + {tc("storeCredit")} -{cart.display_store_credit_total} @@ -119,7 +124,7 @@ export function Summary({ cart }: SummaryProps) { parseFloat(cart.amount_due) > 0 && (
- Amount due + {tc("amountDue")}
diff --git a/src/components/layout/CartButton.tsx b/src/components/layout/CartButton.tsx index b3dc4351..9149ea73 100644 --- a/src/components/layout/CartButton.tsx +++ b/src/components/layout/CartButton.tsx @@ -1,10 +1,12 @@ "use client"; import { ShoppingBag } from "lucide-react"; +import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { useCart } from "@/contexts/CartContext"; export function CartButton() { + const t = useTranslations("header"); const { itemCount, openCart } = useCart(); return ( @@ -12,7 +14,7 @@ export function CartButton() { variant="ghost" size="icon-lg" onClick={openCart} - aria-label="Open cart" + aria-label={t("openCart")} className="relative" > diff --git a/src/components/layout/CountrySwitcher.tsx b/src/components/layout/CountrySwitcher.tsx index 6cb999b6..76650d9b 100644 --- a/src/components/layout/CountrySwitcher.tsx +++ b/src/components/layout/CountrySwitcher.tsx @@ -1,6 +1,7 @@ "use client"; import { Check, ChevronDown } from "lucide-react"; +import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -26,6 +27,7 @@ function countryToFlag(countryCode: string): string { export function CountrySwitcher() { const { country, currency, countries, loading } = useStore(); + const tc = useTranslations("common"); const { isCountryNavigating, handleCountrySelect } = useCountrySwitch({ currentCountry: country, }); @@ -51,7 +53,7 @@ export function CountrySwitcher() { - Select Country + {tc("selectCountry")} {countries.map((c) => { const isSelected = c.iso.toLowerCase() === country.toLowerCase(); return ( diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 8b11939f..98b55ead 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,18 +1,25 @@ import type { Category } from "@spree/sdk"; import Link from "next/link"; +import { getTranslations } from "next-intl/server"; import { POLICY_LINKS } from "@/lib/constants/policies"; const storeName = process.env.NEXT_PUBLIC_STORE_NAME || "Spree Store"; -const storeDescription = - process.env.NEXT_PUBLIC_STORE_DESCRIPTION || - "A modern e-commerce storefront powered by Spree Commerce and Next.js."; +const envStoreDescription = process.env.NEXT_PUBLIC_STORE_DESCRIPTION; interface FooterProps { rootCategories: Category[]; basePath: string; + locale: Locale; } -export function Footer({ rootCategories, basePath }: FooterProps) { +export async function Footer({ + rootCategories, + basePath, + locale, +}: FooterProps) { + const t = await getTranslations({ locale, namespace: "footer" }); + const tp = await getTranslations({ locale, namespace: "policies" }); + return (