main
zggsong 2 weeks ago
parent 44ee45df5b
commit 970e408aa9

@ -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; } = "";
}

@ -1,41 +1,12 @@
using System.IO.Compression; using System.IO.Compression;
using System.Net;
using System.Text; using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace STranslateDLL; namespace STranslateDLL;
public static class LocalMode public static class Program
{ {
private static long _nextId;
private static bool _hasInit;
private static readonly Dictionary<bool, string> 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<string> ExecuteAsync( public static async Task<string> ExecuteAsync(
string content, string content,
string sourceLang = "auto", string sourceLang = "auto",
@ -43,35 +14,37 @@ public static class LocalMode
CancellationToken? token = null CancellationToken? token = null
) )
{ {
if (!_hasInit) Initial(); Utilities.Initial();
var getToken = token ?? CancellationToken.None; var getToken = token ?? CancellationToken.None;
var splitResult = await SplitTextAsync(content, getToken); var splitResult = await SplitTextAsync(content, getToken);
//TODO: Error handling
var splitObj = JsonSerializer.Deserialize<JsonObject>(splitResult); var splitData = JsonNode.Parse(splitResult);
if (sourceLang.Equals("auto", StringComparison.CurrentCultureIgnoreCase) || sourceLang.Equals("")) 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<Job>(); var jobs = new List<Job>();
var chunks = splitObj?["result"]?["texts"]?[0]?["chunks"]; var chunks = splitData?["result"]?["texts"]?[0]?["chunks"];
if (chunks is JsonArray chunkArray) if (chunks is JsonArray chunkArray)
{
for (var i = 0; i < chunkArray.Count; i++) for (var i = 0; i < chunkArray.Count; i++)
{ {
var sentence = chunkArray[i]?["sentences"]?[0]; var sentence = chunkArray[i]?["sentences"]?[0];
var contextBefore = Array.Empty<string>(); var contextBefore = Array.Empty<string>();
var contextAfter = Array.Empty<string>(); var contextAfter = Array.Empty<string>();
if (i > 0) if (i > 0) contextBefore = [chunkArray[i - 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""];
{
contextBefore = [chunkArray[i - 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""];
}
if (i < chunkArray.Count - 1) if (i < chunkArray.Count - 1)
{
contextAfter = [chunkArray[i + 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""]; contextAfter = [chunkArray[i + 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""];
}
var job = new Job var job = new Job
{ {
@ -85,13 +58,12 @@ public static class LocalMode
{ {
Prefix = sentence?["prefix"]?.ToString() ?? "", Prefix = sentence?["prefix"]?.ToString() ?? "",
Text = sentence?["text"]?.ToString() ?? "", Text = sentence?["text"]?.ToString() ?? "",
Id = i + 1, Id = i + 1
} }
] ]
}; };
jobs.Add(job); jobs.Add(job);
} }
}
var hasRegionalVariant = false; var hasRegionalVariant = false;
var targetLangCode = targetLang; var targetLangCode = targetLang;
@ -102,7 +74,7 @@ public static class LocalMode
hasRegionalVariant = true; hasRegionalVariant = true;
} }
var id = CreateId(); var id = Utilities.CreateId();
var reqData = new DeepLRequest var reqData = new DeepLRequest
{ {
Jsonrpc = "2.0", Jsonrpc = "2.0",
@ -123,12 +95,12 @@ public static class LocalMode
}, },
Jobs = jobs.ToArray(), Jobs = jobs.ToArray(),
Priority = 1, Priority = 1,
Timestamp = GenerateTimestamp(content) Timestamp = Utilities.GenerateTimestamp(content)
} }
}; };
var json = JsonSerializer.Serialize(reqData, GetOptions); var json = Utilities.Serialize(reqData);
json = AdjustJsonContent(json, id); json = Utilities.AdjustJsonContent(json, id);
using var client = new HttpClient(); using var client = new HttpClient();
@ -174,7 +146,7 @@ public static class LocalMode
{ {
responseBody = await resp.Content.ReadAsStringAsync(getToken); responseBody = await resp.Content.ReadAsStringAsync(getToken);
} }
var jNode = JsonNode.Parse(responseBody); var jNode = JsonNode.Parse(responseBody);
var data = jNode?["result"]?["translations"]?[0]?["beams"]?[0]?["sentences"]?[0]?["text"]?.ToString(); var data = jNode?["result"]?["translations"]?[0]?["beams"]?[0]?["sentences"]?[0]?["text"]?.ToString();
// data = UnicodeToString(data); // data = UnicodeToString(data);
@ -189,17 +161,17 @@ public static class LocalMode
Data = data ?? error Data = data ?? error
}; };
return JsonSerializer.Serialize(response, GetOptions); return Utilities.Serialize(response);
} }
private static async Task<string> SplitTextAsync(string text, CancellationToken? token) private static async Task<string> SplitTextAsync(string text, CancellationToken? token)
{ {
var id = CreateId(); var id = Utilities.CreateId();
var getToken = token ?? CancellationToken.None; var getToken = token ?? CancellationToken.None;
var requestData = new DeepLRequest var requestData = new DeepLRequest
{ {
Jsonrpc = "2.0", Jsonrpc = "2.0",
Method = "LMT_split_text", Method = "LMT_split_text2",
Id = id, Id = id,
Params = new ReqParams Params = new ReqParams
{ {
@ -212,12 +184,12 @@ public static class LocalMode
SourceLangUserSelected = "auto" SourceLangUserSelected = "auto"
}, },
Texts = [text], Texts = [text],
TextType = TextTypeDic[ /*tagHandling*/false || IsRichText(text)] TextType = Utilities.TextTypeDic[ /*tagHandling*/false || Utilities.IsRichText(text)]
} }
}; };
var json = JsonSerializer.Serialize(requestData, GetOptions); var json = Utilities.Serialize(requestData);
json = AdjustJsonContent(json, id); json = Utilities.AdjustJsonContent(json, id);
using var client = new HttpClient(); using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, var request = new HttpRequestMessage(HttpMethod.Post,
"https://www2.deepl.com/jsonrpc?client=chrome-extension,1.28.0&method=LMT_split_text") "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; 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; } = "";
} }

@ -3,16 +3,16 @@
https://go.microsoft.com/fwlink/?LinkID=208121. https://go.microsoft.com/fwlink/?LinkID=208121.
--> -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Configuration>Release</Configuration> <Configuration>Release</Configuration>
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>bin\Release\net8.0\publish\win-x64\</PublishDir> <PublishDir>bin\Release\net8.0\publish\win-x64\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId> <_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained> <SelfContained>false</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AssemblyName>localmode</AssemblyName> <AssemblyName>localmode</AssemblyName>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType> <DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

@ -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<bool, string> 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);
}
}

@ -5,7 +5,7 @@ using Xunit.Abstractions;
namespace STranslateDLLTests; namespace STranslateDLLTests;
public class LocalModeTests(ITestOutputHelper testOutputHelper) public class ProgramTests(ITestOutputHelper testOutputHelper)
{ {
[Fact()] [Fact()]
public async Task ExecuteAsyncTestAsync() public async Task ExecuteAsyncTestAsync()
@ -13,7 +13,7 @@ public class LocalModeTests(ITestOutputHelper testOutputHelper)
try try
{ {
var cts = new CancellationTokenSource(); 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); testOutputHelper.WriteLine(ret);
} }
catch (Exception ex) catch (Exception ex)
Loading…
Cancel
Save