✨ More & localized notifications
This commit is contained in:
parent
39d9d8a839
commit
bb739c1d90
@ -114,7 +114,7 @@ public class NotificationService
|
|||||||
_db.Add(notification);
|
_db.Add(notification);
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
if (!isSilent) _ = DeliveryNotification(notification).ConfigureAwait(false);
|
if (!isSilent) _ = DeliveryNotification(notification);
|
||||||
|
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
|
using DysonNetwork.Sphere.Localization;
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Realm;
|
using DysonNetwork.Sphere.Realm;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Chat;
|
namespace DysonNetwork.Sphere.Chat;
|
||||||
@ -19,7 +21,8 @@ public class ChatRoomController(
|
|||||||
RealmService rs,
|
RealmService rs,
|
||||||
ActionLogService als,
|
ActionLogService als,
|
||||||
NotificationService nty,
|
NotificationService nty,
|
||||||
RelationshipService rels
|
RelationshipService rels,
|
||||||
|
IStringLocalizer<NotificationResource> localizer
|
||||||
) : ControllerBase
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("{id:guid}")]
|
[HttpGet("{id:guid}")]
|
||||||
@ -74,7 +77,7 @@ public class ChatRoomController(
|
|||||||
var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId);
|
var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId);
|
||||||
if (relatedUser is null)
|
if (relatedUser is null)
|
||||||
return BadRequest("Related user was not found");
|
return BadRequest("Related user was not found");
|
||||||
|
|
||||||
if (await rels.HasRelationshipWithStatus(currentUser.Id, relatedUser.Id, RelationshipStatus.Blocked))
|
if (await rels.HasRelationshipWithStatus(currentUser.Id, relatedUser.Id, RelationshipStatus.Blocked))
|
||||||
return StatusCode(403, "You cannot create direct message with a user that blocked you.");
|
return StatusCode(403, "You cannot create direct message with a user that blocked you.");
|
||||||
|
|
||||||
@ -121,7 +124,8 @@ public class ChatRoomController(
|
|||||||
);
|
);
|
||||||
|
|
||||||
var invitedMember = dmRoom.Members.First(m => m.AccountId == request.RelatedUserId);
|
var invitedMember = dmRoom.Members.First(m => m.AccountId == request.RelatedUserId);
|
||||||
await _SendInviteNotify(invitedMember);
|
invitedMember.ChatRoom = dmRoom;
|
||||||
|
await _SendInviteNotify(invitedMember, currentUser);
|
||||||
|
|
||||||
return Ok(dmRoom);
|
return Ok(dmRoom);
|
||||||
}
|
}
|
||||||
@ -377,7 +381,7 @@ public class ChatRoomController(
|
|||||||
|
|
||||||
var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId);
|
var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId);
|
||||||
if (relatedUser is null) return BadRequest("Related user was not found");
|
if (relatedUser is null) return BadRequest("Related user was not found");
|
||||||
|
|
||||||
if (await rels.HasRelationshipWithStatus(currentUser.Id, relatedUser.Id, RelationshipStatus.Blocked))
|
if (await rels.HasRelationshipWithStatus(currentUser.Id, relatedUser.Id, RelationshipStatus.Blocked))
|
||||||
return StatusCode(403, "You cannot invite a user that blocked you.");
|
return StatusCode(403, "You cannot invite a user that blocked you.");
|
||||||
|
|
||||||
@ -428,7 +432,8 @@ public class ChatRoomController(
|
|||||||
db.ChatMembers.Add(newMember);
|
db.ChatMembers.Add(newMember);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
await _SendInviteNotify(newMember);
|
newMember.ChatRoom = chatRoom;
|
||||||
|
await _SendInviteNotify(newMember, currentUser);
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
als.CreateActionLogFromRequest(
|
||||||
ActionLogType.ChatroomInvite,
|
ActionLogType.ChatroomInvite,
|
||||||
@ -680,9 +685,9 @@ public class ChatRoomController(
|
|||||||
return BadRequest("The last owner cannot leave the chat. Transfer ownership first or delete the chat.");
|
return BadRequest("The last owner cannot leave the chat. Transfer ownership first or delete the chat.");
|
||||||
}
|
}
|
||||||
|
|
||||||
member.LeaveAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow);
|
member.LeaveAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
crs.PurgeRoomMembersCache(roomId);
|
await crs.PurgeRoomMembersCache(roomId);
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
als.CreateActionLogFromRequest(
|
||||||
ActionLogType.ChatroomLeave,
|
ActionLogType.ChatroomLeave,
|
||||||
@ -692,9 +697,14 @@ public class ChatRoomController(
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task _SendInviteNotify(ChatMember member)
|
private async Task _SendInviteNotify(ChatMember member, Account.Account sender)
|
||||||
{
|
{
|
||||||
await nty.SendNotification(member.Account, "invites.chats", "New Chat Invitation", null,
|
string title = localizer["ChatInviteTitle"];
|
||||||
$"You just got invited to join {member.ChatRoom.Name}");
|
|
||||||
|
string body = member.ChatRoom.Type == ChatRoomType.DirectMessage
|
||||||
|
? localizer["ChatInviteDirectBody", sender.Nick]
|
||||||
|
: localizer["ChatInviteBody", member.ChatRoom.Name ?? "Unnamed"];
|
||||||
|
|
||||||
|
await nty.SendNotification(member.Account, "invites.chats", title, null, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -257,6 +257,7 @@ public class PostController(
|
|||||||
var post = await db.Posts
|
var post = await db.Posts
|
||||||
.Where(e => e.Id == id)
|
.Where(e => e.Id == id)
|
||||||
.Include(e => e.Publisher)
|
.Include(e => e.Publisher)
|
||||||
|
.ThenInclude(e => e.Account)
|
||||||
.FilterWithVisibility(currentUser, userFriends)
|
.FilterWithVisibility(currentUser, userFriends)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
@ -274,7 +275,14 @@ public class PostController(
|
|||||||
PostId = post.Id,
|
PostId = post.Id,
|
||||||
AccountId = currentUser.Id
|
AccountId = currentUser.Id
|
||||||
};
|
};
|
||||||
var isRemoving = await ps.ModifyPostVotes(post, reaction, isExistingReaction, isSelfReact);
|
var isRemoving = await ps.ModifyPostVotes(
|
||||||
|
post,
|
||||||
|
reaction,
|
||||||
|
currentUser,
|
||||||
|
post.Publisher.Account,
|
||||||
|
isExistingReaction,
|
||||||
|
isSelfReact
|
||||||
|
);
|
||||||
|
|
||||||
if (isRemoving) return NoContent();
|
if (isRemoving) return NoContent();
|
||||||
|
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using DysonNetwork.Sphere.Account;
|
||||||
using DysonNetwork.Sphere.Activity;
|
using DysonNetwork.Sphere.Activity;
|
||||||
|
using DysonNetwork.Sphere.Localization;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Post;
|
namespace DysonNetwork.Sphere.Post;
|
||||||
|
|
||||||
public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
public class PostService(
|
||||||
|
AppDatabase db,
|
||||||
|
FileService fs,
|
||||||
|
ActivityService act,
|
||||||
|
IStringLocalizer<NotificationResource> localizer,
|
||||||
|
NotificationService nty
|
||||||
|
)
|
||||||
{
|
{
|
||||||
public static List<Post> TruncatePostContent(List<Post> input)
|
public static List<Post> TruncatePostContent(List<Post> input)
|
||||||
{
|
{
|
||||||
@ -158,8 +167,18 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="post">Post that modifying</param>
|
/// <param name="post">Post that modifying</param>
|
||||||
/// <param name="reaction">The new / target reaction adding / removing</param>
|
/// <param name="reaction">The new / target reaction adding / removing</param>
|
||||||
|
/// <param name="op">The original poster account of this post</param>
|
||||||
/// <param name="isRemoving">Indicate this operation is adding / removing</param>
|
/// <param name="isRemoving">Indicate this operation is adding / removing</param>
|
||||||
public async Task<bool> ModifyPostVotes(Post post, PostReaction reaction, bool isRemoving, bool isSelfReact)
|
/// <param name="isSelfReact">Indicate this reaction is by the original post himself</param>
|
||||||
|
/// <param name="sender">The account that creates this reaction</param>
|
||||||
|
public async Task<bool> ModifyPostVotes(
|
||||||
|
Post post,
|
||||||
|
PostReaction reaction,
|
||||||
|
Account.Account sender,
|
||||||
|
Account.Account? op,
|
||||||
|
bool isRemoving,
|
||||||
|
bool isSelfReact
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var isExistingReaction = await db.Set<PostReaction>()
|
var isExistingReaction = await db.Set<PostReaction>()
|
||||||
.AnyAsync(r => r.PostId == post.Id && r.AccountId == reaction.AccountId);
|
.AnyAsync(r => r.PostId == post.Id && r.AccountId == reaction.AccountId);
|
||||||
@ -197,6 +216,21 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
|||||||
}
|
}
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!isSelfReact && op is not null)
|
||||||
|
{
|
||||||
|
await nty.SendNotification(
|
||||||
|
op,
|
||||||
|
"posts.reactions.new",
|
||||||
|
localizer["PostReactTitle", sender.Nick],
|
||||||
|
null,
|
||||||
|
string.IsNullOrWhiteSpace(post.Title)
|
||||||
|
? localizer["PostReactBody", sender.Nick, reaction.Symbol]
|
||||||
|
: localizer["PostReactContentBody", sender.Nick, reaction.Symbol,
|
||||||
|
post.Title]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return isRemoving;
|
return isRemoving;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Publisher;
|
namespace DysonNetwork.Sphere.Publisher;
|
||||||
|
|
||||||
public class PublisherSubscriptionService(AppDatabase db, NotificationService nty)
|
public class PublisherSubscriptionService(
|
||||||
|
AppDatabase db,
|
||||||
|
NotificationService nty,
|
||||||
|
IStringLocalizer<Notification> localizer)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a subscription exists between the account and publisher
|
/// Checks if a subscription exists between the account and publisher
|
||||||
@ -48,12 +52,11 @@ public class PublisherSubscriptionService(AppDatabase db, NotificationService nt
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Create notification data
|
// Create notification data
|
||||||
var title = $"@{post.Publisher.Name} Posted";
|
var message = !string.IsNullOrEmpty(post.Description)
|
||||||
var message = !string.IsNullOrEmpty(post.Title)
|
? post.Description?.Length > 40 ? post.Description[..37] + "..." : post.Description
|
||||||
? post.Title
|
: post.Content?.Length > 100
|
||||||
: (post.Content?.Length > 100
|
|
||||||
? string.Concat(post.Content.AsSpan(0, 97), "...")
|
? string.Concat(post.Content.AsSpan(0, 97), "...")
|
||||||
: post.Content);
|
: post.Content;
|
||||||
|
|
||||||
// Data to include with the notification
|
// Data to include with the notification
|
||||||
var data = new Dictionary<string, object>
|
var data = new Dictionary<string, object>
|
||||||
@ -71,8 +74,8 @@ public class PublisherSubscriptionService(AppDatabase db, NotificationService nt
|
|||||||
await nty.SendNotification(
|
await nty.SendNotification(
|
||||||
subscription.Account,
|
subscription.Account,
|
||||||
"posts.new",
|
"posts.new",
|
||||||
title,
|
localizer["New post from {0}", post.Publisher.Name],
|
||||||
post.Description?.Length > 40 ? post.Description[..37] + "..." : post.Description,
|
string.IsNullOrWhiteSpace(post.Title) ? null : post.Title,
|
||||||
message,
|
message,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@ -169,9 +172,7 @@ public class PublisherSubscriptionService(AppDatabase db, NotificationService nt
|
|||||||
{
|
{
|
||||||
var subscription = await GetSubscriptionAsync(accountId, publisherId);
|
var subscription = await GetSubscriptionAsync(accountId, publisherId);
|
||||||
if (subscription is not { Status: SubscriptionStatus.Active })
|
if (subscription is not { Status: SubscriptionStatus.Active })
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
subscription.Status = SubscriptionStatus.Cancelled;
|
subscription.Status = SubscriptionStatus.Cancelled;
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
@ -111,6 +111,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Re
|
|||||||
);
|
);
|
||||||
|
|
||||||
member.Account = relatedUser;
|
member.Account = relatedUser;
|
||||||
|
member.Realm = realm;
|
||||||
await rs.SendInviteNotify(member);
|
await rs.SendInviteNotify(member);
|
||||||
|
|
||||||
return Ok(member);
|
return Ok(member);
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
|
using DysonNetwork.Sphere.Localization;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Realm;
|
namespace DysonNetwork.Sphere.Realm;
|
||||||
|
|
||||||
public class RealmService(AppDatabase db, NotificationService nty)
|
public class RealmService(AppDatabase db, NotificationService nty, IStringLocalizer<NotificationResource> localizer)
|
||||||
{
|
{
|
||||||
public async Task SendInviteNotify(RealmMember member)
|
public async Task SendInviteNotify(RealmMember member)
|
||||||
{
|
{
|
||||||
await nty.SendNotification(member.Account, "invites.realms", "New Realm Invitation", null,
|
await nty.SendNotification(
|
||||||
$"You just got invited to join {member.Realm.Name}");
|
member.Account,
|
||||||
|
"invites.realms",
|
||||||
|
localizer["RealmInviteTitle"],
|
||||||
|
null,
|
||||||
|
localizer["RealmInviteBody", member.Realm.Name]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> IsMemberWithRole(Guid realmId, Guid accountId, params RealmMemberRole[] requiredRoles)
|
public async Task<bool> IsMemberWithRole(Guid realmId, Guid accountId, params RealmMemberRole[] requiredRoles)
|
||||||
{
|
{
|
||||||
if (requiredRoles.Length == 0)
|
if (requiredRoles.Length == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var maxRequiredRole = requiredRoles.Max();
|
var maxRequiredRole = requiredRoles.Max();
|
||||||
var member = await db.RealmMembers
|
var member = await db.RealmMembers
|
||||||
.FirstOrDefaultAsync(m => m.RealmId == realmId && m.AccountId == accountId);
|
.FirstOrDefaultAsync(m => m.RealmId == realmId && m.AccountId == accountId);
|
||||||
|
@ -44,5 +44,59 @@ namespace DysonNetwork.Sphere.Resources.Localization {
|
|||||||
resourceCulture = value;
|
resourceCulture = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string ChatInviteTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ChatInviteTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string ChatInviteBody {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ChatInviteBody", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string ChatInviteDirectBody {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ChatInviteDirectBody", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string RealmInviteTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RealmInviteTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string RealmInviteBody {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RealmInviteBody", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string PostSubscriptionTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PostSubscriptionTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string PostReactTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PostReactTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string PostReactBody {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PostReactBody", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string PostReactContentBody {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PostReactContentBody", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,4 +18,31 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<data name="ChatInviteTitle" xml:space="preserve">
|
||||||
|
<value>New Chat Invitation</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatInviteBody" xml:space="preserve">
|
||||||
|
<value>You just got invited to join {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatInviteDirectBody" xml:space="preserve">
|
||||||
|
<value>{0} sent an direct message invitation to you</value>
|
||||||
|
</data>
|
||||||
|
<data name="RealmInviteTitle" xml:space="preserve">
|
||||||
|
<value>New Realm Invitation</value>
|
||||||
|
</data>
|
||||||
|
<data name="RealmInviteBody" xml:space="preserve">
|
||||||
|
<value>You just got invited to join {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostSubscriptionTitle" xml:space="preserve">
|
||||||
|
<value>{0} just posted</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostReactTitle" xml:space="preserve">
|
||||||
|
<value>{0} reacted your post</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostReactBody" xml:space="preserve">
|
||||||
|
<value>{0} added a reaction {1} to your post</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostReactContentBody" xml:space="preserve">
|
||||||
|
<value>{0} added a reaction {1} to your post {2}</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -11,4 +11,31 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<data name="ChatInviteTitle" xml:space="preserve">
|
||||||
|
<value>新聊天邀请</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatInviteBody" xml:space="preserve">
|
||||||
|
<value>你刚被邀请加入聊天 {}</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatInviteDirectBody" xml:space="preserve">
|
||||||
|
<value>{0} 向你发送了一个私聊邀请</value>
|
||||||
|
</data>
|
||||||
|
<data name="RealmInviteTitle" xml:space="preserve">
|
||||||
|
<value>新加入领域邀请</value>
|
||||||
|
</data>
|
||||||
|
<data name="RealmInviteBody" xml:space="preserve">
|
||||||
|
<value>你刚被邀请加入领域 {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostSubscriptionTitle" xml:space="preserve">
|
||||||
|
<value>{0} 有新帖子</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostReactTitle" xml:space="preserve">
|
||||||
|
<value>{0} 反应了你的帖子</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostReactBody" xml:space="preserve">
|
||||||
|
<value>{0} 给你的帖子添加了一个 {1} 的反应</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostReactContentBody" xml:space="preserve">
|
||||||
|
<value>{0} 给你的帖子添加了一个 {1} 的反应 {2}</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -101,7 +101,7 @@
|
|||||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FEmail_002ELandingResource/@EntryIndexedValue">False</s:Boolean>
|
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FEmail_002ELandingResource/@EntryIndexedValue">False</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FEmails_002FEmail_002ELandingResource/@EntryIndexRemoved">True</s:Boolean>
|
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FEmails_002FEmail_002ELandingResource/@EntryIndexRemoved">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FEmail_002ELandingResource/@EntryIndexRemoved">True</s:Boolean>
|
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FEmail_002ELandingResource/@EntryIndexRemoved">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FEmailResource/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FEmailResource/@EntryIndexedValue">False</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FNotificationResource/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FResources_002FLocalization_002FNotificationResource/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user