mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
feat(backend): notify users when a followed user posted (#8313)
This commit is contained in:
parent
bda0de0249
commit
3734e2ef56
420
backend/src/middleware/notifications/followed-users.spec.ts
Normal file
420
backend/src/middleware/notifications/followed-users.spec.ts
Normal file
@ -0,0 +1,420 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import { createGroupMutation } from '@graphql/groups'
|
||||
import CONFIG from '@src/config'
|
||||
import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, firstFollower, secondFollower
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
const createPostMutation = gql`
|
||||
mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) {
|
||||
CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const notificationQuery = gql`
|
||||
query ($read: Boolean) {
|
||||
notifications(read: $read, orderBy: updatedAt_desc) {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
relatedUser {
|
||||
id
|
||||
}
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Group {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const followUserMutation = gql`
|
||||
mutation ($id: ID!) {
|
||||
followUser(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const createServerResult = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode,
|
||||
driver,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
server = createServerResult.server
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
})
|
||||
|
||||
describe('following users notifications', () => {
|
||||
beforeAll(async () => {
|
||||
postAuthor = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'post-author',
|
||||
name: 'Post Author',
|
||||
slug: 'post-author',
|
||||
},
|
||||
{
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
firstFollower = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'first-follower',
|
||||
name: 'First Follower',
|
||||
slug: 'first-follower',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
secondFollower = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'second-follower',
|
||||
name: 'Second Follower',
|
||||
slug: 'second-follower',
|
||||
},
|
||||
{
|
||||
email: 'test3@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
await secondFollower.update({ emailNotificationsFollowingUsers: false })
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await mutate({
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
authenticatedUser = await secondFollower.toJson()
|
||||
await mutate({
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
})
|
||||
|
||||
describe('the followed user writes a post', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: 'This is the content of the post',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the first follower', async () => {
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the second follower', async () => {
|
||||
authenticatedUser = await secondFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('followed user posts in public group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g-1',
|
||||
name: 'A group',
|
||||
description: 'A group to test the follow user notification',
|
||||
groupType: 'public',
|
||||
actionRadius: 'national',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'group-post',
|
||||
title: 'This is the post in the group',
|
||||
content: 'This is the content of the post in the group',
|
||||
groupId: 'g-1',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the first follower although he is no member of the group', async () => {
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'group-post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('followed user posts in closed group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g-2',
|
||||
name: 'A closed group',
|
||||
description: 'A group to test the follow user notification',
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'closed-group-post',
|
||||
title: 'This is the post in the closed group',
|
||||
content: 'This is the content of the post in the closed group',
|
||||
groupId: 'g-2',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the first follower', async () => {
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'group-post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('followed user posts in hidden group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g-3',
|
||||
name: 'A hidden group',
|
||||
description: 'A hidden group to test the follow user notification',
|
||||
groupType: 'hidden',
|
||||
actionRadius: 'national',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'hidden-group-post',
|
||||
title: 'This is the post in the hidden group',
|
||||
content: 'This is the content of the post in the hidden group',
|
||||
groupId: 'g-3',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the first follower', async () => {
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'group-post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -111,6 +111,7 @@ const handleRemoveUserFromGroup = async (resolve, root, args, context, resolveIn
|
||||
}
|
||||
|
||||
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { groupId } = args
|
||||
const idsOfUsers = extractMentionedUsers(args.content)
|
||||
const post = await resolve(root, args, context, resolveInfo)
|
||||
if (post) {
|
||||
@ -119,6 +120,11 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo
|
||||
[notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)],
|
||||
'emailNotificationsMention',
|
||||
)
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyFollowingUsers(post.id, groupId, context)],
|
||||
'emailNotificationsFollowingUsers',
|
||||
)
|
||||
}
|
||||
return post
|
||||
}
|
||||
@ -171,6 +177,45 @@ const postAuthorOfComment = async (commentId, { context }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const notifyFollowingUsers = async (postId, groupId, context) => {
|
||||
const reason = 'followed_user_posted'
|
||||
const cypher = `
|
||||
MATCH (post:Post { id: $postId })<-[:WROTE]-(author:User { id: $userId })<-[:FOLLOWS]-(user:User)
|
||||
OPTIONAL MATCH (post)-[:IN]->(group:Group { id: $groupId })
|
||||
WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public'
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, author, user,
|
||||
post {.*, author: properties(author) } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(user),
|
||||
relatedUser: properties(author)
|
||||
}
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
postId,
|
||||
reason,
|
||||
groupId: groupId || null,
|
||||
userId: context.user.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
const notifyOwnersOfGroup = async (groupId, userId, reason, context) => {
|
||||
const cypher = `
|
||||
MATCH (user:User { id: $userId })
|
||||
|
||||
@ -101,7 +101,12 @@ const validateReview = async (resolve, root, args, context, info) => {
|
||||
}
|
||||
|
||||
export const validateNotifyUsers = async (label, reason) => {
|
||||
const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
|
||||
const reasonsAllowed = [
|
||||
'mentioned_in_post',
|
||||
'mentioned_in_comment',
|
||||
'commented_on_post',
|
||||
'followed_user_posted',
|
||||
]
|
||||
if (!reasonsAllowed.includes(reason)) throw new Error('Notification reason is not allowed!')
|
||||
if (
|
||||
(label === 'Post' && reason !== 'mentioned_in_post') ||
|
||||
|
||||
@ -185,6 +185,10 @@ export default {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
emailNotificationsFollowingUsers: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
locale: {
|
||||
type: 'string',
|
||||
|
||||
@ -675,6 +675,10 @@ describe('emailNotificationSettings', () => {
|
||||
name: 'mention',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'followingUsers',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -765,6 +769,10 @@ describe('emailNotificationSettings', () => {
|
||||
name: 'mention',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'followingUsers',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -383,6 +383,10 @@ export default {
|
||||
name: 'mention',
|
||||
value: parent.emailNotificationsMention ?? true,
|
||||
},
|
||||
{
|
||||
name: 'followingUsers',
|
||||
value: parent.emailNotificationsFollowingUsers ?? true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
enum EmailNotificationSettingsName {
|
||||
commentOnObservedPost
|
||||
mention
|
||||
followingUsers
|
||||
chatMessage
|
||||
groupMemberJoined
|
||||
groupMemberLeft
|
||||
groupMemberRemoved
|
||||
groupMemberRoleChanged
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ enum NotificationReason {
|
||||
user_left_group
|
||||
changed_group_member_role
|
||||
removed_user_from_group
|
||||
followed_user_posted
|
||||
}
|
||||
|
||||
type Query {
|
||||
|
||||
@ -737,7 +737,8 @@
|
||||
"post": "Beitrag oder Gruppe",
|
||||
"reason": {
|
||||
"changed_group_member_role": "Hat Deine Rolle in der Gruppe geändert …",
|
||||
"commented_on_post": "Hat Deinen Beitrag kommentiert …",
|
||||
"commented_on_post": "Hat einen Beitrag den du beobachtest kommentiert …",
|
||||
"followed_user_posted": "Hat einen neuen Betrag geschrieben …",
|
||||
"mentioned_in_comment": "Hat Dich in einem Kommentar erwähnt …",
|
||||
"mentioned_in_post": "Hat Dich in einem Beitrag erwähnt …",
|
||||
"removed_user_from_group": "Hat Dich aus der Gruppe entfernt …",
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Nachricht erhalten während Abwesenheit",
|
||||
"checkAll": "Alle auswählen",
|
||||
"commentOnObservedPost": "Kommentare zu beobachteten Beiträgen",
|
||||
"followingUsers": "Ein Nutzer dem ich folge veröffentlichte einen neuen Beitrag",
|
||||
"group": "Gruppen",
|
||||
"groupMemberJoined": "Ein Mitglied ist deiner Gruppe beigetreten",
|
||||
"groupMemberLeft": "Ein Mitglied hat deine Gruppe verlassen",
|
||||
|
||||
@ -737,7 +737,8 @@
|
||||
"post": "Post or Group",
|
||||
"reason": {
|
||||
"changed_group_member_role": "Changed your role in group …",
|
||||
"commented_on_post": "Commented on your post …",
|
||||
"commented_on_post": "Commented on a post you observe …",
|
||||
"followed_user_posted": "Wrote a new post …",
|
||||
"mentioned_in_comment": "Mentioned you in a comment …",
|
||||
"mentioned_in_post": "Mentioned you in a post …",
|
||||
"removed_user_from_group": "Removed you from group …",
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Message received while absent",
|
||||
"checkAll": "Check all",
|
||||
"commentOnObservedPost": "Comments on observed posts",
|
||||
"followingUsers": "User I follow published a new post",
|
||||
"group": "Groups",
|
||||
"groupMemberJoined": "Member joined a group I own",
|
||||
"groupMemberLeft": "Member left a group I own",
|
||||
|
||||
@ -738,6 +738,7 @@
|
||||
"reason": {
|
||||
"changed_group_member_role": null,
|
||||
"commented_on_post": "Comentó su contribución ...",
|
||||
"followed_user_posted": null,
|
||||
"mentioned_in_comment": "Le mencionó en un comentario …",
|
||||
"mentioned_in_post": "Le mencionó en una contribución …",
|
||||
"removed_user_from_group": null,
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Mensaje recibido mientras estaba ausente",
|
||||
"checkAll": "Seleccionar todo",
|
||||
"commentOnObservedPost": "Comentario en una contribución que estoy observando",
|
||||
"followingUsers": null,
|
||||
"group": "Grupos",
|
||||
"groupMemberJoined": "Un nuevo miembro se unió a un grupo mio",
|
||||
"groupMemberLeft": "Un miembro dejó un grupo mio",
|
||||
|
||||
@ -738,6 +738,7 @@
|
||||
"reason": {
|
||||
"changed_group_member_role": null,
|
||||
"commented_on_post": "Commenté sur votre post…",
|
||||
"followed_user_posted": null,
|
||||
"mentioned_in_comment": "Vous a mentionné dans un commentaire…",
|
||||
"mentioned_in_post": "Vous a mentionné dans un post…",
|
||||
"removed_user_from_group": null,
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Message reçu pendant l'absence",
|
||||
"checkAll": "Tout cocher",
|
||||
"commentOnObservedPost": "Commentez une contribution que je suis",
|
||||
"followingUsers": null,
|
||||
"group": "Groups",
|
||||
"groupMemberJoined": "Un nouveau membre a rejoint un de mes groupes",
|
||||
"groupMemberLeft": "Un membre a quitté un de mes groupes",
|
||||
|
||||
@ -738,6 +738,7 @@
|
||||
"reason": {
|
||||
"changed_group_member_role": null,
|
||||
"commented_on_post": null,
|
||||
"followed_user_posted": null,
|
||||
"mentioned_in_comment": null,
|
||||
"mentioned_in_post": null,
|
||||
"removed_user_from_group": null,
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Messaggio ricevuto durante l'assenza",
|
||||
"checkAll": "Seleziona tutto",
|
||||
"commentOnObservedPost": "Commenta un contributo che sto guardando",
|
||||
"followingUsers": null,
|
||||
"group": "Gruppi",
|
||||
"groupMemberJoined": "Un nuovo membro si è unito a un mio gruppo",
|
||||
"groupMemberLeft": "Un membro ha lasciato un mio gruppo",
|
||||
|
||||
@ -738,6 +738,7 @@
|
||||
"reason": {
|
||||
"changed_group_member_role": null,
|
||||
"commented_on_post": null,
|
||||
"followed_user_posted": null,
|
||||
"mentioned_in_comment": null,
|
||||
"mentioned_in_post": null,
|
||||
"removed_user_from_group": null,
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Bericht ontvangen tijdens afwezigheid",
|
||||
"checkAll": "Vink alles aan",
|
||||
"commentOnObservedPost": "Geef commentaar op een bijdrage die ik volg",
|
||||
"followingUsers": null,
|
||||
"group": "Groepen",
|
||||
"groupMemberJoined": "Een nieuw lid is lid geworden van een groep van mij",
|
||||
"groupMemberLeft": "Een lid heeft een groep van mij verlaten",
|
||||
|
||||
@ -738,6 +738,7 @@
|
||||
"reason": {
|
||||
"changed_group_member_role": null,
|
||||
"commented_on_post": null,
|
||||
"followed_user_posted": null,
|
||||
"mentioned_in_comment": null,
|
||||
"mentioned_in_post": null,
|
||||
"removed_user_from_group": null,
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Wiadomość otrzymana podczas nieobecności",
|
||||
"checkAll": "Wybierz wszystko",
|
||||
"commentOnObservedPost": "Skomentuj wpis, który obserwuję",
|
||||
"followingUsers": null,
|
||||
"group": "Grupy",
|
||||
"groupMemberJoined": "Nowy członek dołączył do mojej grupy",
|
||||
"groupMemberLeft": "Członek opuścił moją grupę",
|
||||
|
||||
@ -738,6 +738,7 @@
|
||||
"reason": {
|
||||
"changed_group_member_role": null,
|
||||
"commented_on_post": "Comentou no seu post …",
|
||||
"followed_user_posted": null,
|
||||
"mentioned_in_comment": "Mentionou você em um comentário …",
|
||||
"mentioned_in_post": "Mencinou você em um post …",
|
||||
"removed_user_from_group": null,
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Mensagem recebida durante a ausência",
|
||||
"checkAll": "Marcar tudo",
|
||||
"commentOnObservedPost": "Comentários sobre as mensagens observadas",
|
||||
"followingUsers": null,
|
||||
"group": "Grupos",
|
||||
"groupMemberJoined": "Member joined a group I own",
|
||||
"groupMemberLeft": "Membro saiu de um grupo de que sou proprietário",
|
||||
|
||||
@ -738,6 +738,7 @@
|
||||
"reason": {
|
||||
"changed_group_member_role": null,
|
||||
"commented_on_post": "Комментарий к посту...",
|
||||
"followed_user_posted": null,
|
||||
"mentioned_in_comment": "Упоминание в комментарии....",
|
||||
"mentioned_in_post": "Упоминание в посте....",
|
||||
"removed_user_from_group": null,
|
||||
@ -1043,6 +1044,7 @@
|
||||
"chatMessage": "Сообщение, полученное во время отсутствия",
|
||||
"checkAll": "Отметить все",
|
||||
"commentOnObservedPost": "Комментарии по поводу замеченных сообщений",
|
||||
"followingUsers": null,
|
||||
"group": "Группы",
|
||||
"groupMemberJoined": "Участник присоединился к группе, которой я владею",
|
||||
"groupMemberLeft": "Участник вышел из группы, которой владею",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user