-
Notifications
You must be signed in to change notification settings - Fork 20
Feat [Shopify]: Internationalization Support for Shopify Loaders and Queries #1371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThreads optional languageCode and countryCode (defaults PT/BR) through multiple Shopify loaders and storefront GraphQL queries via Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant App as UI/App
participant Loader as Page Loader
participant SF as Storefront API
User->>App: navigate
App->>Loader: loader({ languageCode?, countryCode? })
note right of Loader `#E8F8F5`: defaults language=PT, country=BR
Loader->>SF: GraphQL query with `@inContext`(languageCode, countryCode)
SF-->>Loader: localized response (cart may include buyerIdentity)
Loader-->>App: localized data
App-->>User: render
sequenceDiagram
autonumber
participant CartLoader as Cart Loader
participant SF as Storefront API
CartLoader->>SF: CreateCart(countryCode?) / 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
sequenceDiagram
autonumber
participant Proxy as Proxy Loader
participant Builder as buildProxyRoutes
participant Routes as Route Output
Proxy->>Builder: { extraPathsToProxy?, extraPaths?, excludePathsFromShopifySiteMap? }
Builder-->>Proxy: routes (uses finalExtraPathsToProxy)
note right of Routes `#F7F9F9`: `/sitemap.xml` route includes `exclude` and __resolveType based on pathTemplate
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Points needing extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Tagging OptionsShould a new tag be published when this PR is merged?
|
There was a problem hiding this 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.hasand.deletedon’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-basedpage.Use
currentPage(orpage + 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 mixupsEven with
no-cachetoday, 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 imagesThe current casts can hide shape issues at runtime. Narrow by checking for an
imageproperty 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 brittleDownstream 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.valueto 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 reuseYour 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 collisionMetafield 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 collisionsEven 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 inferenceExplicitly 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 localesSlug-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 defaultsMinor: 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
GetProductQueryVariablesandProductRecommendationsQueryVariablesalready includeidentifiers,languageCode, andcountryCode. The intersections withHasMetafieldsMetafieldsArgsandLanguageContextArgsunnecessarily 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: optionalReturning only
idis fine; consider addingbuyerIdentity.countryCodeif you plan to display locale-specific cart state immediately after creation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 optionalcountryCode/languageCodearguments or correctly falls back to defaults.
7729-7731: Confirm CreateCart country scoping is actually used
CreateCartMutationVariablesgainedcountryCode, but many implementations instead setbuyerIdentity.countryCodein theinput. Ensure the mutation or its wrapper applies the variable (via@inContextorinput.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 handlingEnsure a public function wraps this mutation and maps
userErrorsinto your app’s error model. Also add a timeout/retry policy in the caller.#!/bin/bash rg -nP -C2 'CartBuyerIdentityUpdateMutation' && rg -nP -C2 'buyerIdentityUpdate' shopifyshopify/utils/types.ts (1)
7-7: LGTM: source-of-truth for locale enumsImporting 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$languageCodeand$countryCodewith@inContextacross search, collection, and recommendation paths.shopify/loaders/ProductDetailsPage.ts (2)
9-11: Imports align with codegen and shared typesBringing 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 usageVerified 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 changesGetShopInfo 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 goodOptional props + docs read well.
18-19: ConfirmingMetafieldaligns with GraphQLHasMetafieldsIdentifier; no changes needed. TheMetafieldinterface (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 queriesNice job threading
languageCode/countryCodeand 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 goodIncluding
buyerIdentity.countryCode/
506-513: Customer query doesn’t need @incontextFields returned aren’t localized; keeping it simple here makes sense.
528-556: Good UX: surface user errorsExplicitly returning
customerUserErrorson registration and sign-in is 👍.Also applies to: 608-623
There was a problem hiding this 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 anchorRelying 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 handlingURL.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 semanticsAdd 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
📒 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 LGTMThreading exclude into the handler signature looks good and is backward compatible.
50-56: Proxy integration looks correctForwarding digest cookie via customHeaders and preserving upstream headers/status is aligned with existing proxy behavior.
57-57: Early return on non-OK is appropriateAvoids touching body/headers for error responses.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this 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
TODAYconstant is declared but never used. Line 13 correctly computes a freshtodayvalue at runtime insidebuildIncludeSitemaps, 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
📒 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: unknowntoprops: Propsproperly 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
GetCartQueryVariableswithLanguageContextArgsand 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
todayfresh 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
excludeproperty follows the same pattern asinclude, 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:
- Proxy to Shopify
- Replace origin URLs (line 80)
- Apply exclusions (line 81)
- Inject inclusions (lines 84-86)
The origin normalization (line 79) correctly handles trailing slashes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
shopify/utils/storefront/storefront.graphql.gen.ts (1)
7736-7750: Remove duplicateCartFragmenttype alias at line 7737Verification confirms two consecutive
export type CartFragmentdeclarations exist at lines 7736–7737. TypeScript does not permit duplicate type alias exports in the same module—the second declaration will cause a compile error and shadow the first.Keep the definition at line 7736 (which includes
buyerIdentity) and remove the second definition starting at line 7737.
♻️ Duplicate comments (1)
shopify/loaders/ProductListingPage.ts (1)
112-113: Verify the hardcoded locale defaults are appropriate.The defaults
"PT"/"BR"(Portuguese/Brazil) are hardcoded, same as in ProductList.ts. Ensure these defaults align with your primary market and consider whether they should be configurable at the application level.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
shopify/loaders/ProductList.ts(6 hunks)shopify/loaders/ProductListingPage.ts(11 hunks)shopify/utils/storefront/storefront.graphql.gen.ts(7 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
shopify/utils/storefront/storefront.graphql.gen.ts (1)
wake/utils/graphql/storefront.graphql.gen.ts (8)
CreateCartMutationVariables(5028-5028)Exact(12-12)InputMaybe(11-11)CreateCartMutation(5031-5031)GetCartQueryVariables(5021-5023)Scalars(18-33)GetCartQuery(5026-5026)GetProductQueryVariables(5012-5016)
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/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)
🪛 GitHub Actions: ci
shopify/loaders/ProductListingPage.ts
[error] 12-12: Deno lint: 'Product' is defined but never used. (no-unused-vars)
🪛 GitHub Check: Bundle & Check Apps (ubuntu-latest)
shopify/loaders/ProductList.ts
[failure] 12-12:
�[0m�[1mProductShopify is never used
shopify/loaders/ProductListingPage.ts
[failure] 12-12:
�[0m�[1mProduct is never used
🔇 Additional comments (11)
shopify/loaders/ProductList.ts (4)
76-87: LGTM!The new optional locale props are well-documented with clear examples and maintain backward compatibility.
110-111: Verify the hardcoded locale defaults are appropriate.The defaults
"PT"/"BR"(Portuguese/Brazil) are hardcoded. Ensure these defaults align with your primary market and consider whether they should be configurable at the application level rather than hardcoded in the loader.If these defaults need to be more flexible, consider accepting them from the app context or configuration:
const languageCode = expandedProps?.languageCode ?? ctx.defaultLanguageCode ?? "EN"; const countryCode = expandedProps?.countryCode ?? ctx.defaultCountryCode ?? "US";
141-150: LGTM!The locale context is correctly threaded through the search query with proper type safety.
161-170: LGTM!The locale context is correctly threaded through the collection query with proper type safety, maintaining consistency with the search path.
shopify/loaders/ProductListingPage.ts (6)
75-86: LGTM!The new optional locale props are well-documented and maintain backward compatibility.
144-147: LGTM!The locale context is correctly threaded through, and the safe spread pattern for the sort mapping (line 146) properly addresses the previous review feedback.
179-182: LGTM!The locale context is correctly threaded through, and the safe spread pattern for the sort mapping (line 181) properly addresses the previous review feedback.
194-194: LGTM!Capturing the collection ID enables proper entity identification in the breadcrumb.
232-242: LGTM with a note on the TODO.The breadcrumb now includes the collection ID and uses consistent pathname extraction logic (last segment). The TODO comment on line 232 suggests this area may need further enhancement for multi-level paths, but the current changes are a step in the right direction.
Note: The
@idfield on BreadcrumbList is not standard schema.org. If you're using this for internal tracking, that's fine, but be aware it may not validate against schema.org specifications.
159-162: Verify collection path handling works with existing URLs and fix incomplete TODO.The pathname extraction change is intentional (documented in comments) and consistent, but verification reveals incomplete work. Line 232 has a TODO comment indicating the breadcrumb logic still needs updating for multi-level paths. Manually test that existing single and multi-level collection URLs resolve correctly, and address the breadcrumb TODO before merging.
shopify/utils/storefront/storefront.graphql.gen.ts (1)
7754-7898: i18n variables threading looks consistent and backward‑compatibleThe updates to:
CreateCartMutationVariables(new optionalcountryCode?: InputMaybe<CountryCode>)GetCartQueryVariables(new optionallanguageCode?/countryCode?)GetProductQueryVariablesListProductsQueryVariablesSearchWithFiltersQueryVariablesAllProductsQueryVariablesProductRecommendationsQueryVariablesGetShopInfoQueryVariablesall follow the same pattern:
languageCode?: InputMaybe<LanguageCode>and/orcountryCode?: InputMaybe<CountryCode>as optional fields. This is type-safe and backward‑compatible ({}still satisfies these types) and matches the PR goal of powering@inContextand cart localization via variables.Just ensure the underlying
.graphqldocuments and any codegen pipeline were the source of these changes so the operation signatures and server schema stay in sync.
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
shopify/loaders/ProductList.ts (1)
192-227: Critical: Cache key missing languageCode and countryCode.The
cacheKeyfunction doesn't includelanguageCodeorcountryCodein the cache key, causing products fetched with different locales to incorrectly share the same cache entry. For example, a request for English/US products would be cached, and a subsequent French/France request would receive the cached English/US products.Apply this diff to include locale in the cache key:
export const cacheKey = (expandedProps: Props, req: Request): string => { const props = expandedProps.props ?? (expandedProps as unknown as Props["props"]); const count = (props.count ?? 12).toString(); const sort = props.sort ?? ""; + const languageCode = expandedProps?.languageCode ?? "PT"; + const countryCode = expandedProps?.countryCode ?? "BR"; const searchParams = new URLSearchParams({ count, sort, + languageCode, + countryCode, }); expandedProps.filters?.tags?.forEach((tag) => {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
shopify/loaders/ProductList.ts(6 hunks)shopify/loaders/ProductListingPage.ts(11 hunks)shopify/utils/storefront/storefront.graphql.gen.ts(12 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- shopify/loaders/ProductListingPage.ts
🧰 Additional context used
🧬 Code graph analysis (2)
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/utils/storefront/storefront.graphql.gen.ts (2)
wake/utils/graphql/storefront.graphql.gen.ts (9)
CreateCartMutationVariables(5028-5028)Exact(12-12)InputMaybe(11-11)CreateCartMutation(5031-5031)GetCartQueryVariables(5021-5023)Scalars(18-33)GetCartQuery(5026-5026)AddItemToCartMutation(5075-5075)AddCouponMutation(5068-5068)vtex/utils/types.ts (1)
FilterType(901-901)
🔇 Additional comments (8)
shopify/utils/storefront/storefront.graphql.gen.ts (5)
7736-7749: CartFragment: buyerIdentity projection looks consistent and non-breakingIncluding
buyerIdentitywith{ countryCode?: CountryCode | null; email?: string | null }inCartFragmentmatches theCartBuyerIdentityshape and is correctly threaded through cart types. This is additive only and should be backwards compatible for all existing fragment consumers.
7760-7764: Locale args on query variable types are well-threaded and backward compatibleAdding optional
languageCode?: InputMaybe<LanguageCode>andcountryCode?: InputMaybe<CountryCode>toGetCartQueryVariables,GetProductQueryVariables,ListProductsQueryVariables,SearchWithFiltersQueryVariables,AllProductsQueryVariables,ProductRecommendationsQueryVariables, andGetShopInfoQueryVariablescleanly exposes locale context without breaking existing callers (all new fields are optional). The types line up with theLanguageCodeandCountryCodeenums defined earlier in this file.Also applies to: 7783-7787, 7803-7810, 7826-7838, 7857-7869, 7885-7890, 7906-7910
7872-7883: AllProductsQuery: expanded collection surface is coherentExtending
AllProductsQuerysocollectionnow exposesid,handle,description, andtitle(in addition toproducts) matches theCollectiontype definition and should simplify callers that need collection metadata (e.g., breadcrumbs, SEO, sitemap) alongside products. The added fields are read-only and won’t affect existing product consumers.
7934-7947: Cart mutations now returning buyerIdentity + new CartBuyerIdentityUpdateMutation look correct
AddItemToCartMutation,AddCouponMutation, andUpdateItemsMutationpayloads now includebuyerIdentity: { countryCode?: CountryCode | null; email?: string | null }on the returned cart, consistent with the updatedCartFragment.CartBuyerIdentityUpdateMutationVariablesandCartBuyerIdentityUpdateMutationcorrectly mirrorCartBuyerIdentityInputandCartBuyerIdentityUpdatePayload(cart plususerErrors { field, message }), and reuse the same cart projection as other cart mutations.These are additive result-shape changes and a new mutation type, so they shouldn’t break existing consumers while enabling the new buyer identity flow.
Also applies to: 7966-7979, 7987-8000, 8002-8021
7753-7755: The review comment makes an incorrect recommendation and should be disregarded.The
CreateCartmutation (line 274 in queries.ts) only accepts$countryCodeand does not reference$languageCodeanywhere. AddinglanguageCodetoCreateCartMutationVariableswould create an unused parameter, which violates GraphQL best practices.The apparent "inconsistency" with
GetCartis by design:
GetCartuses@inContext(language: $languageCode, country: $countryCode)to enrich query results, so it requires both variables.CreateCartcallscartCreate(input: { buyerIdentity: { countryCode: $countryCode } })and only needs country context.- Other mutations (
AddItemToCart,AddCoupon,UpdateItems, etc.) follow the same pattern: each accepts only the variables it uses.The generated types correctly reflect the actual GraphQL operation signatures.
Likely an incorrect or invalid review comment.
shopify/loaders/ProductList.ts (3)
75-86: LGTM! Well-documented internationalization props.The new optional
languageCodeandcountryCodeprops are well-documented with clear examples and maintain backward compatibility.
138-153: LGTM! Locale variables correctly passed to search query.The
LanguageContextArgstype extension and variable passing are implemented correctly for the search-based query path.
155-175: LGTM! Locale variables correctly passed to collection query.The
LanguageContextArgstype extension and variable passing are implemented correctly for the collection-based query path, consistent with the search query implementation.
| const languageCode = expandedProps?.languageCode ?? "PT"; | ||
| const countryCode = expandedProps?.countryCode ?? "BR"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the hardcoded Portuguese/Brazil defaults.
The defaults to "PT" (Portuguese) and "BR" (Brazil) may not be appropriate for all Shopify storefronts. Consider whether these should:
- Default to
undefinedto let Shopify use the storefront's default locale - Be configurable at the app/store level
- Have a documented business reason for the PT/BR choice
Hardcoded locale defaults could deliver incorrect localized content to international users.
Would you like to verify if there's a specific reason for the PT/BR defaults, or should these default to undefined instead?
🏁 Script executed:
#!/bin/bash
# Examine ProductList.ts file context around lines 109-110
cat shopify/loaders/ProductList.ts | head -130 | tail -50Length of output: 1425
🏁 Script executed:
#!/bin/bash
# Search for expandedProps initialization and definition
rg -n "expandedProps" shopify/loaders/ProductList.ts -B5 -A5Length of output: 2856
🏁 Script executed:
#!/bin/bash
# Search for any configuration or settings related to languageCode/countryCode
rg -n "languageCode|countryCode" shopify/loaders/ProductList.ts -B3 -A3Length of output: 1177
🏁 Script executed:
#!/bin/bash
# Look for environment variables or config files that might set these defaults
fd -e "config\|env\|settings" -type f | head -20Length of output: 170
🏁 Script executed:
#!/bin/bash
# Search for comments or documentation about PT/BR choice in the file
rg -n "PT|BR|Portuguese|Brazil|locale|language|country" shopify/loaders/ProductList.ts -B2 -A2Length of output: 850
🏁 Script executed:
#!/bin/bash
# Search for README, docs, or comments explaining the PT/BR choice
find . -maxdepth 2 -type f \( -name "README*" -o -name "*.md" \) | xargs grep -l "PT\|BR\|Portuguese\|Brazil\|locale\|language\|country" 2>/dev/null | head -10Length of output: 109
🏁 Script executed:
#!/bin/bash
# Search entire codebase for any configuration or context about locale defaults
rg -n "languageCode.*PT|countryCode.*BR|PT.*BR" --type-add "config:*.{json,yaml,yml,toml,env}" -t config -t ts -t js 2>/dev/null | head -20Length of output: 92990
🏁 Script executed:
#!/bin/bash
# Check if there's an AppContext or global config that might set these
rg -n "AppContext|LanguageCode|CountryCode" shopify/ -l | head -10Length of output: 299
Document or reconfigure the hardcoded PT/BR locale defaults.
The defaults for languageCode="PT" and countryCode="BR" are hardcoded with no documented business reason or configuration mechanism. While the interface design suggests these should be optional and configurable, there's no way for app or store-level settings to override them. This will cause incorrect localized content for non-Brazilian storefronts.
Either:
- Add a config/environment way to override these defaults
- Change defaults to
undefinedand rely on Shopify's storefront settings - Document why PT/BR is the required default for your use case
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
languageCodeandcountryCodevalues via the@inContextdirective, 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
excludeprop in the proxy handler.🧩 How It Works
✅ Loaders
Most loaders now accept the following optional props:
These values are passed as variables to GraphQL queries and injected using the
@inContextdirective.✅ Queries
All core Shopify queries have been refactored to use GraphQL variables in the
@inContextdirective (instead of interpolated JavaScript), making them fully compatible with GraphQL tooling and static code analysis like GraphQL Codegen.Example:
On the TypeScript side:
🗺️ Sitemap Enhancements
The sitemap proxy handler now accepts two optional props:
This enables:
include)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:
ListProductsSearchProductsProductsByCollectionProductRecommendationsGetProductand related queriesShopCart🌍 Impact
🎥 Loom Video
https://www.loom.com/share/8a8f204de9a745528ac2f84f44f428fa
Summary by CodeRabbit
New Features
Improvements
Chores