i18next / i18next-express-middleware

[deprecated] can be replaced with i18next-http-middleware
https://github.com/i18next/i18next-http-middleware
MIT License
207 stars 55 forks source link

Server only renders language set on first request #165

Closed lucasfeliciano closed 5 years ago

lucasfeliciano commented 5 years ago

After implementing a custom detector I'm experiencing the following scenario:

  1. Run server
  2. First request on server with domain .nl ( Page renders correctly )
  3. For any other request the server renders the page with the language of the first request (nl in this case), then the client's detector kick in and correct the language.

If after starting the server my first request was to a .com domain, the same scenario happens but rendering english on the server.

I'm not really sure what is going on.

Also, I realised that the detector runs on EVERY request like _next | static, which doesn't seems optimal in my opinion

customDetector.js

const getDomainFromRequest = require('./getDomainFromRequest')
const mapDomainToLanguage = require('./mapDomainToLanguage')

module.exports = {
  name: 'domain',

  lookup(req, res, options) {
    const domain = getDomainFromRequest(req)
    const language = mapDomainToLanguage(domain)

    return language
  },

  cacheUserLanguage(req, res, lng, options = {}) {
    //todo
  },
}

i18n.js

if (process.browser) {
  // Clientside i18n setup
  const clientSideLanguageDetector = new i18nextBrowserLanguageDetector()
  clientSideLanguageDetector.addDetector(clientLanguageDetectorByDomain)

  i18n.use(i18nextXHRBackend).use(clientSideLanguageDetector)
} else {
  // Serverside i18n setup
  const i18nextNodeBackend = require('i18next-node-fs-backend')
  const i18nextMiddleware = require('i18next-express-middleware')
  const serverLanguageDetectorByDomain = require('./serverLanguageDetectionByDomain')

  const serverSideLanguageDetector = new i18nextMiddleware.LanguageDetector()
  serverSideLanguageDetector.addDetector(serverLanguageDetectorByDomain)

  i18n.use(i18nextNodeBackend).use(serverSideLanguageDetector)
}

server.js


;(async () => {
  await app.prepare()
  const server = express()

  server.use(compression())

  server.use(i18nextMiddleware.handle(i18n))

  server.get('*', (req, res) => handle(req, res))

  await server.listen(port)
  console.log(`> Ready on http://localhost:${port}`)
})()
jamuhl commented 5 years ago

You can ignoreRoutes: https://github.com/i18next/i18next-express-middleware#wire-up-i18next-to-request-object

Either have same detector on client too - or sync them using the cookie detector/save

lucasfeliciano commented 5 years ago

But the interface for server and client are different, since the server receives req, res, options and the client just options

or sync them using the cookie detector/save

What do you mean by this?

jamuhl commented 5 years ago

on client you will need to read window.location.host (or whatever)

jamuhl commented 5 years ago

cookie setting caches: ['cookie'] https://github.com/i18next/i18next-express-middleware#detector-options it will set a cookie - using cookie detector on client too will sync those

lucasfeliciano commented 5 years ago

I see, but in this case shouldn't matter, even thought because the domains are different, so the cookie its not there yet on visiting the second domain

So I can access a .com domain and it will set the i18next: en cookie but when I access the .nl domain the cookie it will be empty and It should set the language from the domain. But somehow it is getting the en language.

detection: {
      order: ['domain', 'cookie'],
      caches: ['cookie'],
    },

I also tried to change the domain, cookie order or even remove cookie.

This is how my detector looks right now

const mapDomainToLanguage = require('./mapDomainToLanguage')

module.exports = {
  name: 'domain',

  lookup(req, res, options) {
    let domain

    if (process.browser) {
      const getDomainFromHostname = require('./getDomainFromHostname')
      domain = getDomainFromHostname(window.location.hostname)
    } else {
      const getDomainFromRequest = require('./getDomainFromRequest')
      domain = getDomainFromRequest(req)
    }
    const language = mapDomainToLanguage(domain)

    console.log('language', language)
    return language
  },

  cacheUserLanguage(req, res, lng, options = {}) {
    //todo
  },
}

And the return is correct for every request. I'm probably missing something but 🤔

jamuhl commented 5 years ago

en from the fallbackLng? do you get the cookie set?

lucasfeliciano commented 5 years ago

I do have the cookie set to the correct language (based on domain) and it is not falling back to fallbackLng.

I have en as the fallback language however if I first visit .nl first and then go to .com it will SSR nl as language and then update to en. If I first visit .es it will always SSR es and then update to the correct one.

lucasfeliciano commented 5 years ago

Browser console output:

image

Server console output: image

This comes from the custom language detector

jamuhl commented 5 years ago

Using next.js - i see. Than not an issue with the cookie i guess but with having initialLanguage not set correctly...

If this is the case - could you please open an issue here: https://github.com/i18next/react-i18next so isaachinman can have a look (he did the newer next.js samples and is deeper into next.js than me)

lucasfeliciano commented 5 years ago

Will do, but I don't think it is a problem with the initialLanguage

image

As you can see the different blocks are related to different domains, I also checked the initialStore since I thought that the store could be only with one language set, but it contains the expected languages.

I'll create an issue there anyway and also debug the whole stack trace again. 🤞

Thank you for the help :)

jamuhl commented 5 years ago

Either way...a sample for reproduction might help...like you say ... debugging the stack is easier to spot the issue. Really hard to say where things go wrong even with the provided information.

Just the next.js sample goes on client like:

1) as usual take the detector and set the language... 2) coming on the first initialLanguage setting HOC / _app.js it changes the language based on given initialLanguage

lucasfeliciano commented 5 years ago

I tried to reproduce in a fresh setup but I couldn't. The funny thing is that I have the same i18n setup on my project. Exactly the same, I triple checked.

@jamuhl github has a feature of moving issues across repositories, maybe you could move this issue to the react-i18next repository, maybe isaachinman has some insight or better eyes than me

jamuhl commented 5 years ago

If it's not reproducible it seems to not make a lot of sense to forward this.

Could it be related to the changes done a few minutes ago:

https://github.com/i18next/react-i18next/issues/626#issuecomment-442513443

https://github.com/i18next/react-i18next/commit/896c979df516cde7a4ad55d05cfdcf9840dd364c https://github.com/i18next/react-i18next/commit/b0a789be6258c3667d32fa997fcfc13653914683

Beside that looking at https://github.com/isaachinman/next-i18next might help to setup next.js with react-i18next ---> this is the way to go from now on

lucasfeliciano commented 5 years ago

Yes! This was the problem: https://github.com/i18next/react-i18next/commit/896c979df516cde7a4ad55d05cfdcf9840dd364c Which was copy and past code from the example

After I changing that it was fixed :) I'll take a look at https://github.com/isaachinman/next-i18next Looks promising since reduce a lot the boilerplate and make configuration easier

jamuhl commented 5 years ago

If you like this module don’t forget to star this repo. Make a tweet, share the word or have a look at our https://locize.com to support the devs of this project -> there are many ways to help this project :pray: