Skip to content

feat(core): TRAC-421 consume merchant-configured locale subfolders from BC backend#3015

Draft
mfaris9 wants to merge 6 commits into
canaryfrom
TRAC-421
Draft

feat(core): TRAC-421 consume merchant-configured locale subfolders from BC backend#3015
mfaris9 wants to merge 6 commits into
canaryfrom
TRAC-421

Conversation

@mfaris9
Copy link
Copy Markdown
Contributor

@mfaris9 mfaris9 commented May 18, 2026

What / Why

PROJECT-5002 added merchant-configurable per-locale URL subfolders in the BC Control Panel — e.g., fr-CA can be served at /fr/ instead of /fr-CA/. The Storefront GraphQL API exposes this as Locale.fullPath.

This PR teaches Catalyst to use that configured subfolder for URL routing, while keeping the locale code for translations, <html lang>, Accept-Language, and BC content negotiation.

JIRA

TRAC-421

Testing

Manual testing performed against a dev store with three configured locales in CP:

| Locale | CP Path | Default? |
| en | /en | yes |
| es | /es | no |
| de | /ge | no |

Screenshot 2026-05-15 at 4 40 06 PM Screenshot 2026-05-15 at 4 40 28 PM Screenshot 2026-05-15 at 4 40 54 PM

Migration

None needed.

…om BC backend

Catalyst now reads each locale's fullPath from BC's Storefront GraphQL
API and uses the merchant-configured subfolder for URL routing instead
of the locale code. Locales without a configured subfolder fall back to
the locale code, preserving backwards compatibility.

Refs: TRAC-421
@mfaris9 mfaris9 requested a review from a team as a code owner May 18, 2026 02:11
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 18, 2026

🦋 Changeset detected

Latest commit: e9e560c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

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

Project Deployment Actions Updated (UTC)
catalyst Error Error May 20, 2026 3:41pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 90 94 83 88

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 77 81 65 87
Accessibility 95 95 100 100
Best Practices 100 100 95 95
SEO 88 88 71 71

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 3.4 s 5.1 s 3.6 s 3.4 s
CLS 0.001 0 0 0
FCP 1.2 s 1.2 s 2.7 s 2.7 s
TBT 0 ms 0 ms 0 ms 0 ms
Max Potential FID 50 ms 50 ms 30 ms 30 ms
Time to Interactive 3.4 s 5.2 s 3.6 s 3.4 s

Full Unlighthouse report →

…sn't in schema

BC's `Locale.fullPath` field is gated by the `availableLocaleFullPathEnabled`
experiment. On stores with the flag off, querying for `fullPath` returns a
400 "Cannot query field 'fullPath' on type 'Locale'", which crashes Catalyst
at startup.

Detect that specific error and retry with a legacy query that omits the
field. Downstream consumers treat a missing `fullPath` as "no custom
subfolder configured" and fall back to using the locale code as the URL
prefix — identical to today's behavior.

Refs: TRAC-421
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

Bundle Size Report

Comparing against baseline from 7b38d98 (2026-05-20).

No bundle size changes detected.

@mfaris9 mfaris9 marked this pull request as draft May 18, 2026 03:12
@mfaris9 mfaris9 marked this pull request as ready for review May 18, 2026 03:43
@mfaris9
Copy link
Copy Markdown
Contributor Author

mfaris9 commented May 18, 2026

Question: since fullPath is removed from the schema entirely when the availableLocaleFullPathEnabled flag is off, I went with a try/catch fallback to a legacy query. I am open to suggestions if there's a cleaner way to do this.

Comment thread core/i18n/locales.ts

const localeNodes = buildConfig.get('locales');

function pathFromFullPath(fullPath: string | null | undefined): string | undefined {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can output also path if needed, this is an easy task.

store {	
  locales{
    code   : 'fr'
    isDefault : true
    path: 'france'
    fullPath : 'site.com/france'
 
  }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That would be great, it would be cleaner to have the path output as well

Comment thread core/next.config.ts Outdated
return parsed.data.graphqlErrors.some((e) => e.message.includes("Cannot query field 'fullPath'"));
}

async function fetchSettings() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this the standard pattern for new GQL schemas in storefront? I would double check if we can just change the schema without the flag, it should just return null anyway. May not be possible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I didn't love this change either because we'll have to go back and clean it up once the flag graduates... BC's storefront removes from the schema entirely when the flag is off. I agree it feels weird and I feel as though this issue probably comes up often i'll send a message in trac-team to see if anyone else handled this differently.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hey Parth, looks like this is standard pattern on the storefront side (see SearchSchema.scala:28). Thanks for the review.

Comment thread core/i18n/locales.ts Outdated
Comment thread core/i18n/routing.ts
import { defaultLocale, locales } from './locales';

enum LocalePrefixes {
ALWAYS = 'always',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just curious: why did we remove this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I didn't think we needed the enum anymore as we are only using as-needed. I can add a comment like this if you prefer:

localePrefix: {
// 'as-needed' removes the prefix for the default locale.
// 'always' would prefix every locale including the default.
// 'never' has cache-related issues — avoid:
// amannn/next-intl#786
mode: 'as-needed',
prefixes,
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Enum change reverted to provide context for merchant configuration

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.

3 participants