I have a web application with reactjs and node. Visitors can choose their language for the interface of the frontend. We use i18next
to manage that in the frontend.
To sign up on the web application, a visitor needs to enter their email, we will send them a verification email with a link like https://localhost:3000/1/auth/verification/1ab3d0f189191?language=zh
. He then finds out the email and clicks on the link to validate his email. In the end, we send him a congratulation email for signing up.
The emails need to be written in the language set in the verification link (which is made based on the language sent from the frontend of a visitor). I would like to use AsyncLocalStorage
to store the language which can be accessible from different functions in the backend.
Here is Context.js
:
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
module.exports = asyncLocalStorage;
Here is the middleware to save the language
router.use((req, res, next) => {
asyncLocalStorage.run(new Map(), () => {
console.log("asyncLocalStorage, previous language:", asyncLocalStorage.getStore().get('language'));
const languageFromBody = req.body.language; // for requests such as register
const languageFromQuery = req.query.language; // for links such as /auth/verification/
const language = languageFromBody || languageFromQuery;
asyncLocalStorage.getStore().set('language', language);
console.log("asyncLocalStorage, current language:", language);
next();
});
});
The endpoint of the verification link like https://localhost:3000/1/auth/verification/1ab3d0f189191?language=zh
sent to visitors:
router.get('/' + process.env.versionNumber + '/auth/verification/:code', function (req, res) { //HANDLE VERIFICATION
const language = asyncLocalStorage.getStore()?.get('language') ?? 'en';
const i18n = language === "zh" ? i18next_zh : i18next_en;
console.log("language in router:", language);
userModel.verificationAccount(req.params.code)
.then(data => {
res.send(i18n.t('Verified successfully! You can now sign in with your credentials for our tools'))
})
.catch(err => {
res.send(err)
})
});
verificationAccount
in userModel
:
verificationAccount (code) {
const language = asyncLocalStorage.getStore()?.get('language') ?? 'en';
console.log("language in verificationAccount:", language);
return new Promise((resolve, reject) => {
this.user.find({ "local.verificationCodes": { $in: [code] } }, { }, (err, users) => {
if (err) {
console.log("Error in verificationAccount:", err);
reject("Database error");
return
}
if (users.length > 0) {
if (users[0].local.verified === 'no') {
this.user.findByIdAndUpdate(users[0]._id, { $set: { "local.verified": 'yes' } }, { new: true }, (err, userAfterChanged) => {
if (err) {
console.log("Error in verificationAccount update:", err);
reject("Update error");
return
}
verfModule.sendNewUserSignUpSuccess(users[0].local.id, users[0].currentName, users[0].currentProvider);
resolve(userAfterChanged)
})
}
} else {
reject('not found the code of verification')
}
})
})
}
sendNewUserSignUpSuccess
in verfModule
:
sendNewUserSignUpSuccess (email, currentName, currentProvider) {
const language = asyncLocalStorage.getStore()?.get('language') ?? 'en';
const i18n = language === "zh" ? i18next_zh : i18next_en;
console.log("language in sendNewUserSignUpSuccess:", language);
return emailService.sendMail({
to: email,
subject: i18n.t('Congratulations! You have successfully signed up!'),
html: "<div>body</div>",
text: "text",
category: 'sign_up_success',
unique_args: {
user_email: email
},
});
}
Test logs in the console:
My tests show that, because of language=zh
in the verification link, after the visitor's click on the link, language
in asyncLocalStorage
is set as zh
. language in router: zh
and language in verificationAccount: zh
are correct. By contrast, language in sendNewUserSignUpSuccess: en
is not correct. As a result, the title of the congratulation email sent is still in English.
Does anyone know why we cannot correctly get the language
of asyncLocalStorage
in sendNewUserSignUpSuccess
?