Skip to content

Conversation

agurtovoy
Copy link
Contributor

What/Why?

Fix locale switcher on localized Makeswift pages with different paths.

Testing

Kapture.2025-09-23.at.16.16.32.mp4

Migration

N/A

@agurtovoy agurtovoy requested a review from a team as a code owner September 23, 2025 21:41
Copy link

changeset-bot bot commented Sep 23, 2025

🦋 Changeset detected

Latest commit: eb2d4e1

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-makeswift Patch

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

Copy link

vercel bot commented Sep 23, 2025

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

Project Deployment Preview Comments Updated (UTC)
catalyst-b2b Ready Ready Preview Comment Sep 24, 2025 3:38pm
catalyst-canary Ready Ready Preview Comment Sep 24, 2025 3:38pm
2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
catalyst Ignored Ignored Sep 24, 2025 3:38pm
catalyst-uk Ignored Ignored Sep 24, 2025 3:38pm

@agurtovoy agurtovoy changed the base branch from canary to integrations/makeswift September 23, 2025 21:41
@agurtovoy agurtovoy changed the title Fix/locale switcher makeswift localized paths fix: locale switcher correctly works w/ localized Makeswift paths Sep 23, 2025
// are used in combination with a given `pathname`. Since the two will
// always match for the current route, we can skip runtime checks.
{ pathname, params, query: Object.fromEntries(searchParams.entries()) },
{ locale },
Copy link
Contributor Author

@agurtovoy agurtovoy Sep 23, 2025

Choose a reason for hiding this comment

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

The canonical way to handle this in next-intl is to use the pathnames configuration, but it's too inflexible for our purposes here:

  • If we kept it as a build-time configuration, it would require a site redeploy after each Makeswift publish that updates page paths.
  • A build-time configuration would also be incorrect for previews.
  • If we converted it into a run-time configuration, we’d have to rework our code to retrieve the derived navigation components from a context and then cache and revalidate them at runtime.

The approach in this PR is more localized and arguably more performant, as we don't have to retrieve the whole sitemap.

Copy link
Collaborator

@migueloller migueloller left a comment

Choose a reason for hiding this comment

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

Thanks @agurtovoy!

Comment on lines 876 to 894
const localizedPathname = await getLocalizedPathname({
pathname,
activeLocale: activeLocale?.id,
targetLocale: locale,
});

startTransition(() =>
router.push(
{
pathname: localizedPathname,
// @ts-expect-error -- TypeScript will validate that only known `params`
// are used in combination with a given `pathname`. Since the two will
// always match for the current route, we can skip runtime checks.
params,
query: Object.fromEntries(searchParams.entries()),
},
{ locale },
),
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

My understanding is that route changes already trigger transitions. I'm curious why we're using it here.

Perhaps instead we meant to do this:

return useCallback(startTransition(async () => {
  const localizedPathname = await getLocalizedPathname({})

  router.push()
}))

Note how the async transition will mark the transition as pending when the action itself is triggered. This pattern is actually what a "React Action" is, by definition.

Copy link
Contributor Author

@agurtovoy agurtovoy Sep 24, 2025

Choose a reason for hiding this comment

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

There is already a top-level startTransition call wrapping this callback here; the nested startTransition call follows the pattern explicitly set out for async transitions here: https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition.

That said, I didn't know that using router.push guarantees a startTransition call, which does make the explicit startTransition call unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a clarifying comment.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, TIL. Thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

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

A separate thought is: should the transition be handles outside of the hook, where it's not known whether there's a state update, or should it be handled inside as in my suggestion comment, so that the state update and transition are colocated 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think there's a significant semantic difference in this case; you could argue it either way. I originally left it out of this particular hook and kept it in the LocaleSwitcher because, conceptually, switching the locale involves navigation, and that should be a transition, regardless of how the navigation is triggered.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You could argue that because switching the locale involves navigation, the hook should encapsulate that and return the pending state together with the switchLocale callback. I'm sympathetic to that argument, but not enough to go rework this, haha.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, I would argue the latter. And also, APIs like router.push encapsulate the transition as well.

That said, I'm not expecting you to rework this. Thanks for the fix!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

APIs like router.push encapsulate the transition as well.

But it doesn't return a pending state, so you have to wrap it in a startTransition call if you want that anyway. Similarly, the useSwitchLocale hook doesn't return a pending state because it was conceived as a lower-level primitive, and the pending state handling was left to the LocaleSwitcher component itself.

Now, if it occurred to me at the time, I'd likely go with the design you're advocating, I'm just pointing out that either of them is reasonable.

@agurtovoy agurtovoy force-pushed the fix/locale-switcher-makeswift-localized-paths branch from 66a39fa to eb2d4e1 Compare September 24, 2025 15:35
@agurtovoy agurtovoy merged commit 3fcf2c2 into integrations/makeswift Sep 24, 2025
13 checks passed
@agurtovoy agurtovoy deleted the fix/locale-switcher-makeswift-localized-paths branch September 24, 2025 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants