Skip to content

Conversation

@yuriassuncx
Copy link
Contributor

@yuriassuncx yuriassuncx commented Sep 10, 2025

What is this Contribution About?

This PR introduces internationalization (i18n) support for the main Shopify queries and loaders used in Deco.cx. With this update, it's now possible to dynamically pass languageCode and countryCode values via the @inContext directive, allowing the Shopify Storefront API to return localized content for different languages and regions.

Additionally, this PR enhances the sitemap handling logic by introducing the ability to exclude specific URLs from the sitemap response using a new exclude prop in the proxy handler.


🧩 How It Works

✅ Loaders

Most loaders now accept the following optional props:

/**
 * @title Language Code
 * @description Language code for the storefront API
 * @example "EN" for English, "FR" for French, etc.
 */
languageCode?: LanguageCode;

/**
 * @title Country Code
 * @description Country code for the storefront API
 * @example "US" for United States, "FR" for France, etc.
 */
countryCode?: CountryCode;

These values are passed as variables to GraphQL queries and injected using the @inContext directive.


✅ Queries

All core Shopify queries have been refactored to use GraphQL variables in the @inContext directive (instead of interpolated JavaScript), making them fully compatible with GraphQL tooling and static code analysis like GraphQL Codegen.

Example:

query GetProduct(
  $handle: String, 
  $identifiers: [HasMetafieldsIdentifier!]!,
  $languageCode: LanguageCode,
  $countryCode: CountryCode
) @inContext(language: $languageCode, country: $countryCode) {
  product(handle: $handle) {
    ...Product
  }
}

On the TypeScript side:

variables: {
  handle,
  identifiers: metafields,
  languageCode,
  countryCode,
}

🗺️ Sitemap Enhancements

The sitemap proxy handler now accepts two optional props:

include?: string[];
exclude?: string[];

This enables:

  • Injecting custom sitemap entries (include)
  • 🚫 Removing unwanted URLs from the proxied Shopify sitemap (exclude)

Example usage:

{
  "include": ["/sitemap_pages_custom.xml"],
  "exclude": [
    "/sitemap_products_1.xml",
    "/sitemap_pages_1.xml",
    "/sitemap_collections_1.xml",
    "/sitemap_blogs_1.xml"
  ]
}

This resolves issues where Shopify includes non-localized or duplicated sitemap entries that should not be indexed, allowing more control over SEO structure.


🛠 Affected Queries

This pattern has been applied consistently across:

  • ListProducts
  • SearchProducts
  • ProductsByCollection
  • ProductRecommendations
  • GetProduct and related queries
  • Shop
  • Cart

🌍 Impact

  • ✅ Native internationalization support
  • 📈 Improved SEO control with sitemap inclusion/exclusion
  • 🌐 Global readiness: Better scaling for multilingual/multiregional stores
  • 🛡️ Backward compatible: All new props are optional
  • 🔧 Improved DX: Cleaner GraphQL with full tooling compatibility

🎥 Loom Video

https://www.loom.com/share/8a8f204de9a745528ac2f84f44f428fa

Summary by CodeRabbit

  • New Features

    • Locale-aware storefront across product pages, listings, related products, cart, and shop (defaults to PT/BR, optional overrides).
    • Cart buyer identity can be set/updated for locale-aware pricing, taxes, and behavior.
    • Option to exclude specific paths from the Shopify sitemap and to inject additional sitemap entries.
  • Improvements

    • Search, collections, recommendations, and shop data respect language/country context.
    • Breadcrumbs handle nested collection paths better.
    • More consistent metafield image handling and clearer auth/cart error responses.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 10, 2025

Walkthrough

Threads optional languageCode and countryCode (defaults PT/BR) through multiple Shopify loaders and storefront queries via @incontext, expands cart buyerIdentity and adds CartBuyerIdentityUpdate, introduces sitemap include/exclude handling, simplifies metafield image transform, and adjusts collection breadcrumb/path behavior.

Changes

Cohort / File(s) Summary of Changes
Locale context in loaders
shopify/loaders/ProductDetailsPage.ts, shopify/loaders/ProductList.ts, shopify/loaders/ProductListingPage.ts, shopify/loaders/RelatedProducts.ts, shopify/loaders/shop.ts
Add optional Props languageCode?: LanguageCode, countryCode?: CountryCode (defaults PT/BR); import LanguageCode, CountryCode, LanguageContextArgs; extend GraphQL typings to include LanguageContextArgs; pass language/country in query variables.
Product listing path & breadcrumb
shopify/loaders/ProductListingPage.ts
Use last non-empty URL segment for collection path, capture collectionId from response, add @id to breadcrumb, and derive breadcrumb name from last path segment for non-search results.
Cart loader & buyer identity
shopify/loaders/cart.ts, shopify/utils/storefront/queries.ts, shopify/utils/storefront/storefront.graphql.gen.ts
Export Props with language/country; change loader signature to accept props; CreateCart now accepts countryCode; GetCart/GetCart types accept languageCode/countryCode; cart surfaces now include buyerIdentity; add CartBuyerIdentityUpdate mutation and types.
Proxy routes & sitemap exclusions
shopify/loaders/proxy.ts
Add excludePathsFromShopifySiteMap?: string[] to Props; introduce extraPathsToProxy with deprecation handling for extraPaths; compute finalExtraPathsToProxy; set route __resolveType based on pathTemplate including "sitemap"; expose sitemap exclude on /sitemap.xml.
Storefront queries & mutations
shopify/utils/storefront/queries.ts
Thread languageCode/countryCode into many queries/mutations with @inContext; expand Cart fragment to include buyerIdentity; update CreateCart, GetProduct, ListProducts, SearchProducts, ProductsByCollection, ProductRecommendations, GetShopInfo and various cart/auth mutations to accept localization args and return richer payloads/errors.
Generated GraphQL types
shopify/utils/storefront/storefront.graphql.gen.ts
Regenerated types: many Query/Mutation variables now accept languageCode/countryCode; cart types include buyerIdentity; added CartBuyerIdentityUpdate types; AllProductsQuery collection includes id.
Metafield image transform
shopify/utils/transform.ts
Simplify image extraction using optional chaining and array checks; build value as reference image URL or comma-separated edge image URLs; reduce branching and guard checks.
Shared types
shopify/utils/types.ts
Import LanguageCode/CountryCode from generated types; add exported LanguageContextArgs interface.
Sitemap handler include/exclude pipeline
shopify/handlers/sitemap.ts
Add exclude?: string[] to Props; introduce buildIncludeSitemaps and excludeSitemaps helpers; normalize origin replacement; apply exclusions to proxied sitemap and inject includes; adjust headers/response handling.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as App
  participant Loader as Page Loader
  participant SF as Storefront API

  User->>UI: Navigate to page
  UI->>Loader: call loader({ languageCode?, countryCode? })
  note right of Loader #E8F8F5: defaults language=PT, country=BR
  Loader->>SF: GraphQL query @inContext(languageCode, countryCode)
  SF-->>Loader: Localized response (includes buyerIdentity when cart)
  Loader-->>UI: Return localized data
  UI-->>User: Render localized UI
Loading
sequenceDiagram
  autonumber
  participant CartLoader as Cart Loader
  participant SF as Storefront API

  CartLoader->>SF: CreateCart(countryCode?) or GetCart(id, languageCode?, countryCode?)
  SF-->>CartLoader: cart { id, buyerIdentity, lines... }
  CartLoader->>SF: CartBuyerIdentityUpdate(cartId, buyerIdentity)
  SF-->>CartLoader: updated cart + userErrors
  CartLoader-->>Client: Cart with buyerIdentity context
Loading
sequenceDiagram
  autonumber
  participant ProxyLoader as Proxy Loader
  participant Builder as buildProxyRoutes
  participant Routes as Route Output

  ProxyLoader->>Builder: { extraPathsToProxy?, extraPaths?, excludePathsFromShopifySiteMap? }
  Builder-->>ProxyLoader: route definitions (use finalExtraPathsToProxy)
  note right of Routes #F7F9F9: `/sitemap.xml` route includes `exclude` and sets __resolveType based on pathTemplate
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Points needing extra attention:

  • GraphQL type alignment between queries/mutations and regenerated typings (storefront.graphql.gen.ts).
  • Cart buyerIdentity flows and new CartBuyerIdentityUpdate mutation/error handling.
  • Deprecation handling and compatibility between extraPaths and extraPathsToProxy.
  • Sitemap include/exclude normalization and header/response preservation.
  • Breadcrumb collectionId plumbing and frontend consumer expectations.

Suggested reviewers

  • viktormarinho

Poem

Thump-thump I hop through code and rails,
PT whispers, BR sets compass sails.
Sitemaps pruned and breadcrumbs steer,
Images strung like carrots near.
Hooray — locales snug, the build prevails! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Feat [Shopify]: Internationalization Support for Shopify Loaders and Queries" directly and clearly summarizes the main objective of the changeset. The raw summary confirms that the core changes involve adding languageCode and countryCode props to multiple loaders (ProductDetailsPage, ProductList, ProductListingPage, RelatedProducts, cart, and shop) and refactoring GraphQL queries to use these values via the @incontext directive for localization. The title captures this primary change accurately without being vague or misleading. While the PR description also mentions sitemap enhancements with exclude functionality, the title appropriately focuses on the main internationalization feature, which is acceptable as per the instruction that a title doesn't need to cover every detail.
Description Check ✅ Passed The PR description is substantially complete and well-structured. It includes the required "What is this Contribution About?" section with comprehensive explanation and a Loom video link demonstrating the changes. The description provides detailed information about how the internationalization feature works, includes code examples for both GraphQL and TypeScript, and explains the sitemap enhancement feature. However, the description is missing two template sections: "Issue Link" (no issue reference provided) and "Demonstration Link" (no branch or test environment URL provided). Despite these missing template sections, the description is rich with detail, properly formatted, and clearly communicates the contribution's purpose and impact.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0178dd and 0220c31.

📒 Files selected for processing (10)
  • shopify/handlers/sitemap.ts (1 hunks)
  • shopify/loaders/ProductDetailsPage.ts (4 hunks)
  • shopify/loaders/ProductList.ts (6 hunks)
  • shopify/loaders/ProductListingPage.ts (11 hunks)
  • shopify/loaders/RelatedProducts.ts (5 hunks)
  • shopify/loaders/cart.ts (1 hunks)
  • shopify/loaders/proxy.ts (7 hunks)
  • shopify/utils/storefront/queries.ts (2 hunks)
  • shopify/utils/transform.ts (1 hunks)
  • shopify/utils/types.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • shopify/utils/transform.ts
  • shopify/loaders/RelatedProducts.ts
  • shopify/loaders/ProductDetailsPage.ts
🧰 Additional context used
🧬 Code graph analysis (7)
shopify/utils/types.ts (1)
shopify/utils/storefront/storefront.graphql.gen.ts (2)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
shopify/handlers/sitemap.ts (4)
shopify/mod.ts (2)
  • Props (13-41)
  • AppContext (10-10)
website/handlers/proxy.ts (2)
  • Props (44-89)
  • Proxy (94-235)
website/handlers/router.ts (1)
  • ConnInfo (15-15)
shopify/utils/password.ts (1)
  • withDigestCookie (3-9)
shopify/loaders/cart.ts (3)
shopify/utils/storefront/storefront.graphql.gen.ts (6)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • GetCartQuery (7743-7743)
  • CreateCartMutation (7734-7734)
  • CreateCartMutationVariables (7729-7731)
  • GetCartQueryVariables (7736-7740)
shopify/utils/storefront/queries.ts (1)
  • CreateCart (272-282)
shopify/utils/types.ts (1)
  • LanguageContextArgs (196-199)
shopify/loaders/ProductList.ts (2)
shopify/utils/storefront/storefront.graphql.gen.ts (4)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • QueryRootSearchArgs (6798-6810)
  • HasMetafieldsMetafieldsArgs (3752-3754)
shopify/utils/types.ts (1)
  • LanguageContextArgs (196-199)
shopify/loaders/proxy.ts (4)
shopify/handlers/sitemap.ts (1)
  • Props (58-61)
shopify/mod.ts (2)
  • Props (13-41)
  • AppContext (10-10)
website/handlers/proxy.ts (1)
  • Props (44-89)
shopify/utils/password.ts (1)
  • withDigestCookie (3-9)
shopify/loaders/ProductListingPage.ts (3)
shopify/utils/storefront/storefront.graphql.gen.ts (7)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • QueryRootSearchArgs (6798-6810)
  • HasMetafieldsMetafieldsArgs (3752-3754)
  • QueryRoot (6476-6579)
  • QueryRootCollectionArgs (6638-6641)
  • CollectionProductsArgs (1951-1959)
shopify/utils/types.ts (1)
  • LanguageContextArgs (196-199)
shopify/utils/utils.ts (2)
  • searchSortShopify (39-55)
  • sortShopify (57-92)
shopify/utils/storefront/queries.ts (1)
shopify/utils/storefront/storefront.graphql.gen.ts (6)
  • Cart (527-578)
  • Product (5933-6018)
  • ProductVariant (6333-6386)
  • Collection (1881-1912)
  • Filter (3596-3605)
  • Customer (3022-3061)
🪛 ast-grep (0.39.6)
shopify/handlers/sitemap.ts

[warning] 17-20: Manual HTML sanitization detected using string replacement methods. Manual sanitization is error-prone and can be bypassed. Use dedicated HTML sanitization libraries like 'sanitize-html' or 'DOMPurify' instead.
Context: s
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://cwe.mitre.org/data/definitions/79.html

(manual-html-sanitization)


[warning] 17-19: Manual HTML sanitization detected using string replacement methods. Manual sanitization is error-prone and can be bypassed. Use dedicated HTML sanitization libraries like 'sanitize-html' or 'DOMPurify' instead.
Context: s
.replaceAll("&", "&")
.replaceAll("<", "<")
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://cwe.mitre.org/data/definitions/79.html

(manual-html-sanitization)


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.

@github-actions
Copy link
Contributor

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 0.121.6 update
  • 🎉 for Minor 0.122.0 update
  • 🚀 for Major 1.0.0 update

Copy link
Contributor

@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: 8

Caution

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

⚠️ Outside diff range comments (3)
shopify/utils/transform.ts (1)

353-370: Bug: URLSearchParams API misused; toggle logic never works

URLSearchParams.has and .delete don’t accept a value argument. As written, removing a single selected filter never happens.

Apply:

   const label = getFilterValue(value);
-
-  if (params.has(filter.id, label)) {
-    params.delete(filter.id, label);
-  } else {
-    params.append(filter.id, label);
-  }
+  const existing = params.getAll(filter.id);
+  const idx = existing.indexOf(label);
+  if (idx >= 0) {
+    existing.splice(idx, 1);
+    params.delete(filter.id);
+    for (const v of existing) params.append(filter.id, v);
+  } else {
+    params.append(filter.id, label);
+  }
shopify/loaders/ProductListingPage.ts (1)

249-251: Canonical uses zero-based page.

Use currentPage (or page + currentPageoffset) to avoid emitting ?page=1 for the second page request of ?page=2.

Apply:

-      canonical: `${url.origin}${url.pathname}${
-        page >= 1 ? `?page=${page}` : ""
-      }`,
+      canonical: `${url.origin}${url.pathname}${
+        currentPage > 1 ? `?page=${currentPage}` : ""
+      }`,
shopify/loaders/RelatedProducts.ts (1)

99-109: Include locale in cacheKey to prevent cross-locale cache mixups

Even with no-cache today, adding these guards against future caching or upstream overrides.

-export const cacheKey = (props: Props, req: Request): string => {
-  const { slug, count } = props;
+export const cacheKey = (props: Props, req: Request): string => {
+  const { slug, count, languageCode, countryCode } = props;
   const searchParams = new URLSearchParams({
     slug,
     count: count.toString(),
+    languageCode: (languageCode ?? "PT"),
+    countryCode: (countryCode ?? "BR"),
   });
🧹 Nitpick comments (22)
shopify/utils/transform.ts (2)

165-173: Use type guards instead of casts when reading metafield reference images

The current casts can hide shape issues at runtime. Narrow by checking for an image property on the union node to avoid undefined access.

-      const referenceImageUrl =
-        (reference as { image?: { url?: string } } | undefined)?.image?.url ?? null;
+      let referenceImageUrl: string | null = null;
+      const refAny = reference as unknown;
+      if (refAny && typeof refAny === "object" && "image" in refAny) {
+        referenceImageUrl =
+          (refAny as { image?: { url?: string } }).image?.url ?? null;
+      }
@@
-      const edgeImages = Array.isArray(references?.edges)
-        ? references.edges.map(
-            (edge) =>
-              (edge?.node as { image?: { url?: string } } | undefined)?.image
-                ?.url ?? null
-          )
-        : null;
+      const edgeImages = Array.isArray(references?.edges)
+        ? references.edges.map((edge) => {
+            const node = edge?.node as unknown;
+            if (node && typeof node === "object" && "image" in node) {
+              return (node as { image?: { url?: string } }).image?.url ?? null;
+            }
+            return null;
+          })
+        : null;

176-183: Joining multiple image URLs into a comma string can be brittle

Downstream code may expect a single string; comma-joining also makes parsing ambiguous. Consider serializing arrays as JSON strings for multi-image values, or add a separate field for images while preserving the original value.

If you choose JSON, a minimal change is:

-      const valueToReturn =
+      const valueToReturn =
         referenceImageUrl ??
         (validEdgeImages && validEdgeImages.length > 0
-          ? validEdgeImages.join(",")
+          ? JSON.stringify(validEdgeImages)
           : value);

Please confirm no consumer relies on comma-splitting today. I can scan usages of additionalProperty/PropertyValue.value to de-risk before changing.

shopify/loaders/proxy.ts (3)

63-64: Avoid substring match for route type selection

includes("sitemap") may misclassify unrelated paths (e.g., custom routes containing “sitemap”). Match only Shopify sitemap endpoints.

-          __resolveType: pathTemplate.includes("sitemap") ? "shopify/handlers/sitemap.ts" : "website/handlers/proxy.ts",
+          __resolveType: /^\/sitemap(\.xml.*|\/.*)$/i.test(pathTemplate)
+            ? "shopify/handlers/sitemap.ts"
+            : "website/handlers/proxy.ts",

127-134: Doc titles don’t match actual routes

  • Deco sitemap path is /sitemap/deco.xml (not /deco-sitemap.xml).
  • Shopify sitemap lives at /sitemap.xml (title says /shopify-sitemap.xml).
   /**
-   * @title If deco site map should be exposed at /deco-sitemap.xml
+   * @title If deco site map should be exposed at /sitemap/deco.xml
    */
@@
   /**
-   * @title Exclude paths from /shopify-sitemap.xml
+   * @title Exclude paths from /sitemap.xml
    */

6-25: Duplicate entry in PATHS_TO_PROXY

"/services/*" appears twice.

   "/services/*",
-  "/.well-known/*",
-  "/services/*",
+  "/.well-known/*",
shopify/utils/types.ts (1)

193-196: Make LanguageContextArgs optional to match GraphQL InputMaybe and improve reuse

Your queries pass defaults, but keeping these optional aligns with codegen types and lets other operations omit them without widening intersections.

-export interface LanguageContextArgs {
-  languageCode: LanguageCode;
-  countryCode: CountryCode;
-}
+export interface LanguageContextArgs {
+  languageCode?: LanguageCode;
+  countryCode?: CountryCode;
+}

Side note: CartBuyerIdentity.countryCode is a string in this file; consider CountryCode for consistency.

shopify/loaders/ProductList.ts (3)

16-18: Imports look good; consider aliasing Metafield to avoid name collision

Metafield here represents identifiers (namespace/key) while GraphQL also defines a Metafield type. Aliasing can reduce confusion.

-import { LanguageContextArgs, Metafield } from "../utils/types.ts";
+import { LanguageContextArgs, Metafield as MetafieldIdentifier } from "../utils/types.ts";

Then update type uses (metafields?: MetafieldIdentifier[]; and identifiers: metafields).

Also applies to: 26-26


75-87: Include locale in cacheKey to prevent cross-locale collisions

Even with cache = "no-cache" today, future changes or upstream caching could serve the wrong locale. Add languageCode/countryCode to cacheKey.

TypeScript snippet (outside the changed range) showing the minimal addition:

export const cacheKey = (expandedProps: Props, req: Request): string => {
  // ...existing code...
  const languageCode = expandedProps.languageCode ?? "PT";
  const countryCode = expandedProps.countryCode ?? "BR";
  searchParams.append("languageCode", languageCode);
  searchParams.append("countryCode", countryCode);
  // ...existing code...
}

109-111: Narrow types on defaults for stronger inference

Explicitly annotate to keep these as LanguageCode/CountryCode (not widened unions).

-const languageCode = expandedProps?.languageCode ?? "PT";
-const countryCode = expandedProps?.countryCode ?? "BR";
+const languageCode: LanguageCode = expandedProps.languageCode ?? "PT";
+const countryCode: CountryCode = expandedProps.countryCode ?? "BR";
shopify/loaders/ProductDetailsPage.ts (2)

22-34: Add locale to cacheKey to avoid mixing pages across locales

Slug-only cache keys risk serving the wrong localized PDP later if caching is enabled.

TypeScript snippet (outside the changed range):

export const cacheKey = (props: Props, req: Request): string => {
  const { slug } = props;
  const languageCode = props.languageCode ?? "PT";
  const countryCode = props.countryCode ?? "BR";
  const searchParams = new URLSearchParams({ slug, languageCode, countryCode });
  const url = new URL(req.url);
  url.search = searchParams.toString();
  return url.href;
};

46-47: Preserve literal types on defaults

Minor: explicitly annotate to keep these as the enum literal types.

-const { slug, languageCode = "PT", countryCode = "BR" } = props;
+const { slug } = props;
+const languageCode: LanguageCode = props.languageCode ?? "PT";
+const countryCode: CountryCode = props.countryCode ?? "BR";
shopify/loaders/shop.ts (2)

17-28: Document defaults on new locale props.

Add JSDoc @default tags so CMS/UI surfaces the implicit PT/BR defaults.

Apply:

   /**
    * @title Language Code
    * @description Language code for the storefront API
    * @example "EN" for English, "FR" for French, etc.
+   * @default "PT"
    */
   languageCode?: LanguageCode;
   /**
    * @title Country Code
    * @description Country code for the storefront API
    * @example "US" for United States, "FR" for France, etc.
+   * @default "BR"
    */
   countryCode?: CountryCode;

39-39: Default locale derivation.

Optional: derive defaults from ctx (storefront/shop defaults or request Accept-Language) to avoid surprising PT/BR fallback in non-BR stores.

shopify/loaders/ProductListingPage.ts (4)

74-86: Include locale in cache key or confirm no-cache.

If caching is enabled later, include languageCode/countryCode in cacheKey; otherwise different locales can collide. Current export sets cache="no-cache", but please confirm this won’t change.

Proposed cacheKey patch:

-  const searchParams = new URLSearchParams({
+  const searchParams = new URLSearchParams({
     count,
     query,
     page,
     endCursor,
     startCursor,
     sort,
+    languageCode: (props.languageCode ?? "PT"),
+    countryCode: (props.countryCode ?? "BR"),
   });

111-113: Locale defaults OK; consider specificity.

Minor: with countryCode "BR", using languageCode "PT_BR" may better match most Brazilian stores.


158-161: Handle edge cases in last-segment collection handle extraction.

Guard against trailing “collections” or “products” segments to avoid querying invalid handles.

Apply:

-const pathname = props.collectionName || url.pathname.split("/").filter(Boolean).pop();
+const segments = (props.collectionName
+  ? [props.collectionName]
+  : url.pathname.split("/").filter(Boolean));
+let pathname = segments.at(-1);
+if (pathname === "collections" || pathname === "products") {
+  pathname = segments.at(-2);
+}

227-227: Breadcrumb @id should be a page URL, not a Shopify GID.

Use a stable canonical URL so search engines can reconcile entities.

Apply:

-      "@id": collectionId,
+      "@id": isSearch ? url.href : `${url.origin}${url.pathname}`,
shopify/loaders/cart.ts (2)

30-35: Locale defaults OK; consider specificity.

Optional: prefer PT_BR when country BR to better align language/country.


34-45: Optional: normalize existing carts to new country.

If a cookie cart exists with a different buyerIdentity.countryCode than props.countryCode, consider issuing cartBuyerIdentityUpdate to keep pricing consistent with the chosen locale.

I can draft the mutation wiring if you want it added in this PR.

shopify/loaders/RelatedProducts.ts (2)

56-57: Consider sourcing defaults from config, not hardcoding "PT"/"BR"

If multiple shops/locales coexist, pulling defaults from a central config (or ctx) avoids drift.


63-69: Avoid intersecting generated variables with local types

GetProductQueryVariables and ProductRecommendationsQueryVariables already include identifiers, languageCode, and countryCode. The intersections with HasMetafieldsMetafieldsArgs and LanguageContextArgs unnecessarily tighten the contract (e.g., making optionals required) and can drift from the generated schema.

Apply:

-  const query = await storefront.query<
-    GetProductQuery,
-    GetProductQueryVariables & HasMetafieldsMetafieldsArgs & LanguageContextArgs
-  >({
+  const query = await storefront.query<
+    GetProductQuery,
+    GetProductQueryVariables
+  >({
     variables: { handle, identifiers: metafields, languageCode, countryCode },
     ...GetProduct,
   });

-  const data = await storefront.query<
-    ProductRecommendationsQuery,
-    ProductRecommendationsQueryVariables & HasMetafieldsMetafieldsArgs & LanguageContextArgs
-  >({
+  const data = await storefront.query<
+    ProductRecommendationsQuery,
+    ProductRecommendationsQueryVariables
+  >({
     variables: {
       productId: query.product.id,
       identifiers: metafields,
       languageCode,
       countryCode
     },
     ...ProductRecommendations,
   });

Also applies to: 75-87

shopify/utils/storefront/queries.ts (1)

273-282: CreateCart: optional

Returning only id is fine; consider adding buyerIdentity.countryCode if you plan to display locale-specific cart state immediately after creation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8824dc7 and 5cc3c6d.

📒 Files selected for processing (11)
  • shopify/loaders/ProductDetailsPage.ts (4 hunks)
  • shopify/loaders/ProductList.ts (6 hunks)
  • shopify/loaders/ProductListingPage.ts (10 hunks)
  • shopify/loaders/RelatedProducts.ts (5 hunks)
  • shopify/loaders/cart.ts (1 hunks)
  • shopify/loaders/proxy.ts (6 hunks)
  • shopify/loaders/shop.ts (2 hunks)
  • shopify/utils/storefront/queries.ts (2 hunks)
  • shopify/utils/storefront/storefront.graphql.gen.ts (6 hunks)
  • shopify/utils/transform.ts (1 hunks)
  • shopify/utils/types.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
shopify/utils/types.ts (1)
shopify/utils/storefront/storefront.graphql.gen.ts (2)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
shopify/loaders/shop.ts (2)
shopify/utils/types.ts (2)
  • Metafield (188-191)
  • LanguageContextArgs (193-196)
shopify/utils/storefront/storefront.graphql.gen.ts (3)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • ShopMetafieldsArgs (7233-7235)
shopify/loaders/RelatedProducts.ts (2)
shopify/utils/storefront/storefront.graphql.gen.ts (5)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • GetProductQueryVariables (7745-7750)
  • HasMetafieldsMetafieldsArgs (3752-3754)
  • ProductRecommendationsQueryVariables (7801-7806)
shopify/utils/types.ts (1)
  • LanguageContextArgs (193-196)
shopify/loaders/ProductDetailsPage.ts (2)
shopify/utils/storefront/storefront.graphql.gen.ts (4)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • GetProductQueryVariables (7745-7750)
  • HasMetafieldsMetafieldsArgs (3752-3754)
shopify/utils/types.ts (1)
  • LanguageContextArgs (193-196)
shopify/loaders/ProductListingPage.ts (2)
shopify/utils/storefront/storefront.graphql.gen.ts (2)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
shopify/utils/types.ts (1)
  • LanguageContextArgs (193-196)
shopify/loaders/cart.ts (3)
shopify/utils/storefront/storefront.graphql.gen.ts (6)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • GetCartQuery (7743-7743)
  • CreateCartMutation (7734-7734)
  • CreateCartMutationVariables (7729-7731)
  • GetCartQueryVariables (7736-7740)
shopify/utils/storefront/queries.ts (1)
  • CreateCart (272-282)
shopify/utils/types.ts (1)
  • LanguageContextArgs (193-196)
shopify/loaders/proxy.ts (2)
website/handlers/proxy.ts (1)
  • Props (44-89)
shopify/handlers/sitemap.ts (1)
  • Props (31-33)
shopify/loaders/ProductList.ts (2)
shopify/utils/storefront/storefront.graphql.gen.ts (5)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • QueryRoot (6476-6579)
  • QueryRootSearchArgs (6798-6810)
  • HasMetafieldsMetafieldsArgs (3752-3754)
shopify/utils/types.ts (1)
  • LanguageContextArgs (193-196)
shopify/utils/storefront/storefront.graphql.gen.ts (1)
wake/utils/graphql/storefront.graphql.gen.ts (7)
  • CreateCartMutationVariables (5028-5028)
  • Exact (12-12)
  • InputMaybe (11-11)
  • CreateCartMutation (5031-5031)
  • GetCartQueryVariables (5021-5023)
  • Scalars (18-33)
  • GetCartQuery (5026-5026)
shopify/utils/storefront/queries.ts (1)
shopify/utils/storefront/storefront.graphql.gen.ts (6)
  • Cart (527-578)
  • Product (5933-6018)
  • ProductVariant (6333-6386)
  • Collection (1881-1912)
  • Filter (3596-3605)
  • Customer (3022-3061)
🔇 Additional comments (22)
shopify/utils/storefront/storefront.graphql.gen.ts (3)

7725-7744: Verify that all GraphQL callers handle the new optional i18n variables
I didn’t find any direct references to the generated query hooks or documents (e.g. useGetCartQuery, GetCartDocument), so please manually confirm that every GraphQL call now provides the optional countryCode/languageCode arguments or correctly falls back to defaults.


7729-7731: Confirm CreateCart country scoping is actually used

CreateCartMutationVariables gained countryCode, but many implementations instead set buyerIdentity.countryCode in the input. Ensure the mutation or its wrapper applies the variable (via @inContext or input.buyerIdentity), otherwise it’s unused.

#!/bin/bash
rg -nP -C3 'CreateCartMutation\W' && rg -nP -C3 'cartCreate\W' shopify | sed -n '1,200p'

7862-7869: New CartBuyerIdentityUpdate mutation: wire through loaders and add basic error handling

Ensure a public function wraps this mutation and maps userErrors into your app’s error model. Also add a timeout/retry policy in the caller.

#!/bin/bash
rg -nP -C2 'CartBuyerIdentityUpdateMutation' &&
rg -nP -C2 'buyerIdentityUpdate' shopify
shopify/utils/types.ts (1)

7-7: LGTM: source-of-truth for locale enums

Importing LanguageCode and CountryCode from the generated GraphQL types removes drift risk vs hand-rolled enums. No concerns.

shopify/loaders/ProductList.ts (1)

141-142: Locale threading verified All storefront queries declare and use $languageCode and $countryCode with @inContext across search, collection, and recommendation paths.

shopify/loaders/ProductDetailsPage.ts (2)

9-11: Imports align with codegen and shared types

Bringing LanguageCode/CountryCode from codegen and LanguageContextArgs from utils is consistent with the rest of the PR.

Also applies to: 13-13


56-56: Approve locale variables usage

Verified that the GetProduct query in shopify/utils/storefront/queries.ts declares both $languageCode and $countryCode and applies them in the @incontext(language: $languageCode, country: $countryCode) directive.

shopify/loaders/shop.ts (2)

4-9: Imports look good.

Types are sourced from the generated schema and shared types. No issues.


41-47: Approve code changes

GetShopInfo query declares $languageCode and $countryCode and applies @incontext; the type intersection with ShopMetafieldsArgs & LanguageContextArgs is correct and future-proof.

shopify/loaders/ProductListingPage.ts (4)

133-145: Search query: locale variables threaded correctly.

Intersection with LanguageContextArgs looks good; variable bag is consistent.


167-168: Collection query: locale variables—LGTM.

Types and variable wiring are correct.

Also applies to: 177-179


192-192: Storing collectionId—LGTM.

Used later for breadcrumb; see note below on @id value.


230-230: Breadcrumb name derivation—LGTM.

Using the last path segment for non-search pages is reasonable.

shopify/loaders/cart.ts (3)

14-27: Props + docs—LGTM.

Public API looks clear and consistent with other loaders.


47-53: GetCart with locale—LGTM.

Variable typing via intersection ensures both codes are required at callsite.


55-55: Verify cookie encoding consistency.

We decode before querying and then write raw cartId back. Ensure getCartCookie/setCartCookie agree on encoding to avoid double-encoding or invalid IDs.

Run:

#!/bin/bash
rg -nC2 'getCartCookie|setCartCookie' shopify | sed -n '1,200p'
shopify/loaders/RelatedProducts.ts (2)

32-44: Props for locale look good

Optional props + docs read well.


18-19: Confirming Metafield aligns with GraphQL HasMetafieldsIdentifier; no changes needed. The Metafield interface (namespace: string; key: string) exactly matches the identifier shape expected by the query.

shopify/utils/storefront/queries.ts (4)

286-297: Consistent @incontext adoption across queries

Nice job threading languageCode/countryCode and moving to variable-based @inContext. This will play well with tooling and typed vars.

Also applies to: 301-313, 316-333, 335-379, 380-426, 428-442, 444-502


172-181: Cart fragment enrichment looks good

Including buyerIdentity.countryCode/email is useful for UI and pricing diagnostics.


506-513: Customer query doesn’t need @incontext

Fields returned aren’t localized; keeping it simple here makes sense.


528-556: Good UX: surface user errors

Explicitly returning customerUserErrors on registration and sign-in is 👍.

Also applies to: 608-623

Copy link
Contributor

@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: 4

🧹 Nitpick comments (3)
shopify/handlers/sitemap.ts (3)

64-68: Use a resilient insertion anchor

Relying on an exact opening-tag string is brittle. Insert before the closing instead.

-    const finalXml = includeBlock
-      ? excludedXml.replace(XML_HEADER, `${XML_HEADER}\n${includeBlock}`)
-      : excludedXml;
+    const finalXml = includeBlock
+      ? excludedXml.replace("</sitemapindex>", `${includeBlock}\n</sitemapindex>`)
+      : excludedXml;

59-63: Simplify origin handling

URL.origin never ends with “/”; the extra normalization and the variable name are misleading.

-    const originWithSlash = reqOrigin.endsWith("/") ? reqOrigin.slice(0, -1) : reqOrigin;
-    const originReplacedXml = originalXml.replaceAll(shopifyUrl, originWithSlash);
+    const originReplacedXml = originalXml.replaceAll(shopifyUrl, reqOrigin);

38-38: Document exclude semantics

Add JSDoc to clarify expected values (path prefixes, leading “/”, examples).

 export interface Props {
   include?: string[];
-  exclude?: string[];
+  /** @description Path prefixes to remove from the Shopify sitemap index (e.g., "/sitemap_blogs", "/sitemap_pages"). Leading "/" optional. */
+  exclude?: string[];
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc3c6d and ce35c85.

📒 Files selected for processing (1)
  • shopify/handlers/sitemap.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
shopify/handlers/sitemap.ts (3)
shopify/mod.ts (2)
  • Props (13-41)
  • AppContext (10-10)
website/handlers/proxy.ts (2)
  • Props (44-89)
  • Proxy (94-235)
shopify/utils/password.ts (1)
  • withDigestCookie (3-9)
🔇 Additional comments (3)
shopify/handlers/sitemap.ts (3)

45-45: Signature change LGTM

Threading exclude into the handler signature looks good and is backward compatible.


50-56: Proxy integration looks correct

Forwarding digest cookie via customHeaders and preserving upstream headers/status is aligned with existing proxy behavior.


57-57: Early return on non-OK is appropriate

Avoids touching body/headers for error responses.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@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 (1)
shopify/handlers/sitemap.ts (1)

8-8: Remove unused TODAY constant.

The module-level TODAY constant is declared but never used. Line 13 correctly computes a fresh today value at runtime inside buildIncludeSitemaps, which is the proper approach to avoid staleness on long-lived workers.

Apply this diff to remove the unused constant:

 const XML_HEADER = '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
-const TODAY = new Date().toISOString().substring(0, 10);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce35c85 and f0178dd.

📒 Files selected for processing (2)
  • shopify/handlers/sitemap.ts (1 hunks)
  • shopify/loaders/cart.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
shopify/loaders/cart.ts (3)
shopify/utils/storefront/storefront.graphql.gen.ts (6)
  • LanguageCode (3911-4199)
  • CountryCode (2138-2628)
  • GetCartQuery (7743-7743)
  • CreateCartMutation (7734-7734)
  • CreateCartMutationVariables (7729-7731)
  • GetCartQueryVariables (7736-7740)
shopify/utils/storefront/queries.ts (1)
  • CreateCart (272-282)
shopify/utils/types.ts (1)
  • LanguageContextArgs (193-196)
shopify/handlers/sitemap.ts (3)
shopify/mod.ts (2)
  • Props (13-41)
  • AppContext (10-10)
website/handlers/proxy.ts (2)
  • Props (44-89)
  • Proxy (94-235)
shopify/utils/password.ts (1)
  • withDigestCookie (3-9)
🪛 ast-grep (0.39.5)
shopify/handlers/sitemap.ts

[warning] 14-17: Manual HTML sanitization detected using string replacement methods. Manual sanitization is error-prone and can be bypassed. Use dedicated HTML sanitization libraries like 'sanitize-html' or 'DOMPurify' instead.
Context: s
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://cwe.mitre.org/data/definitions/79.html

(manual-html-sanitization)


[warning] 14-16: Manual HTML sanitization detected using string replacement methods. Manual sanitization is error-prone and can be bypassed. Use dedicated HTML sanitization libraries like 'sanitize-html' or 'DOMPurify' instead.
Context: s
.replaceAll("&", "&")
.replaceAll("<", "<")
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://cwe.mitre.org/data/definitions/79.html

(manual-html-sanitization)

🪛 Biome (2.1.2)
shopify/loaders/cart.ts

[error] 39-39: Expected an expression but instead found 'const'.

Expected an expression here.

(parse)


[error] 39-39: Shouldn't redeclare 'cartId'. Consider to delete it or rename it.

'cartId' is defined here:

(lint/suspicious/noRedeclare)

🪛 GitHub Actions: ci
shopify/loaders/cart.ts

[error] 39-39: Deno fmt failed: Expression expected at cart.ts:39:3 during 'deno task check'.

🔇 Additional comments (8)
shopify/loaders/cart.ts (4)

14-27: LGTM! Well-documented Props interface.

The Props interface is well-structured with clear JSDoc annotations and examples. The optional nature of both properties maintains backward compatibility.


30-30: LGTM! Loader signature correctly accepts Props.

The signature change from _props: unknown to props: Props properly enables typed access to the new locale parameters.


34-34: LGTM! Locale defaults are consistent with PR objectives.

The default values (PT/BR) align with the stated internationalization strategy and the pattern used across other loaders in this PR.


49-55: LGTM! GetCart query properly includes locale context.

The query correctly extends GetCartQueryVariables with LanguageContextArgs and passes all required variables including the new locale parameters. The typing is explicit and correct.

shopify/handlers/sitemap.ts (4)

10-29: LGTM! XML escaping and fresh timestamp correctly implemented.

The function properly addresses previous review concerns:

  • Computes today fresh at runtime (line 13) to avoid staleness
  • Correctly escapes XML entities in proper order (& first, lines 14-20)
  • Uses escaped URLs in <loc> elements (line 25)

Note: The static analysis warning about "manual HTML sanitization" is a false positive. This code performs standard XML entity escaping for sitemap <loc> elements, which is the correct approach for this use case. The escaping sequence properly handles all required XML entities.


31-53: LGTM! Robust URL handling and prefix normalization.

The function correctly implements all previously requested improvements:

  • Normalizes exclude prefixes to ensure they start with "/" (line 35)
  • Safely parses URLs with try-catch, using origin as base to support both absolute and relative URLs (lines 41-44)
  • Falls back gracefully on parse errors, leaving entries untouched (lines 45-47)
  • Uses normalized prefixes for consistent matching (line 50)

55-58: LGTM! Clean API extension.

The exclude property follows the same pattern as include, maintaining consistency and backward compatibility.


63-101: LGTM! Proper header handling and clean XML pipeline.

The implementation correctly addresses the previous header passthrough concerns:

  • Response headers are cloned (line 88)
  • Body-specific headers are removed to prevent mismatches (lines 89-92): content-length, content-encoding, etag, accept-ranges
  • Content-type is set appropriately for XML (lines 93-95)

The XML processing pipeline is well-structured:

  1. Proxy to Shopify
  2. Replace origin URLs (line 80)
  3. Apply exclusions (line 81)
  4. Inject inclusions (lines 84-86)

The origin normalization (line 79) correctly handles trailing slashes.

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.

1 participant