Skip to content

UI translations v2#66

Merged
damianlegawiec merged 31 commits intomainfrom
ui-translations-v2
Apr 8, 2026
Merged

UI translations v2#66
damianlegawiec merged 31 commits intomainfrom
ui-translations-v2

Conversation

@Cichorek
Copy link
Copy Markdown
Contributor

@Cichorek Cichorek commented Mar 16, 2026

Summary by CodeRabbit

  • New Features

    • Full multi-language support: English, German, and Polish translations across storefront, checkout, and account areas.
  • Localization

    • Comprehensive UI text translated (navigation, product pages, cart, checkout, account, orders, policies, gift cards, etc.).
    • Date and currency formatting honor selected locale; pluralization and interpolations supported.
  • Accessibility

    • Improved ARIA attributes for checkout summary and other controls for better screen-reader behavior.

Cichorek and others added 10 commits March 13, 2026 13:12
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>
@Cichorek Cichorek requested a review from damianlegawiec March 16, 2026 14:44
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds full internationalization with next-intl: three locale JSONs (en/de/pl), runtime request config and types, a locale-parity check script, Next.js config/plugin wiring, and widespread replacement of hard-coded UI strings with translation lookups across pages, components, and utilities.

Changes

Cohort / File(s) Summary
Localization Files
messages/en.json, messages/de.json, messages/pl.json
Added comprehensive English, German, and Polish translation dictionaries with interpolation and pluralization keys.
i18n Config & Types
src/i18n/request.ts, src/types/next-intl.d.ts, next.config.ts
Added request-scoped next-intl config (locale resolution + dynamic messages), TypeScript augmentations for locales/messages, and integrated next-intl plugin into Next config.
Locale Parity & Dependencies
scripts/check-locale-parity.ts, package.json
Added parity-check script and introduced next-intl, @swc/helpers, and tsx (dev) plus new npm script check:locales.
Root/Locale Layout & Providers
src/app/layout.tsx, src/app/[country]/[locale]/layout.tsx
Swapped to runtime store SEO helpers, moved providers into NextIntlClientProvider/Suspense/CartProvider/StoreProvider/AuthProvider tree and added CartDrawer/Toaster; layout now wires locale messages.
Checkout Flow
src/app/[country]/[locale]/(checkout)/**/*.tsx, src/components/checkout/*
Wired checkout pages and components to translations, replaced hardcoded labels/errors, introduced router/t refs to avoid stale closures, and adjusted some dependency lists and UI aria attributes.
Account Pages & Shell
src/app/[country]/[locale]/(storefront)/account/*, src/app/[country]/[locale]/(storefront)/account/layout.tsx
Added server/client translations across account pages, replaced nav labels with getNavItems(t), and localized all account UI strings.
Product UI & Filters
src/app/.../products/*, src/components/products/*, src/components/products/filters/*
Localized product pages, cards, gallery, carousel, grids, listing layout, filters, and filter chips; filter label utilities updated to accept translation functions.
Utilities: Filters / Price / Format / Status
src/lib/utils/filters.ts, src/lib/utils/price-buckets.ts, src/lib/utils/format.ts, src/lib/utils/order-status.ts
Refactored sort/availability label mapping to message-key maps and translator-aware functions, made price-bucket labels locale/translation-aware, added locale params to date formatting, and added order status → i18n key maps and status color helpers.
Layout Components
src/components/layout/Header.tsx, src/components/layout/Footer.tsx, src/components/layout/*
Converted Header/Footer to async server components using getTranslations; localized country switcher, cart button, mobile menu, search toggle, breadcrumbs, and related layout strings.
Cart / Search / Home / Misc
src/app/.../cart/page.tsx, src/components/cart/CartDrawer.tsx, src/app/.../page.tsx, src/components/search/SearchBar.tsx, policy pages, store context
Localized cart, cart drawer, homepage hero/sections, search UI, policy pages/consent, updated StoreContext to use getStoreName(); minor structural/provider changes.
Account & Order Components
src/components/account/*, src/components/order/*, src/components/addresses/*
Localized credit card, gift card, order list/detail, order totals, payment info, and address management UIs and messages.
Tests
src/app/.../confirm-payment/__tests__/page.test.tsx, src/components/products/__tests__/ProductCard.test.tsx
Added next-intl mocks and adjusted expectations to translation keys instead of hardcoded English strings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Add i18n support with next-intl (EN, PL, DE) #56: Implements the same next-intl i18n integration and adds matching locale JSONs, config, scripts, and many translation-driven component changes.
  • One-page checkout #58: Overlaps changes to checkout-related components (PaymentSection, AddressSection, CouponCode, Summary) that this PR localizes.
  • Redesign product filters #48: Touches the same filter/sort label utilities (src/lib/utils/filters.ts, price-buckets.ts) and may conflict with the refactor to translator-aware functions.

Suggested reviewers

  • damianlegawiec

🐰 A Rabbit's i18n Celebration

Across the globe, our store now speaks,
In German, Polish—what joy it peeks!
Translation keys replace hard strings of yore,
And locales dance where bytes did pour.
✨🌍 The storefront awakens to a multilingual roar! 🌍✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'UI translations v2' is vague and generic, using a non-descriptive version reference that doesn't convey meaningful information about the extensive internationalization changes made throughout the codebase. Consider a more descriptive title such as 'Add internationalization (i18n) support with German and Polish translations' or 'Integrate next-intl for multi-language UI support' to clearly summarize the primary change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ui-translations-v2

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟡 Minor

Translate 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 | 🟡 Minor

Translate the default footer link as well.

This branch still renders Back to sign in literally, 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 for emptyMessage fallback.

Using || will ignore an intentional empty string. ?? keeps explicit empty values and only falls back on null/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 partial next-intl mock.

This replaces the whole module with only useTranslations, so any later useLocale or NextIntlClientProvider usage under ConfirmPaymentPage will 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.href bypasses App Router navigation and drops client-side state. If NextIntlClientProvider is 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 uses query.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 including aria-controls for better accessibility.

The aria-controls attribute is conditionally added only when the panel is open. For consistent screen reader behavior, it's typically better to always include aria-controls pointing 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 messages directory exists. If run from a misconfigured environment, fs.readdirSync on 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 extracting supportedLocales to 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_on or out_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: Add dynamic export 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 use generateStaticParams for static generation or set dynamic = '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

📥 Commits

Reviewing files that changed from the base of the PR and between 1e6e845 and a754edb.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (61)
  • messages/de.json
  • messages/en.json
  • messages/pl.json
  • next.config.ts
  • package.json
  • scripts/check-locale-parity.ts
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[country]/[locale]/(checkout)/confirm-payment/[id]/page.tsx
  • src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/addresses/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/credit-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/profile/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/reset-password/page.tsx
  • src/app/[country]/[locale]/(storefront)/c/[...permalink]/CategoryProductsContent.tsx
  • src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx
  • src/app/[country]/[locale]/(storefront)/cart/page.tsx
  • src/app/[country]/[locale]/(storefront)/page.tsx
  • src/app/[country]/[locale]/(storefront)/products/ProductsContent.tsx
  • src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/app/layout.tsx
  • src/components/cart/CartDrawer.tsx
  • src/components/checkout/AddressEditModal.tsx
  • src/components/checkout/AddressFormFields.tsx
  • src/components/checkout/AddressSection.tsx
  • src/components/checkout/AddressSelector.tsx
  • src/components/checkout/CouponCode.tsx
  • src/components/checkout/PaymentSection.tsx
  • src/components/checkout/ShippingMethodSection.tsx
  • src/components/checkout/Summary.tsx
  • src/components/layout/CountrySwitcher.tsx
  • src/components/layout/Footer.tsx
  • src/components/layout/Header.tsx
  • src/components/navigation/Breadcrumbs.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/products/ProductCard.tsx
  • src/components/products/ProductCarousel.tsx
  • src/components/products/ProductGrid.tsx
  • src/components/products/ProductListingLayout.tsx
  • src/components/products/VariantPicker.tsx
  • src/components/products/__tests__/ProductCard.test.tsx
  • src/components/products/filters/AvailabilityDropdownContent.tsx
  • src/components/products/filters/FilterChips.tsx
  • src/components/products/filters/MobileFilterDrawer.tsx
  • src/components/products/filters/ProductFilters.tsx
  • src/components/products/filters/SortDropdownContent.tsx
  • src/components/search/SearchBar.tsx
  • src/contexts/StoreContext.tsx
  • src/i18n/request.ts
  • src/lib/utils/filters.ts
  • src/lib/utils/order-status.ts
  • src/lib/utils/price-buckets.ts
  • src/types/next-intl.d.ts

Comment thread messages/de.json Outdated
Comment thread package.json Outdated
Comment thread src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx Outdated
Comment thread src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx Outdated
Comment thread src/components/cart/CartDrawer.tsx
Comment thread src/components/checkout/PaymentSection.tsx
Comment thread src/components/products/filters/FilterChips.tsx
Comment thread src/lib/utils/order-status.ts
Cichorek and others added 2 commits March 16, 2026 18:16
…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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟡 Minor

Add error handling to prevent infinite loading state.

Unlike the orders list page, this useEffect lacks error handling. If getOrder throws, the component remains in the loading state indefinitely since setLoading(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 using useActionState for form handling.

The current useState-based form management works, but the coding guidelines prefer useActionState for 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 generateMetadata function. 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

AddressFormFields should 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 __dirname here.

Line 3 documents running this as tsx scripts/check-locale-parity.ts; if that ends up in ESM mode, __dirname is undefined and the script fails before any checks run. Resolving from process.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

📥 Commits

Reviewing files that changed from the base of the PR and between a754edb and 9f18891.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (21)
  • messages/de.json
  • messages/en.json
  • messages/pl.json
  • package.json
  • scripts/check-locale-parity.ts
  • src/app/[country]/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/forgot-password/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/page.tsx
  • src/components/cart/CartDrawer.tsx
  • src/components/checkout/AddressFormFields.tsx
  • src/components/checkout/PaymentSection.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/products/ProductListingLayout.tsx
  • src/components/products/filters/FilterChips.tsx
  • src/components/search/SearchBar.tsx
  • src/lib/utils/filters.ts
  • src/lib/utils/order-status.ts
  • src/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

Comment thread src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx
Comment thread src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx Outdated
Comment thread src/app/[country]/[locale]/(storefront)/account/orders/page.tsx Outdated
Comment thread src/components/products/filters/FilterChips.tsx
Comment thread src/lib/utils/filters.ts
Comment thread src/types/next-intl.d.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/lib/utils/filters.ts (2)

85-92: ⚠️ Potential issue | 🟡 Minor

Same any type issue in getAvailabilityLabel.

The t parameter 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 | 🟡 Minor

Avoid any type in function parameter signature.

The t parameter uses (key: any) => string which violates the coding guideline to avoid any. This also undermines type safety when integrating with useTranslations.

Consider using a union type of the valid message keys or unknown with 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.

OrdersPage and loadOrders should 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 catch path hides failure causes. Please record the error via the project logger/telemetry before falling back to setOrders([]) 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 with satisfies to eliminate the cast and improve type safety.

The cast as keyof IntlMessages["orders"] at lines 205–208 weakens compile-time guarantees. Type SHIPMENT_STATE_KEY in src/lib/utils/order-status.ts using satisfies 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 unknownShipmentStatus are 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9f18891 and 0c1d738.

📒 Files selected for processing (6)
  • src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/page.tsx
  • src/lib/utils/filters.ts
  • src/lib/utils/price-buckets.ts
  • src/types/next-intl.d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/next-intl.d.ts

Comment thread src/lib/utils/price-buckets.ts
Cichorek and others added 3 commits March 16, 2026 19:04
- 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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrate 48 commits from main (SDK 0.20, server-side header/footer,
breadcrumbs, color swatches, store policies, sitemap, gift cards,
email templates) while preserving and extending i18n support
(next-intl EN/PL/DE).

Key merge resolutions:
- Header/Footer: kept as async server components using getTranslations()
- SDK renames: shipments→fulfillments, promo→discount, etc.
- StoreContext: adopted main's derived-values approach + useCountrySwitch
- 14 new components from main wrapped with i18n translations
- New translation keys for gift cards, policies, breadcrumbs, etc.

Bug fixes from code review:
- Fix CouponCode hasAppliedCode using && instead of || (pre-existing)
- Fix Summary.tsx using wrong namespace t() vs tc() for gift card labels
- Fix formatDate/formatDateTime hardcoding en-US locale
- Restore Footer copyright hyperlink to spreecommerce.org

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
storefront Ready Ready Preview, Comment Apr 8, 2026 10:27am

Request Review

Cichorek and others added 3 commits April 7, 2026 19:34
Next.js 16 requires components using useSearchParams() to be wrapped
in a <Suspense> boundary to avoid blocking prerendering. Split
CheckoutPage into CheckoutPageContent (inner) and CheckoutPage
(wrapper with Suspense fallback).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cookies() API is runtime data that blocks prerendering. Since the
locale is always available from the [locale] route segment, the cookie
fallback was unnecessary. Simplified to only use requestLocale with
"en" as default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root layout was calling getLocale()/getMessages() which access runtime
data (cookies/headers), blocking prerendering in Next.js 16.

Fix: move NextIntlClientProvider to [country]/[locale]/layout.tsx where
the locale is available from route params. Pass locale explicitly to
getMessages() so the i18n request config uses the explicit locale param
instead of awaiting requestLocale (runtime data).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (2)
src/components/account/OrderList.tsx (1)

1-21: ⚠️ Potential issue | 🟠 Major

Convert OrderList to a Server Component to avoid unnecessary client boundary.

This read-only table component has no event handlers, state, effects, or browser APIs—only useLocale and useTranslations which can be replaced with their server-side equivalents. Following the pattern established in Header.tsx and Footer.tsx, convert it to an async Server Component using getTranslations and getLocale from "next-intl/server" to eliminate unnecessary hydration overhead.

💡 Suggested refactor
-"use client";
-
 import type { Order } from "@spree/sdk";
 import Link from "next/link";
-import { useLocale, useTranslations } from "next-intl";
+import { getLocale, getTranslations } from "next-intl/server";
 import { Button } from "@/components/ui/button";
 import {
   formatDate,
   getFulfillmentStatusColor,
   getPaymentStatusColor,
 } from "@/lib/utils/format";

 interface OrderListProps {
   orders: Order[];
   basePath: string;
 }

-export function OrderList({ orders, basePath }: OrderListProps) {
-  const t = useTranslations("orders");
-  const locale = useLocale();
+export async function OrderList({ orders, basePath }: OrderListProps) {
+  const t = await getTranslations("orders");
+  const locale = await getLocale();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/account/OrderList.tsx` around lines 1 - 21, Remove the "use
client" boundary and convert the OrderList component into an async Server
Component: delete the "use client" directive, change the exported function
OrderList to async, replace the client hooks useTranslations and useLocale with
server imports getTranslations and getLocale from "next-intl/server" and call
them at the top of the async function to obtain t (via
getTranslations("orders")) and locale (via getLocale()), and keep the rest of
the rendering logic (formatDate, getFulfillmentStatusColor,
getPaymentStatusColor, Link, Button) unchanged so the component no longer
hydrates on the client.
src/components/checkout/CouponCode.tsx (1)

39-53: ⚠️ Potential issue | 🟠 Major

Reset loading state in finally for all three async handlers.

If any of these callbacks rejects, setApplying(false) / setRemoving(null) never runs. The control stays disabled until the component remounts.

💡 Proposed fix
   const handleApply = async (e: React.FormEvent) => {
     e.preventDefault();
     if (!code.trim()) return;

     setApplying(true);
     setError(null);

-    const result = await onApply(code.trim());
-    if (result.success) {
-      setCode("");
-    } else {
-      setError(result.error || t("invalidCode"));
-    }
-
-    setApplying(false);
+    try {
+      const result = await onApply(code.trim());
+      if (result.success) {
+        setCode("");
+      } else {
+        setError(result.error || t("invalidCode"));
+      }
+    } finally {
+      setApplying(false);
+    }
   };

Mirror the same try/finally pattern in handleRemoveDiscount and handleRemoveGiftCard. If those callbacks can reject with user-facing failures, add a catch that maps them to the existing translated fallback keys as well.

Based on learnings: Ensure components in checkout flows guard auto-save or backend calls with a processing state to prevent redundant or erroneous backend calls during loading or processing states.

Also applies to: 56-66, 68-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/checkout/CouponCode.tsx` around lines 39 - 53, The async
handlers (handleApply, handleRemoveDiscount, handleRemoveGiftCard) currently set
UI state (setApplying / setRemoving) but rely on normal completion to clear it;
if the awaited callbacks reject the state remains stuck. Wrap the body of each
handler in a try/finally so setApplying(false) or setRemoving(null) always runs
in the finally block, and add a catch block that maps rejections to user-facing
errors (use the same t("invalidCode")-style fallback) before rethrowing or
returning the existing result shape; locate and update the functions named
handleApply, handleRemoveDiscount, and handleRemoveGiftCard accordingly.
🧹 Nitpick comments (7)
src/components/products/filters/FilterChips.tsx (1)

47-51: Consider using translation interpolation for option chip labels.

The option chip label uses hardcoded : separator which doesn't give translators control over punctuation or word order. While optionFilter.label and option.label come from the API, the punctuation should ideally be translator-controlled for consistency with the price chip pattern.

♻️ Suggested refactor
         if (option) {
           chips.push({
             key: `option-${optionValueId}`,
-            label: `${optionFilter.label}: ${option.label}`,
+            label: t("optionFilterChip", {
+              filter: optionFilter.label,
+              value: option.label,
+            }),
             onRemove: () => onRemoveOptionValue(optionValueId),
           });

Add the corresponding key to your translation files:

{
  "optionFilterChip": "{filter}: {value}"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/filters/FilterChips.tsx` around lines 47 - 51,
Replace the hardcoded colon-separated label in the chips.push call inside
FilterChips.tsx so translators control punctuation and order: instead of
`${optionFilter.label}: ${option.label}`, use the i18n translation key (e.g.,
"optionFilterChip") with interpolation passing filter=optionFilter.label and
value=option.label; keep the same key and onRemove handler (onRemoveOptionValue)
and add "optionFilterChip": "{filter}: {value}" to your translation files.
src/components/navigation/Breadcrumbs.tsx (1)

1-2: Consider keeping this as a Server Component using getTranslations.

The component was converted to a client component solely for the useTranslations hook, but next-intl provides getTranslations for Server Components. Since there are no other client-side requirements (no state, effects, or event handlers), the server-side approach would reduce the client bundle.

♻️ Server Component alternative
-"use client";
-
 import type { Category } from "@spree/sdk";
 import { ChevronRight } from "lucide-react";
 import Link from "next/link";
-import { useTranslations } from "next-intl";
+import { getTranslations } from "next-intl/server";

 // ...

-export function Breadcrumbs({
+export async function Breadcrumbs({
   category,
   basePath,
   productName,
 }: BreadcrumbsProps) {
-  const t = useTranslations("navigation");
+  const t = await getTranslations("navigation");

As per coding guidelines: "Use Server Components by default. Only add 'use client' when you need event handlers, hooks like useState/useReducer/useEffect/useContext, browser-only APIs, or custom hooks that use state/effects."

Also applies to: 6-6, 19-19

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/navigation/Breadcrumbs.tsx` around lines 1 - 2, The
Breadcrumbs component was made a client component only for useTranslations;
revert it to a Server Component by removing the "use client" directive and
replacing useTranslations with next-intl's getTranslations (call getTranslations
for the appropriate namespace inside an async Breadcrumbs component or at server
render time), update any signature to be async if needed, and ensure no
client-only hooks/event handlers remain; similarly apply the same change for the
other occurrences noted (lines 6-6, 19-19).
src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx (2)

22-23: Expand metadata beyond title for SEO completeness.

generateMetadata still returns only title. Add description and openGraph fields for both found/not-found branches.

♻️ Suggested patch
 export async function generateMetadata({
   params,
 }: PolicyPageProps): Promise<Metadata> {
   const { slug } = await params;
   const policy = await getPolicy(slug);

   if (!policy) {
     const t = await getTranslations("policies");
-    return { title: t("policyNotFound") };
+    const title = t("policyNotFound");
+    return {
+      title,
+      description: title,
+      openGraph: {
+        title,
+        description: title,
+      },
+    };
   }

   const storeName = getStoreName();
+  const title = storeName ? `${policy.name} | ${storeName}` : policy.name;
+  const description = (policy.body ?? policy.name).slice(0, 160);

   return {
-    title: storeName ? `${policy.name} | ${storeName}` : policy.name,
+    title,
+    description,
+    openGraph: {
+      title,
+      description,
+    },
   };
 }

As per coding guidelines, src/app/**/page.tsx: "Use the Metadata API with generateMetadata function to set page titles, descriptions, and Open Graph images for SEO."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx around
lines 22 - 23, generateMetadata currently returns only a title; update the
function (the generateMetadata implementation that calls getTranslations and
returns { title: t("policyNotFound") }) to also return description and openGraph
metadata for both the found and not-found branches. Use the translations from t
(e.g., t("policyNotFound") and a suitable t key for the description) to populate
description, and add an openGraph object with at least title, description, and
images (use a default OG image or construct one from route data). Ensure both
branches return the full metadata shape instead of just title.

33-33: Add an explicit return type to PolicyPage.

The default-exported async function should declare a return type to satisfy strict TS guidelines.

♻️ Suggested patch
+import type { JSX } from "react";
 import type { Metadata } from "next";
 import { notFound } from "next/navigation";
 import { getTranslations } from "next-intl/server";
@@
-export default async function PolicyPage({ params }: PolicyPageProps) {
+export default async function PolicyPage({
+  params,
+}: PolicyPageProps): Promise<JSX.Element> {

As per coding guidelines, **/*.{tsx,ts}: "Use strict TypeScript type checking. Always define explicit return types for functions..."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx at line 33,
The exported async function PolicyPage lacks an explicit return type; update its
signature to declare the resolved return type (e.g. change "export default async
function PolicyPage({ params }: PolicyPageProps)" to "export default async
function PolicyPage({ params }: PolicyPageProps): Promise<JSX.Element>" or
Promise<React.ReactNode> as appropriate so it satisfies strict TS rules while
leaving the existing implementation intact; reference the PolicyPage function
and PolicyPageProps to locate and update the signature.
src/components/policy/PolicyConsent.tsx (1)

45-45: Avoid hardcoded whitespace around translated conjunction.

Line 45 injects spaces in code (\ ${t("policySeparatorAnd")} ``). Let the translation value own spacing/punctuation so each locale can format naturally.

Suggested tweak
-              ` ${t("policySeparatorAnd")} `
+              t("policySeparatorAnd")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/policy/PolicyConsent.tsx` at line 45, In PolicyConsent.tsx
remove the hardcoded spaces around the translated conjunction (the template
expression using ` ${t("policySeparatorAnd")} `) so the translation owns its
spacing and punctuation; instead concatenate or render adjacent strings without
injecting leading/trailing spaces (keep the call to t("policySeparatorAnd")
as-is) and update surrounding literals so locales can control
spacing/punctuation correctly.
src/app/layout.tsx (1)

45-45: Consider adding a fallback to <Suspense>.

The Suspense boundary has no fallback prop, meaning children will render nothing while suspended. For a root-level boundary, consider adding a minimal fallback (e.g., a spinner or skeleton) to avoid a blank screen during any suspense.

💡 Suggested improvement
-        <NextIntlClientProvider messages={messages} locale={locale}>
-          <Suspense>
+        <NextIntlClientProvider messages={messages} locale={locale}>
+          <Suspense fallback={null}>

Or with a minimal loading indicator:

-          <Suspense>
+          <Suspense fallback={<div className="min-h-screen" />}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` at line 45, The Suspense boundary in src/app/layout.tsx
(the <Suspense> wrapper) lacks a fallback, so add a minimal fallback prop to
avoid a blank root while children suspend; update the <Suspense> element to
include a fallback (for example a small LoadingSpinner or simple JSX like
<div>Loading…</div>), ensure any LoadingSpinner component is imported or created
(e.g., LoadingSpinner or RootFallback) and used as the fallback prop of Suspense
so the root-level boundary always shows a minimal indicator during suspension.
src/components/products/ProductCustomFields.tsx (1)

12-17: Consider using namespace-specific typing for the translation function.

The t parameter is typed as ReturnType<typeof useTranslations> (generic), while GiftCardList.tsx uses the more specific ReturnType<typeof useTranslations<"giftCards">>. Using the namespace-specific type enables compile-time validation of translation keys.

💡 Suggested improvement
 function renderBooleanValue(
   value: unknown,
-  t: ReturnType<typeof useTranslations>,
+  t: ReturnType<typeof useTranslations<"products">>,
 ): React.ReactNode {
   return value ? t("yes") : t("no");
 }

 function renderValue(
   field: CustomField,
   normalizedType: string,
-  t: ReturnType<typeof useTranslations>,
+  t: ReturnType<typeof useTranslations<"products">>,
 ): React.ReactNode {

Also applies to: 19-23

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/ProductCustomFields.tsx` around lines 12 - 17, The
translation parameter is typed generically; update the type to a
namespace-specific ReturnType so keys are validated at compile time — change the
t parameter type in renderBooleanValue to ReturnType<typeof
useTranslations<"products">> (and make the same change for the other
translation-taking function in this file at lines 19-23) so both functions use
the products namespace-specific useTranslations type.
🤖 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/components/account/CreditCardList.tsx`:
- Line 119: The alert currently interpolates the raw SDK error via result.error
in CreditCardList (alert(t("failedToRemove", { error: result.error }))), which
can leak internal messages; change the call to omit the error parameter so the
user sees only the generic translation (e.g., alert(t("failedToRemove"))) or
pass a sanitized/known-safe message instead, updating any related UI text keys
if needed (touch the handler that receives result from the actionResult wrapper
and the alert/t("failedToRemove") invocation).

In `@src/components/account/OrderDetail.tsx`:
- Around line 1-7: OrderDetail was marked as a client component but the nested
functions LineItemCard and FulfillmentBlock still call client hooks
(useTranslations/useLocale), so converting only OrderDetail to async breaks the
component; fix by either extracting LineItemCard and FulfillmentBlock into their
own client components under src/components/order/ (so they can keep using
useTranslations/useLocale) or change those nested functions to be pure render
helpers that receive all translated strings and locale as props from OrderDetail
(remove any useTranslations/useLocale calls inside them); update imports/exports
and component names (LineItemCard, FulfillmentBlock, OrderDetail) accordingly
and ensure no client hooks are used in server/async components.

In `@src/components/account/OrderList.tsx`:
- Around line 69-77: The status badges render raw API strings
(order.payment_status and order.fulfillment_status) instead of localized labels;
update the rendering to pass these values through the i18n translator (t) or a
localization map before display — e.g. use t with a namespaced key like
t(`paymentStatus.${order.payment_status}`) for the payment badge and
t(`fulfillmentStatus.${order.fulfillment_status}`) for the fulfillment badge
(keep getFulfillmentStatusColor(order.fulfillment_status) for color logic), and
ensure fallback to t("notAvailable") when the key is missing.

In `@src/components/addresses/AddressManagement.tsx`:
- Line 160: The alert builds the localized message by concatenating
t("failedToDeleteAddress") with ": " and result.error, which can break grammar
in some locales; change it to use a single translation key with an interpolation
placeholder (e.g., add a key like failedToDeleteAddressWithError) and call t
with the error param from the AddressManagement component where the alert
currently exists (replace the concatenation alert(...) that references
t("failedToDeleteAddress") and result.error with a single
t("failedToDeleteAddressWithError", { error: result.error })).

In `@src/components/checkout/CouponCode.tsx`:
- Line 82: The conditional used to hide the input (hasAppliedCode =
couponPromotions.length > 0 || !!appliedGiftCard) incorrectly collapses the
entry form as soon as any single code exists; change the logic to only hide a
specific entry when that entry type is already applied. Replace uses of
hasAppliedCode with checks against the precise arrays/values (e.g.,
couponPromotions.length > 0 for coupon input, appliedGiftCard for gift card
input) in the rendering logic (referencing couponPromotions, appliedGiftCard,
and hasAppliedCode) so both the coupon and gift-card inputs can render
independently and simultaneously; update the similar checks around the other
occurrences (lines noted near 143-145) to follow the same pattern.

In `@src/components/policy/PolicyConsent.tsx`:
- Around line 38-39: CONSENT_POLICIES currently contains hardcoded English names
(policy.name) used in PolicyConsent; add translation keys to each policy (e.g.,
add a nameKey or titleKey like "policies.privacyPolicy" /
"policies.termsOfService") and update the mapping to reference these keys
instead of static strings. In PolicyConsent.tsx, replace usages of policy.name
with the localized string via the i18n translator (e.g., t(policy.nameKey)) and
keep a safe fallback to policy.name if the key is missing. Apply the same change
pattern to Footer.tsx, the policies page template, and the checkout layout where
policy.name is rendered so all link text uses translation keys (update tests or
snapshots as needed).

---

Outside diff comments:
In `@src/components/account/OrderList.tsx`:
- Around line 1-21: Remove the "use client" boundary and convert the OrderList
component into an async Server Component: delete the "use client" directive,
change the exported function OrderList to async, replace the client hooks
useTranslations and useLocale with server imports getTranslations and getLocale
from "next-intl/server" and call them at the top of the async function to obtain
t (via getTranslations("orders")) and locale (via getLocale()), and keep the
rest of the rendering logic (formatDate, getFulfillmentStatusColor,
getPaymentStatusColor, Link, Button) unchanged so the component no longer
hydrates on the client.

In `@src/components/checkout/CouponCode.tsx`:
- Around line 39-53: The async handlers (handleApply, handleRemoveDiscount,
handleRemoveGiftCard) currently set UI state (setApplying / setRemoving) but
rely on normal completion to clear it; if the awaited callbacks reject the state
remains stuck. Wrap the body of each handler in a try/finally so
setApplying(false) or setRemoving(null) always runs in the finally block, and
add a catch block that maps rejections to user-facing errors (use the same
t("invalidCode")-style fallback) before rethrowing or returning the existing
result shape; locate and update the functions named handleApply,
handleRemoveDiscount, and handleRemoveGiftCard accordingly.

---

Nitpick comments:
In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx:
- Around line 22-23: generateMetadata currently returns only a title; update the
function (the generateMetadata implementation that calls getTranslations and
returns { title: t("policyNotFound") }) to also return description and openGraph
metadata for both the found and not-found branches. Use the translations from t
(e.g., t("policyNotFound") and a suitable t key for the description) to populate
description, and add an openGraph object with at least title, description, and
images (use a default OG image or construct one from route data). Ensure both
branches return the full metadata shape instead of just title.
- Line 33: The exported async function PolicyPage lacks an explicit return type;
update its signature to declare the resolved return type (e.g. change "export
default async function PolicyPage({ params }: PolicyPageProps)" to "export
default async function PolicyPage({ params }: PolicyPageProps):
Promise<JSX.Element>" or Promise<React.ReactNode> as appropriate so it satisfies
strict TS rules while leaving the existing implementation intact; reference the
PolicyPage function and PolicyPageProps to locate and update the signature.

In `@src/app/layout.tsx`:
- Line 45: The Suspense boundary in src/app/layout.tsx (the <Suspense> wrapper)
lacks a fallback, so add a minimal fallback prop to avoid a blank root while
children suspend; update the <Suspense> element to include a fallback (for
example a small LoadingSpinner or simple JSX like <div>Loading…</div>), ensure
any LoadingSpinner component is imported or created (e.g., LoadingSpinner or
RootFallback) and used as the fallback prop of Suspense so the root-level
boundary always shows a minimal indicator during suspension.

In `@src/components/navigation/Breadcrumbs.tsx`:
- Around line 1-2: The Breadcrumbs component was made a client component only
for useTranslations; revert it to a Server Component by removing the "use
client" directive and replacing useTranslations with next-intl's getTranslations
(call getTranslations for the appropriate namespace inside an async Breadcrumbs
component or at server render time), update any signature to be async if needed,
and ensure no client-only hooks/event handlers remain; similarly apply the same
change for the other occurrences noted (lines 6-6, 19-19).

In `@src/components/policy/PolicyConsent.tsx`:
- Line 45: In PolicyConsent.tsx remove the hardcoded spaces around the
translated conjunction (the template expression using `
${t("policySeparatorAnd")} `) so the translation owns its spacing and
punctuation; instead concatenate or render adjacent strings without injecting
leading/trailing spaces (keep the call to t("policySeparatorAnd") as-is) and
update surrounding literals so locales can control spacing/punctuation
correctly.

In `@src/components/products/filters/FilterChips.tsx`:
- Around line 47-51: Replace the hardcoded colon-separated label in the
chips.push call inside FilterChips.tsx so translators control punctuation and
order: instead of `${optionFilter.label}: ${option.label}`, use the i18n
translation key (e.g., "optionFilterChip") with interpolation passing
filter=optionFilter.label and value=option.label; keep the same key and onRemove
handler (onRemoveOptionValue) and add "optionFilterChip": "{filter}: {value}" to
your translation files.

In `@src/components/products/ProductCustomFields.tsx`:
- Around line 12-17: The translation parameter is typed generically; update the
type to a namespace-specific ReturnType so keys are validated at compile time —
change the t parameter type in renderBooleanValue to ReturnType<typeof
useTranslations<"products">> (and make the same change for the other
translation-taking function in this file at lines 19-23) so both functions use
the products namespace-specific useTranslations type.
🪄 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: fc8c7ba7-207e-4b1e-bfba-408063be4f05

📥 Commits

Reviewing files that changed from the base of the PR and between 0c1d738 and a5ddc37.

📒 Files selected for processing (63)
  • messages/de.json
  • messages/en.json
  • messages/pl.json
  • next.config.ts
  • package.json
  • scripts/check-locale-parity.ts
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/addresses/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/credit-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/app/[country]/[locale]/(storefront)/c/[...permalink]/CategoryProductsContent.tsx
  • src/app/[country]/[locale]/(storefront)/cart/page.tsx
  • src/app/[country]/[locale]/(storefront)/page.tsx
  • src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx
  • src/app/[country]/[locale]/(storefront)/products/ProductsContent.tsx
  • src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/app/layout.tsx
  • src/components/account/CreditCardList.tsx
  • src/components/account/GiftCardList.tsx
  • src/components/account/OrderDetail.tsx
  • src/components/account/OrderList.tsx
  • src/components/addresses/AddressManagement.tsx
  • src/components/cart/CartDrawer.tsx
  • src/components/checkout/AddressEditModal.tsx
  • src/components/checkout/AddressFormFields.tsx
  • src/components/checkout/AddressSection.tsx
  • src/components/checkout/AddressSelector.tsx
  • src/components/checkout/CouponCode.tsx
  • src/components/checkout/DeliveryMethodSection.tsx
  • src/components/checkout/PaymentSection.tsx
  • src/components/checkout/Summary.tsx
  • src/components/layout/CartButton.tsx
  • src/components/layout/CountrySwitcher.tsx
  • src/components/layout/Footer.tsx
  • src/components/layout/Header.tsx
  • src/components/layout/MobileMenu.tsx
  • src/components/layout/SearchToggle.tsx
  • src/components/navigation/Breadcrumbs.tsx
  • src/components/order/OrderTotals.tsx
  • src/components/order/PaymentInfo.tsx
  • src/components/policy/PolicyConsent.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/products/ProductCard.tsx
  • src/components/products/ProductCustomFields.tsx
  • src/components/products/ProductGrid.tsx
  • src/components/products/ProductListingLayout.tsx
  • src/components/products/VariantPicker.tsx
  • src/components/products/filters/FilterChips.tsx
  • src/components/products/filters/MobileFilterDrawer.tsx
  • src/components/products/filters/ProductFilters.tsx
  • src/components/search/SearchBar.tsx
  • src/contexts/StoreContext.tsx
  • src/i18n/request.ts
  • src/lib/utils/filters.ts
  • src/lib/utils/format.ts
  • src/lib/utils/price-buckets.ts
  • src/types/next-intl.d.ts
✅ Files skipped from review due to trivial changes (15)
  • src/app/[country]/[locale]/(storefront)/account/addresses/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/page.tsx
  • src/components/products/ProductCard.tsx
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/components/checkout/AddressSelector.tsx
  • src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/components/checkout/AddressEditModal.tsx
  • messages/de.json
  • src/components/checkout/AddressFormFields.tsx
  • src/app/[country]/[locale]/(storefront)/cart/page.tsx
  • src/components/checkout/AddressSection.tsx
  • messages/pl.json
  • src/app/[country]/[locale]/(storefront)/page.tsx
  • messages/en.json
  • src/components/layout/CountrySwitcher.tsx
🚧 Files skipped from review as they are similar to previous changes (22)
  • src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/c/[...permalink]/CategoryProductsContent.tsx
  • src/contexts/StoreContext.tsx
  • src/app/[country]/[locale]/(storefront)/account/credit-cards/page.tsx
  • src/components/checkout/Summary.tsx
  • src/components/products/VariantPicker.tsx
  • src/components/products/ProductListingLayout.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/layout/Footer.tsx
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • scripts/check-locale-parity.ts
  • src/components/products/filters/MobileFilterDrawer.tsx
  • src/components/products/filters/ProductFilters.tsx
  • src/i18n/request.ts
  • src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/layout.tsx
  • src/components/checkout/PaymentSection.tsx
  • package.json
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/components/layout/Header.tsx
  • src/components/cart/CartDrawer.tsx
  • src/lib/utils/price-buckets.ts

Comment thread src/components/account/CreditCardList.tsx Outdated
Comment thread src/components/account/OrderDetail.tsx Outdated
Comment thread src/components/account/OrderList.tsx Outdated
Comment thread src/components/addresses/AddressManagement.tsx Outdated
Comment thread src/components/checkout/CouponCode.tsx Outdated
Comment thread src/components/policy/PolicyConsent.tsx
CartDrawer uses useTranslations which requires NextIntlClientProvider
context. Since the provider was moved to [country]/[locale]/layout.tsx,
CartProvider/CartDrawer/Toaster must also live inside that layout —
not in the root layout which is outside the i18n boundary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/layout.tsx (1)

29-37: ⚠️ Potential issue | 🟡 Minor

Move the <html> element to the locale-aware nested layout to ensure lang reflects the actual user locale.

The root layout hardcodes lang="en", but the actual locale (en, de, or pl) is determined in src/app/[country]/[locale]/layout.tsx. Since nested layouts cannot override the parent's <html> tag, all pages render with lang="en" regardless of the current locale, breaking accessibility and SEO for non-English pages.

Move the <html> wrapper from the root layout to src/app/[country]/[locale]/layout.tsx where the locale is available via params, then set lang dynamically to the resolved locale.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` around lines 29 - 37, The root layout currently renders
the <html lang="en"> wrapper statically in the RootLayout component; move that
<html> wrapper into the locale-aware nested layout (the default export in
src/app/[country]/[locale]/layout.tsx) so you can set lang dynamically from
params.locale (e.g., const { locale } = params). Update the root layout
(src/app/layout.tsx) to return only the body/children fragment (remove the
<html> element and lang="en"), and in the nested layout render <html
lang={locale}> wrapping the nested <body> and children; ensure GoogleTagManager
and geist classes are preserved in the nested layout if needed.
🧹 Nitpick comments (2)
src/app/[country]/[locale]/layout.tsx (2)

68-68: Suspense without a fallback will render nothing during loading.

Per coding guidelines, Suspense boundaries should have fallback components for loading states. Consider adding a minimal loading indicator.

♻️ Suggested refactor
-      <Suspense>
+      <Suspense fallback={null}>

Or provide a meaningful fallback:

-      <Suspense>
+      <Suspense fallback={<div className="min-h-screen" />}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/layout.tsx at line 68, The Suspense boundary in
layout.tsx currently has no fallback and will render nothing while loading;
update the <Suspense> usage to include a fallback prop (e.g.,
fallback={<LoadingIndicator />} or a minimal inline loader) so a loading UI
displays, and if needed add or reuse a small component like LoadingIndicator or
MinimalLoader and import it into layout.tsx before using it as the Suspense
fallback.

61-66: Consider importing the Locale type to avoid duplication.

The inline cast as "en" | "de" | "pl" is repeated and duplicates the Locale type defined in src/i18n/request.ts. Importing and reusing the type (or a shared supportedLocales constant) would improve maintainability.

♻️ Suggested refactor
+import type { Locale } from "next-intl";
 // ...
-  const messages = await getMessages({ locale: locale as "en" | "de" | "pl" });
+  const messages = await getMessages({ locale: locale as Locale });
 // ...
-      locale={locale as "en" | "de" | "pl"}
+      locale={locale as Locale}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/layout.tsx around lines 61 - 66, Replace the
repeated inline cast `as "en" | "de" | "pl"` with the shared `Locale` type from
your i18n types: import Locale and use `locale as Locale` wherever the cast
appears (e.g., the getMessages call and the NextIntlClientProvider locale prop)
or alternatively import a shared supportedLocales constant and use that type;
update any related signatures (getMessages usage and NextIntlClientProvider
props) to reference the imported Locale to eliminate duplication and keep types
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/app/layout.tsx`:
- Around line 29-37: The root layout currently renders the <html lang="en">
wrapper statically in the RootLayout component; move that <html> wrapper into
the locale-aware nested layout (the default export in
src/app/[country]/[locale]/layout.tsx) so you can set lang dynamically from
params.locale (e.g., const { locale } = params). Update the root layout
(src/app/layout.tsx) to return only the body/children fragment (remove the
<html> element and lang="en"), and in the nested layout render <html
lang={locale}> wrapping the nested <body> and children; ensure GoogleTagManager
and geist classes are preserved in the nested layout if needed.

---

Nitpick comments:
In `@src/app/`[country]/[locale]/layout.tsx:
- Line 68: The Suspense boundary in layout.tsx currently has no fallback and
will render nothing while loading; update the <Suspense> usage to include a
fallback prop (e.g., fallback={<LoadingIndicator />} or a minimal inline loader)
so a loading UI displays, and if needed add or reuse a small component like
LoadingIndicator or MinimalLoader and import it into layout.tsx before using it
as the Suspense fallback.
- Around line 61-66: Replace the repeated inline cast `as "en" | "de" | "pl"`
with the shared `Locale` type from your i18n types: import Locale and use
`locale as Locale` wherever the cast appears (e.g., the getMessages call and the
NextIntlClientProvider locale prop) or alternatively import a shared
supportedLocales constant and use that type; update any related signatures
(getMessages usage and NextIntlClientProvider props) to reference the imported
Locale to eliminate duplication and keep types consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: af0c4ffd-b059-4576-bdc5-0a50852e6f45

📥 Commits

Reviewing files that changed from the base of the PR and between a5ddc37 and b7af700.

📒 Files selected for processing (4)
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[country]/[locale]/layout.tsx
  • src/app/layout.tsx
  • src/i18n/request.ts

…lity

Root cause: next-intl's getMessages() in [locale] layout caused all
pages to fail prerendering with "Uncached data accessed outside
<Suspense>".

Fix:
- Root layout: restore main's structure (Suspense > CartProvider >
  CartDrawer + Toaster). CartDrawer reverted to main's version
  (no i18n) since it renders outside NextIntlClientProvider.
- [locale] layout: load translation JSON via static imports instead
  of getMessages() to avoid runtime data access during prerender.
- Restore next-intl plugin in next.config.ts.

Build succeeds with all pages using Partial Prerender.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The lockfile from main was missing next-intl, @swc/helpers, tsx and
their transitive dependencies, causing npm ci to fail in CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cichorek and others added 2 commits April 7, 2026 20:29
All server components using getTranslations() were defaulting to "en"
because they called getTranslations("namespace") without passing the
locale explicitly. The requestLocale from next-intl's config was not
resolving correctly.

Fix: pass locale explicitly to all getTranslations() calls:
- Page components: extract locale from params, pass as
  getTranslations({ locale, namespace })
- Header/Footer: added locale prop, passed from storefront layout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Created messages/es.json and messages/fr.json with full translations
- Updated Locale type to include "es" | "fr"
- Added es/fr to supportedLocales in i18n/request.ts
- Added es/fr message imports to [locale] layout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cichorek and others added 2 commits April 7, 2026 20:43
…ver components

Inline fixes:
- CreditCardList: remove raw SDK error from alert, use generic message
- AddressManagement: replace string concatenation with clean translated message
- OrderList: localize payment/fulfillment status badges via translation keys,
  convert to async server component (no client hooks needed)
- PolicyConsent + policies.ts: add nameKey to policy entries, render translated
  policy names in consent checkbox, footer, and checkout layout
- CouponCode: wrap all async handlers in try/catch/finally to prevent stuck state

Additional improvements:
- Breadcrumbs: convert to async server component with locale prop
- FilterChips: replace hardcoded colon-separated label with translation key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Policy page: add description and openGraph metadata, explicit return type
- Root layout: add explicit fallback={null} to Suspense boundary
- PolicyConsent: move conjunction spacing into translation strings
- ProductCustomFields: use namespace-specific ReturnType<typeof useTranslations<"products">>

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OrderDetail: extracted LineItemCard and FulfillmentBlock into separate
  client component files (src/components/order/), converted OrderDetail
  to async server component with getTranslations
- CouponCode: changed hasAppliedCode logic — input now hides only when
  BOTH a coupon AND a gift card are applied (was hiding on either)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cichorek and others added 2 commits April 7, 2026 21:39
Footer now uses t("footer.description") from locale files when
NEXT_PUBLIC_STORE_DESCRIPTION env var is not set. Removed unused
storeDescription from StoreContext.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
t("description") from locale files now takes precedence over
NEXT_PUBLIC_STORE_DESCRIPTION env var, with env as fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CartDrawer was in root layout (outside NextIntlClientProvider) so all
its strings were hardcoded English. Moved CartDrawer + Toaster into
[country]/[locale]/layout.tsx inside the i18n provider, restored all
useTranslations calls. CartProvider stays in root layout (no i18n needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolved package.json/package-lock.json conflicts: kept next-intl and tsx
from this branch, adopted @vercel/analytics and dropped @swc/helpers from main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
next-intl's @swc/core requires @swc/helpers>=0.5.17 but only 0.5.15
was resolved transitively. Adding it directly ensures the correct
version is hoisted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@damianlegawiec damianlegawiec merged commit 04cfde5 into main Apr 8, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants