From f1a3bd50015379991065e8d02a3a0da47329879d Mon Sep 17 00:00:00 2001 From: Jaka Kranjc Date: Fri, 16 Dec 2016 01:36:54 +0100 Subject: [PATCH] transferGems: use receiver language translation for PM strings #7722 (#8173) * transferGems: use receiver language translation for PM strings #7722 * chore(test): DRY up transfer gems test * chore(test): Allow check for language in translation assertion * chore(test): Add test that member locales are used in transfer gems msg * sendMessage: optionally take also the message in sender's language when present, it is stored in the sender's inbox instead of the version in the target language. * transferGems: prepare pm in both languages #7722 * sendMessage: take an object for the second parameter instead * payments: made two more gift strings translatable * buyGems: send both translations for gifted gems * buyGems: send push notifications in target user's locale * createSubscription: send both translations for gifted subs * createSubscription: send push notifications in target user's locale * transferGems: send push notifications in target user's locale * tests: adjust payment tests for translation changes * added function doc for sendMessage * tests: added bilingual test for buyGems --- .../members/POST-transfer_gems.test.js | 62 ++++++++++++++----- test/api/v3/unit/libs/payments.test.js | 29 ++++++++- test/helpers/translate.js | 4 +- website/common/locales/en/settings.json | 2 + website/server/controllers/api-v3/members.js | 35 +++++++---- website/server/libs/payments.js | 61 ++++++++++++++---- website/server/models/user/methods.js | 19 ++++-- 7 files changed, 165 insertions(+), 47 deletions(-) diff --git a/test/api/v3/integration/members/POST-transfer_gems.test.js b/test/api/v3/integration/members/POST-transfer_gems.test.js index c15467c539..465bcad0b3 100644 --- a/test/api/v3/integration/members/POST-transfer_gems.test.js +++ b/test/api/v3/integration/members/POST-transfer_gems.test.js @@ -4,6 +4,14 @@ import { } from '../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; +function findMessage (messages, receiverId) { + let message = _.find(messages, (inboxMessage) => { + return inboxMessage.uuid === receiverId; + }); + + return message; +} + describe('POST /members/transfer-gems', () => { let userToSendMessage; let receiver; @@ -116,13 +124,8 @@ describe('POST /members/transfer-gems', () => { let updatedReceiver = await receiver.get('/user'); let updatedSender = await userToSendMessage.get('/user'); - let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => { - return inboxMessage.uuid === userToSendMessage._id; - }); - - let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => { - return inboxMessage.uuid === receiver._id; - }); + let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id); + let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id); let messageSentContent = t('privateMessageGiftIntro', { receiverName: receiver.profile.name, @@ -150,13 +153,8 @@ describe('POST /members/transfer-gems', () => { let updatedReceiver = await receiver.get('/user'); let updatedSender = await userToSendMessage.get('/user'); - let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => { - return inboxMessage.uuid === userToSendMessage._id; - }); - - let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => { - return inboxMessage.uuid === receiver._id; - }); + let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id); + let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id); let messageSentContent = t('privateMessageGiftIntro', { receiverName: receiver.profile.name, @@ -173,4 +171,40 @@ describe('POST /members/transfer-gems', () => { expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent); expect(updatedSender.balance).to.equal(0); }); + + it('sends transfer gems message in each participant\'s language', async () => { + await receiver.update({ + 'preferences.language': 'es', + }); + await userToSendMessage.update({ + 'preferences.language': 'cs', + }); + await userToSendMessage.post('/members/transfer-gems', { + gemAmount, + toUserId: receiver._id, + }); + + let updatedReceiver = await receiver.get('/user'); + let updatedSender = await userToSendMessage.get('/user'); + + let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id); + let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id); + + let [receieversMessageContent, sendersMessageContent] = ['es', 'cs'].map((lang) => { + let messageContent = t('privateMessageGiftIntro', { + receiverName: receiver.profile.name, + senderName: userToSendMessage.profile.name, + }, lang); + messageContent += t('privateMessageGiftGemsMessage', {gemAmount}, lang); + + return `\`${messageContent}\` `; + }); + + expect(sendersMessageInReceiversInbox).to.exist; + expect(sendersMessageInReceiversInbox.text).to.equal(receieversMessageContent); + + expect(sendersMessageInSendersInbox).to.exist; + expect(sendersMessageInSendersInbox.text).to.equal(sendersMessageContent); + expect(updatedSender.balance).to.equal(0); + }); }); diff --git a/test/api/v3/unit/libs/payments.test.js b/test/api/v3/unit/libs/payments.test.js index fcb6cd82dd..43f9db0885 100644 --- a/test/api/v3/unit/libs/payments.test.js +++ b/test/api/v3/unit/libs/payments.test.js @@ -5,6 +5,7 @@ import notifications from '../../../../../website/server/libs/pushNotifications' import { model as User } from '../../../../../website/server/models/user'; import { model as Group } from '../../../../../website/server/models/group'; import moment from 'moment'; +import { translate as t } from '../../../../helpers/api-v3-integration.helper'; import { generateGroup, } from '../../../../helpers/api-unit.helper.js'; @@ -181,9 +182,10 @@ describe('payments/index', () => { it('sends a private message about the gift', async () => { await api.createSubscription(data); + let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`'; expect(user.sendMessage).to.be.calledOnce; - expect(user.sendMessage).to.be.calledWith(recipient, '\`Hello recipient, sender has sent you 3 months of subscription!\`'); + expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg }); }); it('sends an email about the gift', async () => { @@ -697,14 +699,37 @@ describe('payments/index', () => { it('sends a message from purchaser to recipient', async () => { await api.buyGems(data); + let msg = '\`Hello recipient, sender has sent you 4 gems!\`'; - expect(user.sendMessage).to.be.calledWith(recipient, '\`Hello recipient, sender has sent you 4 gems!\`'); + expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg }); }); it('sends a push notification if user did not gift to self', async () => { await api.buyGems(data); expect(notifications.sendNotification).to.be.calledOnce; }); + + it('sends gem donation message in each participant\'s language', async () => { + await recipient.update({ + 'preferences.language': 'es', + }); + await user.update({ + 'preferences.language': 'cs', + }); + await api.buyGems(data); + + let [recipientsMessageContent, sendersMessageContent] = ['es', 'cs'].map((lang) => { + let messageContent = t('giftedGemsFull', { + username: recipient.profile.name, + sender: user.profile.name, + gemAmount: data.gift.gems.amount, + }, lang); + + return `\`${messageContent}\``; + }); + + expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent }); + }); }); }); }); diff --git a/test/helpers/translate.js b/test/helpers/translate.js index 81d8aaccba..e146c27a71 100644 --- a/test/helpers/translate.js +++ b/test/helpers/translate.js @@ -7,8 +7,8 @@ const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/; // Use this to verify error messages returned by the server // That way, if the translated string changes, the test // will not break. NOTE: it checks against errors with string as well. -export function translate (key, variables) { - let translatedString = i18n.t(key, variables); +export function translate (key, variables, language) { + let translatedString = i18n.t(key, variables, language); expect(translatedString).to.not.be.empty; expect(translatedString).to.not.eql(STRING_ERROR_MSG); diff --git a/website/common/locales/en/settings.json b/website/common/locales/en/settings.json index ae282e9254..af522dadd4 100644 --- a/website/common/locales/en/settings.json +++ b/website/common/locales/en/settings.json @@ -113,8 +113,10 @@ "sentGems": "Sent gems!", "giftedGems": "Gifted Gems", "giftedGemsInfo": "<%= name %> gifted you <%= amount %> Gems", + "giftedGemsFull": "Hello <%= username %>, <%= sender %> has sent you <%= gemAmount %> gems!", "giftedSubscription": "Gifted Subscription", "giftedSubscriptionInfo": "<%= name %> gifted you a <%= months %> month subscription", + "giftedSubscriptionFull": "Hello <%= username %>, <%= sender %> has sent you <%= monthCount %> months of subscription!", "invitedParty": "Invited To Party", "invitedGuild": "Invited To Guild", "importantAnnouncements": "Your account is inactive", diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index 40208a8f9f..3cb628355b 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -422,7 +422,7 @@ api.sendPrivateMessage = { throw new NotAuthorized(res.t('notAuthorizedToSendMessageToThisUser')); } - await sender.sendMessage(receiver, message); + await sender.sendMessage(receiver, { receiverMsg: message }); if (receiver.preferences.emailNotifications.newPM !== false) { sendTxnEmail(receiver, 'new-pm', [ @@ -492,18 +492,27 @@ api.transferGems = { let promises = [receiver.save(), sender.save()]; await Bluebird.all(promises); - let message = res.t('privateMessageGiftIntro', { - receiverName: receiver.profile.name, - senderName: sender.profile.name, + // generate the message in both languages, so both users can understand it + let receiverLang = receiver.preferences.language; + let senderLang = sender.preferences.language; + let [receiverMsg, senderMsg] = [receiverLang, senderLang].map((lang) => { + let messageContent = res.t('privateMessageGiftIntro', { + receiverName: receiver.profile.name, + senderName: sender.profile.name, + }, lang); + messageContent += res.t('privateMessageGiftGemsMessage', {gemAmount}, lang); + messageContent = `\`${messageContent}\` `; + + if (req.body.message) { + messageContent += req.body.message; + } + return messageContent; }); - message += res.t('privateMessageGiftGemsMessage', {gemAmount}); - message = `\`${message}\` `; - if (req.body.message) { - message += req.body.message; - } - - await sender.sendMessage(receiver, message); + await sender.sendMessage(receiver, { + senderMsg, + receiverMsg, + }); let byUsername = getUserInfo(sender, ['name']).name; @@ -516,8 +525,8 @@ api.transferGems = { if (receiver.preferences.pushNotifications.giftedGems !== false) { sendPushNotification(receiver, { - title: res.t('giftedGems'), - message: res.t('giftedGemsInfo', {amount: gemAmount, name: byUsername}), + title: res.t('giftedGems', receiverLang), + message: res.t('giftedGemsInfo', {amount: gemAmount, name: byUsername}, receiverLang), identifier: 'giftedGems', payload: {replyTo: sender._id}, }); diff --git a/website/server/libs/payments.js b/website/server/libs/payments.js index 65cfb960a4..c9193aeabb 100644 --- a/website/server/libs/payments.js +++ b/website/server/libs/payments.js @@ -138,13 +138,31 @@ api.createSubscription = async function createSubscription (data) { if (!group) data.user.purchased.txnCount++; if (data.gift) { - let message = `\`Hello ${data.gift.member.profile.name}, ${data.user.profile.name} has sent you ${shared.content.subscriptionBlocks[data.gift.subscription.key].months} months of subscription!\``; - if (data.gift.message) message += ` ${data.gift.message}`; - - data.user.sendMessage(data.gift.member, message); - let byUserName = getUserInfo(data.user, ['name']).name; + // generate the message in both languages, so both users can understand it + let languages = [data.user.preferences.language, data.gift.member.preferences.language]; + let senderMsg = shared.i18n.t('giftedSubscriptionFull', { + username: data.gift.member.profile.name, + sender: byUserName, + monthCount: shared.content.subscriptionBlocks[data.gift.subscription.key].months, + }, languages[0]); + senderMsg = `\`${senderMsg}\``; + + let receiverMsg = shared.i18n.t('giftedSubscriptionFull', { + username: data.gift.member.profile.name, + sender: byUserName, + monthCount: shared.content.subscriptionBlocks[data.gift.subscription.key].months, + }, languages[1]); + receiverMsg = `\`${receiverMsg}\``; + + if (data.gift.message) { + receiverMsg += ` ${data.gift.message}`; + senderMsg += ` ${data.gift.message}`; + } + + data.user.sendMessage(data.gift.member, { receiverMsg, senderMsg }); + if (data.gift.member.preferences.emailNotifications.giftedSubscription !== false) { txnEmail(data.gift.member, 'gifted-subscription', [ {name: 'GIFTER', content: byUserName}, @@ -156,8 +174,8 @@ api.createSubscription = async function createSubscription (data) { if (data.gift.member.preferences.pushNotifications.giftedSubscription !== false) { sendPushNotification(data.gift.member, { - title: shared.i18n.t('giftedSubscription'), - message: shared.i18n.t('giftedSubscriptionInfo', {months, name: byUserName}), + title: shared.i18n.t('giftedSubscription', languages[1]), + message: shared.i18n.t('giftedSubscriptionInfo', {months, name: byUserName}, languages[1]), identifier: 'giftedSubscription', payload: {replyTo: data.user._id}, } @@ -276,9 +294,28 @@ api.buyGems = async function buyGems (data) { let byUsername = getUserInfo(data.user, ['name']).name; let gemAmount = data.gift.gems.amount || 20; - let message = `\`Hello ${data.gift.member.profile.name}, ${data.user.profile.name} has sent you ${gemAmount} gems!\``; - if (data.gift.message) message += ` ${data.gift.message}`; - data.user.sendMessage(data.gift.member, message); + // generate the message in both languages, so both users can understand it + let languages = [data.user.preferences.language, data.gift.member.preferences.language]; + let senderMsg = shared.i18n.t('giftedGemsFull', { + username: data.gift.member.profile.name, + sender: byUsername, + gemAmount, + }, languages[0]); + senderMsg = `\`${senderMsg}\``; + + let receiverMsg = shared.i18n.t('giftedGemsFull', { + username: data.gift.member.profile.name, + sender: byUsername, + gemAmount, + }, languages[1]); + receiverMsg = `\`${receiverMsg}\``; + + if (data.gift.message) { + receiverMsg += ` ${data.gift.message}`; + senderMsg += ` ${data.gift.message}`; + } + + data.user.sendMessage(data.gift.member, { receiverMsg, senderMsg }); if (data.gift.member.preferences.emailNotifications.giftedGems !== false) { txnEmail(data.gift.member, 'gifted-gems', [ @@ -292,8 +329,8 @@ api.buyGems = async function buyGems (data) { sendPushNotification( data.gift.member, { - title: shared.i18n.t('giftedGems'), - message: shared.i18n.t('giftedGemsInfo', {amount: gemAmount, name: byUsername}), + title: shared.i18n.t('giftedGems', languages[1]), + message: shared.i18n.t('giftedGemsInfo', {amount: gemAmount, name: byUsername}, languages[1]), identifier: 'giftedGems', } ); diff --git a/website/server/models/user/methods.js b/website/server/models/user/methods.js index 85778fdd43..e3132af324 100644 --- a/website/server/models/user/methods.js +++ b/website/server/models/user/methods.js @@ -20,15 +20,26 @@ schema.methods.getGroups = function getUserGroups () { return userGroups; }; -schema.methods.sendMessage = async function sendMessage (userToReceiveMessage, message) { - let sender = this; - common.refPush(userToReceiveMessage.inbox.messages, chatDefaults(message, sender)); +/** + * Sends a message to a user. Archives a copy in sender's inbox. + * + * @param userToReceiveMessage The receiver + * @param options + * @param options.receiverMsg The message to send to the receiver + * @param options.senderMsg The message to archive instead of receiverMsg + * @return N/A + */ +schema.methods.sendMessage = async function sendMessage (userToReceiveMessage, options) { + let sender = this; + let senderMsg = options.senderMsg || options.receiverMsg; + + common.refPush(userToReceiveMessage.inbox.messages, chatDefaults(options.receiverMsg, sender)); userToReceiveMessage.inbox.newMessages++; userToReceiveMessage._v++; userToReceiveMessage.markModified('inbox.messages'); - common.refPush(sender.inbox.messages, defaults({sent: true}, chatDefaults(message, userToReceiveMessage))); + common.refPush(sender.inbox.messages, defaults({sent: true}, chatDefaults(senderMsg, userToReceiveMessage))); sender.markModified('inbox.messages'); let promises = [userToReceiveMessage.save(), sender.save()];