diff --git a/DysonNetwork.Pass/Account/AccountEventService.cs b/DysonNetwork.Pass/Account/AccountEventService.cs index 5c07cff7..d23c49ce 100644 --- a/DysonNetwork.Pass/Account/AccountEventService.cs +++ b/DysonNetwork.Pass/Account/AccountEventService.cs @@ -5,7 +5,7 @@ using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Queue; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; using NATS.Client.Core; using NodaTime; using NodaTime.Extensions; @@ -15,7 +15,7 @@ namespace DysonNetwork.Pass.Account; public class AccountEventService( AppDatabase db, ICacheService cache, - IStringLocalizer localizer, + ILocalizationService localizer, RingService.RingServiceClient pusher, Pass.Leveling.ExperienceService experienceService, RemotePaymentService payment, @@ -359,8 +359,8 @@ public class AccountEventService( new CheckInFortuneTip { IsPositive = true, - Title = localizer["FortuneTipSpecialTitle_Birthday"].Value, - Content = localizer["FortuneTipSpecialContent_Birthday", user.Nick].Value, + Title = localizer.Get("fortuneTipSpecialTitleBirthday"), + Content = localizer.Get("fortuneTipSpecialContentBirthday", args: new { user.Nick }), } ]; } @@ -374,8 +374,8 @@ public class AccountEventService( tips = positiveIndices.Select(index => new CheckInFortuneTip { IsPositive = true, - Title = localizer[$"FortuneTipPositiveTitle_{index}"].Value, - Content = localizer[$"FortuneTipPositiveContent_{index}"].Value + Title = localizer.Get($"fortuneTipPositiveTitle{index}"), + Content = localizer.Get($"fortuneTipPositiveContent{index}") }).ToList(); // Generate 2 negative tips @@ -387,8 +387,8 @@ public class AccountEventService( tips.AddRange(negativeIndices.Select(index => new CheckInFortuneTip { IsPositive = false, - Title = localizer[$"FortuneTipNegativeTitle_{index}"].Value, - Content = localizer[$"FortuneTipNegativeContent_{index}"].Value + Title = localizer.Get($"fortuneTipNegativeTitle{index}"), + Content = localizer.Get($"fortuneTipNegativeContent{index}") })); // The 5 is specialized, keep it alone. diff --git a/DysonNetwork.Pass/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs index 1d8c2d0b..bd6371c4 100644 --- a/DysonNetwork.Pass/Account/AccountService.cs +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -6,11 +6,11 @@ using DysonNetwork.Pass.Mailer; using DysonNetwork.Pass.Resources.Emails; using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Localization; using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Queue; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; using NATS.Client.Core; using NATS.Net; using NodaTime; @@ -28,8 +28,7 @@ public class AccountService( AffiliationSpellService ars, EmailService mailer, RingService.RingServiceClient pusher, - IStringLocalizer localizer, - IStringLocalizer emailLocalizer, + ILocalizationService localizer, ICacheService cache, ILogger logger, RemoteSubscriptionService remoteSubscription, @@ -434,8 +433,8 @@ public class AccountService( Notification = new PushNotification { Topic = "auth.verification", - Title = localizer["AuthCodeTitle"], - Body = localizer["AuthCodeBody", code], + Title = localizer.Get("authCodeTitle"), + Body = localizer.Get("authCodeBody", args: new { code }), IsSavable = false } } @@ -466,7 +465,7 @@ public class AccountService( .SendTemplatedEmailAsync( account.Nick, contact.Content, - emailLocalizer["CodeEmailTitle"], + localizer.Get("codeEmailTitle"), new VerificationEmailModel { Name = account.Name, diff --git a/DysonNetwork.Pass/Account/MagicSpellService.cs b/DysonNetwork.Pass/Account/MagicSpellService.cs index ea812bab..c1b8b9de 100644 --- a/DysonNetwork.Pass/Account/MagicSpellService.cs +++ b/DysonNetwork.Pass/Account/MagicSpellService.cs @@ -5,7 +5,7 @@ using DysonNetwork.Pass.Resources.Emails; using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; using NodaTime; using EmailResource = DysonNetwork.Pass.Localization.EmailResource; @@ -15,7 +15,7 @@ public class MagicSpellService( AppDatabase db, IConfiguration configuration, ILogger logger, - IStringLocalizer localizer, + ILocalizationService localizer, EmailService email, ICacheService cache ) @@ -98,7 +98,7 @@ public class MagicSpellService( await email.SendTemplatedEmailAsync( contact.Account.Nick, contact.Content, - localizer["RegConfirmTitle"], + localizer.Get("regConfirmTitle"), new LandingEmailModel { Name = contact.Account.Name, @@ -110,7 +110,7 @@ public class MagicSpellService( await email.SendTemplatedEmailAsync( contact.Account.Nick, contact.Content, - localizer["AccountDeletionTitle"], + localizer.Get("accountDeletionTitle"), new AccountDeletionEmailModel { Name = contact.Account.Name, @@ -122,7 +122,7 @@ public class MagicSpellService( await email.SendTemplatedEmailAsync( contact.Account.Nick, contact.Content, - localizer["PasswordResetTitle"], + localizer.Get("passwordResetTitle"), new PasswordResetEmailModel { Name = contact.Account.Name, @@ -136,7 +136,7 @@ public class MagicSpellService( await email.SendTemplatedEmailAsync( contact.Account.Nick, contactMethod!, - localizer["ContractVerificationTitle"], + localizer.Get("contractVerificationTitle"), new ContactVerificationEmailModel { Name = contact.Account.Name, diff --git a/DysonNetwork.Pass/Account/RelationshipService.cs b/DysonNetwork.Pass/Account/RelationshipService.cs index 1833d22f..d1869801 100644 --- a/DysonNetwork.Pass/Account/RelationshipService.cs +++ b/DysonNetwork.Pass/Account/RelationshipService.cs @@ -3,7 +3,7 @@ using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; using NodaTime; namespace DysonNetwork.Pass.Account; @@ -12,7 +12,7 @@ public class RelationshipService( AppDatabase db, ICacheService cache, RingService.RingServiceClient pusher, - IStringLocalizer localizer + ILocalizationService localizer ) { private const string UserFriendsCacheKeyPrefix = "accounts:friends:"; @@ -117,8 +117,8 @@ public class RelationshipService( Notification = new PushNotification { Topic = "relationships.friends.request", - Title = localizer["FriendRequestTitle", sender.Nick], - Body = localizer["FriendRequestBody"], + Title = localizer.Get("friendRequestTitle", args: new { sender.Nick }), + Body = localizer.Get("friendRequestBody"), ActionUri = "/account/relationships", IsSavable = true } diff --git a/DysonNetwork.Pass/Auth/AuthController.cs b/DysonNetwork.Pass/Auth/AuthController.cs index a0cb2b72..c3874bff 100644 --- a/DysonNetwork.Pass/Auth/AuthController.cs +++ b/DysonNetwork.Pass/Auth/AuthController.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore; using DysonNetwork.Pass.Localization; using DysonNetwork.Shared.Geometry; using DysonNetwork.Shared.Proto; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; using AccountService = DysonNetwork.Pass.Account.AccountService; using ActionLogService = DysonNetwork.Pass.Account.ActionLogService; using DysonNetwork.Shared.Models; @@ -22,7 +22,7 @@ public class AuthController( ActionLogService als, RingService.RingServiceClient pusher, IConfiguration configuration, - IStringLocalizer localizer, + ILocalizationService localizer, ILogger logger ) : ControllerBase { @@ -237,9 +237,8 @@ public class AuthController( Notification = new PushNotification { Topic = "auth.login", - Title = localizer["NewLoginTitle"], - Body = localizer["NewLoginBody", challenge.DeviceName ?? "unknown", - challenge.IpAddress ?? "unknown"], + Title = localizer.Get("newLoginTitle"), + Body = localizer.Get("newLoginBody", args: new { deviceName = challenge.DeviceName ?? "unknown", ipAddress = challenge.IpAddress ?? "unknown" }), IsSavable = true }, UserId = challenge.AccountId.ToString() diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index 50830d64..24643860 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -134,4 +134,8 @@ + + + + diff --git a/DysonNetwork.Pass/Realm/RealmService.cs b/DysonNetwork.Pass/Realm/RealmService.cs index 73f83402..44fdfbb0 100644 --- a/DysonNetwork.Pass/Realm/RealmService.cs +++ b/DysonNetwork.Pass/Realm/RealmService.cs @@ -5,14 +5,14 @@ using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; namespace DysonNetwork.Pass.Realm; public class RealmService( AppDatabase db, RingService.RingServiceClient pusher, - IStringLocalizer localizer, + ILocalizationService localizer, ICacheService cache ) { @@ -55,8 +55,8 @@ public class RealmService( Notification = new PushNotification { Topic = "invites.realms", - Title = localizer["RealmInviteTitle"], - Body = localizer["RealmInviteBody", member.Realm.Name], + Title = localizer.Get("realmInviteTitle"), + Body = localizer.Get("realmInviteBody", args: new { realmName = member.Realm.Name }), ActionUri = "/realms", IsSavable = true } diff --git a/DysonNetwork.Pass/Realm/RealmServiceGrpc.cs b/DysonNetwork.Pass/Realm/RealmServiceGrpc.cs index ab3dcc47..aba6e242 100644 --- a/DysonNetwork.Pass/Realm/RealmServiceGrpc.cs +++ b/DysonNetwork.Pass/Realm/RealmServiceGrpc.cs @@ -5,14 +5,14 @@ using Microsoft.EntityFrameworkCore; using DysonNetwork.Pass.Localization; using DysonNetwork.Shared; using DysonNetwork.Shared.Cache; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; namespace DysonNetwork.Pass.Realm; public class RealmServiceGrpc( AppDatabase db, RingService.RingServiceClient pusher, - IStringLocalizer localizer, + ILocalizationService localizer, ICacheService cache ) : Shared.Proto.RealmService.RealmServiceBase @@ -127,8 +127,8 @@ public class RealmServiceGrpc( Notification = new PushNotification { Topic = "invites.realms", - Title = localizer["RealmInviteTitle"], - Body = localizer["RealmInviteBody", member.Realm?.Name ?? "Unknown Realm"], + Title = localizer.Get("realmInviteTitle"), + Body = localizer.Get("realmInviteBody", args: new { realmName = member.Realm?.Name ?? "Unknown Realm" }), ActionUri = "/realms", IsSavable = true } diff --git a/DysonNetwork.Pass/Resources/Locales/en.json b/DysonNetwork.Pass/Resources/Locales/en.json new file mode 100644 index 00000000..6a660e01 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Locales/en.json @@ -0,0 +1,22 @@ +{ + "welcomeMessage": "Hello, {name}!", + "goodbyeMessage": "Goodbye, {name}!", + "itemCount": { + "one": "{count} item", + "other": "{count} items" + }, + "userCount": { + "one": "There is {count} user online", + "other": "There are {count} users online" + }, + "notificationMessage": "You have {count} new message(s) from {sender}", + "profileUpdated": "Your profile has been updated successfully", + "errorOccurred": "An error occurred: {error}", + "loginSuccess": "Welcome back, {username}!", + "loginFailed": "Login failed. Please try again.", + "accountCreated": "Your account has been created successfully", + "emailVerification": "Please verify your email address", + "passwordReset": "Password reset link has been sent to your email", + "permissionDenied": "You do not have permission to access this resource", + "lotteryWon": "Congratulations! You won the lottery!" +} diff --git a/DysonNetwork.Pass/Resources/Locales/zh-hans.json b/DysonNetwork.Pass/Resources/Locales/zh-hans.json new file mode 100644 index 00000000..6c9eccc5 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Locales/zh-hans.json @@ -0,0 +1,22 @@ +{ + "welcomeMessage": "你好,{name}!", + "goodbyeMessage": "再见,{name}!", + "itemCount": { + "one": "{count}个项目", + "other": "{count}个项目" + }, + "userCount": { + "one": "有 {count} 位用户在线", + "other": "有 {count} 位用户在线" + }, + "notificationMessage": "您有 {count} 条来自 {sender} 的新消息", + "profileUpdated": "您的个人资料已成功更新", + "errorOccurred": "发生错误:{error}", + "loginSuccess": "欢迎回来,{username}!", + "loginFailed": "登录失败,请重试", + "accountCreated": "您的账户已成功创建", + "emailVerification": "请验证您的电子邮件地址", + "passwordReset": "密码重置链接已发送到您的邮箱", + "permissionDenied": "您没有权限访问此资源", + "lotteryWon": "恭喜!您赢得了抽奖!" +} diff --git a/DysonNetwork.Shared/Localization/ILocalizationService.cs b/DysonNetwork.Shared/Localization/ILocalizationService.cs new file mode 100644 index 00000000..735377b1 --- /dev/null +++ b/DysonNetwork.Shared/Localization/ILocalizationService.cs @@ -0,0 +1,6 @@ +namespace DysonNetwork.Shared.Localization; + +public interface ILocalizationService +{ + string Get(string key, string? locale = null, object? args = null); +} diff --git a/DysonNetwork.Shared/Localization/JsonLocalizationService.cs b/DysonNetwork.Shared/Localization/JsonLocalizationService.cs new file mode 100644 index 00000000..2ff9edaa --- /dev/null +++ b/DysonNetwork.Shared/Localization/JsonLocalizationService.cs @@ -0,0 +1,262 @@ +using System.Globalization; +using System.Reflection; +using System.Text.Json; + +namespace DysonNetwork.Shared.Localization; + +public class JsonLocalizationService : ILocalizationService +{ + private readonly Dictionary> _localeCache = new(); + private readonly Assembly _assembly; + private readonly string _resourceNamespace; + private readonly object _lock = new(); + private readonly List _availableLocales = new(); + + public JsonLocalizationService(Assembly? assembly = null, string? resourceNamespace = null) + { + _assembly = assembly ?? Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly(); + _resourceNamespace = resourceNamespace ?? "DysonNetwork.Pass.Resources.Locales"; + DiscoverAvailableLocales(); + } + + private void DiscoverAvailableLocales() + { + var resourceNames = _assembly.GetManifestResourceNames(); + var prefix = $"{_resourceNamespace}."; + var suffix = ".json"; + + foreach (var resourceName in resourceNames) + { + if (resourceName.StartsWith(prefix) && resourceName.EndsWith(suffix)) + { + var locale = resourceName.Substring(prefix.Length, resourceName.Length - prefix.Length - suffix.Length); + _availableLocales.Add(locale); + } + } + } + + public string Get(string key, string? locale = null, object? args = null) + { + locale ??= CultureInfo.CurrentUICulture.Name; + + // Try the requested locale first + var entries = GetLocaleEntries(locale); + if (entries.TryGetValue(key, out var entry)) + { + return FormatEntry(entry, args); + } + + // Fallback: search all available locales + foreach (var availableLocale in _availableLocales) + { + if (availableLocale.Equals(locale, StringComparison.OrdinalIgnoreCase)) + continue; + + var fallbackEntries = GetLocaleEntries(availableLocale); + if (fallbackEntries.TryGetValue(key, out entry)) + { + return FormatEntry(entry, args); + } + } + + // If no translation found, return key with args joined + return FormatFallback(key, args); + } + + private string FormatEntry(LocalizationEntry entry, object? args) + { + string template; + if (args != null && entry.IsPlural) + { + template = SelectPluralForm(entry, args); + } + else + { + template = !string.IsNullOrEmpty(entry.Value) ? entry.Value : (entry.Other ?? entry.One ?? string.Empty); + } + + return FormatTemplate(template, args); + } + + private string FormatFallback(string key, object? args) + { + if (args == null) + { + return key; + } + + // Extract argument values and join them with the key + var argValues = new List(); + var type = args.GetType(); + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + + foreach (var prop in properties) + { + var value = prop.GetValue(args); + if (value != null) + { + argValues.Add(value.ToString() ?? string.Empty); + } + } + + foreach (var field in fields) + { + var value = field.GetValue(args); + if (value != null) + { + argValues.Add(value.ToString() ?? string.Empty); + } + } + + if (argValues.Count == 0) + { + return key; + } + + return $"{key} {string.Join(" ", argValues)}"; + } + + private Dictionary GetLocaleEntries(string locale) + { + lock (_lock) + { + if (_localeCache.TryGetValue(locale, out var cached)) + { + return cached; + } + + var entries = LoadLocale(locale); + _localeCache[locale] = entries; + return entries; + } + } + + private Dictionary LoadLocale(string locale) + { + var resourceName = $"{_resourceNamespace}.{locale}.json"; + + using var stream = _assembly.GetManifestResourceStream(resourceName); + if (stream == null) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + using var reader = new StreamReader(stream); + var json = reader.ReadToEnd(); + + var root = JsonSerializer.Deserialize>(json); + if (root == null) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kvp in root) + { + entries[kvp.Key] = ParseLocalizationEntry(kvp.Value); + } + + return entries; + } + + private LocalizationEntry ParseLocalizationEntry(JsonElement element) + { + if (element.ValueKind == JsonValueKind.String) + { + return new LocalizationEntry { Value = element.GetString() ?? string.Empty }; + } + + if (element.ValueKind == JsonValueKind.Object) + { + var entry = new LocalizationEntry(); + if (element.TryGetProperty("value", out var valueProp)) + { + entry.Value = valueProp.GetString() ?? string.Empty; + } + if (element.TryGetProperty("one", out var oneProp)) + { + entry.One = oneProp.GetString(); + } + if (element.TryGetProperty("other", out var otherProp)) + { + entry.Other = otherProp.GetString(); + } + return entry; + } + + return new LocalizationEntry { Value = element.ToString() }; + } + + private string SelectPluralForm(LocalizationEntry entry, object args) + { + var countValue = GetCountValue(args); + if (countValue.HasValue) + { + var count = countValue.Value; + if (count == 1 && !string.IsNullOrEmpty(entry.One)) + { + return entry.One; + } + } + + return entry.Other ?? entry.One ?? string.Empty; + } + + private int? GetCountValue(object args) + { + if (args == null) return null; + + var type = args.GetType(); + var countProperty = type.GetProperty("Count", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + if (countProperty != null) + { + var value = countProperty.GetValue(args); + if (value is int intValue) return intValue; + if (value is long longValue) return (int)longValue; + if (value is double doubleValue) return (int)doubleValue; + if (value is decimal decimalValue) return (int)decimalValue; + } + + var countField = type.GetField("Count", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + if (countField != null) + { + var value = countField.GetValue(args); + if (value is int intValue) return intValue; + if (value is long longValue) return (int)longValue; + if (value is double doubleValue) return (int)doubleValue; + if (value is decimal decimalValue) return (int)decimalValue; + } + + return null; + } + + private string FormatTemplate(string template, object? args) + { + if (args == null || string.IsNullOrEmpty(template)) + { + return template; + } + + var result = template; + var type = args.GetType(); + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + + foreach (var prop in properties) + { + var placeholder = $"{{{prop.Name}}}"; + var value = prop.GetValue(args); + result = result.Replace(placeholder, value?.ToString() ?? string.Empty); + } + + foreach (var field in fields) + { + var placeholder = $"{{{field.Name}}}"; + var value = field.GetValue(args); + result = result.Replace(placeholder, value?.ToString() ?? string.Empty); + } + + return result; + } +} diff --git a/DysonNetwork.Shared/Localization/LocalizationEntry.cs b/DysonNetwork.Shared/Localization/LocalizationEntry.cs new file mode 100644 index 00000000..ad0c8873 --- /dev/null +++ b/DysonNetwork.Shared/Localization/LocalizationEntry.cs @@ -0,0 +1,12 @@ +namespace DysonNetwork.Shared.Localization; + +public class LocalizationEntry +{ + public string Value { get; set; } = string.Empty; + + public string? One { get; set; } + + public string? Other { get; set; } + + public bool IsPlural => One != null || Other != null; +} diff --git a/DysonNetwork.Shared/Localization/LocalizationExtensions.cs b/DysonNetwork.Shared/Localization/LocalizationExtensions.cs new file mode 100644 index 00000000..34ac9092 --- /dev/null +++ b/DysonNetwork.Shared/Localization/LocalizationExtensions.cs @@ -0,0 +1,54 @@ +using System.Globalization; + +namespace DysonNetwork.Shared.Localization; + +public static class LocalizationExtensions +{ + public static string Localize(this string key, string? locale = null, object? args = null) + { + var service = LocalizationServiceLocator.Service; + if (service == null) + { + return key; + } + return service.Get(key, locale, args); + } + + public static string LocalizeCount(this string key, int count, string? locale = null, object? additionalArgs = null) + { + object args; + if (additionalArgs != null) + { + args = new CountWrapper(count, additionalArgs); + } + else + { + args = new { count }; + } + + var service = LocalizationServiceLocator.Service; + if (service == null) + { + return key; + } + return service.Get(key, locale, args); + } + + public class CountWrapper + { + public int Count { get; } + + private readonly object? _additionalArgs; + + public CountWrapper(int count, object? additionalArgs) + { + Count = count; + _additionalArgs = additionalArgs; + } + } +} + +public static class LocalizationServiceLocator +{ + public static ILocalizationService? Service { get; set; } +} diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index ace2fc40..05e70a75 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -95,4 +95,8 @@ + + + + diff --git a/DysonNetwork.Sphere/Post/PostService.cs b/DysonNetwork.Sphere/Post/PostService.cs index a6f07573..719827b6 100644 --- a/DysonNetwork.Sphere/Post/PostService.cs +++ b/DysonNetwork.Sphere/Post/PostService.cs @@ -7,7 +7,7 @@ using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.ActivityPub; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; using NodaTime; using Markdig; using AngleSharp.Html.Parser; @@ -19,7 +19,7 @@ namespace DysonNetwork.Sphere.Post; public partial class PostService( AppDatabase db, - IStringLocalizer localizer, + ILocalizationService localizer, IServiceScopeFactory factory, FlushBufferService flushBuffer, ICacheService cache, @@ -124,9 +124,9 @@ public partial class PostService( ? string.Concat(post.Content.AsSpan(0, 97), "...") : post.Content; var title = post.Title ?? (post.Content?.Length >= 10 ? post.Content[..10] + "..." : post.Content); - title ??= localizer["PostOnlyMedia"]; + title ??= localizer.Get("postOnlyMedia"); if (string.IsNullOrWhiteSpace(content)) - content = localizer["PostOnlyMedia"]; + content = localizer.Get("postOnlyMedia"); return (title, content); } @@ -229,7 +229,7 @@ public partial class PostService( Notification = new PushNotification { Topic = "post.replies", - Title = localizer["PostReplyTitle", sender!.Nick], + Title = localizer.Get("postReplyTitle", args: new { senderNick = sender!.Nick }), Body = ChopPostForNotification(post).content, IsSavable = true, ActionUri = $"/posts/{post.Id}" @@ -703,11 +703,10 @@ public partial class PostService( Notification = new PushNotification { Topic = "posts.reactions.new", - Title = localizer["PostReactTitle", sender.Nick], + Title = localizer.Get("postReactTitle", args: new { senderNick = sender.Nick }), Body = string.IsNullOrWhiteSpace(post.Title) - ? localizer["PostReactBody", sender.Nick, reaction.Symbol] - : localizer["PostReactContentBody", sender.Nick, reaction.Symbol, - post.Title], + ? localizer.Get("postReactBody", args: new { senderNick = sender.Nick, reaction.Symbol }) + : localizer.Get("postReactContentBody", args: new { senderNick = sender.Nick, reaction.Symbol, post.Title }), IsSavable = true, ActionUri = $"/posts/{post.Id}" } @@ -1157,11 +1156,10 @@ public partial class PostService( Notification = new PushNotification { Topic = "posts.awards.new", - Title = localizer["PostAwardedTitle", sender.Nick], + Title = localizer.Get("postAwardedTitle", args: new { senderNick = sender.Nick }), Body = string.IsNullOrWhiteSpace(post.Title) - ? localizer["PostAwardedBody", sender.Nick, amount] - : localizer["PostAwardedContentBody", sender.Nick, amount, - post.Title], + ? localizer.Get("postAwardedBody", args: new { senderNick = sender.Nick, amount }) + : localizer.Get("postAwardedContentBody", args: new { senderNick = sender.Nick, amount, post.Title }), IsSavable = true, ActionUri = $"/posts/{post.Id}" } diff --git a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs index 36871c8f..123a18e9 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs @@ -4,14 +4,14 @@ using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Localization; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; namespace DysonNetwork.Sphere.Publisher; public class PublisherSubscriptionService( AppDatabase db, Post.PostService ps, - IStringLocalizer localizer, + ILocalizationService localizer, ICacheService cache, RingService.RingServiceClient pusher, AccountService.AccountServiceClient accounts @@ -118,7 +118,7 @@ public class PublisherSubscriptionService( var notification = new PushNotification { Topic = "posts.new", - Title = localizer["PostSubscriptionTitle", post.Publisher!.Nick, title], + Title = localizer.Get("postSubscriptionTitle", args: new { publisherNick = post.Publisher!.Nick, title }), Body = message, Meta = GrpcTypeHelper.ConvertObjectToByteString(data), IsSavable = true, diff --git a/DysonNetwork.Sphere/Resources/Locales/en.json b/DysonNetwork.Sphere/Resources/Locales/en.json new file mode 100644 index 00000000..4b90358a --- /dev/null +++ b/DysonNetwork.Sphere/Resources/Locales/en.json @@ -0,0 +1,13 @@ +{ + "postOnlyMedia": "Media only", + "postReplyTitle": "{user} replied to your post", + "postReactTitle": "{user} reacted to your post", + "postReactBody": "{user} reacted with {reaction}", + "postReactContentBody": "{user} reacted with {reaction} to \"{title}\"", + "postAwardedTitle": "{user} awarded your post", + "postAwardedBody": "{user} awarded {amount} points", + "postAwardedContentBody": "{user} awarded {amount} points to \"{title}\"", + "postSubscriptionTitle": "{publisher}: {title}", + "realmInviteTitle": "Realm Invitation", + "realmInviteBody": "You have been invited to join {realm}" +} diff --git a/DysonNetwork.Sphere/Resources/Locales/zh-hans.json b/DysonNetwork.Sphere/Resources/Locales/zh-hans.json new file mode 100644 index 00000000..f0281706 --- /dev/null +++ b/DysonNetwork.Sphere/Resources/Locales/zh-hans.json @@ -0,0 +1,13 @@ +{ + "postOnlyMedia": "仅媒体", + "postReplyTitle": "{user} 回复了您的帖子", + "postReactTitle": "{user} 对您的帖子做出了反应", + "postReactBody": "{user} 用 {reaction} 做出了反应", + "postReactContentBody": "{user} 用 {reaction} 对 \"{title}\" 做出了反应", + "postAwardedTitle": "{user} 打赏了您的帖子", + "postAwardedBody": "{user} 打赏了 {amount} 积分", + "postAwardedContentBody": "{user} 打赏了 {amount} 积分给 \"{title}\"", + "postSubscriptionTitle": "{publisher}: {title}", + "realmInviteTitle": "领域邀请", + "realmInviteBody": "您被邀请加入 {realm}" +} diff --git a/DysonNetwork.Sphere/Translation/TranslationController.cs b/DysonNetwork.Sphere/Translation/TranslationController.cs index 639a0a47..7b7ea5e7 100644 --- a/DysonNetwork.Sphere/Translation/TranslationController.cs +++ b/DysonNetwork.Sphere/Translation/TranslationController.cs @@ -49,4 +49,4 @@ public class TranslationController(ITranslationProvider provider, ICacheService return result; } -} \ No newline at end of file +} diff --git a/DysonNetwork.Wallet/DysonNetwork.Wallet.csproj b/DysonNetwork.Wallet/DysonNetwork.Wallet.csproj index d55a8649..bd718e55 100644 --- a/DysonNetwork.Wallet/DysonNetwork.Wallet.csproj +++ b/DysonNetwork.Wallet/DysonNetwork.Wallet.csproj @@ -29,4 +29,8 @@ + + + + diff --git a/DysonNetwork.Wallet/Payment/PaymentService.cs b/DysonNetwork.Wallet/Payment/PaymentService.cs index bbd35faa..6830a575 100644 --- a/DysonNetwork.Wallet/Payment/PaymentService.cs +++ b/DysonNetwork.Wallet/Payment/PaymentService.cs @@ -6,7 +6,7 @@ using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Queue; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; using NATS.Client.Core; using NATS.Net; using NodaTime; @@ -17,7 +17,7 @@ public class PaymentService( AppDatabase db, WalletService wat, RingService.RingServiceClient pusher, - IStringLocalizer localizer, + ILocalizationService localizer, INatsConnection nats ) { @@ -189,14 +189,16 @@ public class PaymentService( Notification = new PushNotification { Topic = "wallets.transactions", - Title = localizer["TransactionNewTitle", readableTransactionRemark], + Title = localizer.Get("transactionNewTitle", args: new { remark = readableTransactionRemark }), Body = transaction.Amount > 0 - ? localizer["TransactionNewBodyMinus", - transaction.Amount.ToString(CultureInfo.InvariantCulture), - transaction.Currency] - : localizer["TransactionNewBodyPlus", - transaction.Amount.ToString(CultureInfo.InvariantCulture), - transaction.Currency], + ? localizer.Get("transactionNewBodyMinus", args: new { + amount = transaction.Amount.ToString(CultureInfo.InvariantCulture), + currency = transaction.Currency + }) + : localizer.Get("transactionNewBodyPlus", args: new { + amount = transaction.Amount.ToString(CultureInfo.InvariantCulture), + currency = transaction.Currency + }), IsSavable = true } } @@ -216,14 +218,16 @@ public class PaymentService( Notification = new PushNotification { Topic = "wallets.transactions", - Title = localizer["TransactionNewTitle", readableTransactionRemark], + Title = localizer.Get("transactionNewTitle", args: new { remark = readableTransactionRemark }), Body = transaction.Amount > 0 - ? localizer["TransactionNewBodyPlus", - transaction.Amount.ToString(CultureInfo.InvariantCulture), - transaction.Currency] - : localizer["TransactionNewBodyMinus", - transaction.Amount.ToString(CultureInfo.InvariantCulture), - transaction.Currency], + ? localizer.Get("transactionNewBodyPlus", args: new { + amount = transaction.Amount.ToString(CultureInfo.InvariantCulture), + currency = transaction.Currency + }) + : localizer.Get("transactionNewBodyMinus", args: new { + amount = transaction.Amount.ToString(CultureInfo.InvariantCulture), + currency = transaction.Currency + }), IsSavable = true } } @@ -320,10 +324,12 @@ public class PaymentService( Notification = new PushNotification { Topic = "wallets.orders.paid", - Title = localizer["OrderPaidTitle", $"#{readableOrderId}"], - Body = localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), - order.Currency, - readableOrderRemark], + Title = localizer.Get("orderPaidTitle", args: new { orderId = $"#{readableOrderId}" }), + Body = localizer.Get("orderPaidBody", args: new { + amount = order.Amount.ToString(CultureInfo.InvariantCulture), + currency = order.Currency, + remark = readableOrderRemark + }), IsSavable = true } } @@ -343,10 +349,12 @@ public class PaymentService( Notification = new PushNotification { Topic = "wallets.orders.received", - Title = localizer["OrderReceivedTitle", $"#{readableOrderId}"], - Body = localizer["OrderReceivedBody", order.Amount.ToString(CultureInfo.InvariantCulture), - order.Currency, - readableOrderRemark], + Title = localizer.Get("orderReceivedTitle", args: new { orderId = $"#{readableOrderId}" }), + Body = localizer.Get("orderReceivedBody", args: new { + amount = order.Amount.ToString(CultureInfo.InvariantCulture), + currency = order.Currency, + remark = readableOrderRemark + }), IsSavable = true } } diff --git a/DysonNetwork.Wallet/Payment/SubscriptionService.cs b/DysonNetwork.Wallet/Payment/SubscriptionService.cs index 2b687ffd..2a438a21 100644 --- a/DysonNetwork.Wallet/Payment/SubscriptionService.cs +++ b/DysonNetwork.Wallet/Payment/SubscriptionService.cs @@ -8,7 +8,7 @@ using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; +using DysonNetwork.Shared.Localization; using NodaTime; using Duration = NodaTime.Duration; @@ -19,7 +19,7 @@ public class SubscriptionService( PaymentService payment, AccountService.AccountServiceClient accounts, RingService.RingServiceClient pusher, - IStringLocalizer localizer, + ILocalizationService localizer, IConfiguration configuration, ICacheService cache, ILogger logger @@ -423,8 +423,8 @@ public class SubscriptionService( var notification = new PushNotification { Topic = "subscriptions.begun", - Title = localizer["SubscriptionAppliedTitle", humanReadableName], - Body = localizer["SubscriptionAppliedBody", duration, humanReadableName], + Title = localizer.Get("subscriptionAppliedTitle", args: new { subscriptionName = humanReadableName }), + Body = localizer.Get("subscriptionAppliedBody", args: new { duration, subscriptionName = humanReadableName }), Meta = GrpcTypeHelper.ConvertObjectToByteString(new Dictionary { ["subscription_id"] = subscription.Id.ToString() @@ -920,8 +920,8 @@ public class SubscriptionService( var notification = new PushNotification { Topic = "gifts.claimed", - Title = localizer["GiftClaimedTitle"], - Body = localizer["GiftClaimedBody", humanReadableName, redeemer.Name], + Title = localizer.Get("giftClaimedTitle"), + Body = localizer.Get("giftClaimedBody", args: new { subscriptionName = humanReadableName, redeemerName = redeemer.Name }), Meta = GrpcTypeHelper.ConvertObjectToByteString(new Dictionary { ["gift_id"] = gift.Id.ToString(), diff --git a/DysonNetwork.Wallet/Resources/Locales/en.json b/DysonNetwork.Wallet/Resources/Locales/en.json new file mode 100644 index 00000000..9a136426 --- /dev/null +++ b/DysonNetwork.Wallet/Resources/Locales/en.json @@ -0,0 +1,13 @@ +{ + "transactionNewTitle": "New Transaction", + "transactionNewBodyMinus": "You spent {amount} {currency}", + "transactionNewBodyPlus": "You received {amount} {currency}", + "orderPaidTitle": "Order Paid", + "orderPaidBody": "Paid {amount} {currency} for {remark}", + "orderReceivedTitle": "Order Received", + "orderReceivedBody": "Received {amount} {currency} for {remark}", + "subscriptionAppliedTitle": "Subscription Applied", + "subscriptionAppliedBody": "Your subscription for {duration} days of {subscription} has been applied", + "giftClaimedTitle": "Gift Claimed", + "giftClaimedBody": "Your gift of {subscription} has been claimed by {user}" +} diff --git a/DysonNetwork.Wallet/Resources/Locales/zh-hans.json b/DysonNetwork.Wallet/Resources/Locales/zh-hans.json new file mode 100644 index 00000000..06b5d0d2 --- /dev/null +++ b/DysonNetwork.Wallet/Resources/Locales/zh-hans.json @@ -0,0 +1,13 @@ +{ + "transactionNewTitle": "新交易", + "transactionNewBodyMinus": "您花费了 {amount} {currency}", + "transactionNewBodyPlus": "您收到了 {amount} {currency}", + "orderPaidTitle": "订单已支付", + "orderPaidBody": "支付了 {amount} {currency} 用于 {remark}", + "orderReceivedTitle": "订单已接收", + "orderReceivedBody": "收到了 {amount} {currency} 用于 {remark}", + "subscriptionAppliedTitle": "订阅已应用", + "subscriptionAppliedBody": "您的 {duration} 天 {subscription} 订阅已应用", + "giftClaimedTitle": "礼物已领取", + "giftClaimedBody": "您的 {subscription} 礼物已被 {user} 领取" +} diff --git a/settings/develop.json b/settings/develop.json deleted file mode 100644 index a180de4d..00000000 --- a/settings/develop.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Debug": true, - "BaseUrl": "http://localhost:5071", - "SiteUrl": "https://solian.app", - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "App": "Host=host.docker.internal;Port=5432;Database=dyson_develop;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" - }, - "KnownProxies": ["127.0.0.1", "::1"] -} diff --git a/settings/drive.json b/settings/drive.json deleted file mode 100644 index 312811ec..00000000 --- a/settings/drive.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "Debug": true, - "BaseUrl": "http://localhost:5090", - "GatewayUrl": "http://localhost:5094", - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "App": "Host=host.docker.internal;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=5;Connection Idle Lifetime=30" - }, - "Authentication": { - "Schemes": { - "Bearer": { - "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], - "ValidIssuer": "solar-network" - } - } - }, - "AuthToken": { - "PublicKeyPath": "app/keys/PublicKey.pem", - "PrivateKeyPath": "app/keys/PrivateKey.pem" - }, - "OidcProvider": { - "IssuerUri": "https://nt.solian.app", - "PublicKeyPath": "Keys/PublicKey.pem", - "PrivateKeyPath": "Keys/PrivateKey.pem", - "AccessTokenLifetime": "01:00:00", - "RefreshTokenLifetime": "30.00:00:00", - "AuthorizationCodeLifetime": "00:30:00", - "RequireHttpsMetadata": true - }, - "Tus": { - "StorePath": "/app/uploads" - }, - "Storage": { - "PreferredRemote": "2adceae3-981a-4564-9b8d-5d71a211c873", - "Remote": [ - { - "Id": "minio", - "Label": "Minio", - "Region": "auto", - "Bucket": "solar-network-development", - "Endpoint": "minio.orb.local:9000", - "SecretId": "littlesheep", - "SecretKey": "password", - "EnabledSigned": true, - "EnableSsl": false - }, - { - "Id": "cloudflare", - "Label": "Cloudflare R2", - "Region": "auto", - "Bucket": "solar-network", - "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", - "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", - "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", - "EnableSigned": true, - "EnableSsl": true - } - ] - }, - "Captcha": { - "Provider": "cloudflare", - "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", - "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" - }, - "Notifications": { - "Topic": "dev.solsynth.solian", - "Endpoint": "http://localhost:8088" - }, - "Email": { - "Server": "smtp4dev.orb.local", - "Port": 25, - "UseSsl": false, - "Username": "no-reply@mail.solsynth.dev", - "Password": "password", - "FromAddress": "no-reply@mail.solsynth.dev", - "FromName": "Alphabot", - "SubjectPrefix": "Solar Network" - }, - "RealtimeChat": { - "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", - "ApiKey": "APIs6TiL8wj3A4j", - "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" - }, - "GeoIp": { - "DatabasePath": "./Keys/GeoLite2-City.mmdb" - }, - "Oidc": { - "Google": { - "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", - "ClientSecret": "" - }, - "Apple": { - "ClientId": "dev.solsynth.solian", - "TeamId": "W7HPZ53V6B", - "KeyId": "B668YP4KBG", - "PrivateKeyPath": "./Keys/Solarpass.p8" - }, - "Microsoft": { - "ClientId": "YOUR_MICROSOFT_CLIENT_ID", - "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", - "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" - } - }, - "Payment": { - "Auth": { - "Afdian": "" - }, - "Subscriptions": { - "Afdian": { - "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", - "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", - "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" - } - } - }, - "KnownProxies": ["127.0.0.1", "::1"] -} diff --git a/settings/pass.json b/settings/pass.json deleted file mode 100644 index 7ed4df6b..00000000 --- a/settings/pass.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "Debug": true, - "BaseUrl": "http://localhost:5001", - "SiteUrl": "http://localhost:3000", - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "App": "Host=host.docker.internal;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" - }, - "Authentication": { - "Schemes": { - "Bearer": { - "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], - "ValidIssuer": "solar-network" - } - } - }, - "AuthToken": { - "CookieDomain": "localhost", - "PublicKeyPath": "/app/keys/PublicKey.pem", - "PrivateKeyPath": "/app/keys/PrivateKey.pem" - }, - "OidcProvider": { - "IssuerUri": "https://nt.solian.app", - "PublicKeyPath": "Keys/PublicKey.pem", - "PrivateKeyPath": "Keys/PrivateKey.pem", - "AccessTokenLifetime": "01:00:00", - "RefreshTokenLifetime": "30.00:00:00", - "AuthorizationCodeLifetime": "00:30:00", - "RequireHttpsMetadata": true - }, - "Captcha": { - "Provider": "cloudflare", - "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", - "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" - }, - "GeoIp": { - "DatabasePath": "./Keys/GeoLite2-City.mmdb" - }, - "Oidc": { - "Google": { - "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", - "ClientSecret": "" - }, - "Apple": { - "ClientId": "dev.solsynth.solian", - "TeamId": "W7HPZ53V6B", - "KeyId": "B668YP4KBG", - "PrivateKeyPath": "./Keys/Solarpass.p8" - }, - "Microsoft": { - "ClientId": "YOUR_MICROSOFT_CLIENT_ID", - "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", - "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" - } - }, - "Payment": { - "Auth": { - "Afdian": "" - }, - "Subscriptions": { - "Afdian": { - "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", - "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", - "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" - } - } - }, - "KnownProxies": ["127.0.0.1", "::1"] -} diff --git a/settings/ring.json b/settings/ring.json deleted file mode 100644 index 3b049856..00000000 --- a/settings/ring.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "Debug": true, - "BaseUrl": "http://localhost:5212", - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "App": "Host=host.docker.internal;Port=5432;Database=dyson_ring;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" - }, - "Notifications": { - "Push": { - "Production": true, - "Google": "/app/keys/Solian.json", - "Apple": { - "PrivateKey": "./Keys/Solian.p8", - "PrivateKeyId": "4US4KSX4W6", - "TeamId": "W7HPZ53V6B", - "BundleIdentifier": "dev.solsynth.solian" - } - } - }, - "Email": { - "Server": "smtp4dev.orb.local", - "Port": 25, - "UseSsl": false, - "Username": "no-reply@mail.solsynth.dev", - "Password": "password", - "FromAddress": "no-reply@mail.solsynth.dev", - "FromName": "Alphabot", - "SubjectPrefix": "Solar Network" - }, - "GeoIp": { - "DatabasePath": "/app/keys/GeoLite2-City.mmdb" - }, - "KnownProxies": ["127.0.0.1", "::1"] -} diff --git a/settings/sphere.json b/settings/sphere.json deleted file mode 100644 index f1a87156..00000000 --- a/settings/sphere.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "Debug": true, - "BaseUrl": "http://localhost:5071", - "SiteUrl": "https://solian.app", - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "App": "Host=host.docker.internal;Port=5432;Database=dyson_sphere;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" - }, - "GeoIp": { - "DatabasePath": "/app/keys/GeoLite2-City.mmdb" - }, - "RealtimeChat": { - "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", - "ApiKey": "", - "ApiSecret": "" - }, - "Translation": { - "Provider": "Tencent", - "Region": "ap-hongkong", - "ProjectId": "0", - "SecretId": "", - "SecretKey": "" - }, - "KnownProxies": ["127.0.0.1", "::1"] -}