├── .gitignore ├── LICENSE ├── README.md ├── TsubakiTest ├── FakeApiKeys.cs ├── TestTranslator.cs └── TsubakiTest.csproj ├── TsubakiTranslator.sln └── TsubakiTranslator ├── AboutMePage.xaml ├── AboutMePage.xaml.cs ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── BasicLibrary ├── ClipboardHookHandler.cs ├── FileHandler.cs ├── GDI32.cs ├── GamesConfig.cs ├── HotkeyHandler.cs ├── OcrProgram.cs ├── OtherConfig.cs ├── ProcessHelper.cs ├── ScreenshotHandler.cs ├── SourceTextHandler.cs ├── TTSHandler.cs ├── TextHookHandler.cs ├── TranslateAPIConfig.cs ├── TranslateDataList.cs ├── TranslateHandler.cs ├── User32.cs └── WindowConfig.cs ├── HookResultDisplay.xaml ├── HookResultDisplay.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── OtherSettingPage.xaml ├── OtherSettingPage.xaml.cs ├── Properties ├── PublishProfiles │ └── FolderProfile.pubxml ├── Resources.Designer.cs └── Resources.resx ├── Resources ├── Icon │ ├── Tsubaki.ico │ └── Tsubaki.png └── Textractor │ ├── x64 │ ├── TextractorCLI.exe │ └── texthook.dll │ └── x86 │ ├── TextractorCLI.exe │ └── texthook.dll ├── ScreenshotWindow.xaml ├── ScreenshotWindow.xaml.cs ├── Themes └── ScrollViewerStyle.xaml ├── TranslateAPILibrary ├── AliyunTranslator.cs ├── BaiduTranslator.cs ├── BingTranslator.cs ├── CaiyunTranslator.cs ├── ChatGptTranslator.cs ├── CommonFunction.cs ├── DeepLTranslator.cs ├── IBMTranslator.cs ├── ICiBaTranslator.cs ├── ITranslator.cs ├── TencentTranslator.cs ├── VolcengineTranslator.cs ├── XiaoniuTranslator.cs └── YeekitTranslator.cs ├── TranslateWindow.xaml ├── TranslateWindow.xaml.cs ├── TranslatedResultDisplay.xaml ├── TranslatedResultDisplay.xaml.cs ├── TranslatedResultItem.xaml ├── TranslatedResultItem.xaml.cs ├── TsubakiTranslator.csproj ├── TsubakiTranslator.csproj.user ├── UserConfigPage.xaml ├── UserConfigPage.xaml.cs ├── UserGamePage.xaml ├── UserGamePage.xaml.cs ├── WinStylePage.xaml └── WinStylePage.xaml.cs /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs/ 2 | /TsubakiTranslator/bin/ 3 | /TsubakiTranslator/obj/ 4 | /TsubakiTest/ApiKeys.cs 5 | /TsubakiTest/bin/ 6 | /TsubakiTest/obj/ 7 | 8 | *.user 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 此项目不再维护,推荐使用[LunaTranslator](https://github.com/HIllya51/LunaTranslator)来替代。 2 | 3 | #


TsubakiTranslator
4 |

5 | 6 |

7 | GPL 3.0 LICENSE 8 | Build status 9 |

10 | 11 |

终归还是来了呢。

12 | 13 |

我寻找到的,神明大人。

14 | 15 | 16 | 17 | ## 项目的由来 18 | 19 | 在开发这个翻译器之前,我用过几款其他类似的翻译器,其中最喜欢的就是[YUKI Galgame 翻译器](https://github.com/project-yuki/YUKI),它的简洁和可扩展非常对我的胃口,遗憾的是有各种奇奇怪怪的bug且GUI不够完善,作者也很久没有维护了。因此我产生了在该项目的设计的基础上开发新项目并进行完善的想法。 20 | 21 | ## 项目特点 22 | 23 | - 以Hook方式提取游戏文本,支持32位和64位的游戏。 24 | - 基于.NET 6 + WPF开发。 25 | - 界面采用Material Design设计风格,简洁易用。 26 | - 去除了所有的离线翻译接口,完全采用在线API进行翻译。 27 | - 支持翻译中文和英文两种源语言的游戏。 28 | - 新增了多种类的翻译API,可同时对照翻译。 29 | - 对于Hook文本文字重复的现象,提供了按重复字数去重的功能。 30 | - 对于混乱的Hook文本,提供自定义正则规则进行文本替换的功能。 31 | - 支持翻译剪切板文本并提供文本处理功能。 32 | - 支持Windows 10自带的OCR功能。 33 | 34 | ## 使用方法 35 | - [下载地址](https://github.com/Isayama-Kagura/TsubakiTranslator/releases) 36 | 1. 第一次使用时,先进入设置,选择想要使用的翻译API,填写必要信息。 37 | 2. 打开游戏,点击右上角“进程号打开”,选择需要翻译的游戏进程,填写必要的信息后点“确定”。 38 | 3. 进入到hook文本选择界面,让游戏的文本变化,选择提取文本和游戏文本完全一致的项。 39 | 4. 愉快的进行游戏。 40 | 5. 翻译器正常退出后会保存本次使用翻译器的配置和游戏数据,下次进入游戏打开历史游戏记录,选择对应游戏进程按上述步骤进行翻译。 41 | 6. 当Hook获得的文本有规律的混乱时,支持自定义正则表达式进行文本匹配替换(e.g. aaabbbccc的文本要转换成abc,匹配表达式为`(.){3}`,替换表达式为`$1`)。**注意:正则表达式的匹配和替换的模式遵循C#规范,请认真学习相关格式后再进行配置!!!** 42 | 43 | ## 监视剪切板功能 44 | 监视剪切板功能,使翻译器除了对Hook提取的文本进行翻译,还可以对一些游戏(AGTH提取/RPGMaker/Unity)进行特殊处理后再进行翻译。详情可以去VNR吧找相关教程学习。 45 | 46 | ## 文本转语音(TTS)功能 47 | 通过TTS功能可以播放一些文本的语音。该功能采用目前TTS领域最先进的微软Azure的接口,最接近人类真实的语音语调。使用该功能需要用户自行注册一个Azure免费账号。 48 | 49 | ## 光学字符识别(OCR)功能 50 | 基于Windows 10 UWP自带的OCR接口实现,在Windows 10 Build 10240以上版本的系统可以使用,分为手动截图和选区自动截图。可在翻译界面中点击截图按钮或者按快捷键逐个翻译,或者选定区域后,自动对该区域截图翻译。 51 | 52 | ## 支持的翻译API 53 | 目前有支持的翻译API包括阿里、百度、彩云、DeepL、IBM、爱词霸、腾讯、小牛、火山、Yeekit。 54 | 55 | ## 疑问解答 56 | 57 | Q:为什么我玩xxx游戏时提取不到文本/闪退/卡死/翻译API不正常?
58 | A:这类问题可能是本项目的程序设计有缺陷,也可能是依赖项目的不足。其中提取不到文本时请尝试用管理员权限运行翻译器。如遇这类问题无法解决,请详细描述现象、所做的操作、配置,最好能配图,然后提出issue或者给我发邮件,在我项目范围内的会尽量帮助解决。 59 | 60 | Q:游戏的配置文件保存在哪里?
61 | A:在游戏根目录的`config/`文件夹下,更新软件时可以备份该目录,然后复制到新的翻译器根目录下。 62 | 63 | Q:自动提取的游戏文本混乱怎么办?
64 | A:当文本单字重复时,可设置重复次数进行去重(e.g. aaabbbccc的文本,即重复3次),或自定义正则规则去除杂乱文字,或用其他方法把文本导出至剪切板进行翻译,仍无法解决请尝试使用特殊码。 65 | 66 | 67 | ## 联系作者 68 | 69 | 如对本项目有任何建议或疑问,可提出issue或者发送邮件至`isayama_kagura@qq.com`。 70 | 71 | ## 依赖的项目 72 | - [Textractor](https://github.com/Artikash/Textractor) 73 | - [MaterialDesignInXamlToolkit](https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit) 74 | - [Windows Community Toolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit) 75 | - [RestSharp](https://github.com/restsharp/RestSharp) 76 | 77 | ## 部分代码参考 78 | - [御坂翻译器](https://github.com/hanmin0822/MisakaTranslator) 79 | - [YUKI Galgame 翻译器](https://github.com/project-yuki/YUKI) 80 | 81 | -------------------------------------------------------------------------------- /TsubakiTest/FakeApiKeys.cs: -------------------------------------------------------------------------------- 1 | namespace TsubakiTest 2 | { 3 | public static class FakeApiKeys 4 | { 5 | public const string ChatGptKey = "api token"; 6 | } 7 | } -------------------------------------------------------------------------------- /TsubakiTest/TestTranslator.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using TsubakiTranslator.TranslateAPILibrary; 3 | 4 | namespace TsubakiTest 5 | { 6 | public class Tests 7 | { 8 | string translateWithTranslator(ITranslator translator,int index, string param1, string param2, string content) 9 | { 10 | translator.TranslatorInit(index, param1, param2); 11 | return translator.Translate(content); 12 | } 13 | 14 | [Test] 15 | public void TestChatGpt() 16 | { 17 | // var content = @"宇宙に始まりはあるが、終わりはない。---無限 18 | //星にもまた始まりはあるが、自らの力をもって滅び逝く。---有限"; 19 | // var translator = new ChatGptTranslator { 20 | // SourceLanguage = "Japanese" 21 | // }; 22 | //var result = translateWithTranslator(translator, 23 | // ApiKeys.ChatGptKey, 24 | // "", 25 | // content); 26 | 27 | /* 28 | * a possible result is: 29 | * 宇宙有开端,但没有终点。---无限 30 | * 星辰也有起源,但注定会因自身力量的消耗而灭亡。---有限 31 | */ 32 | //Console.WriteLine(result); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /TsubakiTest/TsubakiTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0-windows10.0.17763.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /TsubakiTranslator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33103.184 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TsubakiTranslator", "TsubakiTranslator\TsubakiTranslator.csproj", "{75F37E85-1AA1-45AB-AC0A-5443B01015F9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {75F37E85-1AA1-45AB-AC0A-5443B01015F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {75F37E85-1AA1-45AB-AC0A-5443B01015F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {75F37E85-1AA1-45AB-AC0A-5443B01015F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {75F37E85-1AA1-45AB-AC0A-5443B01015F9}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {E5AC39DD-C3B6-414E-A242-297F794125E0} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /TsubakiTranslator/AboutMePage.xaml: -------------------------------------------------------------------------------- 1 |  9 | 12 | 13 | 14 | 15 | 16 | 19 | 22 | 23 | 26 | 27 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TsubakiTranslator/AboutMePage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | 5 | namespace TsubakiTranslator 6 | { 7 | /// 8 | /// AboutMePage.xaml 的交互逻辑 9 | /// 10 | public partial class AboutMePage : UserControl 11 | { 12 | public AboutMePage() 13 | { 14 | InitializeComponent(); 15 | } 16 | 17 | private void GitHubButton_OnClick(object sender, RoutedEventArgs e) 18 | { 19 | 20 | // 激活的是当前默认的浏览器 21 | Process.Start("explorer.exe", "https://github.com/Isayama-Kagura/TsubakiTranslator"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TsubakiTranslator/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TsubakiTranslator/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Linq; 3 | using System.Windows; 4 | using TsubakiTranslator.BasicLibrary; 5 | 6 | namespace TsubakiTranslator 7 | { 8 | /// 9 | /// Interaction logic for App.xaml 10 | /// 11 | public partial class App : Application 12 | { 13 | private readonly string baseDir = System.AppDomain.CurrentDomain.BaseDirectory; 14 | 15 | public static WindowConfig WindowConfig { get; private set; } 16 | 17 | public static GamesConfig GamesConfig { get; private set; } 18 | 19 | public static TranslateAPIConfig TranslateAPIConfig { get; private set; } 20 | 21 | public static OtherConfig OtherConfig { get; private set; } 22 | 23 | protected override void OnStartup(StartupEventArgs e) 24 | { 25 | base.OnStartup(e); 26 | 27 | WindowConfig = FileHandler.DeserializeObject( 28 | baseDir + @"config/WindowConfig.json" 29 | ) ?? new WindowConfig(); 30 | 31 | GamesConfig = FileHandler.DeserializeObject( 32 | baseDir + @"config/GamesData.json" 33 | ) ?? new GamesConfig(); 34 | 35 | TranslateAPIConfig = 36 | FileHandler.DeserializeObject(baseDir + 37 | @"config/APIConfig.json") ?? 38 | new TranslateAPIConfig(); 39 | 40 | OtherConfig = FileHandler.DeserializeObject( 41 | baseDir + @"config/OtherConfig.json" 42 | ) ?? new OtherConfig(); 43 | 44 | var processes = Process.GetProcessesByName("TsubakiTranslator") 45 | .Where(proc => proc.Id != System.Environment.ProcessId); 46 | foreach (var proc in processes) 47 | { 48 | try 49 | { 50 | proc.Kill(); 51 | } 52 | catch 53 | { 54 | // ignored 55 | } 56 | } 57 | } 58 | 59 | protected override void OnExit(ExitEventArgs e) 60 | { 61 | FileHandler.SerializeObject(WindowConfig, 62 | baseDir + @"config/WindowConfig.json"); 63 | FileHandler.SerializeObject(GamesConfig, 64 | baseDir + @"config/GamesData.json"); 65 | FileHandler.SerializeObject(TranslateAPIConfig, 66 | baseDir + @"config/APIConfig.json"); 67 | FileHandler.SerializeObject(OtherConfig, 68 | baseDir + @"config/OtherConfig.json"); 69 | 70 | base.OnExit(e); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /TsubakiTranslator/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/ClipboardHookHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Interop; 4 | 5 | namespace TsubakiTranslator.BasicLibrary 6 | { 7 | public class ClipboardHookHandler : IDisposable 8 | { 9 | const int WM_CLIPBOARDUPDATE = 0x031D; 10 | 11 | 12 | 13 | HwndSource _hwndSource; 14 | 15 | public void Dispose() 16 | { 17 | _hwndSource.RemoveHook(new HwndSourceHook(OnHooked)); 18 | User32.RemoveClipboardFormatListener(_hwndSource.Handle); 19 | //_hwndSource?.Dispose(); 20 | } 21 | 22 | public ClipboardHookHandler(Window window) 23 | { 24 | WindowInteropHelper helper = new WindowInteropHelper(window); 25 | _hwndSource = HwndSource.FromHwnd(helper.Handle); 26 | bool r = User32.AddClipboardFormatListener(_hwndSource.Handle); 27 | if (r) 28 | { 29 | _hwndSource.AddHook(new HwndSourceHook(OnHooked)); 30 | } 31 | } 32 | 33 | private IntPtr OnHooked(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 34 | { 35 | if (msg == WM_CLIPBOARDUPDATE) 36 | { 37 | ClipboardUpdated?.Invoke(this, EventArgs.Empty); 38 | return IntPtr.Zero; 39 | } 40 | return IntPtr.Zero; 41 | } 42 | 43 | public event EventHandler ClipboardUpdated; 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/FileHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.Json; 4 | using System.Windows.Forms; 5 | 6 | namespace TsubakiTranslator.BasicLibrary 7 | { 8 | class FileHandler 9 | { 10 | public static string SelectFolderPath() 11 | { 12 | string path = ""; 13 | FolderBrowserDialog dialog = new FolderBrowserDialog(); 14 | dialog.Description = "请选择文件夹作为路径"; 15 | if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) 16 | { 17 | path = dialog.SelectedPath; // "e:/go" 18 | } 19 | 20 | return path; 21 | 22 | } 23 | 24 | public static void SerializeObject(T value, string path) 25 | { 26 | var jsonString = JsonSerializer.SerializeToUtf8Bytes(value); 27 | try 28 | { 29 | using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write)) 30 | { 31 | fs.Write(jsonString, 0, jsonString.Length); 32 | } 33 | } 34 | catch (Exception e) 35 | { 36 | System.Windows.MessageBox.Show(e.Message); 37 | } 38 | } 39 | 40 | public static T DeserializeObject(string path) 41 | { 42 | T result = default(T); 43 | if (CreateFile(path)) 44 | return result; 45 | 46 | try 47 | { 48 | using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) 49 | { 50 | byte[] byteArray = new byte[fs.Length]; 51 | fs.Read(byteArray, 0, byteArray.Length); 52 | var utf8Reader = new Utf8JsonReader(byteArray); 53 | result = JsonSerializer.Deserialize(ref utf8Reader); 54 | } 55 | 56 | } 57 | catch (Exception e) 58 | { 59 | System.Windows.MessageBox.Show(e.Message); 60 | } 61 | 62 | return result; 63 | } 64 | 65 | public static bool CreateFile(string path) 66 | { 67 | string directory = System.IO.Path.GetDirectoryName(path); 68 | bool flag = false; 69 | 70 | if (!Directory.Exists(directory)) // 返回bool类型,存在返回true,不存在返回false 71 | { 72 | Directory.CreateDirectory(directory); //不存在则创建路径 73 | } 74 | 75 | if (!File.Exists(path)) // 返回bool类型,存在返回true,不存在返回false 76 | { 77 | File.Create(path).Close(); //不存在则创建文件 78 | flag = true; 79 | } 80 | 81 | return flag; 82 | 83 | } 84 | 85 | public static void AppendTextToFile(string text, string path) 86 | { 87 | using (StreamWriter sw = File.AppendText(path)) 88 | { 89 | sw.WriteLine(text); 90 | } 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/GDI32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace TsubakiTranslator.BasicLibrary 5 | { 6 | public static class GDI32 7 | { 8 | #region Enums 9 | /// 10 | /// Specifies a raster-operation code. These codes define how the color data for the 11 | /// source rectangle is to be combined with the color data for the destination 12 | /// rectangle to achieve the final color. 13 | /// 14 | public enum TernaryRasterOperations : uint 15 | { 16 | /// dest = source 17 | SRCCOPY = 0x00CC0020, 18 | /// dest = source OR dest 19 | SRCPAINT = 0x00EE0086, 20 | /// dest = source AND dest 21 | SRCAND = 0x008800C6, 22 | /// dest = source XOR dest 23 | SRCINVERT = 0x00660046, 24 | /// dest = source AND (NOT dest) 25 | SRCERASE = 0x00440328, 26 | /// dest = (NOT source) 27 | NOTSRCCOPY = 0x00330008, 28 | /// dest = (NOT src) AND (NOT dest) 29 | NOTSRCERASE = 0x001100A6, 30 | /// dest = (source AND pattern) 31 | MERGECOPY = 0x00C000CA, 32 | /// dest = (NOT source) OR dest 33 | MERGEPAINT = 0x00BB0226, 34 | /// dest = pattern 35 | PATCOPY = 0x00F00021, 36 | /// dest = DPSnoo 37 | PATPAINT = 0x00FB0A09, 38 | /// dest = pattern XOR dest 39 | PATINVERT = 0x005A0049, 40 | /// dest = (NOT dest) 41 | DSTINVERT = 0x00550009, 42 | /// dest = BLACK 43 | BLACKNESS = 0x00000042, 44 | /// dest = WHITE 45 | WHITENESS = 0x00FF0062, 46 | /// 47 | /// Capture window as seen on screen. This includes layered windows 48 | /// such as WPF windows with AllowsTransparency="true" 49 | /// 50 | CAPTUREBLT = 0x40000000 51 | } 52 | #endregion 53 | 54 | #region DLL Imports 55 | 56 | [DllImport("gdi32.dll", EntryPoint = "BitBlt", SetLastError = true)] 57 | [return: MarshalAs(UnmanagedType.Bool)] 58 | public static extern bool BitBlt([In] IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, [In] IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop); 59 | #endregion 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/GamesConfig.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace TsubakiTranslator.BasicLibrary 5 | { 6 | public partial class RegexRuleData : ObservableObject 7 | { 8 | [ObservableProperty] 9 | private string sourceRegex; 10 | [ObservableProperty] 11 | private string destinationRegex; 12 | 13 | public RegexRuleData(string sourceRegex, string destinationRegex) 14 | { 15 | this.sourceRegex = sourceRegex; 16 | this.destinationRegex = destinationRegex; 17 | } 18 | 19 | } 20 | 21 | public partial class GameData : ObservableObject 22 | { 23 | [ObservableProperty] 24 | /// 25 | /// 游戏名(非进程名,但在游戏名未知的情况下先使用进程名替代) 26 | /// 27 | private string gameName; 28 | 29 | [ObservableProperty] 30 | /// 31 | /// 游戏名(非进程名,但在游戏名未知的情况下先使用进程名替代) 32 | /// 33 | private string processName; 34 | 35 | [ObservableProperty] 36 | /// 37 | /// 特殊码值,仅在hook模式有效 38 | /// 39 | private string hookCode; 40 | 41 | [ObservableProperty] 42 | /// 43 | /// 文本重复次数 44 | /// 45 | private int duplicateTimes; 46 | 47 | public ObservableCollection RegexRuleItems { get; set; } 48 | 49 | public GameData() 50 | { 51 | RegexRuleItems = new ObservableCollection(); 52 | } 53 | 54 | } 55 | 56 | public partial class GamesConfig : ObservableObject 57 | { 58 | [ObservableProperty] 59 | private ObservableCollection gameDatas; 60 | [ObservableProperty] 61 | private ObservableCollection clipBoardRegexRules; 62 | 63 | 64 | public GamesConfig() 65 | { 66 | GameDatas = new ObservableCollection(); 67 | ClipBoardRegexRules = new ObservableCollection(); 68 | } 69 | 70 | 71 | 72 | } 73 | } -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/HotkeyHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TsubakiTranslator.BasicLibrary 4 | { 5 | public class HotkeyHandler 6 | { 7 | private IntPtr mainFormHandle; 8 | public IntPtr MainFormHandle { get => mainFormHandle; } 9 | 10 | private int id = 856; 11 | private byte modifiers; 12 | private int key; 13 | 14 | public int Id { get => id; } 15 | public void RegisterHotKey(IntPtr mainFormHandle, ScreenshotHotkey hotkey) 16 | { 17 | this.mainFormHandle = mainFormHandle; 18 | 19 | modifiers = hotkey.Modifiers; 20 | key = hotkey.Key; 21 | 22 | if (key != 0) 23 | { 24 | hotkey.Conflict = !User32.RegisterHotKey(mainFormHandle, id, modifiers, key); 25 | } 26 | 27 | } 28 | 29 | public void UnRegisterHotKey() 30 | { 31 | User32.UnregisterHotKey(mainFormHandle, id); 32 | } 33 | 34 | //public void ReRegisterHotKey(ScreenshotHotkey hotkey) 35 | //{ 36 | // if (key == 0) 37 | // { 38 | // User32.UnregisterHotKey(mainFormHandle, id); 39 | // } 40 | // else if (modifiers != hotkey.Modifiers || key != hotkey.Key) 41 | // { 42 | // User32.UnregisterHotKey(mainFormHandle, id); 43 | // hotkey.Conflict = !User32.RegisterHotKey(mainFormHandle, id, hotkey.Modifiers, hotkey.Key); 44 | // } 45 | // modifiers = hotkey.Modifiers; 46 | // key = hotkey.Key; 47 | 48 | //} 49 | 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/OcrProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.Versioning; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Interop; 9 | using System.Windows.Media.Imaging; 10 | using Windows.Globalization; 11 | using Windows.Graphics.Imaging; 12 | using Windows.Media.Ocr; 13 | 14 | namespace TsubakiTranslator.BasicLibrary 15 | { 16 | public class OcrProgram 17 | { 18 | private string language; 19 | 20 | 21 | public OcrProgram(int srcLangIndex) 22 | { 23 | if (srcLangIndex == (int)ConstantValues.Language.Japanese) 24 | { 25 | language = "ja"; 26 | } 27 | else if (srcLangIndex == (int)ConstantValues.Language.English) 28 | { 29 | language = "en-US"; 30 | } 31 | } 32 | 33 | [SupportedOSPlatform("windows10.0.10240")] 34 | public static System.Collections.Generic.IEnumerable GetSupportedLanguages() 35 | { 36 | 37 | //Console.WriteLine("Supported languages:"); 38 | var result = from lang in OcrEngine.AvailableRecognizerLanguages 39 | where lang.LanguageTag.Equals("ja") || lang.LanguageTag.Equals("en-US") 40 | select lang.DisplayName; 41 | 42 | return result; 43 | 44 | } 45 | 46 | [SupportedOSPlatform("windows10.0.10240")] 47 | public async Task RecognizeAsync(Bitmap CaptureBitmap) 48 | { 49 | //Bitmap to BitmapSource 50 | BitmapSource bitmapSource; 51 | try 52 | { 53 | bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(CaptureBitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); 54 | } 55 | catch 56 | { 57 | bitmapSource = default; 58 | } 59 | 60 | //BitmapSource to SoftwareBitmap 61 | // 画像データをメモリストリームへ書き出し 62 | PngBitmapEncoder encoder = new PngBitmapEncoder(); 63 | encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bitmapSource)); 64 | MemoryStream temp_stream = new MemoryStream(); 65 | encoder.Save(temp_stream); 66 | 67 | // メモリストリームを変換 68 | var converted_stream = WindowsRuntimeStreamExtensions.AsRandomAccessStream(temp_stream); 69 | 70 | // メモリストリームからOCR用画像データの生成 71 | var decorder = await Windows.Graphics.Imaging.BitmapDecoder.CreateAsync(converted_stream); 72 | SoftwareBitmap softwareBitmap = await decorder.GetSoftwareBitmapAsync(); 73 | converted_stream.Dispose(); 74 | temp_stream.Close(); 75 | 76 | 77 | Language lang = new Language(language); 78 | string space = language.Contains("zh") || language.Contains("ja") ? "" : " "; 79 | string result = null; 80 | if (OcrEngine.IsLanguageSupported(lang)) 81 | { 82 | OcrEngine engine = OcrEngine.TryCreateFromLanguage(lang); 83 | if (engine != null) 84 | { 85 | OcrResult ocrResult = await engine.RecognizeAsync(softwareBitmap); 86 | foreach (var tempLine in ocrResult.Lines) 87 | { 88 | string line = ""; 89 | foreach (var word in tempLine.Words) 90 | { 91 | line += word.Text + space; 92 | } 93 | //result += line + Environment.NewLine; 94 | result += line; 95 | } 96 | } 97 | } 98 | else 99 | { 100 | throw new Exception(string.Format("Language {0} is not supported", language)); 101 | }; 102 | softwareBitmap.Dispose(); 103 | return await Task.Run(() => 104 | { 105 | return result; 106 | }); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/OtherConfig.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using System; 3 | 4 | namespace TsubakiTranslator.BasicLibrary 5 | { 6 | public class ConstantValues 7 | { 8 | public enum Language { Japanese, English }; 9 | } 10 | 11 | public partial class OtherConfig : ObservableObject 12 | { 13 | [ObservableProperty] 14 | private bool isAutoScreenshot = false; 15 | [ObservableProperty] 16 | private ScreenshotHotkey screenshotHotkey = new ScreenshotHotkey(); 17 | [ObservableProperty] 18 | private int interval = 3; 19 | [ObservableProperty] 20 | private int sourceLangIndex = 0; 21 | [ObservableProperty] 22 | private bool saveLogEnabled = false; 23 | [ObservableProperty] 24 | private string logFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 25 | 26 | } 27 | public partial class ScreenshotHotkey : ObservableObject 28 | { 29 | [ObservableProperty] 30 | private byte modifiers = 0; 31 | [ObservableProperty] 32 | private int key = 115; 33 | [ObservableProperty] 34 | private string text = "F4"; 35 | [ObservableProperty] 36 | private bool conflict = false; 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/ProcessHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace TsubakiTranslator.BasicLibrary 7 | { 8 | public class ProcessHelper 9 | { 10 | 11 | [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] 12 | [return: MarshalAs(UnmanagedType.Bool)] 13 | internal static extern bool IsWow64Process([In] IntPtr process, [Out] out bool wow64Process); 14 | 15 | /// 16 | /// 获得当前系统进程列表 形式:直接用于显示的字串和进程PID 17 | /// 18 | /// 19 | public static LinkedList GetProcessList() 20 | { 21 | LinkedList list = new LinkedList(); 22 | 23 | //获取系统进程列表 24 | Process[] ps = Process.GetProcesses(); 25 | foreach (Process p in ps) 26 | { 27 | if (p.MainWindowHandle != IntPtr.Zero) 28 | { 29 | string info = p.Id + " — " + p.ProcessName; 30 | list.AddLast(info); 31 | } 32 | } 33 | return list; 34 | } 35 | 36 | 37 | public static bool IsWinX86(Process process) 38 | { 39 | 40 | IntPtr processHandle; 41 | bool retVal; 42 | 43 | try 44 | { 45 | processHandle = Process.GetProcessById(process.Id).Handle; 46 | } 47 | catch 48 | { 49 | return false; 50 | } 51 | return IsWow64Process(processHandle, out retVal) && retVal; 52 | 53 | } 54 | 55 | 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/ScreenshotHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | using System.Windows; 5 | using System.Windows.Forms; 6 | 7 | namespace TsubakiTranslator.BasicLibrary 8 | { 9 | public static class ScreenshotHandler 10 | { 11 | public static Bitmap GetCapture(Rect CaptureRegion) 12 | { 13 | var bitmap = new Bitmap((int)CaptureRegion.Width, (int)CaptureRegion.Height); 14 | var graphic = Graphics.FromImage(bitmap); 15 | var screen = SystemInformation.VirtualScreen; 16 | 17 | IntPtr hWnd = IntPtr.Zero; 18 | IntPtr hDC = IntPtr.Zero; 19 | IntPtr graphDC = IntPtr.Zero; 20 | try 21 | { 22 | hWnd = User32.GetDesktopWindow(); 23 | hDC = User32.GetWindowDC(hWnd); 24 | graphDC = graphic.GetHdc(); 25 | var copyResult = GDI32.BitBlt(graphDC, 0, 0, (int)CaptureRegion.Width, (int)CaptureRegion.Height, hDC, (int)CaptureRegion.Left, (int)CaptureRegion.Top, GDI32.TernaryRasterOperations.SRCCOPY | GDI32.TernaryRasterOperations.CAPTUREBLT); 26 | if (!copyResult) 27 | { 28 | throw new Exception("Screen capture failed."); 29 | } 30 | graphic.ReleaseHdc(graphDC); 31 | User32.ReleaseDC(hWnd, hDC); 32 | 33 | // Get cursor information to draw on the screenshot. 34 | //var ci = new User32.CursorInfo(); 35 | //ci.cbSize = Marshal.SizeOf(ci); 36 | //User32.GetCursorInfo(out ci); 37 | //if (ci.flags == User32.CURSOR_SHOWING) 38 | //{ 39 | // using (var icon = System.Drawing.Icon.FromHandle(ci.hCursor)) 40 | // { 41 | // graphic.DrawIcon(icon, (int)(ci.ptScreenPos.x - screen.Left - CaptureRegion.Left), (int)(ci.ptScreenPos.y - screen.Top - CaptureRegion.Top)); 42 | // } 43 | //} 44 | 45 | } 46 | catch (Exception ex) 47 | { 48 | graphic.ReleaseHdc(graphDC); 49 | User32.ReleaseDC(hWnd, hDC); 50 | //throw ex; 51 | System.Windows.MessageBox.Show(ex.Message, "GetCapture Function Error", MessageBoxButton.OK, MessageBoxImage.Error); 52 | } 53 | return bitmap; 54 | } 55 | 56 | 57 | public static bool ImageBase64Compare(Bitmap firstImage, Bitmap secondImage) 58 | { 59 | using (MemoryStream ms = new MemoryStream()) 60 | { 61 | firstImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png); 62 | String firstBitmap = Convert.ToBase64String(ms.ToArray()); 63 | ms.Position = 0; 64 | 65 | secondImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png); 66 | String secondBitmap = Convert.ToBase64String(ms.ToArray()); 67 | 68 | if (firstBitmap.Equals(secondBitmap)) 69 | { 70 | return true; 71 | } 72 | else 73 | { 74 | return false; 75 | } 76 | }; 77 | 78 | } 79 | 80 | //public static void SaveCapture(Bitmap CaptureBitmap) 81 | //{ 82 | // System.Windows.Forms.Clipboard.SetImage(CaptureBitmap); 83 | 84 | //} 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/SourceTextHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace TsubakiTranslator.BasicLibrary 6 | { 7 | public class SourceTextHandler 8 | { 9 | public int DuplicateTimes { get; } 10 | public LinkedList RegexRules { get; } 11 | 12 | public SourceTextHandler(int duplicateTimes, LinkedList regexRules) 13 | { 14 | DuplicateTimes = duplicateTimes; 15 | RegexRules = regexRules; 16 | } 17 | 18 | public string HandleText(string text) 19 | { 20 | string result = text; 21 | 22 | if (DuplicateTimes >= 2) 23 | { 24 | StringBuilder sb = new StringBuilder(); 25 | for (int i = 0; i < text.Length; i += DuplicateTimes) 26 | sb.Append(text[i]); 27 | result = sb.ToString(); 28 | } 29 | 30 | if (RegexRules.Count != 0) 31 | { 32 | foreach (RegexRuleData rule in RegexRules) 33 | if (rule.SourceRegex.Trim().Length != 0) 34 | result = Regex.Replace(result, rule.SourceRegex, rule.DestinationRegex); 35 | } 36 | 37 | return result; 38 | } 39 | 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/TTSHandler.cs: -------------------------------------------------------------------------------- 1 | using RestSharp; 2 | using System; 3 | using System.IO; 4 | using System.Media; 5 | using System.Threading.Tasks; 6 | using System.Timers; 7 | using TsubakiTranslator.TranslateAPILibrary; 8 | 9 | namespace TsubakiTranslator.BasicLibrary 10 | { 11 | public class TTSHandler 12 | { 13 | private string Region { get; } 14 | private string Key { get; } 15 | 16 | private string Language { get; } 17 | 18 | private string VoiceName { get; } 19 | 20 | private Timer RequestTokenTimer { get; } 21 | private string Token { get; set; } 22 | 23 | private RestClient Client { get; } 24 | 25 | private string errorMessage; 26 | public string ErrorMessage { get => errorMessage; } 27 | 28 | public TTSHandler(string region, string key, string language, string voiceName) 29 | { 30 | Region = region; 31 | Key = key; 32 | Language = language; 33 | VoiceName = voiceName; 34 | 35 | Client = new RestClient(CommonFunction.Client); 36 | 37 | //每9分钟请求一次 38 | int interval = 9 * 60 * 1000; 39 | RequestTokenTimer = new Timer(interval); 40 | RequestTokenTimer.AutoReset = true; 41 | RequestTokenTimer.Elapsed += OnTimedEvent; 42 | 43 | OnTimedEvent(null, null); 44 | RequestTokenTimer.Start(); 45 | 46 | } 47 | 48 | public async Task SpeakTextAsync(string text) 49 | { 50 | var request = new RestRequest($"https://{Region}.tts.speech.microsoft.com/cognitiveservices/v1", Method.Post); 51 | request.AddHeader("Authorization", "Bearer " + Token); 52 | request.AddHeader("X-Microsoft-OutputFormat", "riff-24khz-16bit-mono-pcm"); 53 | //request.AddHeader("content-type", "application/ssml+xml"); 54 | 55 | string bodyString = @$"{text} "; 56 | 57 | //request.AddParameter("application/ssml+xml", bodyString, ParameterType.RequestBody); 58 | //request.AddXmlBody(bodyString); 59 | request.AddStringBody(bodyString, "application/ssml+xml"); 60 | 61 | RestResponse response = await Client.ExecuteAsync(request); 62 | 63 | if (response.StatusCode == System.Net.HttpStatusCode.OK) 64 | { 65 | byte[] result = response.RawBytes; 66 | using (MemoryStream ms = new MemoryStream(result)) 67 | { 68 | // Construct the sound player 69 | SoundPlayer player = new SoundPlayer(ms); 70 | player.Play(); 71 | } 72 | return true; 73 | } 74 | else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) 75 | { 76 | OnTimedEvent(null, null); 77 | errorMessage = response.ErrorException.Message; 78 | return false; 79 | } 80 | else 81 | { 82 | errorMessage = response.ErrorException.Message; 83 | return false; 84 | } 85 | 86 | } 87 | 88 | private void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e) 89 | { 90 | var request = new RestRequest($"https://{Region}.api.cognitive.microsoft.com/sts/v1.0/issuetoken", Method.Post); 91 | request.AddHeader("Ocp-Apim-Subscription-Key", Key); 92 | 93 | var response = Client.Execute(request); 94 | 95 | Token = response.Content; 96 | 97 | } 98 | 99 | public void Dispose() 100 | { 101 | RequestTokenTimer.Stop(); 102 | RequestTokenTimer.Dispose(); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/TextHookHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TsubakiTranslator.BasicLibrary 8 | { 9 | public class TextHookHandler 10 | { 11 | /// 12 | /// Textractor进程 13 | /// 14 | private Process processTextractor; 15 | public Process ProcessTextractor { get => processTextractor; } 16 | 17 | private Process processGame; 18 | public Process ProcessGame { get => processGame; } 19 | 20 | 21 | public HashSet SelectedHookCode { get; set; } = new HashSet(); 22 | 23 | public TextHookHandler(Process p, string hookCode) 24 | { 25 | Init(p, hookCode); 26 | } 27 | 28 | 29 | ~TextHookHandler() 30 | { 31 | CloseTextractor(); 32 | } 33 | 34 | /// 35 | /// 初始化Textractor,建立CLI与本软件间的通信 36 | /// 37 | /// 成功返回真,失败返回假 38 | public async void Init(Process gameProcess, string hookCode) 39 | { 40 | bool isX86 = ProcessHelper.IsWinX86(gameProcess); 41 | 42 | //当游戏退出时可以获得退出事件。 43 | processGame = gameProcess; 44 | processGame.EnableRaisingEvents = true; 45 | 46 | string path = Environment.CurrentDirectory + @"\Resources\Textractor\" + (isX86 ? "x86" : "x64") + @"\TextractorCLI.exe";//打开对应的Textractor路径 47 | 48 | ProcessStartInfo processStartInfo = new ProcessStartInfo() 49 | { 50 | UseShellExecute = false, 51 | CreateNoWindow = true, 52 | StandardOutputEncoding = Encoding.Unicode, 53 | StandardInputEncoding = new UnicodeEncoding(false, false), 54 | FileName = path, 55 | RedirectStandardInput = true, 56 | RedirectStandardOutput = true, 57 | RedirectStandardError = true 58 | }; 59 | 60 | processTextractor = Process.Start(processStartInfo); 61 | await AttachProcess(); 62 | 63 | if (hookCode != null) 64 | await AttachProcessByHookCode(hookCode); 65 | 66 | ProcessTextractor.BeginOutputReadLine(); 67 | 68 | } 69 | 70 | /// 71 | /// 向Textractor CLI写入命令 72 | /// 注入进程 73 | /// 74 | /// 75 | private async Task AttachProcess() 76 | { 77 | //ProcessTextractor.StandardInput.WriteLine("attach -P" + GamePID); 78 | //Console.Write("attach -P" + GamePID); 79 | 80 | //适用多个同名进程的情况,只在通过进程启动有效。 81 | Process[] processes = Process.GetProcessesByName(ProcessGame.ProcessName); 82 | 83 | foreach (Process process in processes) 84 | { 85 | await ProcessTextractor.StandardInput.WriteLineAsync("attach -P" + process.Id); 86 | await ProcessTextractor.StandardInput.FlushAsync(); 87 | } 88 | 89 | } 90 | 91 | /// 92 | /// 向Textractor CLI写入命令 93 | /// 结束注入进程 94 | /// 95 | /// 96 | private async Task DetachProcess() 97 | { 98 | //适用多个同名进程的情况,只在通过进程启动有效。 99 | Process[] processes = Process.GetProcessesByName(ProcessGame.ProcessName); 100 | foreach (Process process in processes) 101 | { 102 | await ProcessTextractor.StandardInput.WriteLineAsync("detach -P" + process.Id); 103 | await ProcessTextractor.StandardInput.FlushAsync(); 104 | } 105 | } 106 | 107 | /// 108 | /// 向Textractor CLI写入命令 109 | /// 给定特殊码注入,由Textractor作者指导方法 110 | /// 111 | /// 112 | private async Task AttachProcessByHookCode(string hookCode) 113 | { 114 | //解决有hookcode时莫名的报错。 115 | //await Task.Delay(10); 116 | 117 | Process[] processes = Process.GetProcessesByName(ProcessGame.ProcessName); 118 | foreach (Process process in processes) 119 | { 120 | await ProcessTextractor.StandardInput.WriteLineAsync(hookCode + " -P" + process.Id); 121 | await ProcessTextractor.StandardInput.FlushAsync(); 122 | } 123 | } 124 | 125 | /// 126 | /// 关闭Textractor进程,关闭前Detach所有Hook 127 | /// 128 | public async void CloseTextractor() 129 | { 130 | /* 131 | * TODO:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 132 | * 这里如果进程退出会出现异常 133 | */ 134 | if (ProcessTextractor != null && ProcessTextractor.HasExited == false) 135 | { 136 | await DetachProcess(); 137 | ProcessTextractor.Kill(); 138 | 139 | } 140 | 141 | processTextractor = null; 142 | } 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/TranslateAPIConfig.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace TsubakiTranslator.BasicLibrary 4 | { 5 | public partial class TranslateAPIConfig : ObservableObject 6 | { 7 | [ObservableProperty] 8 | private bool ttsIsEnabled; 9 | [ObservableProperty] 10 | private string ttsRegion; 11 | [ObservableProperty] 12 | private string ttsResourceKey; 13 | 14 | [ObservableProperty] 15 | private bool aliIsEnabled; 16 | [ObservableProperty] 17 | private string aliSecretId; 18 | [ObservableProperty] 19 | private string aliSecretKey; 20 | 21 | [ObservableProperty] 22 | private bool baiduIsEnabled; 23 | [ObservableProperty] 24 | private string baiduAppID; 25 | [ObservableProperty] 26 | private string baiduSecretKey; 27 | 28 | [ObservableProperty] 29 | private bool bingIsEnabled; 30 | 31 | [ObservableProperty] 32 | private bool caiyunIsEnabled; 33 | [ObservableProperty] 34 | private string caiyunToken; 35 | 36 | [ObservableProperty] 37 | private bool chatGptIsEnabled; 38 | [ObservableProperty] 39 | private string chatGptToken; 40 | 41 | [ObservableProperty] 42 | private bool deeplIsEnabled; 43 | [ObservableProperty] 44 | private bool deeplIsFreeApi; 45 | [ObservableProperty] 46 | private string deeplSecretKey; 47 | 48 | [ObservableProperty] 49 | private bool ibmIsEnabled; 50 | 51 | [ObservableProperty] 52 | private bool iCiBaIsEnabled; 53 | 54 | [ObservableProperty] 55 | private bool tencentIsEnabled; 56 | [ObservableProperty] 57 | private string tencentSecretID; 58 | [ObservableProperty] 59 | private string tencentSecretKey; 60 | 61 | [ObservableProperty] 62 | private bool xiaoniuIsEnabled; 63 | [ObservableProperty] 64 | private string xiaoniuApiKey; 65 | 66 | [ObservableProperty] 67 | private bool volcengineIsEnabled; 68 | 69 | [ObservableProperty] 70 | private bool yeekitIsEnabled; 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/TranslateDataList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using TsubakiTranslator.BasicLibrary; 6 | 7 | namespace TsubakiTranslator 8 | { 9 | //存储单次翻译的结果数据 10 | public class TranslateData 11 | { 12 | public string SourceText { get; } 13 | public Dictionary ResultText { get; } 14 | 15 | public TranslateData(string sourceText, Dictionary resultText) 16 | { 17 | SourceText = sourceText; 18 | ResultText = resultText; 19 | } 20 | 21 | } 22 | 23 | public class TranslateDataList 24 | { 25 | 26 | private int MaxLength { get; } 27 | private LinkedList list; 28 | 29 | private LinkedListNode currentData; 30 | private LinkedListNode CurrentData { get => currentData; } 31 | 32 | private string DataLogFilePath { get; } 33 | 34 | public TranslateDataList(int maxLength) 35 | { 36 | MaxLength = maxLength; 37 | list = new LinkedList(); 38 | } 39 | public TranslateDataList(int maxLength, string logPath) 40 | { 41 | MaxLength = maxLength; 42 | list = new LinkedList(); 43 | 44 | DateTime dt = DateTime.Now; 45 | DataLogFilePath = logPath + "\\translated_" + string.Format("{0:yyMMddHHmmss}", dt) + ".log"; 46 | FileHandler.CreateFile(DataLogFilePath); 47 | 48 | } 49 | 50 | /// 51 | /// 输入TranslateData,插入队尾 52 | /// 53 | /// 54 | /// 55 | public void AddTranslateData(TranslateData translateData) 56 | { 57 | if (list.Count >= MaxLength) 58 | { 59 | if (DataLogFilePath != null) 60 | SaveDataToFile(list.First.Value); 61 | list.RemoveFirst(); 62 | } 63 | list.AddLast(translateData); 64 | currentData = list.Last; 65 | } 66 | 67 | //获得最新的翻译结果数据 68 | public TranslateData GetCurrentData() 69 | { 70 | return list.Last.Value; 71 | } 72 | 73 | public TranslateData GetNextData() 74 | { 75 | if (CurrentData != list.Last) 76 | currentData = CurrentData.Next; 77 | return CurrentData.Value; 78 | } 79 | 80 | public TranslateData GetPreviousData() 81 | { 82 | if (CurrentData != list.First) 83 | currentData = CurrentData.Previous; 84 | return CurrentData.Value; 85 | } 86 | 87 | public TranslateData GetFirstData() 88 | { 89 | currentData = list.First; 90 | return CurrentData.Value; 91 | } 92 | 93 | public TranslateData GetLastData() 94 | { 95 | currentData = list.Last; 96 | return CurrentData.Value; 97 | } 98 | 99 | public int Count() 100 | { 101 | return list.Count(); 102 | } 103 | 104 | public void SaveAllDataToFile() 105 | { 106 | foreach (TranslateData data in list) 107 | { 108 | SaveDataToFile(data); 109 | } 110 | } 111 | private void SaveDataToFile(TranslateData data) 112 | { 113 | var sb = new StringBuilder(); 114 | sb.AppendLine(data.SourceText); 115 | foreach (string key in data.ResultText.Keys) 116 | sb.Append(key).Append(": ").Append(data.ResultText[key]).AppendLine(); 117 | 118 | string result = sb.ToString(); 119 | 120 | FileHandler.AppendTextToFile(result, DataLogFilePath); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/TranslateHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using TsubakiTranslator.TranslateAPILibrary; 4 | 5 | namespace TsubakiTranslator.BasicLibrary 6 | { 7 | class TranslateHandler 8 | { 9 | //对翻译API进行初始化。 10 | public static LinkedList GetSelectedTranslators(TranslateAPIConfig translateAPIConfig, int srcLangIndex) 11 | { 12 | LinkedList translators = new LinkedList(); 13 | 14 | if (translateAPIConfig.AliIsEnabled) 15 | { 16 | ITranslator aliyun = new AliyunTranslator(); 17 | aliyun.TranslatorInit(srcLangIndex, translateAPIConfig.AliSecretId, translateAPIConfig.AliSecretKey); 18 | translators.AddLast(aliyun); 19 | } 20 | 21 | if (translateAPIConfig.BaiduIsEnabled) 22 | { 23 | ITranslator baidu = new BaiduTranslator(); 24 | baidu.TranslatorInit(srcLangIndex, translateAPIConfig.BaiduAppID, translateAPIConfig.BaiduSecretKey); 25 | translators.AddLast(baidu); 26 | } 27 | 28 | if (translateAPIConfig.BingIsEnabled) 29 | { 30 | ITranslator bing = new BingTranslator(); 31 | bing.TranslatorInit(srcLangIndex, null, null); 32 | translators.AddLast(bing); 33 | } 34 | 35 | if (translateAPIConfig.CaiyunIsEnabled) 36 | { 37 | ITranslator caiyun = new CaiyunTranslator(); 38 | caiyun.TranslatorInit(srcLangIndex, translateAPIConfig.CaiyunToken, ""); 39 | translators.AddLast(caiyun); 40 | } 41 | 42 | if (translateAPIConfig.ChatGptIsEnabled) 43 | { 44 | ITranslator chatgpt = new ChatGptTranslator(); 45 | chatgpt.TranslatorInit(srcLangIndex, translateAPIConfig.ChatGptToken, ""); 46 | translators.AddLast(chatgpt); 47 | } 48 | 49 | if (translateAPIConfig.DeeplIsEnabled) 50 | { 51 | ITranslator deepl = new DeepLTranslator(); 52 | deepl.TranslatorInit(srcLangIndex, translateAPIConfig.DeeplSecretKey, translateAPIConfig.DeeplIsFreeApi ? null : ""); 53 | translators.AddLast(deepl); 54 | } 55 | 56 | 57 | if (translateAPIConfig.IbmIsEnabled) 58 | { 59 | ITranslator ibm = new IBMTranslator(); 60 | ibm.TranslatorInit(srcLangIndex, null, null); 61 | translators.AddLast(ibm); 62 | 63 | } 64 | 65 | if (translateAPIConfig.ICiBaIsEnabled) 66 | { 67 | ITranslator iCiBa = new ICiBaTranslator(); 68 | iCiBa.TranslatorInit(srcLangIndex, null, null); 69 | translators.AddLast(iCiBa); 70 | 71 | } 72 | 73 | if (translateAPIConfig.TencentIsEnabled) 74 | { 75 | ITranslator tencent = new TencentTranslator(); 76 | tencent.TranslatorInit(srcLangIndex, translateAPIConfig.TencentSecretID, translateAPIConfig.TencentSecretKey); 77 | translators.AddLast(tencent); 78 | } 79 | 80 | if (translateAPIConfig.XiaoniuIsEnabled) 81 | { 82 | ITranslator xiaoniu = new XiaoniuTranslator(); 83 | xiaoniu.TranslatorInit(srcLangIndex, translateAPIConfig.XiaoniuApiKey, ""); 84 | translators.AddLast(xiaoniu); 85 | } 86 | 87 | if (translateAPIConfig.VolcengineIsEnabled) 88 | { 89 | ITranslator volcengine = new VolcengineTranslator(); 90 | volcengine.TranslatorInit(srcLangIndex, null, null); 91 | translators.AddLast(volcengine); 92 | } 93 | 94 | if (translateAPIConfig.YeekitIsEnabled) 95 | { 96 | ITranslator yeekit = new YeekitTranslator(); 97 | yeekit.TranslatorInit(srcLangIndex, null, null); 98 | translators.AddLast(yeekit); 99 | } 100 | 101 | return translators; 102 | } 103 | 104 | 105 | /// 106 | /// 根据翻译类名创建对象实例 107 | /// 108 | /// 109 | /// 命名空间.类型名 110 | /// 111 | public static T CreateInstance(string fullName) 112 | { 113 | //string path = fullName + "," + assemblyName;//命名空间.类型名,程序集 114 | Type o = Type.GetType(fullName);//加载类型 115 | object obj = Activator.CreateInstance(o, true);//根据类型创建实例 116 | return (T)obj;//类型转换并返回 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/User32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace TsubakiTranslator.BasicLibrary 5 | { 6 | public static class User32 7 | { 8 | public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); 9 | [StructLayout(LayoutKind.Sequential)] 10 | public struct RECT 11 | { 12 | public int Left, Top, Right, Bottom; 13 | 14 | public RECT(int left, int top, int right, int bottom) 15 | { 16 | Left = left; 17 | Top = top; 18 | Right = right; 19 | Bottom = bottom; 20 | } 21 | 22 | public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { } 23 | 24 | public int X 25 | { 26 | get { return Left; } 27 | set { Right -= (Left - value); Left = value; } 28 | } 29 | 30 | public int Y 31 | { 32 | get { return Top; } 33 | set { Bottom -= (Top - value); Top = value; } 34 | } 35 | 36 | public int Height 37 | { 38 | get { return Bottom - Top; } 39 | set { Bottom = value + Top; } 40 | } 41 | 42 | public int Width 43 | { 44 | get { return Right - Left; } 45 | set { Right = value + Left; } 46 | } 47 | 48 | public System.Drawing.Point Location 49 | { 50 | get { return new System.Drawing.Point(Left, Top); } 51 | set { X = value.X; Y = value.Y; } 52 | } 53 | 54 | public System.Drawing.Size Size 55 | { 56 | get { return new System.Drawing.Size(Width, Height); } 57 | set { Width = value.Width; Height = value.Height; } 58 | } 59 | 60 | public static implicit operator System.Drawing.Rectangle(RECT r) 61 | { 62 | return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height); 63 | } 64 | 65 | public static implicit operator RECT(System.Drawing.Rectangle r) 66 | { 67 | return new RECT(r); 68 | } 69 | 70 | public static bool operator ==(RECT r1, RECT r2) 71 | { 72 | return r1.Equals(r2); 73 | } 74 | 75 | public static bool operator !=(RECT r1, RECT r2) 76 | { 77 | return !r1.Equals(r2); 78 | } 79 | 80 | public bool Equals(RECT r) 81 | { 82 | return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom; 83 | } 84 | 85 | public override bool Equals(object obj) 86 | { 87 | if (obj is RECT) 88 | return Equals((RECT)obj); 89 | else if (obj is System.Drawing.Rectangle) 90 | return Equals(new RECT((System.Drawing.Rectangle)obj)); 91 | return false; 92 | } 93 | 94 | public override int GetHashCode() 95 | { 96 | return ((System.Drawing.Rectangle)this).GetHashCode(); 97 | } 98 | 99 | public override string ToString() 100 | { 101 | return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom); 102 | } 103 | } 104 | public const Int32 CURSOR_SHOWING = 0x00000001; 105 | [StructLayout(LayoutKind.Sequential)] 106 | public struct POINT 107 | { 108 | public Int32 x; 109 | public Int32 y; 110 | } 111 | [StructLayout(LayoutKind.Sequential)] 112 | public struct CursorInfo 113 | { 114 | public Int32 cbSize; 115 | public Int32 flags; 116 | public IntPtr hCursor; 117 | public POINT ptScreenPos; 118 | } 119 | [DllImport("user32.dll", SetLastError = false)] 120 | public static extern IntPtr GetDesktopWindow(); 121 | [DllImport("user32.dll", SetLastError = false)] 122 | public static extern IntPtr GetShellWindow(); 123 | 124 | [DllImport("user32.dll")] 125 | public static extern IntPtr GetWindowDC(IntPtr hWnd); 126 | [DllImport("User32.dll")] 127 | public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); 128 | [DllImport("user32.dll")] 129 | public static extern bool GetCursorInfo(out CursorInfo pci); 130 | 131 | [DllImport("user32.dll")] 132 | public static extern IntPtr WindowFromPoint(System.Drawing.Point p); 133 | [DllImport("user32.dll", SetLastError = true)] 134 | [return: MarshalAs(UnmanagedType.Bool)] 135 | public static extern bool GetCursorPos(out System.Drawing.Point lpPoint); 136 | [DllImport("user32.dll", SetLastError = true)] 137 | public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); 138 | [DllImport("user32.dll")] 139 | [return: MarshalAs(UnmanagedType.Bool)] 140 | public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); 141 | [DllImport("user32.dll")] 142 | public static extern bool IsWindowVisible(IntPtr hwnd); 143 | 144 | [DllImport("User32.dll")] 145 | public static extern bool AddClipboardFormatListener(IntPtr hwnd); 146 | 147 | [DllImport("User32.dll")] 148 | public static extern bool RemoveClipboardFormatListener(IntPtr hwnd); 149 | 150 | /// 151 | /// 注册全局热键 152 | /// 153 | /// 要定义热键的窗口的句柄 154 | /// 定义热键ID(不能与其它ID重复,全局唯一) 155 | /// 标识热键是否在按Alt、Ctrl、Shift、Windows等键时才会生效 156 | /// 定义热键的内容 157 | /// 成功,返回值不为0,失败,返回值为0。要得到扩展错误信息,调用GetLastError 158 | [DllImport("user32.dll", SetLastError = true)] 159 | public static extern bool RegisterHotKey(IntPtr hWnd, int id, byte fsModifiers, int vk); 160 | 161 | /// 162 | /// 取消注册全局热键 163 | /// 164 | /// 要取消热键的窗口的句柄 165 | /// 要取消热键的ID 166 | /// 167 | [DllImport("user32.dll", SetLastError = true)] 168 | public static extern bool UnregisterHotKey(IntPtr hWnd, int id); 169 | 170 | /// 171 | /// 该函数将指定的窗口设置到Z序的顶部。 172 | /// 173 | /// 174 | [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)] 175 | public static extern int BringWindowToTop(IntPtr hWnd); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /TsubakiTranslator/BasicLibrary/WindowConfig.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using System.Windows.Media; 3 | 4 | namespace TsubakiTranslator.BasicLibrary 5 | { 6 | public partial class WindowConfig : ObservableObject 7 | { 8 | [ObservableProperty] 9 | private double mainWindowWidth = 400; 10 | [ObservableProperty] 11 | private double mainWindowHeight = 800; 12 | [ObservableProperty] 13 | private double translateWindowHeight = 400; 14 | [ObservableProperty] 15 | private double translateWindowWidth = 800; 16 | [ObservableProperty] 17 | private double translateWindowLeft = 400; 18 | [ObservableProperty] 19 | private double translateWindowTop = 200; 20 | [ObservableProperty] 21 | private bool translateWindowTopmost = false; 22 | [ObservableProperty] 23 | private int translateWindowTransparency = 165; 24 | [ObservableProperty] 25 | private Color sourceTextColor = Colors.BurlyWood; 26 | [ObservableProperty] 27 | private Color translatedTextColor = Colors.WhiteSmoke; 28 | [ObservableProperty] 29 | private bool sourceTextVisibility = true; 30 | [ObservableProperty] 31 | private bool translatorNameVisibility = true; 32 | [ObservableProperty] 33 | private string sourceTextFontFamily = "Microsoft YaHei UI"; 34 | [ObservableProperty] 35 | private string translatedTextFontFamily = "Microsoft YaHei UI"; 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TsubakiTranslator/HookResultDisplay.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 14 | 15 | 16 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /TsubakiTranslator/HookResultDisplay.xaml.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Windows.Controls; 6 | 7 | namespace TsubakiTranslator 8 | { 9 | /// 10 | /// HookResultDisplay.xaml 的交互逻辑 11 | /// 12 | public partial class HookResultDisplay : UserControl 13 | { 14 | private Dictionary HookItemDict; 15 | private TranslateWindow translateWindow; 16 | 17 | public HookResultDisplay(TranslateWindow translateWindow) 18 | { 19 | InitializeComponent(); 20 | 21 | HookItemDict = new Dictionary(); 22 | this.translateWindow = translateWindow; 23 | 24 | HookDataSet = new ObservableCollection(); 25 | HookDataGrid.ItemsSource = HookDataSet; 26 | } 27 | 28 | public void UpdateHookResultItem(string hookcode, string content) 29 | { 30 | if (HookItemDict.ContainsKey(hookcode)) 31 | HookItemDict[hookcode].HookText = content; 32 | else 33 | { 34 | HookData item = new HookData(hookcode, content); 35 | HookItemDict.Add(hookcode, item); 36 | //Dispatcher是一个线程控制器,要控制线程里跑的东西,就要经过它。 37 | //WPF里面,有个所谓UI线程,后台代码不能直接操作UI控件,需要控制,就要通过这个Dispatcher。 38 | App.Current.Dispatcher.Invoke((Action)(() => 39 | { 40 | /// start 你的逻辑代码 41 | HookDataSet.Add(item); 42 | /// end 43 | })); 44 | 45 | 46 | } 47 | } 48 | 49 | public HashSet GetSelectedHookData() 50 | { 51 | HashSet result = new HashSet(); 52 | foreach(HookData data in HookDataSet) 53 | { 54 | if (data.IsSelected) 55 | { 56 | result.Add(data); 57 | } 58 | } 59 | return result; 60 | } 61 | 62 | private ObservableCollection HookDataSet { get; } 63 | 64 | 65 | } 66 | 67 | public class HookData : ObservableObject 68 | { 69 | 70 | private string hookCode; 71 | private string hookText; 72 | private bool isSelected; 73 | 74 | public HookData(string hookCode, string hookText) 75 | { 76 | this.hookCode = hookCode; 77 | this.hookText = hookText; 78 | } 79 | public string HookCode 80 | { 81 | get => hookCode; 82 | set => SetProperty(ref hookCode, value); 83 | } 84 | public string HookText 85 | { 86 | get => hookText; 87 | set => SetProperty(ref hookText, value); 88 | } 89 | 90 | public bool IsSelected 91 | { 92 | get => isSelected; 93 | set => SetProperty(ref isSelected, value); 94 | } 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /TsubakiTranslator/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 |