Closed acmoune closed 6 years ago
hm, is your detected language something like fr-FR
?!? a language-Region combination if so that would not go through the whitelist -> you will need to additional set nonExplicitWhitelist
on init - https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
just guessing as everything else looks right...
No, it is exactly FR, I set it myself withi18n.changeLanguage('fr')
from React app.
I tried the nonExplicitWhitelist
config. It seems to make req.language
return en-US
instead of en
, even if the language is set to en
. But
console.log(lng) // on top
is still returning 'EN-US', instead of 'FR-FR'.
So if I understand you well, both LanguageDetectors (browser and express) should naturally detect the same thing, I think from the browser's LocalStorage ?
the client detector is not running same time on server...the client pick up from server via set cookie. But on ssr you set it like initialStore via initialLanguage: https://react.i18next.com/components/translate-hoc#the-translate-hoc-props
if i get it right you do something like:
export default function (req, res) {
const lng = req.language.toUpperCase()
console.log(lng) // Always display [EN]
MyModel
.findOne(query, { fieldEN: 1, fieldFR: 1 })
.exec()
.then(res => reply(res[`field${lng}`]))
.catch(err => handle(err))
}
in an express middleware and somewhere inside the react render on server you set language via i18n.changeLanguage('fr')
...right?
That can't work middleware run before render...language there will be what detector detected if order of middlewares is right - setting it inside rendering is to late.
I create a new middleware that add a component field to the request like this:
...
import i18n from '../i18n-server'
exports.addComponentToRequestPipeline = (req, res, next) => {
const i18n_ = i18n.cloneInstance()
const store = storeFactory(serverStore.getState())
i18n_.changeLanguage(req.language)
req.component = {
state: store.getState(),
html: renderToString(
<I18nextProvider i18n={i18n_}>
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
<Route component={App} />
</StaticRouter>
</Provider>
</I18nextProvider>
)
}
next()
}
This middleware is added after i18next-express-middleware
.
So I use my req.component
html and state to generate the markup.
Note that when I set a new language on React app, and I refresh the page, the new language is used, but only when I put wait: true
. If not it is always in english after refresh. I admit that I am not really understanding what is going on.
you create a i18next instance clone - use that for serverside render. Do you set initialStore, initialLanguage for client?
using an own clone does nothing beside using that for render on server - the instance used on request does not change language but stays on its own language set. req.i18n
I am trying to better understand your module and get it to work since ... but by the way I am getting a micro sub issue.
I am loading theinitialI18nStore
on the server with this (from your razzle-ssr
example):
const initialI18nStore = {}
req.i18n.languages.forEach((l) => {
initialI18nStore[l] = req.i18n.services.resourceStore.data[l]
})
My backend option is set like this:
backend: {
loadPath: `${__dirname}/public/locales/{{lng}}/{{ns}}.json`
}
But when I check the value of my window.initialI18nStore
, from the page source on the browser, I see that only one langue (en) is loaded, with its namespaces.
Mylocales
directory contains two folders, en and fr, with respectives json files.
The preload
option is set to ['en', 'fr']
.
Is that normal ?
yes that is normal...if detection was en there is no reason to push down fr
req.i18n.languages
is the array of languages it uses for the translation lookup....eg. for en-US it would be en-US
-> en
-> fallbackLng
--> in your case with whitelist and fallback en only [en]
...
if you like to pass down all preloaded you will need to change that to:
const initialI18nStore = {}
req.i18n.options.preload.forEach((l) => {
initialI18nStore[l] = req.i18n.services.resourceStore.data[l]
})
but be aware the more languages you add the bigger this gets resulting in pushing all languages to the client...
So i18n.services.resourceStore.data
keeps all the translations.
On SSR I create the initialStore
and set the initialLanguage
. This will be done using the i18n-server init
config. At this point, I load only the detected language's translations.
Now on the client side, on initial rendering, nothing will be added to the translations store, since the initialLanguage
translations are there already.
As soon as I i18n.changeLanguage('another-language')
, the translations store is populated with new translations from the added language.
Am I right up to there ?
Now, on the server side, once app.use(i18nMiddleware.handle(i18n))
is called, the req.i18n.language
is set with whatever will be detected as the default language from thei18n-server
config. So the initialLanguage
for the client will always be set to that language on SSR, en in my case.
If I am still right then my question is: How can I update req.i18n.language according to the changed language from the client, before rendering on the server ? I think I should do it by calling i18n.changeLanguage(...)
before app.use(i18nMiddleware.handle(i18n))
, but where would I take the changed language ?
I don't know if the translations store is acting like the Redux store, so whatever update I do on the client store, I can first dispatch the same update to the server store, so on SSR, everything will be in sync.
First part yes...correct
Second...idk...there is no magic syncing the resourceStore from client to backend. There is nothing doing this for redux neither. On server resourceStore already contains every translation - while to client you only pass what is needed down there.
If I am still right then my question is: How can I update req.i18n.language according to the changed language from the client, before rendering on the server ?
You mean for a second server side render?
changeLanguage can set cookie on client...next load of a serverside rendered page will pass the cookie up to server which will take that for language detection.
Ok. Thanks for the clarifications.
Now is there a convention for the cookie, so the server LanguageDetector will autmaticaly take its value, or is there a hook point to push the cookie value to the LanguageDetector, or should I call i18n.changeLanguage
before app.use(i18nMiddleware.handle(i18n))
?
for browser set: caches: ['localStorage', 'cookie']
https://github.com/i18next/i18next-browser-languageDetector#detector-options
same for server: https://github.com/i18next/i18next-express-middleware/blob/master/src/LanguageDetector.js#L17
I am almost done. My only problem now is the language region.
When I set the cookie, withi18n.changeLanguage(...)
, I have total control on the language format, I set it to either en or fr. Once set, the detector always take from there.
The problem is with the initial detection. The Detector always add the region, and I need to remove it.
This is my actual config, on both side (client and server)
...
whitelist: ['en', 'en-US', 'fr', 'fr-FR'],
fallbackLng: 'en',
load: 'languageOnly',
nonExplicitWhitelist: true,
...
detection will always be best match -> so i18next.language will be en-US
but i18next.languages[0] will be best language you support. (as languageOnly is set it won't load or use en-US
https://github.com/i18next/i18next/blob/master/src/LanguageUtils.js#L100)
Thank you so much for your time, I am really grateful.
In my server's routes, I do something like this (Note: The
i18next-express-middleware
is already in the pipeline, and Server Side Rendering is working good):So I try to return the right version of the field, base on the USER's selected language.
But no matter what language is selected on the browser side, on the server side it is always set to the default, EN.
Is there a way to let the server side LanguageDetector know about the current language on the browser side ?
This is my
Client i18n init
:And this is my server
i18n init
: