Change language using Remix Link
Closed this issue · 9 comments
Describe the bug
If I use component to let the user change the language of the website, I get a strange behaviour:
- first click the url is updated but nothing change
- second click on the same link the page update base on the language selected
Your Example Website or App
atm just on localhost
Steps to Reproduce the Bug or Issue
Click on the link
Expected behavior
The click should change page and url
Screenshots or Videos
No response
Platform
- OS: macOS
- Browser: Chrome
Additional context
No response
Are you doing language detection based on the path? If so check this thread #186 (reply in thread).
And the reason why the second click works is because you're basically going from /a y /b and then once in /b you click a link to itself (/b) and Remix revalidate every loader when you click a link to the current location.
hello @sergiodxa thanks for your answer!
I'm actually don't have the $lang param in the routes but in the loader of root.tsx
i get the locale like this:
const locale = getLocale(request)
using this function
// Get the language from the request
export const getLocale = (request: Request) => {
if (request.url.indexOf('/en/') > 0) {
return 'en'
}
// Check if the latest part of the request.url is '/it'
const urlParts = request.url.split('/en')
if (urlParts[1] === '' || urlParts[1] === '/') {
return 'en'
}
// Default to italian
return 'it'
}
Not the most elegant solution but it works.
But the <Link>
components are not working correctly
While you don't use a route param you're basically doing the same by extracting the locale from the URL pathname.
The way Link component is working is the correct one in Remix, and the solutions in the linked thread should work for your implementation too.
Hey @sergiodxa anyway using let loaderData = useRouteLoaderData<typeof loader>('routes/($lang)')
I always get undefined
If you get undefined it means the route is not being matched or the route id is wrong, do a console.log(useMatches())
to confirm that the route is matched and the correct route id.
@sergiodxa this is the console.log of useMatches
:
but if I extract the locale from useRouteLoaderData ('root')
is remain the same, it doesn't update before the second click
The reason useRouteLoaderData<typeof loader>('routes/($lang)')
gives you undefined
is because that's not the route id of your route. The route ID is en-/en/movies/:slug
, you're not matching a route with an optional parameter for the locale, instead it seems to have the locale hardcoded in the pathname.
For the root not updating until a second link click, that's expected, that's a Remix feature, not a remix-i18next feature or bug.
Unless you enable Single Fetch, Remix will only fetch the loaders for the routes that changed when doing a navigation, this means that the root is never fetched on a navigation because the route is always matching.
The root loader will be fetched again when the search params change (since those can be read from any route) or when a revalidation happens (after an action or when using useRevalidator). Additionally, when you click a link to the current URL, Remix simulates the same behavior done by browser and revalidate every loader (browsers reload the page).
This last one is why the second clicks works, let me put a better example, let's say you have these routes:
root
routes/users/new
routes/users/$username
If you're on /users/new
only 1 and 2 are matching, if you move to /users/123
then 1 is still matching, 2 doesn't match anymore and now 3 matches. So Remix will fetch the loader of 3 but not the loader of 1 because that didn't changed.
If you go from /users/123
to /users/456
then 1 is still matching and 3 is also matching, but because the parameter username
changed from 123 to 456 then Remix will fetch the loader of the route 3 with the new parameter, but because the loader of 1 doesn't depend on that parameter it will not fetch it.
If the user is on /users/456
and clicks a link to /users/456
, the browser would have reloaded the whole page, to simulate this Remix fetch the loaders of 1 and 3, so now it does fetch the root loader again.
This is the behavior you're seeing, your root loader is depending on pathname parameter of another route, Remix doesn't know this and will not revalidate it on a client-side navigation.
This whole behavior also change if you enable Single Fetch future flag, with this enabled every client-side navigation will trigger every matching route loader, so here the root will be fetched again when you go from /users/123
to /users/456
. Which in your case means it run again when the locale change.
All of that say, I recommend you to either use a dynamic route segment and maybe an optional one, so you can read directly from the route params, or enable Single Fetch.
hey @sergiodxa,
thank you so much for you clarification. I've changed a little bit the ids of my routes, eg:
English -> /en/movies/:slug
Italian -> /film/:slug
the reason the language is hardcoded is because the routes are created using a json file passed through defineRoutes
return defineRoutes((route) => {
routes.forEach((r) => {
route(r.path, r.component, {
id: `${r.path}`,
})
})
routes.forEach((r) => {
route(r.originalPath, r.component, {
id: `${r.originalPath}`,
})
})
})
So i'm not passing the lang ($lang) as parameter also because my default in Italian doesn't show the locale in the url.
In any case is too early to adopt the single fetch approach, so I'll need to figure out a different way to change the language.
Maybe instead of using I will use button inside a form.