diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..af94712 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,39 @@ +{ + "projectName": "ja-learner", + "projectOwner": "ks233", + "files": [ + "README.md" + ], + "commitType": "docs", + "commitConvention": "angular", + "contributorsPerLine": 7, + "contributors": [ + { + "login": "ks233", + "name": "ks233", + "avatar_url": "https://avatars.githubusercontent.com/u/38981529?v=4", + "profile": "https://ks233.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "shaka0919", + "name": "Harvey Wang", + "avatar_url": "https://avatars.githubusercontent.com/u/17539962?v=4", + "profile": "https://github.com/shaka0919", + "contributions": [ + "code" + ] + }, + { + "login": "ly-nld", + "name": "跨性别", + "avatar_url": "https://avatars.githubusercontent.com/u/38471793?v=4", + "profile": "http://lgbt.sh", + "contributions": [ + "infra" + ] + } + ] +} diff --git a/GUI/MainForm.cs b/GUI/MainForm.cs index 2d40f4e..5e4592e 100644 --- a/GUI/MainForm.cs +++ b/GUI/MainForm.cs @@ -1,13 +1,6 @@ using Microsoft.Web.WebView2.Core; using System.Diagnostics; -using System.Runtime.InteropServices; -using MeCab; -using System.Text; -using System.Reflection.Metadata; -using OpenAI_API.Chat; using Microsoft.VisualBasic; -using System.Windows.Forms.Design; -using System.Net.Http; namespace ja_learner { @@ -16,9 +9,30 @@ public partial class MainForm : Form DictForm dictForm; TextAnalyzer textAnalyzer = new TextAnalyzer(); - GptCaller gptCaller; private string sentence = ""; - + private bool immersiveMode = false; + public bool ImmersiveMode + { + get { return immersiveMode; } + set + { + if (value) + { + webView.Parent = this; + tabControl.Hide(); + panel1.Hide(); + FormBorderStyle = FormBorderStyle.None; + } + else + { + webView.Parent = tabControl.TabPages[0]; + tabControl.Show(); + panel1.Show(); + FormBorderStyle = FormBorderStyle.Sizable; + } + immersiveMode = value; + } + } public string Sentence { get { return sentence; } @@ -82,6 +96,20 @@ private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindo private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e) { string message = e.TryGetWebMessageAsString(); + // 来自webview的特殊按键事件处理 + if (message == "DBLCLICK") + { + ImmersiveMode = !ImmersiveMode; + return; + } + else if (message == "MOUSEDOWN") + { + // if (ImmersiveMode) + { + Drag(); + } + return; + } dictForm.SearchText(message); dictForm.ShowAndFocus(); Focus(); @@ -111,16 +139,16 @@ private void MainForm_SizeChanged(object sender, EventArgs e) { if (timerWindowAttach.Enabled) { - heightAfter = this.Height; + heightAfter = Height; if (dictForm.Visible) { - dictForm.Width = this.Right - WindowAttacher.TargetWindowRect.Right; + dictForm.Width = Right - WindowAttacher.TargetWindowRect.Right; } } } - private System.Drawing.Point locationBefore; // 记录普通模式下窗口的位置 + private Point locationBefore; // 记录普通模式下窗口的位置 private Size sizeBefore; // 记录普通模式下窗口的大小 private int heightAfter = 200; // 附着模式时,窗体通常会比较矮 @@ -131,17 +159,17 @@ private void checkBoxWindowAttach_CheckedChanged(object sender, EventArgs e) if (checkBoxWindowAttach.Checked) { // 记录普通状态的窗口位置,切换到吸附状态下的窗口位置 - sizeBefore = this.Size; - locationBefore = this.Location; - this.Height = heightAfter; + sizeBefore = Size; + locationBefore = Location; + Height = heightAfter; timerWindowAttach.Enabled = true; } else { timerWindowAttach.Enabled = false; - heightAfter = this.Height; - this.Size = sizeBefore; - this.Location = locationBefore; + heightAfter = Height; + Size = sizeBefore; + Location = locationBefore; } } @@ -151,7 +179,7 @@ private void timerWindowAttach_Tick(object sender, EventArgs e) { WindowAttacher.AttachWindows(this, dictForm); } - catch (Exception ex) + catch { WindowAttach = false; } @@ -176,6 +204,10 @@ public bool WindowAttach private void checkBoxClipboardMode_CheckedChanged(object sender, EventArgs e) { ClipBoardMode = checkBoxClipboardMode.Checked; + if (ClipBoardMode) + { + Sentence = Clipboard.GetText(TextDataFormat.UnicodeText).Trim().Replace(" ", ""); + } } private void timerGetClipboard_Tick(object sender, EventArgs e) { @@ -201,7 +233,7 @@ public bool ClipBoardMode private void btnInputText_Click(object sender, EventArgs e) { - Sentence = Microsoft.VisualBasic.Interaction.InputBox("手动输入", "输入句子", "", 0, 0); + Sentence = Interaction.InputBox("手动输入", "输入句子", "", 0, 0); } private void timerSelectWindow_Tick(object sender, EventArgs e) @@ -250,7 +282,7 @@ private void textBoxHwnd_TextChanged(object sender, EventArgs e) string windowTitle = WindowAttacher.GetWindowTitle(hwnd); checkBoxWindowAttach.Text = $"与【{windowTitle}】对齐"; // 判断窗口句柄是不是自己的 - if (hwnd == this.Handle || hwnd == dictForm.Handle) + if (hwnd == Handle || hwnd == dictForm.Handle) { checkBoxWindowAttach.Enabled = false; return; @@ -340,7 +372,51 @@ private void comboBoxExtraPrompts_Click(object sender, EventArgs e) async private void checkBoxTranslateKatakana_CheckedChanged(object sender, EventArgs e) { - await webView.ExecuteScriptAsync($"setTranslateKatakana({checkBoxTranslateKatakana.Checked.ToString().ToLower()})"); + string param = checkBoxTranslateKatakana.Checked ? "true" : "false"; + await webView.ExecuteScriptAsync($"setTranslateKatakana({param})"); + } + + // https://stackoverflow.com/questions/31199437/borderless-and-resizable-form-c + // 从↑抄来的代码,无边框模式下可调整窗口大小 + const int WM_NCHITTEST = 0x0084; + const int HTCLIENT = 1; + const int HTCAPTION = 2; + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + switch (m.Msg) + { + case WM_NCHITTEST: + if (m.Result == (IntPtr)HTCLIENT) + { + m.Result = (IntPtr)HTCAPTION; + } + break; + } + } + protected override CreateParams CreateParams + { + get + { + CreateParams cp = base.CreateParams; + cp.Style |= 0x40000; + return cp; + } + } + + // 窗口拖拽 + public const int WM_NCLBUTTONDOWN = 0xA1; + public const int HT_CAPTION = 0x2; + + [System.Runtime.InteropServices.DllImport("user32.dll")] + public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + [System.Runtime.InteropServices.DllImport("user32.dll")] + public static extern bool ReleaseCapture(); + + private void Drag() + { + ReleaseCapture(); + SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0); } } } \ No newline at end of file diff --git a/README.md b/README.md index b7914f1..64b9c0a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

KS的日语学习工具 v0.2

+

KS的日语学习工具 v0.3

📖 简易日语学习 / 视觉小说阅读辅助工具
@@ -14,6 +14,7 @@
+ ## 功能介绍 * **语句分析**:用不同样式区分句子成分,为句子中的汉字注音 @@ -26,16 +27,25 @@ ## 使用说明 -### 基本的句子分析 +### 分析与查词 * 读取剪贴板或手动输入句子 -* 勾选“片假不留”可以把片假名单词翻译成英文 +* 勾选“片假不留”把片假名单词翻译成英文 * 点击片假名单词上方的英文可以隐藏该单词的翻译,再点一下恢复显示,用于屏蔽错误的翻译结果 * 可以用 Ctrl + 滚轮调整分析界面的显示大小 * 本质浏览器套壳,你甚至可以按 F12 打开控制台( * 在语句分析界面点击单词快速查词 - * 左键点击单词会在词典窗口中显示 MOJi 辞書的搜索结果 - * 中键点击单词会在浏览器中打开单词在 MOJi 辞書的搜索页面 + * 左键点击单词:在词典窗口中显示 MOJi 辞書的搜索结果 + * 中键点击单词:在浏览器中打开单词在 MOJi 辞書的搜索页面 + * SHIFT + 左键点击单词:在搜索内容末尾追加该单词 +* 词典窗口手动搜索 + * 双击搜索框清空搜索内容 + * 点击链接跳转至对应的词典网站 + + +![demo](README/demo.gif) + +搜索框与 SHIFT 追加搜索是 v0.3 新增的功能,MeCab 有时候会过度断句,比如把“二十四节气”当成了三个词,“四”的注音还标错了。用追加搜索功能就可以把拆开的词拼回来,使查词更加灵活。 ### 窗口吸附 @@ -60,43 +70,67 @@ #### 使用 GPT(需要 API Key) -首先要配置 api key,在 `config.txt` 的第一行输入 api key,第二行输入 api url: +首先要配置 api key,在 `appsettings.json` 中配置 ApiKey 与 ApiUrl: ``` -sk-xxxxxxxxx -https://api.openai.com/{0}/{1} +"ApiKey": "sk-xxx", +"ApiUrl": "https://api.openai.com/{0}/{1}", ``` -如果你使用官方 API,第二行就不用修改,如果使用第三方反代,就要修改为相应的域名。 +如果你使用官方 API,就不用修改 ApiUrl,如果使用第三方反代,就要将其修改为相应的域名。 -配置好 api key 就可以使用 GPT 翻译和解说文本了。 +配置好 ApiKey 就可以使用 ChatGPT 翻译和解说文本了。 ![gpt](README/gpt.gif) -## 声明 +### 沉浸模式 -### 分析与翻译仅供参考 +- 双击分析页面的背景,进入仅显示语句分析的沉浸模式 +- CTRL + 左键拖动窗口 +- 左键拖动窗口边缘调整窗口大小 -本项目的分词与注音功能基于 MeCab,有时会犯一些低级错误,比如把「身体からだ」注音为 しんたい、把「二人ふたり」注音为 ににん,用词汇更丰富的 [UniDic](https://clrd.ninjal.ac.jp/unidic/) 词典替换 `dic` 文件夹中默认的 IPADIC 效果会稍好一些。 +![immersive](README/immersive.gif) -翻译毕竟都是机翻,出错也很正常。谷歌翻译遇到复杂的句式和不规范的表达就容易翻车,ChatGPT 比谷歌懂更多俗语、流行语,但偶尔也会发癫,比如使用简体中文以外的语言回复、唐突的[塞氏翻译法](https://zh.moegirl.org.cn/zh-hans/塞氏翻译法)等等。建议把本软件当做一个精读工具而不是翻译器,把注意力放在日语原文上,只在不确定的时候使用翻译作为参考。 +## 使用建议 -外来语标注功能使用谷歌翻译将片假名单词翻译为英语,但不是所有片假名单词都是外来语,外来语也不一定来源于英语,还有像 supplies 和 surprise 这样的“同音词”也不好区分,因此也会出现标记错误的情况。 +本项目的分词与注音功能基于 MeCab,虽然整体准确率还算可以,但有时会犯一些低级错误,比如在某些语境下把身体からだ注音为 しんたい、把二人ふたり注音为 ににん,遇到读音特殊的人名也无法正确注音。用词汇更丰富的 [UniDic](https://clrd.ninjal.ac.jp/unidic/) 词典替换 `dic` 文件夹中默认的 IPADIC 效果会稍好一些。 -根据我个人的使用体验,整体准确率还可以接受,但还是不建议完全初学者使用,以免被误导。如果遇到可疑的注音或翻译,建议点击单词查看 MOJi 辞書的解释和注音,并对照不同引擎的翻译结果,或者使用 ChatGPT 的解说功能。 +翻译毕竟都是机翻,准确率有限。谷歌翻译遇到复杂的句式和不规范的表达就容易翻车,ChatGPT 比谷歌懂更多俗语、流行语,但比较不稳定,偶尔会使用简体中文以外的语言回复、唐突地使用[塞氏翻译法](https://zh.moegirl.org.cn/zh-hans/塞氏翻译法)。建议把本软件当做一个精读工具而不是翻译器,把注意力放在日语原文上,只在不确定的时候使用翻译作为参考。 -### 相关项目 +外来语标注功能使用谷歌翻译将片假名单词翻译为英语,但不是所有片假名单词都是外来语,外来语也不一定来源于英语,还有像 supplies 和 surprise 这样的“同音词”也不好区分,因此也会出现标注错误的情况。 -本项目的前端页面:[ks233/ja-learner-webview](https://github.com/ks233/ja-learner-webview) +根据作者自己的使用体验,整体准确率还可以接受,但还是不建议完全初学者使用,以免被误导。如果遇到可疑的注音或翻译,建议查询更权威的词典,比如 [Weblio 辞書](https://www.weblio.jp/)、大辞林、小学馆日中,网络用语可以查 [ニコニコ大百科](https://dic.nicovideo.jp/)。 -开坑的想法主要来源于 [YUKI 翻译器](https://github.com/project-yuki/YUKI) 和 [Translation-Aggregator](https://github.com/Translation-Aggregator/Translation-Aggregator),前者支持了丰富的翻译接口,内置了文本提取功能,但使用起来比较复杂,且缺少快速查词的功能;后者虽然可以鼠标悬停查词,但只有日英词典、界面比较古老,而且翻译接口几乎炸完了,于是我决定搓一个更简单、更符合自己需求的工具。 +## 相关项目 -[taishi-i/awesome-japanese-nlp-resources](https://github.com/taishi-i/awesome-japanese-nlp-resources) 有非常多日语相关的工具和资源,对我开发这个项目帮助很大。 +开坑的想法主要来源于 [YUKI 翻译器](https://github.com/project-yuki/YUKI) 和 [Translation-Aggregator](https://github.com/Translation-Aggregator/Translation-Aggregator),前者支持了丰富的翻译接口,内置了文本提取功能,但使用起来比较复杂,且缺少快速查词的功能;后者虽然可以鼠标悬停查词,但只有日英词典、界面比较古老,而且翻译接口几乎炸完了,于是我决定搓一个更简单、更符合自己需求的工具。 -### 使用的第三方库与框架 +使用的第三方工具与参考资料: * 形态分析:[taku910/mecab](https://github.com/taku910/mecab) 的 .Net 移植版本 [kekyo/MeCab.DotNet](https://github.com/kekyo/MeCab.DotNet) -* 调用 ChatGPT:[OkGoDoIt/OpenAI-API-dotnet](https://github.com/OkGoDoIt/OpenAI-API-dotnet) -* 谷歌翻译:参考了 [FilipePS/Traduzir-paginas-web](https://github.com/FilipePS/Traduzir-paginas-web) 的 API 调用方式 +* ChatGPT:[OkGoDoIt/OpenAI-API-dotnet](https://github.com/OkGoDoIt/OpenAI-API-dotnet) +* [前端页面](https://github.com/ks233/ja-learner-webview):[WebView2 控件](https://www.nuget.org/packages/Microsoft.Web.WebView2),Vite + Vue * 单词搜索:[MOJi 辞書](https://www.mojidict.com/) -* Webview 页面:Vite + Vue +* 谷歌翻译:参考了 [FilipePS/Traduzir-paginas-web](https://github.com/FilipePS/Traduzir-paginas-web) 的 API 调用方式 +* 其它参考资源:[taishi-i/awesome-japanese-nlp-resources](https://github.com/taishi-i/awesome-japanese-nlp-resources) + +## 贡献者 + + + + + + + + + + + + + +
ks233
ks233

💻
Harvey Wang
Harvey Wang

💻
跨性别
跨性别

🚇
+ + + + + diff --git a/README/demo.gif b/README/demo.gif new file mode 100644 index 0000000..8c041ff Binary files /dev/null and b/README/demo.gif differ diff --git a/README/immersive.gif b/README/immersive.gif new file mode 100644 index 0000000..6becc16 Binary files /dev/null and b/README/immersive.gif differ diff --git a/TextAnalyzer.cs b/TextAnalyzer.cs index 7ca1fde..c83d88c 100644 --- a/TextAnalyzer.cs +++ b/TextAnalyzer.cs @@ -16,7 +16,7 @@ struct TextAnalyzerResult public string Surface; public string Pos;// 词性 public string Basic;// 基本型 - public string Reading;// 渲染 + public string Reading;// 读音 public readonly string ToJson() { diff --git a/appsettings.json b/appsettings.json index 757eb07..ea50488 100644 --- a/appsettings.json +++ b/appsettings.json @@ -1,5 +1,4 @@ { - "SecretKey": "Secret key value", "AppSetting": { "ApiKey": "sk-xxx", "ApiUrl": "https://api.openai.com/{0}/{1}",