diff --git a/STranslateDLL/Models.cs b/STranslateDLL/Models.cs new file mode 100644 index 0000000..6477ae6 --- /dev/null +++ b/STranslateDLL/Models.cs @@ -0,0 +1,80 @@ +using System.Text.Json.Serialization; + +namespace STranslateDLL; + +public class Response +{ + public int Code { get; set; } + public string Data { get; set; } = ""; +} + +public class DeepLRequest +{ + [JsonPropertyName("jsonrpc")] public string Jsonrpc { get; set; } = ""; + + [JsonPropertyName("method")] public string Method { get; set; } = ""; + + [JsonPropertyName("params")] public ReqParams? Params { get; set; } + + [JsonPropertyName("id")] public long Id { get; set; } +} + +public class ReqParams +{ + [JsonPropertyName("commonJobParams")] public ReqParamsCommonJobParams? CommonJobParams { get; set; } + + [JsonPropertyName("lang")] public ReqParamsLang? Lang { get; set; } + + [JsonPropertyName("texts")] public string[]? Texts { get; set; } + + [JsonPropertyName("textType")] public string? TextType { get; set; } + + [JsonPropertyName("jobs")] public Job[]? Jobs { get; set; } + + [JsonPropertyName("priority")] public int Priority { get; set; } + + [JsonPropertyName("timestamp")] public long Timestamp { get; set; } +} + +public class ReqParamsLang +{ + [JsonPropertyName("source_lang_user_selected")] + public string? SourceLangUserSelected { get; set; } + + [JsonPropertyName("source_lang_computed")] + public string? SourceLangComputed { get; set; } + + [JsonPropertyName("target_lang")] public string TargetLang { get; set; } = ""; +} + +public class ReqParamsCommonJobParams +{ + [JsonPropertyName("mode")] public string Mode { get; set; } = ""; + + [JsonPropertyName("regionalVariant")] public string? RegionalVariant { get; set; } +} + +public class Job +{ + [JsonPropertyName("kind")] public string Kind { get; set; } = ""; + + [JsonPropertyName("preferred_num_beams")] + public int PreferredNumBeams { get; set; } + + [JsonPropertyName("raw_en_context_before")] + public string[]? RawEnContextBefore { get; set; } + + [JsonPropertyName("raw_en_context_after")] + public string[]? RawEnContextAfter { get; set; } + + [JsonPropertyName("sentences")] public Sentence[]? Sentences { get; set; } +} + +public class Sentence +{ + [JsonPropertyName("id")] public int Id { get; set; } + + [JsonPropertyName("prefix")] public string Prefix { get; set; } = ""; + + [JsonPropertyName("text")] public string Text { get; set; } = ""; +} \ No newline at end of file diff --git a/STranslateDLL/LocalMode.cs b/STranslateDLL/Program.cs similarity index 58% rename from STranslateDLL/LocalMode.cs rename to STranslateDLL/Program.cs index 58ee34d..ec7b3f0 100644 --- a/STranslateDLL/LocalMode.cs +++ b/STranslateDLL/Program.cs @@ -1,41 +1,12 @@ using System.IO.Compression; -using System.Net; using System.Text; -using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; -using System.Text.Json.Serialization; namespace STranslateDLL; -public static class LocalMode +public static class Program { - private static long _nextId; - - private static bool _hasInit; - - private static readonly Dictionary TextTypeDic = new() - { - { true, "richtext" }, - { false, "plaintext" } - }; - - private static JsonSerializerOptions GetOptions => - new() - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault - }; - - private static void Initial() - { - var rand = new Random(); - var number = rand.NextInt64(99999) + 8300000; - _nextId = number * 1000; - - _hasInit = true; - } - public static async Task ExecuteAsync( string content, string sourceLang = "auto", @@ -43,35 +14,37 @@ public static class LocalMode CancellationToken? token = null ) { - if (!_hasInit) Initial(); + Utilities.Initial(); + var getToken = token ?? CancellationToken.None; var splitResult = await SplitTextAsync(content, getToken); - //TODO: Error handling - var splitObj = JsonSerializer.Deserialize(splitResult); - if (sourceLang.Equals("auto", StringComparison.CurrentCultureIgnoreCase) || sourceLang.Equals("")) + var splitData = JsonNode.Parse(splitResult); + var splitError = splitData?["error"]?["message"]?.ToString(); + if (splitError != null) { - sourceLang = splitObj?["result"]?["lang"]?["detected"]?.ToString()?.ToUpper() ?? "auto"; + return Utilities.Serialize(new Response + { + Code = 500, + Data = splitError + }); } + + if (sourceLang.Equals("auto", StringComparison.CurrentCultureIgnoreCase) || sourceLang.Equals("")) + sourceLang = splitData?["result"]?["lang"]?["detected"]?.ToString()?.ToUpper() ?? "auto"; var jobs = new List(); - var chunks = splitObj?["result"]?["texts"]?[0]?["chunks"]; + var chunks = splitData?["result"]?["texts"]?[0]?["chunks"]; if (chunks is JsonArray chunkArray) - { for (var i = 0; i < chunkArray.Count; i++) { var sentence = chunkArray[i]?["sentences"]?[0]; var contextBefore = Array.Empty(); var contextAfter = Array.Empty(); - if (i > 0) - { - contextBefore = [chunkArray[i - 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""]; - } + if (i > 0) contextBefore = [chunkArray[i - 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""]; if (i < chunkArray.Count - 1) - { contextAfter = [chunkArray[i + 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""]; - } var job = new Job { @@ -85,13 +58,12 @@ public static class LocalMode { Prefix = sentence?["prefix"]?.ToString() ?? "", Text = sentence?["text"]?.ToString() ?? "", - Id = i + 1, + Id = i + 1 } ] }; jobs.Add(job); } - } var hasRegionalVariant = false; var targetLangCode = targetLang; @@ -102,7 +74,7 @@ public static class LocalMode hasRegionalVariant = true; } - var id = CreateId(); + var id = Utilities.CreateId(); var reqData = new DeepLRequest { Jsonrpc = "2.0", @@ -123,12 +95,12 @@ public static class LocalMode }, Jobs = jobs.ToArray(), Priority = 1, - Timestamp = GenerateTimestamp(content) + Timestamp = Utilities.GenerateTimestamp(content) } }; - var json = JsonSerializer.Serialize(reqData, GetOptions); - json = AdjustJsonContent(json, id); + var json = Utilities.Serialize(reqData); + json = Utilities.AdjustJsonContent(json, id); using var client = new HttpClient(); @@ -174,7 +146,7 @@ public static class LocalMode { responseBody = await resp.Content.ReadAsStringAsync(getToken); } - + var jNode = JsonNode.Parse(responseBody); var data = jNode?["result"]?["translations"]?[0]?["beams"]?[0]?["sentences"]?[0]?["text"]?.ToString(); // data = UnicodeToString(data); @@ -189,17 +161,17 @@ public static class LocalMode Data = data ?? error }; - return JsonSerializer.Serialize(response, GetOptions); + return Utilities.Serialize(response); } private static async Task SplitTextAsync(string text, CancellationToken? token) { - var id = CreateId(); + var id = Utilities.CreateId(); var getToken = token ?? CancellationToken.None; var requestData = new DeepLRequest { Jsonrpc = "2.0", - Method = "LMT_split_text", + Method = "LMT_split_text2", Id = id, Params = new ReqParams { @@ -212,12 +184,12 @@ public static class LocalMode SourceLangUserSelected = "auto" }, Texts = [text], - TextType = TextTypeDic[ /*tagHandling*/false || IsRichText(text)] + TextType = Utilities.TextTypeDic[ /*tagHandling*/false || Utilities.IsRichText(text)] } }; - var json = JsonSerializer.Serialize(requestData, GetOptions); - json = AdjustJsonContent(json, id); + var json = Utilities.Serialize(requestData); + json = Utilities.AdjustJsonContent(json, id); using var client = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Post, "https://www2.deepl.com/jsonrpc?client=chrome-extension,1.28.0&method=LMT_split_text") @@ -262,139 +234,4 @@ public static class LocalMode return responseBody; } - - private static string? UnicodeToString(string? srcText) - { - if (srcText == null) return default; - var dst = ""; - var src = srcText; - var len = srcText.Length / 6; - for (var i = 0; i <= len - 1; i++) - { - var str = ""; - str = src[..6][2..]; - src = src[6..]; - var bytes = new byte[2]; - bytes[1] = byte.Parse(int.Parse(str[..2], System.Globalization.NumberStyles.HexNumber).ToString()); - bytes[0] = byte.Parse(int.Parse(str.Substring(2, 2), System.Globalization.NumberStyles.HexNumber).ToString()); - dst += Encoding.Unicode.GetString(bytes); - } - return dst; - } - - private static bool IsRichText(string text) - { - return text.Contains('<') && text.Contains('>'); - } - - private static long GenerateTimestamp(string texts) - { - long iCount = texts.Split('i').Length - 1; - var ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - return iCount != 0 ? ts - ts % (iCount + 1) + iCount + 1 : ts; - } - - private static long CreateId() - { - return Interlocked.Increment(ref _nextId); - } - - private static string AdjustJsonContent(string json, long id) - { - string method; - if ((id + 3) % 13 == 0 || (id + 5) % 29 == 0) - method = "\"method\" : \""; - else - method = "\"method\": \""; - return json.Replace("\"method\":\"", method); - } -} - -public class Response -{ - public int Code { get; set; } - public string Data { get; set; } = ""; -} - -#region Request - -public class DeepLRequest -{ - [JsonPropertyName("jsonrpc")] public string Jsonrpc { get; set; } = ""; - - [JsonPropertyName("method")] public string Method { get; set; } = ""; - - [JsonPropertyName("params")] public ReqParams? Params { get; set; } - - [JsonPropertyName("id")] public long Id { get; set; } -} - -public class ReqParams -{ - [JsonPropertyName("commonJobParams")] public ReqParamsCommonJobParams? CommonJobParams { get; set; } - - [JsonPropertyName("lang")] public ReqParamsLang? Lang { get; set; } - - [JsonPropertyName("texts")] public string[]? Texts { get; set; } - - [JsonPropertyName("textType")] public string? TextType { get; set; } - - [JsonPropertyName("jobs")] public Job[]? Jobs { get; set; } - - [JsonPropertyName("priority")] public int Priority { get; set; } - - [JsonPropertyName("timestamp")] public long Timestamp { get; set; } -} - -public class ReqParamsLang -{ - [JsonPropertyName("source_lang_user_selected")] - public string? SourceLangUserSelected { get; set; } - - [JsonPropertyName("source_lang_computed")] - public string? SourceLangComputed { get; set; } - - [JsonPropertyName("target_lang")] public string TargetLang { get; set; } = ""; -} - -public class ReqParamsCommonJobParams -{ - [JsonPropertyName("mode")] public string Mode { get; set; } = ""; - - [JsonPropertyName("regionalVariant")] public string? RegionalVariant { get; set; } -} - -public class Job -{ - [JsonPropertyName("kind")] public string Kind { get; set; } = ""; - - [JsonPropertyName("preferred_num_beams")] - public int PreferredNumBeams { get; set; } - - [JsonPropertyName("raw_en_context_before")] - public string[]? RawEnContextBefore { get; set; } - - [JsonPropertyName("raw_en_context_after")] - public string[]? RawEnContextAfter { get; set; } - - [JsonPropertyName("sentences")] public Sentence[]? Sentences { get; set; } -} - -public class Sentence -{ - [JsonPropertyName("id")] public int Id { get; set; } - - [JsonPropertyName("prefix")] public string Prefix { get; set; } = ""; - - [JsonPropertyName("text")] public string Text { get; set; } = ""; -} - -#endregion - - -public class RespError -{ - [JsonPropertyName("code")] public int Code { get; set; } - - [JsonPropertyName("message")] public string Message { get; set; } = ""; } \ No newline at end of file diff --git a/STranslateDLL/Properties/PublishProfiles/FolderProfile.pubxml b/STranslateDLL/Properties/PublishProfiles/FolderProfile.pubxml index bd12f79..9302972 100644 --- a/STranslateDLL/Properties/PublishProfiles/FolderProfile.pubxml +++ b/STranslateDLL/Properties/PublishProfiles/FolderProfile.pubxml @@ -3,16 +3,16 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - - Release - Any CPU - bin\Release\net8.0\publish\win-x64\ - FileSystem - <_TargetId>Folder - net8.0 - win-x64 - false - false - true - + + Release + Any CPU + bin\Release\net8.0\publish\win-x64\ + FileSystem + <_TargetId>Folder + net8.0 + win-x64 + false + false + true + \ No newline at end of file diff --git a/STranslateDLL/STranslateDLL.csproj b/STranslateDLL/STranslateDLL.csproj index 01a8a42..0e391f9 100644 --- a/STranslateDLL/STranslateDLL.csproj +++ b/STranslateDLL/STranslateDLL.csproj @@ -1,14 +1,14 @@  - - net8.0 - enable - enable - localmode - + + net8.0 + enable + enable + localmode + - - none - + + none + diff --git a/STranslateDLL/Utilities.cs b/STranslateDLL/Utilities.cs new file mode 100644 index 0000000..0fd3ef1 --- /dev/null +++ b/STranslateDLL/Utilities.cs @@ -0,0 +1,67 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace STranslateDLL; + +public static class Utilities +{ + private static long _nextId; + private static bool _hasInit; + + public static readonly Dictionary TextTypeDic = new() + { + { true, "richtext" }, + { false, "plaintext" } + }; + + private static JsonSerializerOptions GetOptions => + new() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }; + + public static string Serialize(object data) + { + return JsonSerializer.Serialize(data, GetOptions); + } + + public static void Initial() + { + if (_hasInit) return; + + var rand = new Random(); + var number = rand.NextInt64(99999) + 8300000; + _nextId = number * 1000; + + _hasInit = true; + } + + public static bool IsRichText(string text) + { + return text.Contains('<') && text.Contains('>'); + } + + public static long GenerateTimestamp(string texts) + { + long iCount = texts.Split('i').Length - 1; + var ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + return iCount != 0 ? ts - ts % (iCount + 1) + iCount + 1 : ts; + } + + public static long CreateId() + { + return Interlocked.Increment(ref _nextId); + } + + public static string AdjustJsonContent(string json, long id) + { + string method; + if ((id + 3) % 13 == 0 || (id + 5) % 29 == 0) + method = "\"method\" : \""; + else + method = "\"method\": \""; + return json.Replace("\"method\":\"", method); + } +} \ No newline at end of file diff --git a/STranslateDLLTests/LocalModeTests.cs b/STranslateDLLTests/ProgramTests.cs similarity index 73% rename from STranslateDLLTests/LocalModeTests.cs rename to STranslateDLLTests/ProgramTests.cs index 3b5a860..b7aec03 100644 --- a/STranslateDLLTests/LocalModeTests.cs +++ b/STranslateDLLTests/ProgramTests.cs @@ -5,7 +5,7 @@ using Xunit.Abstractions; namespace STranslateDLLTests; -public class LocalModeTests(ITestOutputHelper testOutputHelper) +public class ProgramTests(ITestOutputHelper testOutputHelper) { [Fact()] public async Task ExecuteAsyncTestAsync() @@ -13,7 +13,7 @@ public class LocalModeTests(ITestOutputHelper testOutputHelper) try { var cts = new CancellationTokenSource(); - var ret = await LocalMode.ExecuteAsync("Hello World", "EN", "ZH", cts.Token); + var ret = await Program.ExecuteAsync("Hello World", "EN", "ZH", cts.Token); testOutputHelper.WriteLine(ret); } catch (Exception ex)