Conversation
Integrate next-intl for full internationalization across the storefront, checkout, account, and cart. Translate all hardcoded English strings including filters, sort options, price buckets, order statuses, address forms, and cart drawer. Use locale-aware currency formatting and date formatting. Fix locale switching via hard navigation in CountrySwitcher. DRY up store name/description with centralized getStoreName/getStoreDescription. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded English strings with translation keys across checkout, account, and storefront pages. Add fieldset/legend for radio groups in AddressSelector, localize aria-labels, and use getLocale() in root layout for consistent locale resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@swc/core from next-intl requires @swc/helpers >=0.5.17 but the lockfile only had 0.5.15 via next. Adding it explicitly at 0.5.19 ensures npm ci resolves correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace concatenated translation fragments with cardEndingIn and cardExpires keys so translators can control word order per locale. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…andling, a11y - Gift cards: getStateLabel now checks expired flag to match getStateColor - Order detail: shipment state fallback uses translation key instead of raw API value - Homepage: fix broken /categories link to point to /products - Orders page: add try/catch/finally to loadOrders to prevent stuck loading state - Checkout layout: make aria-controls conditional on panel visibility - Account layout: rename useNavItems to getNavItems (not a hook) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrates one-page checkout, reset password, and account styling changes from main while preserving i18n translations from our branch. - Accept main's deletion of step-based checkout files (AddressStep, DeliveryStep, OrderSummary, PaymentStep) - Use main's new code structure (Card components, RadioGroup, one-page checkout layout) with useTranslations overlaid - All hardcoded English strings replaced with t()/tc() calls
Translate all hardcoded strings in 7 new/updated files from the main merge (one-page checkout, forgot/reset password, confirm payment). Add forgotPassword and resetPassword namespaces to all locales (en/pl/de) and sync missing checkout keys across translation files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test was missing a next-intl mock after i18n was added to the confirm-payment page. Uses the same identity-function mock pattern as ProductCard tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
useRouter() and useTranslations() return unstable references on each render, causing useCallback/useEffect dependency chains to fire in a loop. Stabilize them with useRef (routerRef, tRef) so callbacks keep empty or minimal dependency arrays. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
WalkthroughAdds full internationalization: three locale bundles (en/de/pl), next-intl integration (config, provider, request loader, types), a locale-parity checker, locale-aware utilities, and widespread replacement of hard-coded UI strings with translation hooks across pages, components, and tests. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant RootLayout
participant i18nRequest as i18n Request
participant MessageStore as Messages\n(messages/*.json)
participant NextIntlProvider
participant Component
User->>RootLayout: Request page
RootLayout->>i18nRequest: resolve locale & messages
i18nRequest->>i18nRequest: validate route/cookie locale
i18nRequest->>MessageStore: import messages/{locale}.json
MessageStore-->>i18nRequest: messages (or fallback)
i18nRequest-->>RootLayout: { locale, messages }
RootLayout->>NextIntlProvider: initialize with locale & messages
NextIntlProvider->>Component: provide translations context
Component->>Component: useTranslations(namespace) -> localized strings
Component-->>User: render localized UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/products/MediaGallery.tsx (1)
53-58:⚠️ Potential issue | 🟡 MinorTranslate the remaining zoom button
aria-label.Line 57 still uses hardcoded English (
"Open image zoom"), so this control won’t be localized for non-English users/screen readers.Proposed patch
- aria-label="Open image zoom" + aria-label={t("openImageZoom")}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/products/MediaGallery.tsx` around lines 53 - 58, MediaGallery currently uses a hardcoded English aria-label "Open image zoom" on the zoom button; replace that literal with a localized string via your app's i18n method (e.g., useTranslation/t or i18n.t) and pass the translation key to the button's aria-label (update the MediaGallery component where onClick uses showMainImage and setIsZoomed). Add a new translation key like "product.openImageZoom" to your locale files and use that key in the aria-label so screen readers receive the localized text; ensure the props/hook (useTranslation) are imported/initialized in MediaGallery before using it.src/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsx (1)
146-152:⚠️ Potential issue | 🟡 MinorTranslate the default footer link as well.
This branch still renders
Back to sign inliterally, so the primary forgot-password view falls back to English in non-English locales.💡 Proposed fix
<CardFooter className="justify-center"> <Link href={`${basePath}/account`} className="text-sm text-primary hover:text-primary/70 font-medium" > - Back to sign in + {t("backToSignIn")} </Link> </CardFooter>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/account/forgot-password/page.tsx around lines 146 - 152, Replace the hard-coded "Back to sign in" string in the CardFooter Link with the locale-aware translation lookup used elsewhere in this file (e.g., use the same getDictionary(params.locale) or useTranslations hook used in the page component), e.g., obtain the translations object (or t function) and use t('account.backToSignIn') or the existing dictionary key instead of the literal; update the Link text to use that translated value so CardFooter and Link render localized text across locales.
🧹 Nitpick comments (13)
src/components/cart/CartDrawer.tsx (1)
163-163: Make remove-item aria labels item-specific.Using the same label for every row is less clear for screen-reader users. Include the product name in the localized label.
💡 Suggested change
- aria-label={t("removeItem")} + aria-label={t("removeItemForProduct", { name: item.name })}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/cart/CartDrawer.tsx` at line 163, The remove button in CartDrawer uses a generic aria-label={t("removeItem")} which isn't item-specific; update the aria-label to include the product name (e.g., use the localizer t with an interpolation like t("removeItem", { name: itemName })) so each row's remove control reads uniquely for screen readers; locate the remove button inside the CartDrawer component (the element using aria-label and the t function) and pass the item/product name (from the cart item object, e.g., item or product.name) into the localized string.src/components/products/ProductListingLayout.tsx (1)
68-70: Prefer nullish coalescing foremptyMessagefallback.Using
||will ignore an intentional empty string.??keeps explicit empty values and only falls back onnull/undefined.Proposed patch
- <p className="mt-2 text-gray-500"> - {emptyMessage || t("tryAdjustingFilters")} - </p> + <p className="mt-2 text-gray-500"> + {emptyMessage ?? t("tryAdjustingFilters")} + </p>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/products/ProductListingLayout.tsx` around lines 68 - 70, Replace the fallback operator for emptyMessage in the ProductListingLayout component: change the use of the logical OR (emptyMessage || t("tryAdjustingFilters")) to the nullish coalescing operator (emptyMessage ?? t("tryAdjustingFilters")) so that intentional empty strings are preserved and only null/undefined fall back to t("tryAdjustingFilters").src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx (1)
8-10: Prefer a partialnext-intlmock.This replaces the whole module with only
useTranslations, so any lateruseLocaleorNextIntlClientProviderusage underConfirmPaymentPagewill start failing with missing exports. Keep the real module and override just the hook.Suggested change
-vi.mock("next-intl", () => ({ - useTranslations: () => (key: string) => key, -})); +vi.mock("next-intl", async () => { + const actual = + await vi.importActual<typeof import("next-intl")>("next-intl"); + + return { + ...actual, + useTranslations: () => (key: string) => key, + }; +});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx around lines 8 - 10, The test currently replaces the entire next-intl module via vi.mock, causing missing exports like useLocale or NextIntlClientProvider to break; change the mock to import the real module with vi.importActual(...) and spread its exports, then override only useTranslations to return the simple key-to-key function used in tests so ConfirmPaymentPage and any other imports still get the real implementations of useLocale and NextIntlClientProvider while only useTranslations is stubbed.src/components/layout/CountrySwitcher.tsx (1)
62-68: Please re-check the hard reload on locale changes.
window.location.hrefbypasses App Router navigation and drops client-side state. IfNextIntlClientProvideris already mounted below the[locale]segment,router.push(newPath)should be enough here; keep the hard reload only if you've confirmed the provider sits above that boundary.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/CountrySwitcher.tsx` around lines 62 - 68, The current conditional forces a full page reload using window.location.href when newLocale !== currentLocale, which drops client-side state; change the logic in CountrySwitcher so that on locale changes you call router.push(newPath) (or router.replace/new navigation API) instead of window.location.href and only fall back to a hard reload if you've confirmed NextIntlClientProvider is mounted above the [locale] route segment and therefore cannot be updated via App Router navigation; inspect where NextIntlClientProvider is mounted (RootLayout vs below [locale]) and remove the window.location.href branch unless that provider placement requires it, keeping the newLocale !== currentLocale check but preferring router.push(newPath) to preserve client state.src/components/checkout/AddressFormFields.tsx (1)
137-139: Use a prompt key for state selection text.The default option at Line 138 uses
t("stateProvince"), which reads like a field label. For the select prompt,t("selectState")is clearer (and keeps behavior consistent with country selection).Suggested tweak
<NativeSelectOption value="" disabled> - {t("stateProvince")} + {t("selectState")} </NativeSelectOption>Also applies to: 150-154
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/checkout/AddressFormFields.tsx` around lines 137 - 139, In AddressFormFields, replace the label-like translation key used for the select prompt (currently t("stateProvince") inside the NativeSelectOption) with a prompt-specific key t("selectState") so the default option reads as a selection prompt; update both occurrences (the initial empty/disabled NativeSelectOption and the similar block around the other NativeSelectOption at the later occurrence) to use t("selectState") to match the country select behavior.src/components/search/SearchBar.tsx (1)
241-241: Use trimmed query for the “view all” label for consistency.Line 241 interpolates raw
query, but navigation usesquery.trim(). Using trimmed text in the label keeps UI and resulting URL aligned.Small consistency tweak
- {t("viewAllResultsFor", { query })} + {t("viewAllResultsFor", { query: query.trim() })}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/search/SearchBar.tsx` at line 241, The label currently interpolates the raw query string using t("viewAllResultsFor", { query }) in SearchBar.tsx while navigation uses query.trim(); update the label to use the trimmed value (e.g., use query.trim() or the existing trimmedQuery variable) so the displayed "view all" text matches the navigated URL and keeps UI consistent with the navigation logic.src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx (1)
68-75: Store error keys, not translated strings.At Line 68 and Line 74, state stores translated messages. Prefer storing an error key and translating in render; this avoids stale text if locale changes while the component is mounted.
Refactor pattern
- const [error, setError] = useState<string | null>(null); + type ErrorKey = "orderNotFound" | "failedToLoad"; + const [errorKey, setErrorKey] = useState<ErrorKey | null>(null); - setError(t("orderNotFound")); + setErrorKey("orderNotFound"); - setError(t("failedToLoad")); + setErrorKey("failedToLoad"); - if (error || !order) { + if (errorKey || !order) { return ( <div className="text-center py-12"> <h1 className="text-2xl font-bold text-gray-900 mb-4"> - {error || t("orderNotFound")} + {errorKey ? t(errorKey) : t("orderNotFound")} </h1>Also applies to: 98-103
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(checkout)/order-placed/[id]/page.tsx around lines 68 - 75, The component currently stores translated strings via setError(t("...")); change it to store error keys instead (e.g., setError("orderNotFound") and setError("failedToLoad")) and perform translation at render time (e.g., use t(errorKey) when rendering). Update other occurrences mentioned (the block that sets error around the 98-103 area) to use keys as well, adjust the error state type if needed to reflect keys (string | null), and ensure render uses a safe fallback when errorKey is null or unknown.src/types/next-intl.d.ts (1)
1-7: Use the recommended module augmentation pattern for next-intl v4.8.3 typing.The
declare global { interface IntlMessages }pattern is legacy. Per the current next-intl v4.8.3 docs, use module augmentation instead:import type messages from '../../messages/en.json'; declare module 'next-intl' { interface AppConfig { Messages: typeof messages; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/types/next-intl.d.ts` around lines 1 - 7, Replace the legacy global augmentation that declares interface IntlMessages with the recommended module augmentation for next-intl v4.8.3: keep the import of messages (import type messages from '../../messages/en.json'), remove the declare global { interface IntlMessages ... } block, and instead add a declare module 'next-intl' block that augments AppConfig to include Messages: typeof messages (i.e. export the Messages type via AppConfig) so TypeScript picks up the messages shape correctly; update any references from IntlMessages to the new AppConfig.Messages if needed.src/app/[country]/[locale]/(checkout)/layout.tsx (1)
65-66: Consider always includingaria-controlsfor better accessibility.The
aria-controlsattribute is conditionally added only when the panel is open. For consistent screen reader behavior, it's typically better to always includearia-controlspointing to the panel element, regardless of expanded state. The panel can be hidden with CSS while still being in the DOM.♿ Proposed accessibility improvement
<button type="button" onClick={() => setIsOpen(!isOpen)} className="w-full px-5 py-4 flex items-center justify-between text-left" aria-expanded={isOpen} - {...(isOpen && { "aria-controls": "checkout-summary-panel" })} + aria-controls="checkout-summary-panel" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(checkout)/layout.tsx around lines 65 - 66, The aria-controls attribute is only added when isOpen; always include it for consistent accessibility: remove the conditional spread and ensure the element that uses isOpen and aria-expanded also always has aria-controls="checkout-summary-panel" (keep using isOpen for aria-expanded). Update the JSX where isOpen, aria-expanded and aria-controls are set so aria-controls references the "checkout-summary-panel" regardless of the panel state.scripts/check-locale-parity.ts (1)
25-33: Consider adding directory existence check for robustness.The script assumes the
messagesdirectory exists. If run from a misconfigured environment,fs.readdirSyncon line 35-37 would throw a confusing error.🛠️ Proposed improvement
+if (!fs.existsSync(MESSAGES_DIR)) { + console.error(`Messages directory not found: ${MESSAGES_DIR}`); + process.exit(1); +} + const baseFile = path.join(MESSAGES_DIR, `${BASE_LOCALE}.json`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/check-locale-parity.ts` around lines 25 - 33, The script currently assumes MESSAGES_DIR exists before reading files; add a directory existence check using fs.existsSync(MESSAGES_DIR) (or fs.statSync with isDirectory) before building baseFile and before calling fs.readdirSync, and if the directory is missing or not a directory, log a clear error and process.exit(1). Update the logic around baseFile/baseMessages/getNestedKeys so the script fails fast with a helpful message when MESSAGES_DIR is absent rather than letting later fs.readdirSync throw a confusing error.src/i18n/request.ts (1)
4-4: Consider extractingsupportedLocalesto a shared constant.This array is likely needed in other places (e.g., route validation, middleware). Extracting it to a shared location (e.g.,
@/lib/i18n/constants.ts) would ensure consistency and make adding new locales easier.#!/bin/bash # Check if supportedLocales is defined elsewhere rg -n "supportedLocales|supported.*locale" --type ts -C 2🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/i18n/request.ts` at line 4, Extract the inline array supportedLocales from src/i18n/request.ts into a shared constant (e.g., create a new constant SUPPORTED_LOCALES in a module such as "@/lib/i18n/constants.ts"), export it, replace the local supportedLocales usage in request.ts with an import of SUPPORTED_LOCALES, and update any other modules that need the same locale list to import this shared constant so the locale list is maintained in one place.src/lib/utils/filters.ts (1)
52-75: Keep a human-readable fallback in these label helpers.If a caller misses
t, the UI now drops straight to machine values like-available_onorout_of_stock. A small English fallback map here would make the helpers fail soft instead of leaking internal ids.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/utils/filters.ts` around lines 52 - 75, The helpers getSortLabel and getAvailabilityLabel currently return raw ids when the translation function t is missing; add small English fallback maps for SORT_KEY_TO_MESSAGE and AVAILABILITY_KEY_TO_MESSAGE (or a separate FALLBACK_* map) and use them when t is undefined: in getSortLabel use normalizeSortKey(key) to look up a human-readable fallback label if t is not provided, and in getAvailabilityLabel return a human-friendly fallback (e.g., "In stock", "Out of stock") when t is absent; update the code paths that reference SORT_KEY_TO_MESSAGE and AVAILABILITY_KEY_TO_MESSAGE so they first try t(messageKey) and otherwise return the fallback string instead of the raw id.src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx (1)
177-190: Adddynamicexport for this page route.This page fetches user-specific data and is in a dynamic route (
[country]/[locale]). Per coding guidelines, page.tsx files should either usegenerateStaticParamsfor static generation or setdynamic = 'force-dynamic'for dynamic rendering.Since gift cards are user-specific and require authentication, this page should be dynamically rendered:
Suggested addition at the top of the file (after imports)
import { getGiftCards } from "@/lib/data/gift-cards"; +export const dynamic = "force-dynamic"; + function getStateColor(state: string, expired: boolean): string {As per coding guidelines:
src/app/**/page.tsx: "Use generateStaticParams for static generation of dynamic routes or set dynamic = 'force-dynamic' for dynamic rendering."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/account/gift-cards/page.tsx around lines 177 - 190, This page component (GiftCardsPage) fetches user-specific data via getGiftCards in a dynamic route ([country]/[locale]) and must be forced to render dynamically; add the export declaration to the file (page.tsx) by exporting dynamic = 'force-dynamic' near the top (after imports) so Next.js renders GiftCardsPage server-side per-request rather than statically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@messages/de.json`:
- Line 29: Replace the embedded English phrase "powered by" in the German locale
with a proper German equivalent: update the "description" string in
messages/de.json to use "betrieben von" (e.g., "Ein moderner E‑Commerce‑Shop,
betrieben von Spree Commerce und Next.js.") and do the same for the other
marketing string that contains "powered by" (the one around line 41) so both
entries are fully translated and punctuation/capitalization are preserved.
In `@package.json`:
- Line 14: The package.json script "check:locales" currently calls "npx tsx ..."
without declaring tsx as a dependency; add "tsx" to devDependencies (e.g.,
install as a devDependency) and update the "check:locales" script to invoke the
local bin (use "tsx scripts/check-locale-parity.ts" instead of "npx tsx ...") so
CI and lockfiles resolve a deterministic version; reference the "check:locales"
script and ensure package.json's devDependencies include "tsx".
In
`@src/app/`[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx:
- Around line 128-130: The current assertion only checks for the payment_error
query and could miss a wrong path; update the assertion inside waitFor to
require the exact fallback route plus the query param by changing the
expectation on mockReplace to assert the path and param together (e.g., use
mockReplace
toHaveBeenCalledWith(expect.stringContaining("/us/en/checkout/cart-1?payment_error=paymentError"))
so the test verifies the redirect targets /us/en/checkout/cart-1 with the
payment_error parameter; update the assertion in the test file where waitFor and
mockReplace are used accordingly.
In `@src/app/`[country]/[locale]/(storefront)/account/orders/[id]/page.tsx:
- Around line 203-207: Remove the hardcoded CSS capitalization on the shipment
badge: in the JSX where the badge is rendered (the element using
getShipmentStatusColor(shipment.state) and t(SHIPMENT_STATE_KEY[shipment.state]
|| "unknownShipmentStatus")), delete the "capitalize" utility from the className
string so the translated string from t(...) is displayed with its original
casing; rely on the translation/messages to provide correct capitalization for
multi-word and non-English strings.
In `@src/app/`[country]/[locale]/(storefront)/account/orders/page.tsx:
- Around line 133-150: The code currently calls t(...) with the raw state string
when PAYMENT_STATE_KEY or SHIPMENT_STATE_KEY miss, causing another translation
lookup; change the conditional to only call t for mapped keys and use the raw
state string as a real fallback (not passed to t) — i.e. for PAYMENT_STATE_KEY
and SHIPMENT_STATE_KEY return t(mappedKey) when present, otherwise render
order.payment_state / order.shipment_state directly (optionally apply a
display-safe transform like capitalize) and keep
getShipmentStatusColor(order.shipment_state) as-is.
In `@src/components/cart/CartDrawer.tsx`:
- Around line 80-82: The code currently hardcodes parentheses around the item
count in CartDrawer's JSX (the <span className="text-gray-600"> wrapping
"({t('itemCount', { count: itemCount })})"); remove the surrounding literal "("
and ")" from the JSX and move them into the translation string for key
"itemCount" (update your locale files to include the parentheses as part of the
translated value, e.g. value should render "(n)" where appropriate); keep the
call t("itemCount", { count: itemCount }) and the itemCount variable as-is so
formatting/grammar is handled entirely by the translation.
In `@src/components/checkout/PaymentSection.tsx`:
- Around line 379-385: The displayed saved-card strings concatenate separate
translation calls and lock English word order; update the PaymentSection
rendering (around getCardLabel, card.cc_type, card.last_digits, t("endingIn"),
and t("exp")) to use single i18n keys with interpolation so translators can
reorder parts—for example introduce keys like "savedCardLabel" and "cardExpiry"
that accept variables (brand/label and last_digits; month and year) and call
t(...) once with those variables (ensure month is padded via
String(card.month).padStart(2,"0") before passing).
In `@src/components/products/filters/FilterChips.tsx`:
- Around line 66-69: The chip labels and aria-labels must use full translation
strings with interpolation instead of concatenating t("price") + ": " + value;
update the chips.push call that builds the price chip (the object created in
chips.push with key "price" and label currently using `${t("price")}: ${...}`)
to call t with an interpolation key (e.g., t("chip.priceLabel", { value:
matchingBucket?.label || t("customPrice") })) and do the same for any
aria-label/clear button where code currently does t("clear") + chip.label
(replace with t("chip.clearAria", { label: chip.label })); add corresponding
i18n keys (chip.priceLabel, chip.customPrice, chip.clearAria) and use those in
both places to give translators control over punctuation and word order.
In `@src/lib/utils/order-status.ts`:
- Around line 40-51: getShipmentStatusColor currently falls back to the default
gray for the "partial" and "backorder" shipment states; update the switch in
getShipmentStatusColor to handle "partial" and "backorder" explicitly (choose
appropriate classes, e.g., treat "partial" as yellow-ish or green-ish and
"backorder" as yellow/red depending on UX) so they no longer map to the default
case—add case "partial": and case "backorder": returning the desired badge class
strings and keep the existing cases intact.
---
Outside diff comments:
In `@src/app/`[country]/[locale]/(storefront)/account/forgot-password/page.tsx:
- Around line 146-152: Replace the hard-coded "Back to sign in" string in the
CardFooter Link with the locale-aware translation lookup used elsewhere in this
file (e.g., use the same getDictionary(params.locale) or useTranslations hook
used in the page component), e.g., obtain the translations object (or t
function) and use t('account.backToSignIn') or the existing dictionary key
instead of the literal; update the Link text to use that translated value so
CardFooter and Link render localized text across locales.
In `@src/components/products/MediaGallery.tsx`:
- Around line 53-58: MediaGallery currently uses a hardcoded English aria-label
"Open image zoom" on the zoom button; replace that literal with a localized
string via your app's i18n method (e.g., useTranslation/t or i18n.t) and pass
the translation key to the button's aria-label (update the MediaGallery
component where onClick uses showMainImage and setIsZoomed). Add a new
translation key like "product.openImageZoom" to your locale files and use that
key in the aria-label so screen readers receive the localized text; ensure the
props/hook (useTranslation) are imported/initialized in MediaGallery before
using it.
---
Nitpick comments:
In `@scripts/check-locale-parity.ts`:
- Around line 25-33: The script currently assumes MESSAGES_DIR exists before
reading files; add a directory existence check using fs.existsSync(MESSAGES_DIR)
(or fs.statSync with isDirectory) before building baseFile and before calling
fs.readdirSync, and if the directory is missing or not a directory, log a clear
error and process.exit(1). Update the logic around
baseFile/baseMessages/getNestedKeys so the script fails fast with a helpful
message when MESSAGES_DIR is absent rather than letting later fs.readdirSync
throw a confusing error.
In
`@src/app/`[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx:
- Around line 8-10: The test currently replaces the entire next-intl module via
vi.mock, causing missing exports like useLocale or NextIntlClientProvider to
break; change the mock to import the real module with vi.importActual(...) and
spread its exports, then override only useTranslations to return the simple
key-to-key function used in tests so ConfirmPaymentPage and any other imports
still get the real implementations of useLocale and NextIntlClientProvider while
only useTranslations is stubbed.
In `@src/app/`[country]/[locale]/(checkout)/layout.tsx:
- Around line 65-66: The aria-controls attribute is only added when isOpen;
always include it for consistent accessibility: remove the conditional spread
and ensure the element that uses isOpen and aria-expanded also always has
aria-controls="checkout-summary-panel" (keep using isOpen for aria-expanded).
Update the JSX where isOpen, aria-expanded and aria-controls are set so
aria-controls references the "checkout-summary-panel" regardless of the panel
state.
In `@src/app/`[country]/[locale]/(checkout)/order-placed/[id]/page.tsx:
- Around line 68-75: The component currently stores translated strings via
setError(t("...")); change it to store error keys instead (e.g.,
setError("orderNotFound") and setError("failedToLoad")) and perform translation
at render time (e.g., use t(errorKey) when rendering). Update other occurrences
mentioned (the block that sets error around the 98-103 area) to use keys as
well, adjust the error state type if needed to reflect keys (string | null), and
ensure render uses a safe fallback when errorKey is null or unknown.
In `@src/app/`[country]/[locale]/(storefront)/account/gift-cards/page.tsx:
- Around line 177-190: This page component (GiftCardsPage) fetches user-specific
data via getGiftCards in a dynamic route ([country]/[locale]) and must be forced
to render dynamically; add the export declaration to the file (page.tsx) by
exporting dynamic = 'force-dynamic' near the top (after imports) so Next.js
renders GiftCardsPage server-side per-request rather than statically.
In `@src/components/cart/CartDrawer.tsx`:
- Line 163: The remove button in CartDrawer uses a generic
aria-label={t("removeItem")} which isn't item-specific; update the aria-label to
include the product name (e.g., use the localizer t with an interpolation like
t("removeItem", { name: itemName })) so each row's remove control reads uniquely
for screen readers; locate the remove button inside the CartDrawer component
(the element using aria-label and the t function) and pass the item/product name
(from the cart item object, e.g., item or product.name) into the localized
string.
In `@src/components/checkout/AddressFormFields.tsx`:
- Around line 137-139: In AddressFormFields, replace the label-like translation
key used for the select prompt (currently t("stateProvince") inside the
NativeSelectOption) with a prompt-specific key t("selectState") so the default
option reads as a selection prompt; update both occurrences (the initial
empty/disabled NativeSelectOption and the similar block around the other
NativeSelectOption at the later occurrence) to use t("selectState") to match the
country select behavior.
In `@src/components/layout/CountrySwitcher.tsx`:
- Around line 62-68: The current conditional forces a full page reload using
window.location.href when newLocale !== currentLocale, which drops client-side
state; change the logic in CountrySwitcher so that on locale changes you call
router.push(newPath) (or router.replace/new navigation API) instead of
window.location.href and only fall back to a hard reload if you've confirmed
NextIntlClientProvider is mounted above the [locale] route segment and therefore
cannot be updated via App Router navigation; inspect where
NextIntlClientProvider is mounted (RootLayout vs below [locale]) and remove the
window.location.href branch unless that provider placement requires it, keeping
the newLocale !== currentLocale check but preferring router.push(newPath) to
preserve client state.
In `@src/components/products/ProductListingLayout.tsx`:
- Around line 68-70: Replace the fallback operator for emptyMessage in the
ProductListingLayout component: change the use of the logical OR (emptyMessage
|| t("tryAdjustingFilters")) to the nullish coalescing operator (emptyMessage ??
t("tryAdjustingFilters")) so that intentional empty strings are preserved and
only null/undefined fall back to t("tryAdjustingFilters").
In `@src/components/search/SearchBar.tsx`:
- Line 241: The label currently interpolates the raw query string using
t("viewAllResultsFor", { query }) in SearchBar.tsx while navigation uses
query.trim(); update the label to use the trimmed value (e.g., use query.trim()
or the existing trimmedQuery variable) so the displayed "view all" text matches
the navigated URL and keeps UI consistent with the navigation logic.
In `@src/i18n/request.ts`:
- Line 4: Extract the inline array supportedLocales from src/i18n/request.ts
into a shared constant (e.g., create a new constant SUPPORTED_LOCALES in a
module such as "@/lib/i18n/constants.ts"), export it, replace the local
supportedLocales usage in request.ts with an import of SUPPORTED_LOCALES, and
update any other modules that need the same locale list to import this shared
constant so the locale list is maintained in one place.
In `@src/lib/utils/filters.ts`:
- Around line 52-75: The helpers getSortLabel and getAvailabilityLabel currently
return raw ids when the translation function t is missing; add small English
fallback maps for SORT_KEY_TO_MESSAGE and AVAILABILITY_KEY_TO_MESSAGE (or a
separate FALLBACK_* map) and use them when t is undefined: in getSortLabel use
normalizeSortKey(key) to look up a human-readable fallback label if t is not
provided, and in getAvailabilityLabel return a human-friendly fallback (e.g.,
"In stock", "Out of stock") when t is absent; update the code paths that
reference SORT_KEY_TO_MESSAGE and AVAILABILITY_KEY_TO_MESSAGE so they first try
t(messageKey) and otherwise return the fallback string instead of the raw id.
In `@src/types/next-intl.d.ts`:
- Around line 1-7: Replace the legacy global augmentation that declares
interface IntlMessages with the recommended module augmentation for next-intl
v4.8.3: keep the import of messages (import type messages from
'../../messages/en.json'), remove the declare global { interface IntlMessages
... } block, and instead add a declare module 'next-intl' block that augments
AppConfig to include Messages: typeof messages (i.e. export the Messages type
via AppConfig) so TypeScript picks up the messages shape correctly; update any
references from IntlMessages to the new AppConfig.Messages if needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3b9afb8c-6195-47c9-9ccf-a4b29f0cf740
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (61)
messages/de.jsonmessages/en.jsonmessages/pl.jsonnext.config.tspackage.jsonscripts/check-locale-parity.tssrc/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsxsrc/app/[country]/[locale]/(checkout)/confirm-payment/[id]/page.tsxsrc/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsxsrc/app/[country]/[locale]/(checkout)/layout.tsxsrc/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsxsrc/app/[country]/[locale]/(storefront)/account/addresses/page.tsxsrc/app/[country]/[locale]/(storefront)/account/credit-cards/page.tsxsrc/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsxsrc/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsxsrc/app/[country]/[locale]/(storefront)/account/layout.tsxsrc/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsxsrc/app/[country]/[locale]/(storefront)/account/orders/page.tsxsrc/app/[country]/[locale]/(storefront)/account/page.tsxsrc/app/[country]/[locale]/(storefront)/account/profile/page.tsxsrc/app/[country]/[locale]/(storefront)/account/register/page.tsxsrc/app/[country]/[locale]/(storefront)/account/reset-password/page.tsxsrc/app/[country]/[locale]/(storefront)/c/[...permalink]/CategoryProductsContent.tsxsrc/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsxsrc/app/[country]/[locale]/(storefront)/cart/page.tsxsrc/app/[country]/[locale]/(storefront)/page.tsxsrc/app/[country]/[locale]/(storefront)/products/ProductsContent.tsxsrc/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsxsrc/app/layout.tsxsrc/components/cart/CartDrawer.tsxsrc/components/checkout/AddressEditModal.tsxsrc/components/checkout/AddressFormFields.tsxsrc/components/checkout/AddressSection.tsxsrc/components/checkout/AddressSelector.tsxsrc/components/checkout/CouponCode.tsxsrc/components/checkout/PaymentSection.tsxsrc/components/checkout/ShippingMethodSection.tsxsrc/components/checkout/Summary.tsxsrc/components/layout/CountrySwitcher.tsxsrc/components/layout/Footer.tsxsrc/components/layout/Header.tsxsrc/components/navigation/Breadcrumbs.tsxsrc/components/products/MediaGallery.tsxsrc/components/products/ProductCard.tsxsrc/components/products/ProductCarousel.tsxsrc/components/products/ProductGrid.tsxsrc/components/products/ProductListingLayout.tsxsrc/components/products/VariantPicker.tsxsrc/components/products/__tests__/ProductCard.test.tsxsrc/components/products/filters/AvailabilityDropdownContent.tsxsrc/components/products/filters/FilterChips.tsxsrc/components/products/filters/MobileFilterDrawer.tsxsrc/components/products/filters/ProductFilters.tsxsrc/components/products/filters/SortDropdownContent.tsxsrc/components/search/SearchBar.tsxsrc/contexts/StoreContext.tsxsrc/i18n/request.tssrc/lib/utils/filters.tssrc/lib/utils/order-status.tssrc/lib/utils/price-buckets.tssrc/types/next-intl.d.ts
src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx
Outdated
Show resolved
Hide resolved
src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx
Outdated
Show resolved
Hide resolved
…prove a11y
- Replace embedded English "powered by" in de.json with "betrieben von"
- Add tsx as devDependency and use local bin in check:locales script
- Use vi.importActual in confirm-payment test mock; tighten fallback assertion
- Remove CSS capitalize from order status badges (rely on translations)
- Use raw state string as fallback instead of passing unmapped keys to t()
- Move CartDrawer item count parentheses into translation strings
- Replace concatenated saved-card strings with savedCardLabel/cardExpiry keys
- Replace concatenated price chip label with priceLabel interpolation key
- Add clearFilter key for accessible chip remove button aria-labels
- Add partial (blue) and backorder (yellow) to getShipmentStatusColor
- Replace hardcoded "Back to sign in" with t("backToSignIn")
- Replace hardcoded "Open image zoom" aria-label with t("openImageZoom")
- Add directory existence check to check-locale-parity script
- Always include aria-controls on checkout summary toggle
- Store error keys instead of translated strings in order-placed page
- Use t("selectState") for state select prompt in AddressFormFields
- Use nullish coalescing for emptyMessage fallback in ProductListingLayout
- Use query.trim() in SearchBar viewAllResultsFor label
- Add English fallback maps in filters.ts for when t() is unavailable
- Update next-intl.d.ts to recommended AppConfig module augmentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l.d.ts
- Revert next-intl.d.ts to global IntlMessages pattern (AppConfig caused
cascading type errors across all translator call sites)
- Type order-placed error state as union of known error keys
- Cast dynamic SHIPMENT_STATE_KEY/PAYMENT_STATE_KEY lookups via
keyof IntlMessages["orders"] for type safety
- Fix pre-existing bug: t("orderNumber") -> t("orderTitle") in order detail
- Widen translator param type in filters.ts and price-buckets.ts to accept
next-intl's Translator type (key: any instead of key: string)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx (1)
275-282:⚠️ Potential issue | 🟡 MinorAdd error handling to prevent infinite loading state.
Unlike the orders list page, this
useEffectlacks error handling. IfgetOrderthrows, the component remains in the loading state indefinitely sincesetLoading(false)never executes.🔧 Proposed fix
useEffect(() => { async function loadOrder() { - const orderData = await getOrder(id); - setOrder(orderData); - setLoading(false); + try { + const orderData = await getOrder(id); + setOrder(orderData); + } catch { + setOrder(null); + } finally { + setLoading(false); + } } loadOrder(); }, [id]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/account/orders/[id]/page.tsx around lines 275 - 282, The useEffect's async loader (loadOrder) doesn't handle errors so if getOrder(id) throws the component stays stuck in loading; update loadOrder to wrap the await getOrder(id) in try/catch and in the finally block call setLoading(false), setOrder only on success, and log or surface the error (e.g., via console.error or an error state). Specifically modify the loadOrder function used in the useEffect (referencing loadOrder, getOrder, setOrder, setLoading) to ensure setLoading(false) always runs and errors are handled.
🧹 Nitpick comments (4)
src/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsx (2)
28-31: Consider usinguseActionStatefor form handling.The current useState-based form management works, but the coding guidelines prefer
useActionStatefor form handling. This would simplify the pending/error state management and integrate better with React 19's form patterns.However, given the current implementation is functional and the project may have established patterns, this is an optional improvement.
♻️ Example refactor using useActionState
import { useActionState } from "react"; // Define a server action or wrap the existing function async function resetPasswordAction(prevState: { error: string | null; submitted: boolean }, formData: FormData) { const email = formData.get("email") as string; try { const origin = window.location.origin; const redirectUrl = `${origin}${basePath}/account/reset-password`; const result = await requestPasswordReset(email, redirectUrl); if (result?.message) { return { error: null, submitted: true, email }; } return { error: "genericError", submitted: false, email: "" }; } catch { return { error: "genericError", submitted: false, email: "" }; } } // In component: const [state, formAction, isPending] = useActionState(resetPasswordAction, { error: null, submitted: false, email: "" });Also applies to: 33-52
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/account/forgot-password/page.tsx around lines 28 - 31, The component currently uses local useState variables (email, submitting, error, submitted) for the forgot-password form; replace them with React 19's useActionState to simplify pending/error/submitted handling: create a resetPasswordAction (or wrap the existing requestPasswordReset) that accepts FormData, calls requestPasswordReset with the redirect URL, and returns a state object like { error, submitted, email }; then in the component call useActionState(resetPasswordAction, { error: null, submitted: false, email: "" }) to get [state, formAction, isPending], wire the form to formAction, use isPending instead of submitting, and read state.error/state.submitted/state.email instead of the removed useState variables so you can delete setSubmitting/setError/setSubmitted logic.
23-23: Consider adding SEO metadata.This page file doesn't export a
generateMetadatafunction. For better SEO, consider adding metadata for the forgot password page.Note: Since this is a "use client" component, you would need to extract the metadata generation to a separate server component or use a layout-level approach for this route.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/account/forgot-password/page.tsx at line 23, The ForgotPasswordPage component currently lacks SEO metadata; because ForgotPasswordPage is a client component you should not add generateMetadata inside it — instead create a server component that exports generateMetadata (e.g., export async function generateMetadata({ params }) { return { title: 'Forgot Password', description: 'Reset your account password', openGraph: { title: 'Forgot Password' }, ... } }) and include/compose that server component at this route (or add equivalent metadata in the route/layout-level generateMetadata) so the page has proper title/description (use params.locale or params.country if needed for localized strings).src/components/checkout/AddressFormFields.tsx (1)
21-30: Add an explicit return type to the component function
AddressFormFieldsshould declare a return type for stricter TS contracts.♻️ Proposed fix
export function AddressFormFields({ address, countries, states, loadingStates, onChange, idPrefix, -}: AddressFormFieldsProps) { +}: AddressFormFieldsProps): JSX.Element {As per coding guidelines, "Use strict TypeScript type checking. Always define explicit return types for functions, use 'satisfies' for type checking object literals, and avoid 'any' (use 'unknown' instead)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/checkout/AddressFormFields.tsx` around lines 21 - 30, Add an explicit JSX return type to the AddressFormFields component by updating the function signature for AddressFormFields to declare a return type (e.g., JSX.Element or React.ReactElement); ensure the appropriate JSX/React types are available (import React types if your tsconfig requires it) and keep the existing props typing (AddressFormFieldsProps) unchanged so type checking remains strict.scripts/check-locale-parity.ts (1)
8-8: Avoid__dirnamehere.Line 3 documents running this as
tsx scripts/check-locale-parity.ts; if that ends up in ESM mode,__dirnameis undefined and the script fails before any checks run. Resolving fromprocess.cwd()keeps the documented repo-root invocation working across module formats.💡 Suggested fix
-const MESSAGES_DIR = path.resolve(__dirname, "../messages"); +const MESSAGES_DIR = path.resolve(process.cwd(), "messages");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/check-locale-parity.ts` at line 8, The MESSAGES_DIR constant uses __dirname which breaks in ESM/tsx runs; change its resolution to use the repo root via process.cwd() instead (e.g., resolve from process.cwd() to the "messages" folder) so scripts/check-locale-parity.ts runs correctly under tsx/ESM; update the MESSAGES_DIR declaration (symbol: MESSAGES_DIR) to use path.resolve(process.cwd(), "messages") or equivalent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/`[country]/[locale]/(checkout)/order-placed/[id]/page.tsx:
- Around line 68-74: The component's error state is too broad (string | null)
but only uses the literal keys "orderNotFound" and "failedToLoad", which causes
a type error when passing error into t(...). Narrow the state to a union of the
exact message keys (e.g., type ErrorKey = "orderNotFound" | "failedToLoad";
const [error, setError] = useState<ErrorKey | null>(null)) and update all
setError(...) calls to use those literals; this will make t(error ??
"orderNotFound") accept the correct literal type without casts.
In `@src/app/`[country]/[locale]/(storefront)/account/orders/[id]/page.tsx:
- Around line 328-333: The component uses useTranslations("orders") and calls
t("orderNumber", { number: order.number }) but the "orderNumber" key is missing
from the orders namespace; add an "orderNumber" entry to the orders namespace in
every locale's translation files (e.g., orders.orderNumber: "Order #{number}" or
localized equivalent) so t("orderNumber", { number }) resolves correctly when
rendering the header in page.tsx.
In `@src/app/`[country]/[locale]/(storefront)/account/orders/page.tsx:
- Around line 133-148: The calls to t(...) with
PAYMENT_STATE_KEY[order.payment_state] and
SHIPMENT_STATE_KEY[order.shipment_state] cause TypeScript errors because indexed
lookups return string while t() expects a literal key; fix by asserting the
lookup result to the t() key type (for example:
t(PAYMENT_STATE_KEY[order.payment_state] as Parameters<typeof t>[0]) and
t(SHIPMENT_STATE_KEY[order.shipment_state] as Parameters<typeof t>[0])).
Alternatively, extract the logic into a helper like
getPaymentStateLabel/getShipmentStateLabel (as used in
order-status.ts/filters.ts) that accepts an optional t parameter with loose
typing and returns the correctly typed key or fallback string before passing to
t().
In `@src/components/products/filters/FilterChips.tsx`:
- Around line 93-97: The remove-chip button in FilterChips.tsx currently omits
an explicit type, so it defaults to "submit" and can inadvertently submit a
form; update the button element that uses chip.onRemove (the remove button in
the FilterChips component) to include type="button" to prevent form submission
and keep the onClick handler side-effect-free.
In `@src/lib/utils/filters.ts`:
- Around line 25-34: The SORT_KEY_TO_MESSAGE mapping currently uses
Record<string,string> which erases literal key unions and permits unsafe lookups
via the in operator; change the mapping to use a typed literal union and the
TypeScript satisfies operator (e.g., declare SORT_KEY_TO_MESSAGE = { ... } as
const satisfies Record<SortKeyUnion, MessageKeyUnion>) so the keys remain
literal types compatible with useTranslations("products"), update related
functions (e.g., the functions that check keys using `in`) to use
Object.prototype.hasOwnProperty.call(SORT_KEY_TO_MESSAGE, key) and return an
explicit string type with a safe fallback when lookup fails, and add explicit
function return types to satisfy strictTypeChecking.
In `@src/types/next-intl.d.ts`:
- Around line 1-9: The AppConfig augmentation only provides Messages and needs a
Locale type for full type-safe configuration; add a Locale type (e.g., declare
type Locale = "en" | "de" | "pl" or derive it from your routing export) and
include it on the AppConfig interface alongside Messages so AppConfig exposes
both Locale and Messages; update the top-level symbols (the import of messages,
the Messages type alias, and the declare module "next-intl" block) to reference
the new Locale type and include it in the interface.
---
Outside diff comments:
In `@src/app/`[country]/[locale]/(storefront)/account/orders/[id]/page.tsx:
- Around line 275-282: The useEffect's async loader (loadOrder) doesn't handle
errors so if getOrder(id) throws the component stays stuck in loading; update
loadOrder to wrap the await getOrder(id) in try/catch and in the finally block
call setLoading(false), setOrder only on success, and log or surface the error
(e.g., via console.error or an error state). Specifically modify the loadOrder
function used in the useEffect (referencing loadOrder, getOrder, setOrder,
setLoading) to ensure setLoading(false) always runs and errors are handled.
---
Nitpick comments:
In `@scripts/check-locale-parity.ts`:
- Line 8: The MESSAGES_DIR constant uses __dirname which breaks in ESM/tsx runs;
change its resolution to use the repo root via process.cwd() instead (e.g.,
resolve from process.cwd() to the "messages" folder) so
scripts/check-locale-parity.ts runs correctly under tsx/ESM; update the
MESSAGES_DIR declaration (symbol: MESSAGES_DIR) to use
path.resolve(process.cwd(), "messages") or equivalent.
In `@src/app/`[country]/[locale]/(storefront)/account/forgot-password/page.tsx:
- Around line 28-31: The component currently uses local useState variables
(email, submitting, error, submitted) for the forgot-password form; replace them
with React 19's useActionState to simplify pending/error/submitted handling:
create a resetPasswordAction (or wrap the existing requestPasswordReset) that
accepts FormData, calls requestPasswordReset with the redirect URL, and returns
a state object like { error, submitted, email }; then in the component call
useActionState(resetPasswordAction, { error: null, submitted: false, email: ""
}) to get [state, formAction, isPending], wire the form to formAction, use
isPending instead of submitting, and read
state.error/state.submitted/state.email instead of the removed useState
variables so you can delete setSubmitting/setError/setSubmitted logic.
- Line 23: The ForgotPasswordPage component currently lacks SEO metadata;
because ForgotPasswordPage is a client component you should not add
generateMetadata inside it — instead create a server component that exports
generateMetadata (e.g., export async function generateMetadata({ params }) {
return { title: 'Forgot Password', description: 'Reset your account password',
openGraph: { title: 'Forgot Password' }, ... } }) and include/compose that
server component at this route (or add equivalent metadata in the
route/layout-level generateMetadata) so the page has proper title/description
(use params.locale or params.country if needed for localized strings).
In `@src/components/checkout/AddressFormFields.tsx`:
- Around line 21-30: Add an explicit JSX return type to the AddressFormFields
component by updating the function signature for AddressFormFields to declare a
return type (e.g., JSX.Element or React.ReactElement); ensure the appropriate
JSX/React types are available (import React types if your tsconfig requires it)
and keep the existing props typing (AddressFormFieldsProps) unchanged so type
checking remains strict.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b5198ab5-c310-4320-9863-37f4632ced4d
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (21)
messages/de.jsonmessages/en.jsonmessages/pl.jsonpackage.jsonscripts/check-locale-parity.tssrc/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsxsrc/app/[country]/[locale]/(checkout)/layout.tsxsrc/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsxsrc/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsxsrc/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsxsrc/app/[country]/[locale]/(storefront)/account/orders/page.tsxsrc/components/cart/CartDrawer.tsxsrc/components/checkout/AddressFormFields.tsxsrc/components/checkout/PaymentSection.tsxsrc/components/products/MediaGallery.tsxsrc/components/products/ProductListingLayout.tsxsrc/components/products/filters/FilterChips.tsxsrc/components/search/SearchBar.tsxsrc/lib/utils/filters.tssrc/lib/utils/order-status.tssrc/types/next-intl.d.ts
🚧 Files skipped from review as they are similar to previous changes (11)
- messages/pl.json
- src/components/search/SearchBar.tsx
- src/components/cart/CartDrawer.tsx
- package.json
- src/components/products/MediaGallery.tsx
- src/app/[country]/[locale]/(checkout)/layout.tsx
- messages/en.json
- src/components/products/ProductListingLayout.tsx
- src/components/checkout/PaymentSection.tsx
- src/lib/utils/order-status.ts
- messages/de.json
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/lib/utils/filters.ts (2)
85-92:⚠️ Potential issue | 🟡 MinorSame
anytype issue ingetAvailabilityLabel.The
tparameter at line 87 also uses(key: any) => string, violating the same coding guideline.export function getAvailabilityLabel( id: string, - t?: (key: any) => string, + t?: (key: string) => string, ): string {The type safety and property lookup concerns (using
Object.hasOwn()instead of direct access) from the previous review apply here as well.As per coding guidelines: "avoid 'any' (use 'unknown' instead)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/utils/filters.ts` around lines 85 - 92, Change the t parameter type in getAvailabilityLabel from (key: any) => string to (key: unknown) => string, and replace direct index lookups with safe property checks using Object.hasOwn(AVAILABILITY_KEY_TO_MESSAGE, id) and Object.hasOwn(AVAILABILITY_FALLBACK, id); then narrow types before use (e.g., read messageKey only after hasOwn and ensure typeof messageKey === 'string' before calling t(messageKey)) and similarly return AVAILABILITY_FALLBACK[id] only after hasOwn and proper type-checking so you avoid any/unsafe lookups while keeping the same logic in getAvailabilityLabel.
63-63:⚠️ Potential issue | 🟡 MinorAvoid
anytype in function parameter signature.The
tparameter uses(key: any) => stringwhich violates the coding guideline to avoidany. This also undermines type safety when integrating withuseTranslations.Consider using a union type of the valid message keys or
unknownwith type narrowing:-export function getSortLabel(key: string, t?: (key: any) => string): string { +export function getSortLabel( + key: string, + t?: (key: string) => string, +): string {Or better, use the literal union type as suggested in the existing review comment to ensure type compatibility with next-intl's translator.
As per coding guidelines: "avoid 'any' (use 'unknown' instead)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/utils/filters.ts` at line 63, The t parameter of getSortLabel currently uses (key: any) => string; change this to avoid any—either declare t as unknown and narrow it inside getSortLabel before calling, or (preferably) use a concrete translator-compatible key type (e.g., the literal union type used for your next-intl message keys or the same MessageKey/Translator parameter type) so the signature becomes getSortLabel(key: string, t?: (key: YourMessageKeyType) => string): string; update any internal calls to t to reflect the narrowed/typed signature and ensure compatibility with useTranslations.
🧹 Nitpick comments (3)
src/app/[country]/[locale]/(storefront)/account/orders/page.tsx (2)
28-50: Add explicit return types for component and inner async function.
OrdersPageandloadOrdersshould declare return types explicitly to match the repo’s strict TypeScript guideline.♻️ Proposed fix
-import { useEffect, useState } from "react"; +import { useEffect, useState, type ReactElement } from "react"; ... -export default function OrdersPage() { +export default function OrdersPage(): ReactElement { ... - async function loadOrders() { + async function loadOrders(): Promise<void> {As per coding guidelines, "Use strict TypeScript type checking. Always define explicit return types for functions, use 'satisfies' for type checking object literals, and avoid 'any' (use 'unknown' instead)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/account/orders/page.tsx around lines 28 - 50, Add explicit return types: declare OrdersPage to return JSX.Element (or React.ReactElement) and annotate the inner async function loadOrders with Promise<void>. Update the function signatures for OrdersPage and loadOrders (the ones named OrdersPage and loadOrders inside the useEffect) to include these explicit return types so they comply with the repo's strict TypeScript rules.
43-45: Avoid fully silent error handling in orders loading.The empty
catchpath hides failure causes. Please record the error via the project logger/telemetry before falling back tosetOrders([])so production failures are diagnosable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/account/orders/page.tsx around lines 43 - 45, The empty catch swallows errors when loading orders; change the catch to accept an error (e.g., catch (err)) and forward that error to the project logger/telemetry before calling setOrders([]). Locate the block that calls setOrders (reference: setOrders) and add a call to the project's logging/telemetry API (e.g., projectLogger.error(...) or telemetry.captureException(err, { context: 'orders page' })) including the error object and brief context, then fall back to setOrders([]).src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx (1)
205-208: Type SHIPMENT_STATE_KEY withsatisfiesto eliminate the cast and improve type safety.The cast
as keyof IntlMessages["orders"]at lines 205–208 weakens compile-time guarantees. TypeSHIPMENT_STATE_KEYinsrc/lib/utils/order-status.tsusingsatisfies Record<string, keyof IntlMessages["orders"]>so the callsite no longer needs a cast.♻️ Suggested direction
In
src/lib/utils/order-status.ts:export const SHIPMENT_STATE_KEY = { shipped: "shipped", partial: "partiallyShipped", delivered: "delivered", ready: "ready", pending: "pending", canceled: "canceled", backorder: "backorder", } as const satisfies Record<string, keyof IntlMessages["orders"]>;Then simplify the callsite to:
{t(SHIPMENT_STATE_KEY[shipment.state] ?? "unknownShipmentStatus")}All shipment state values and
unknownShipmentStatusare present in all message files (de.json, en.json, pl.json).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/account/orders/[id]/page.tsx around lines 205 - 208, The current cast at the callsite weakens type safety; update the SHIPMENT_STATE_KEY export in src/lib/utils/order-status.ts to use "as const satisfies Record<string, keyof IntlMessages['orders']>" so each value is typed as a key of IntlMessages["orders"], then simplify the page usage in src/app/.../page.tsx to call t(SHIPMENT_STATE_KEY[shipment.state] ?? "unknownShipmentStatus") without the cast; reference SHIPMENT_STATE_KEY, IntlMessages, and the t(...) call when making these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/utils/price-buckets.ts`:
- Around line 32-35: Replace the loose any on the translation key in
BucketLabelOptions by defining a specific string-union type (e.g., type
BucketLabelKey = 'bucket.empty' | 'bucket.lessThan' | 'bucket.range' | ... )
that includes all translation keys actually used in this module, then change t
to (key: BucketLabelKey, values?: Record<string,string>) => string; do not use
any (use the union or unknown where appropriate), and export/reuse the
BucketLabelKey type so callers and the bucket-labeling functions reference the
same constrained keys.
---
Duplicate comments:
In `@src/lib/utils/filters.ts`:
- Around line 85-92: Change the t parameter type in getAvailabilityLabel from
(key: any) => string to (key: unknown) => string, and replace direct index
lookups with safe property checks using
Object.hasOwn(AVAILABILITY_KEY_TO_MESSAGE, id) and
Object.hasOwn(AVAILABILITY_FALLBACK, id); then narrow types before use (e.g.,
read messageKey only after hasOwn and ensure typeof messageKey === 'string'
before calling t(messageKey)) and similarly return AVAILABILITY_FALLBACK[id]
only after hasOwn and proper type-checking so you avoid any/unsafe lookups while
keeping the same logic in getAvailabilityLabel.
- Line 63: The t parameter of getSortLabel currently uses (key: any) => string;
change this to avoid any—either declare t as unknown and narrow it inside
getSortLabel before calling, or (preferably) use a concrete
translator-compatible key type (e.g., the literal union type used for your
next-intl message keys or the same MessageKey/Translator parameter type) so the
signature becomes getSortLabel(key: string, t?: (key: YourMessageKeyType) =>
string): string; update any internal calls to t to reflect the narrowed/typed
signature and ensure compatibility with useTranslations.
---
Nitpick comments:
In `@src/app/`[country]/[locale]/(storefront)/account/orders/[id]/page.tsx:
- Around line 205-208: The current cast at the callsite weakens type safety;
update the SHIPMENT_STATE_KEY export in src/lib/utils/order-status.ts to use "as
const satisfies Record<string, keyof IntlMessages['orders']>" so each value is
typed as a key of IntlMessages["orders"], then simplify the page usage in
src/app/.../page.tsx to call t(SHIPMENT_STATE_KEY[shipment.state] ??
"unknownShipmentStatus") without the cast; reference SHIPMENT_STATE_KEY,
IntlMessages, and the t(...) call when making these changes.
In `@src/app/`[country]/[locale]/(storefront)/account/orders/page.tsx:
- Around line 28-50: Add explicit return types: declare OrdersPage to return
JSX.Element (or React.ReactElement) and annotate the inner async function
loadOrders with Promise<void>. Update the function signatures for OrdersPage and
loadOrders (the ones named OrdersPage and loadOrders inside the useEffect) to
include these explicit return types so they comply with the repo's strict
TypeScript rules.
- Around line 43-45: The empty catch swallows errors when loading orders; change
the catch to accept an error (e.g., catch (err)) and forward that error to the
project logger/telemetry before calling setOrders([]). Locate the block that
calls setOrders (reference: setOrders) and add a call to the project's
logging/telemetry API (e.g., projectLogger.error(...) or
telemetry.captureException(err, { context: 'orders page' })) including the error
object and brief context, then fall back to setOrders([]).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3126f87a-4834-4929-bddb-0a0966aec4d1
📒 Files selected for processing (6)
src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsxsrc/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsxsrc/app/[country]/[locale]/(storefront)/account/orders/page.tsxsrc/lib/utils/filters.tssrc/lib/utils/price-buckets.tssrc/types/next-intl.d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/types/next-intl.d.ts
- Replace `any` with `string` in translation helper params (filters.ts, price-buckets.ts) - Add type="button" to FilterChips remove button to prevent form submission - Add try/catch/finally to order detail loadOrder to prevent stuck loading state - Fix __dirname to process.cwd() in check-locale-parity for ESM compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary by CodeRabbit
New Features
Chores