Skip to content

Switch locale with a page load instead of the router - #1037

Open
marinaaisa wants to merge 3 commits into
swiftlang:mainfrom
marinaaisa:locale-selector-navigation
Open

marinaaisa wants to merge 3 commits into
swiftlang:mainfrom
marinaaisa:locale-selector-navigation

Conversation

@marinaaisa

Copy link
Copy Markdown
Member

Bug/issue #187696128, if applicable:

Summary

The locale selector offers every locale in availableLocales, but a page can offer a locale whose data does not exist. The route guard already falls back to the English page when the data answers 404, but as an in-app navigation that fallback resolves to the location the router is already at, which vue-router rejects as a duplicated navigation [1]. The redirect never happened, leaving the page claiming a locale that was never rendered.

This change resolves the target href with router.resolve [2] and hands it to location.assign [3], so switching locales is a normal page load. The guard then runs as an initial navigation and the English fallback works. Resolving is what makes the href usable outside the router: the locale is a route param, and router paths are relative to the router base, which is the <base> path the site is deployed under. The browser needs the full path instead, so resolve is the piece that fills the locale into the matched route pattern and prefixes the base, something a hand built string from the current path would miss when the site is not served from the domain root.

The UI is unchanged, and the reloaded app reads its locale from the URL, so updating the i18n state by hand is no longer needed.

[1] https://v3.router.vuejs.org/guide/advanced/navigation-failures.html
[2] https://v3.router.vuejs.org/api/#router-resolve
[3] https://developer.mozilla.org/en-US/docs/Web/API/Location/assign

Dependencies

NA

Testing

Steps:

  1. Provide setup instructions.
  2. Explain in detail how the functionality can be tested.

Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

  • Added tests
  • Ran npm test, and it succeeded
  • Updated documentation if necessary

The locale selector offers every locale in `availableLocales`, but a
page can offer a locale whose data does not exist. The route guard
already falls back to the English page when the data answers 404, but as
an in-app navigation that fallback resolves to the location the router
is already at, which vue-router rejects as a duplicated navigation [1].
The redirect never happened, leaving the page claiming a locale that was
never rendered.

This change resolves the target href with `router.resolve` [2] and hands
it to `location.assign` [3], so switching locales is a normal page load.
The guard then runs as an initial navigation and the English fallback
works. Resolving is what makes the href usable outside the router: the
locale is a route param, and router paths are relative to the router
base, which is the `<base>` path the site is deployed under. The browser
needs the full path instead, so `resolve` is the piece that fills the
locale into the matched route pattern and prefixes the base, something a
hand built string from the current path would miss when the site is not
served from the domain root.

The UI is unchanged, and the reloaded app reads its locale from the URL,
so updating the i18n state by hand is no longer needed.

[1] https://v3.router.vuejs.org/guide/advanced/navigation-failures.html
[2] https://v3.router.vuejs.org/api/#router-resolve
[3] https://developer.mozilla.org/en-US/docs/Web/API/Location/assign
The banner suggesting the reader's preferred locale used a router link,
so picking it stayed inside the app. The suggested locale may have no
data for the current page. The route guard falls back to the English
page when the data answers 404, but from an in-app navigation that
fallback resolves to the location the router is already at, which
vue-router rejects as a duplicated navigation [1]. The redirect never
happened, and the reader was left on a page that claimed a locale it was
never rendered in.

This change renders a plain anchor instead, with the target resolved
through `router.resolve` [2] so the href keeps the base URL and the
locale path segment. The browser performs the navigation, the app boots
again at the localized URL, and its guard runs as an initial navigation,
so the English fallback works. The click handler still stores the
preference, since local storage writes finish before the page is left.
The test file is renamed after the component it covers.

[1] https://v3.router.vuejs.org/guide/advanced/navigation-failures.html
[2] https://v3.router.vuejs.org/api/#router-resolve
@marinaaisa
marinaaisa force-pushed the locale-selector-navigation branch from 3516213 to b1aa8b8 Compare September 18, 2026 20:22
This change resets the preference to the default locale as part of that
fallback. The banner only renders when the preferred locale differs from
the one being displayed, so it stays hidden, and because the preference
is also persisted the banner does not come back after a reload. The
reset is site wide rather than per page, which keeps the guard simple at
the cost of forgetting the preference on pages that do have the
translation.
@marinaaisa

Copy link
Copy Markdown
Member Author

@swift-ci test

@marinaaisa

Copy link
Copy Markdown
Member Author

@swift-ci test

Comment thread src/utils/data.js
AppStore.setPreferredLocale(defaultLocale);
// Call `next` with the same route but without the locale param,
// redirecting to the non-localized version of the route.
next({ ...to, params: paramsWithoutLocale });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the issue with the original fallback PR that we missed is that this should probably be throw false to abort the request instead of doing the next() and return null, which is what was causing the page data to disappear.

We might also need to add something similar to this in the global onError hook for the router to gracefully handle aborted requests without showing the server error page:

router.onError((error) => {
    if (isNavigationFailure(error, NavigationFailureType.aborted)) {
      return;
    }
    ...
}

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