Skip to content

Commit 36c470d

Browse files
committed
shared translations refatored / component core refactored
1 parent 9928f48 commit 36c470d

157 files changed

Lines changed: 1057 additions & 863 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

SD.API/Core/ZeptoMailClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,13 @@ public async Task SendOtpEmail(string toEmail, string reference, string? otp, Ca
185185
<h2 class=""email-title"">
186186
Verification Code
187187
</h2>
188-
188+
189189
<p>Use the code below to confirm your {appName} account.</p>
190190
<div style=""text-align:center"">
191191
<div class=""otp"">{otp}</div>
192192
</div>
193193
<p>This code expires in <span style=""color: #ff4081; font-weight: bold;"">10 minutes</span>.</p>
194-
194+
195195
<hr class=""email-divider"">
196196
<div class=""email-support"">
197197
Questions? Contact

SD.API/Functions/ExternalFunction.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,18 @@ public async Task<HttpResponseData> External(
2222
}
2323

2424
[Function("Country")]
25-
public async Task<string?> Country([HttpTrigger(AuthorizationLevel.Anonymous, Method.Get, Route = "public/country")] HttpRequestData req, CancellationToken cancellationToken)
25+
public async Task<HttpResponseData> Country([HttpTrigger(AuthorizationLevel.Anonymous, Method.Get, Route = "public/country")] HttpRequestData req, CancellationToken cancellationToken)
2626
{
2727
var ip = req.GetUserIP(false);
28-
if (ip.Empty()) return null;
29-
if (ip == "127.0.0.1") return null;
3028

3129
var client = factory.CreateClient("ipinfo");
3230

33-
return await client.GetStringAsync($"https://ipinfo.io/{ip}/country", cancellationToken);
31+
if (ip.NotEmpty() && ip != "127.0.0.1")
32+
{
33+
var result = await client.GetStringAsync($"https://ipinfo.io/{ip}/country", cancellationToken);
34+
return await req.CreateResponse(result, TtlCache.OneMinute, cancellationToken);
35+
}
36+
37+
return await req.CreateResponse(null, TtlCache.OneMinute, cancellationToken);
3438
}
3539
}

SD.Shared/Core/CosmosDocument.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ protected CosmosDocument(string id)
2525
[JsonProperty(PropertyName = "_tsCreated")]
2626
public long? TimestampCreated { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
2727

28+
[System.Text.Json.Serialization.JsonPropertyName("_ts")]
2829
[JsonProperty(PropertyName = "_ts")]
2930
public long TimestampUpdated { get; set; }
3031

@@ -40,4 +41,4 @@ public void SetIds(string id)
4041

4142
Id = id;
4243
}
43-
}
44+
}

SD.Shared/Core/EnumLists.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
public enum EnumLists
44
{
5-
[Custom(Name = "ExpectedMovieOfYear", Description = "Discover the most anticipated movies of 2026, featuring upcoming blockbusters, fan favorites, and highly awaited releases hitting theaters and streaming.", ResourceType = typeof(Resources.Translations))]
5+
[FieldSettings("ExpectedMovieOfYear", Description = "Discover the most anticipated movies of 2026, featuring upcoming blockbusters, fan favorites, and highly awaited releases hitting theaters and streaming.", ResourceType = typeof(Resources.Translations))]
66
ExpectedMovieOf2026 = 8544544,
77

8-
[Custom(Name = "AwardsOfTheYear", Description = "Explore films and series that won the year’s top awards. Discover main categories, winners, and celebrated titles recognized by critics and audiences.", ResourceType = typeof(Resources.Translations))]
8+
[FieldSettings("AwardsOfTheYear", Description = "Explore films and series that won the year’s top awards. Discover main categories, winners, and celebrated titles recognized by critics and audiences.", ResourceType = typeof(Resources.Translations))]
99
AwardsOfTheYear = 8498534,
1010

11-
[Custom(Name = "EditorsChoiceTitle", Description = "Discover our Editor’s Choice: films and series consistently rated highly across IMDb, TMDb, Metacritic, Rotten Tomatoes, Trakt, FilmAffinity, and Letterboxd.", ResourceType = typeof(Resources.Translations))]
11+
[FieldSettings("EditorsChoiceTitle", Description = "Discover our Editor’s Choice: films and series consistently rated highly across IMDb, TMDb, Metacritic, Rotten Tomatoes, Trakt, FilmAffinity, and Letterboxd.", ResourceType = typeof(Resources.Translations))]
1212
CertifiedStreamingDiscoveryMovies = 8498673,
1313

14-
[Custom(Name = "EditorsChoiceTitle", Description = "Discover our Editor’s Choice: films and series consistently rated highly across IMDb, TMDb, Metacritic, Rotten Tomatoes, Trakt, FilmAffinity, and Letterboxd.", ResourceType = typeof(Resources.Translations))]
14+
[FieldSettings("EditorsChoiceTitle", Description = "Discover our Editor’s Choice: films and series consistently rated highly across IMDb, TMDb, Metacritic, Rotten Tomatoes, Trakt, FilmAffinity, and Letterboxd.", ResourceType = typeof(Resources.Translations))]
1515
CertifiedStreamingDiscoveryShows = 8498675,
1616
}
Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,100 @@
1-
using System.Globalization;
1+
using System.Collections.Concurrent;
2+
using System.ComponentModel.DataAnnotations;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
using System.Resources;
26

3-
namespace SD.Shared.Core.Helper;
4-
5-
public static class AttributeHelper
7+
namespace SD.Shared.Core.Helper
68
{
7-
public static DateTime? GetDate(this string? value)
9+
public class ClassFieldObject(string name)
10+
{
11+
public string Name { get; set; } = name;
12+
public string? Group { get; set; }
13+
public string? Placeholder { get; set; }
14+
public string? Description { get; set; }
15+
}
16+
17+
public sealed class EnumFieldObject<T>(string name, T value) : ClassFieldObject(name) where T : Enum
818
{
9-
if (!string.IsNullOrEmpty(value) && DateTime.TryParse(value, out _))
10-
return DateTime.Parse(value, CultureInfo.CurrentCulture);
11-
return null;
19+
public T Value { get; set; } = value;
1220
}
1321

14-
public static string FormatRuntime(this int? runtime)
22+
public static class AttributeHelper
1523
{
16-
if (!runtime.HasValue || runtime == 0) return "";
17-
var time = TimeSpan.FromMinutes(runtime.Value);
18-
return $"{time.Hours}h {time.Minutes}m";
24+
private static readonly ConcurrentDictionary<MemberInfo, FieldSettingsAttribute> AttributeCache = new();
25+
private static readonly ConcurrentDictionary<Type, ResourceManager> ResourceManagers = new();
26+
27+
private const string IncompleteTranslationSuffix = " (incomplete translation)";
28+
29+
public static EnumFieldObject<T> GetFieldSettings<T>(this T value, bool translate = true) where T : Enum
30+
{
31+
var fieldInfo = value.GetType().GetField(value.ToString()) ?? throw new UnhandledException($"{value} field info is null");
32+
33+
return fieldInfo.GetFieldSettings(value, translate);
34+
}
35+
36+
public static ClassFieldObject GetFieldSettings<T>(this Expression<Func<T>>? expression, bool translate = true)
37+
{
38+
if (expression == null) throw new UnhandledException($"{expression} expression is null");
39+
40+
MemberExpression member = expression.Body switch
41+
{
42+
MemberExpression m => m,
43+
UnaryExpression { Operand: MemberExpression m } => m,
44+
_ => throw new ArgumentException("Expression must reference a member.", nameof(expression))
45+
};
46+
47+
return member.Member.GetFieldSettings(translate);
48+
}
49+
50+
private static EnumFieldObject<T> GetFieldSettings<T>(this MemberInfo mi, T value, bool translate = true) where T : Enum
51+
{
52+
var attr = AttributeCache.GetOrAdd(mi, x => x.GetCustomAttribute<FieldSettingsAttribute>() ?? throw new ValidationException($"Field Settings '{x.Name}' is null"));
53+
54+
var obj = new EnumFieldObject<T>(attr.Name, value)
55+
{
56+
Group = attr.Group,
57+
Placeholder = attr.Placeholder,
58+
Description = attr.Description,
59+
};
60+
61+
ApplyTranslations(obj, attr, translate);
62+
63+
return obj;
64+
}
65+
66+
private static ClassFieldObject GetFieldSettings(this MemberInfo mi, bool translate = true)
67+
{
68+
var attr = AttributeCache.GetOrAdd(mi, x => x.GetCustomAttribute<FieldSettingsAttribute>() ?? throw new ValidationException($"Field Settings '{x.Name}' is null"));
69+
70+
var obj = new ClassFieldObject(attr.Name)
71+
{
72+
Group = attr.Group,
73+
Placeholder = attr.Placeholder,
74+
Description = attr.Description,
75+
};
76+
77+
ApplyTranslations(obj, attr, translate);
78+
79+
return obj;
80+
}
81+
82+
private static void ApplyTranslations(ClassFieldObject obj, FieldSettingsAttribute attr, bool translate)
83+
{
84+
if (attr.ResourceType != null && translate)
85+
{
86+
var rm = ResourceManagers.GetOrAdd(attr.ResourceType, t => new ResourceManager(t.FullName!, t.Assembly));
87+
88+
obj.Name = rm.GetResourceString(attr.Name) ?? throw new InvalidOperationException($"Resource not found for key: {attr.Name}");
89+
if (attr.Group.NotEmpty()) obj.Group = rm.GetResourceString(attr.Group);
90+
if (attr.Placeholder.NotEmpty()) obj.Placeholder = rm.GetResourceString(attr.Placeholder)?.Replace(@"\n", Environment.NewLine);
91+
if (attr.Description.NotEmpty()) obj.Description = rm.GetResourceString(attr.Description);
92+
}
93+
}
94+
95+
private static string GetResourceString(this ResourceManager rm, string resourceKey)
96+
{
97+
return rm.GetString(resourceKey) ?? resourceKey + IncompleteTranslationSuffix;
98+
}
1999
}
20100
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace SD.Shared.Core.Helper;
2+
3+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
4+
public class FieldSettingsAttribute(string name) : Attribute
5+
{
6+
public string Name { get; set; } = name;
7+
public string? Group { get; set; }
8+
public string? Placeholder { get; set; }
9+
public string? Description { get; set; }
10+
public Type? ResourceType { get; set; }
11+
}
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace SD.Shared.Core.Helper;
55

6-
public static class ParameterHelper
6+
public static class ConversionHelper
77
{
88
private static readonly JsonSerializerOptions IndentedOptions = new() { WriteIndented = true };
99

@@ -46,4 +46,31 @@ public static string ConvertFromBytesToBase64(this byte[] bytes)
4646
{
4747
return Convert.ToBase64String(bytes);
4848
}
49-
}
49+
50+
public static string ConvertFromStreamToBase64(this Stream stream)
51+
{
52+
if (stream.CanSeek) stream.Position = 0;
53+
54+
using var memoryStream = new MemoryStream();
55+
56+
stream.CopyTo(memoryStream);
57+
58+
return Convert.ToBase64String(memoryStream.ToArray());
59+
}
60+
61+
public static async Task<string> ConvertFromStreamToBase64Async(this Stream stream)
62+
{
63+
if (stream.CanSeek) stream.Position = 0;
64+
65+
using var memoryStream = new MemoryStream();
66+
67+
await stream.CopyToAsync(memoryStream);
68+
69+
return Convert.ToBase64String(memoryStream.ToArray());
70+
}
71+
72+
public static Stream ConvertFromBase64ToStream(this string base64)
73+
{
74+
return new MemoryStream(Convert.FromBase64String(base64));
75+
}
76+
}

SD.Shared/Core/Helper/CustomAttribute.cs

Lines changed: 0 additions & 76 deletions
This file was deleted.

SD.Shared/Core/Helper/EnumHelper.cs

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,37 @@
22

33
public static class EnumHelper
44
{
5-
public static TEnum[] GetArray<TEnum>() where TEnum : struct, Enum
5+
public static T[] GetValues<T>() where T : struct, Enum
66
{
7-
return Enum.GetValues<TEnum>();
7+
return Enum.GetValues<T>();
88
}
99

10-
public static List<EnumObject<TEnum>> GetList<TEnum>(bool translate = true) where TEnum : struct, Enum
10+
public static List<EnumFieldObject<T>> GetList<T>(bool translate = true) where T : struct, Enum
1111
{
12-
var result = new List<EnumObject<TEnum>>();
13-
foreach (var val in GetArray<TEnum>())
14-
{
15-
var attr = val.GetCustomAttribute(translate);
12+
var values = GetValues<T>();
13+
var result = new List<EnumFieldObject<T>>(values.Length);
1614

17-
result.Add(new EnumObject<TEnum>(val, attr?.Name, attr?.Description, attr?.Group));
15+
foreach (var val in values)
16+
{
17+
result.Add(val.GetFieldSettings(translate));
1818
}
19+
1920
return result;
2021
}
2122

22-
public static TEnum ParseToEnum<TEnum>(this string? value) where TEnum : struct, Enum
23+
public static T ParseToEnum<T>(this string? value, T? fallback = null) where T : struct, Enum
2324
{
24-
if (Enum.TryParse<TEnum>(value, true, out var result) && Enum.IsDefined(result))
25+
if (Enum.TryParse<T>(value, true, out var result) && Enum.IsDefined(result))
2526
{
2627
return result;
2728
}
28-
else
29+
else if (fallback.HasValue)
2930
{
30-
throw new ArgumentException($"Invalid value for enum type {typeof(TEnum).Name}: {value}");
31+
return fallback.Value;
3132
}
32-
}
33-
34-
public static TEnum ParseToEnum<TEnum>(this string? value, TEnum fallback) where TEnum : struct, Enum
35-
{
36-
if (Enum.TryParse<TEnum>(value, true, out var result) && Enum.IsDefined(result))
33+
else
3734
{
38-
return result;
35+
throw new ArgumentException($"Invalid value for enum type {typeof(T).Name}: {value}");
3936
}
40-
41-
return fallback;
4237
}
4338
}
44-
45-
public class EnumObject<TEnum>(TEnum value, string? name, string? description, string? group) where TEnum : struct, Enum
46-
{
47-
public TEnum Value { get; set; } = value;
48-
public string? Name { get; set; } = name;
49-
public string? Description { get; set; } = description;
50-
public string? Group { get; set; } = group;
51-
}

0 commit comments

Comments
 (0)