sergiodxa/remix-i18next

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:
Schermata 2024-06-11 alle 15 17 05
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:

  1. root
  2. routes/users/new
  3. 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.